当前位置:   article > 正文

【史上最全】前端难点知识汇总(不定期更新)_前端项目难点

前端项目难点

1.JS垃圾回收机制

标记清除
  当变量进入环境(例如,在函数中声明一个变量)时,将这个变量标记为 “进入环境” 。从逻辑上讲,永远不能释放进入环境的变量所占用的内存,因为我们在这个环境中可能随时会用到它们。当变量离开环境时,则将其标记为 “离开环境”。
  垃圾收集器(GC)在运行的时候会给存储在内存中的所有变量都加上标记。然后,它会去掉环境中的变量以及被环境中的变量引用的标记。而在此之后再被加上标记的变量将被视为准备删除的变量,原因是环境中的变量已经无法访问到这些变量了。最后,GC完成内存清除工作,销毁那些带标记的值,并回收它们所占用的内存空间。
引用计数
  跟踪记录每个值被引用的次数。当声明了一个变量并将一个引用类型值赋给该变量时,这个值的引用次数就是 1。如果同一个值又被赋值给另一个变量,则引用次数加 1。相反,如果保存对该值引用的变量被其他值给覆盖了,则引用次数减 1。当这个值的引用次数变为 0 时,说明已经没法再访问这个值了,因此可以将其占用的内存回收了。
  引用计数策略有一个很严重的问题:循环引用。所以不常用。
  如果对象 A 中包含一个指针指向对象 B,而对象 B 中也包含一个指针指向对象 A。那么这两个对象引用次数都是 2,但实际上已经可以回收了。若这种函数被反复多次调用,会导致大量内存得不到回收。

2.浏览器缓存机制

  浏览器的缓存机制指的是通过在一段时间内保留已接收到的 web 资源的一个副本,如果在资源的有效时间内,发起了对这个资源的再一次请求,那么浏览器会直接使用缓存的副本,而不是向服务器发起请求。使用 web 缓存可以有效地提高页面的打开速度,减少不必要的网络带宽的消耗。
  web 资源的缓存策略一般由服务器来指定,可以分为两种,分别是强缓存策略协商缓存策略
  使用强缓存策略时,如果缓存资源有效,则直接使用缓存资源,不必再向服务器发起请求。强缓存策略可以通过两种方式来设置,分别是 http 头信息中的 Expires 属性和 Cache-Control 属性。
  服务器通过在响应头中添加 Expires 属性,来指定资源的过期时间。在过期时间以内,该资源可以被缓存使用,不必再向服务器发送请求。这个时间是一个绝对时间,它是服务器的时间,因此可能存在这样的问题,就是客户端的时间和服务器端的时间不一致,或者用户可以对客户端时间进行修改的情况,这样就可能会影响缓存命中的结果。
  Expires 是 http1.0 中的方式,因为它的一些缺点,在 http 1.1 中提出了一个新的头部属性就是 Cache-Control 属性,它提供了对资源的缓存的更精确的控制。它有很多不同的值,常用的比如我们可以通过设置 max-age 来指定资源能够被缓存的时间的大小,这是一个相对的时间,它会根据这个时间的大小和资源第一次请求时的时间来计算出资源过期的时间,因此相对于 Expires来说,这种方式更加有效一些。常用的还有比如 private ,用来规定资源只能被客户端缓存,不能够代理服务器所缓存。还有如 no-store ,用来指定资源不能够被缓存,no-cache 代表该资源能够被缓存,但是立即失效,每次都需要向服务器发起请求。
  一般来说只需要设置其中一种方式就可以实现强缓存策略,当两种方式一起使用时,Cache-Control 的优先级要高于 Expires 。
  使用协商缓存策略时,会先向服务器发送一个请求,如果资源没有发生修改,则返回一个 304 状态,让浏览器使用本地的缓存副本;如果资源发生了修改,则返回修改后的资源。协商缓存也可以通过两种方式来设置,分别是 http 头信息中的 Etag 和 Last-Modified 属性。
  服务器通过在响应头中添加 Last-Modified 属性来指出资源最后一次修改的时间,当浏览器下一次发起请求时,会在请求头中添加一个 If-Modified-Since 的属性,属性值为上一次资源返回时的 Last-Modified 的值。当请求发送到服务器后服务器会通过这个属性来和资源的最后一次的修改时间来进行比较,以此来判断资源是否做了修改。如果资源没有修改,那么返回 304 状态,让客户端使用本地的缓存。如果资源已经被修改了,则返回修改后的资源。使用这种方法有一个缺点,就是 Last-Modified 标注的最后修改时间只能精确到秒级,如果某些文件在1秒钟以内,被修改多次的话,那么文件已将改变了但是 Last-Modified 却没有改变,
这样会造成缓存命中的不准确。
  因为 Last-Modified 的这种可能发生的不准确性,http 中提供了另外一种方式,那就是 Etag 属性。服务器在返回资源的时候,在头信息中添加了 Etag 属性,这个属性是资源生成的唯一标识符,当资源发生改变的时候,这个值也会发生改变。在下一次资源请求时,浏览器会在请求头中添加一个 If-None-Match 属性,这个属性的值就是上次返回的资源的 Etag 的值。服务接收到请求后会根据这个值来和资源当前的 Etag 的值来进行比较,以此来判断资源是否发生改变,是否需要返回资源。通过这种方式,比 Last-Modified 的方式更加精确。
  当 Last-Modified 和 Etag 属性同时出现的时候,Etag 的优先级更高。使用协商缓存的时候,服务器需要考虑负载平衡的问题,因此多个服务器上资源的 Last-Modified 应该保持一致,因为每个服务器上 Etag 的值都不一样,因此在考虑负载平衡时,最好不要设置 Etag 属性。
  强缓存策略和协商缓存策略在缓存命中时都会直接使用本地的缓存副本,区别只在于协商缓存会向服务器发送一次请求。它们缓存不命中时,都会向服务器发送请求来获取资源。在实际的缓存机制中,强缓存策略和协商缓存策略是一起合作使用的。浏览器首先会根据请求的信息判断,强缓存是否命中,如果命中则直接使用资源。如果不命中则根据头信息向服务器发起请求,使用协商缓存,如果协商缓存命中的话,则服务器不返回资源,浏览器直接使用本地资源的副本,如果协商缓存不命中,则浏览器返回最新的资源给浏览器。

3.Vue生命周期钩子

(1)beforeCreate在实例初始化之后,在数据监听和event/watcher事件配置之前触发。因此在这个事件中我们是获取不到 data 数据的。
(2)created在实例创建完成后触发,此时可以访问 data、methods 等属性。但这个时候组件还没有被挂载到页面中去,所以这个时候访问不到 $el 属性。一般我们可以在这个函数中进行一些页面初始化的工作,比如通过 ajax 请求数据来对页面进行初始化。
(3)beforeMount在挂载到页面之前触发。在 beforeMount 之前,会找到对应的 template,并编译成 render 函数。
(4)mounted组件挂载到页面之后触发。此时可以通过 DOM API 获取到页面中的 DOM 元素。
(5)beforeUpdate数据更新时触发,发生在虚拟 DOM 重新渲染和打补丁之前,这个时候我们可以对可能会被移除的元素做一些操作,比如移除事件监听器。
(6)updated 钩子函数,虚拟 DOM 重新渲染和打补丁之后调用。
(7)beforeDestroy实例销毁之前调用。一般在这一步我们可以销毁定时器、解绑全局事件等。
(8)destroyed实例销毁之后调用,调用后,Vue 实例中的所有指令都会解绑,所有的事件监听器会被移除,所有的子实例也会被销毁。

4.手撕promise

const PENDING = "pending";
const RESOLVED = "resolved";
const REJECTED = "rejected";

function MyPromise(fn) {
  // 保存初始化状态
  var self = this;

  // 初始化状态
  this.state = PENDING;

  // 用于保存 resolve 或者 rejected 传入的值
  this.value = null;

  // 用于保存 resolve 的回调函数
  this.resolvedCallbacks = [];

  // 用于保存 reject 的回调函数
  this.rejectedCallbacks = [];

  // 状态转变为 resolved 方法
  function resolve(value) {
    // 判断传入元素是否为 Promise 值,如果是,则状态改变必须等待前一个状态改变后再进行改变
    if (value instanceof MyPromise) {
      return value.then(resolve, reject);
    }

    // 保证代码的执行顺序为本轮事件循环的末尾
    setTimeout(() => {
      // 只有状态为 pending 时才能转变,
      if (self.state === PENDING) {
        // 修改状态
        self.state = RESOLVED;

        // 设置传入的值
        self.value = value;

        // 执行回调函数
        self.resolvedCallbacks.forEach(callback => {
          callback(value);
        });
      }
    }, 0);
  }

  // 状态转变为 rejected 方法
  function reject(value) {
    // 保证代码的执行顺序为本轮事件循环的末尾
    setTimeout(() => {
      // 只有状态为 pending 时才能转变
      if (self.state === PENDING) {
        // 修改状态
        self.state = REJECTED;

        // 设置传入的值
        self.value = value;

        // 执行回调函数
        self.rejectedCallbacks.forEach(callback => {
          callback(value);
        });
      }
    }, 0);
  }

  // 将两个方法传入函数执行
  try {
    fn(resolve, reject);
  } catch (e) {
    // 遇到错误时,捕获错误,执行 reject 函数
    reject(e);
  }
}

MyPromise.prototype.then = function(onResolved, onRejected) {
  // 首先判断两个参数是否为函数类型,因为这两个参数是可选参数
  onResolved =
    typeof onResolved === "function"
      ? onResolved
      : function(value) {
          return value;
        };

  onRejected =
    typeof onRejected === "function"
      ? onRejected
      : function(error) {
          throw error;
        };

  // 如果是等待状态,则将函数加入对应列表中
  if (this.state === PENDING) {
    this.resolvedCallbacks.push(onResolved);
    this.rejectedCallbacks.push(onRejected);
  }

  // 如果状态已经凝固,则直接执行对应状态的函数

  if (this.state === RESOLVED) {
    onResolved(this.value);
  }

  if (this.state === REJECTED) {
    onRejected(this.value);
  }
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106

5.JS异步机制

  第一种最常见的是使用回调函数的方式,使用回调函数的方式有一个缺点是,多个回调函数嵌套的时候会造成回调地狱,上下两层的回调函数间的代码耦合度太高,不利于代码的可维护。
  第二种是 Promise 的方式,使用 Promise 的方式可以将嵌套的回调函数作为链式调用。但是使用这种方法,有时会造成多个 then 的链式调用,可能会造成代码的语义不够明确。
  第三种是使用 generator 的方式,它可以在函数的执行过程中,将函数的执行权转移出去,在函数外部我们还可以将执行权转移回来。当我们遇到异步函数执行的时候,将函数执行权转移出去,当异步函数执行完毕的时候我们再将执行权给转移回来。因此我们在 generator 内部对于异步操作的方式,可以以同步的顺序来书写。使用这种方式我们需要考虑的问题是何时将函数的控制权转移回来,因此我们需要有一个自动执行 generator 的机制,比如说 co 模块等方式来实现 generator 的自动执行。
  第四种是使用 async 函数的形式,async 函数是 generator 和 promise 实现的一个自动执行的语法糖,它内部自带执行器,当函数内部执行到一个 await 语句的时候,如果语句返回一个 promise 对象,那么函数将会等待 promise 对象的状态变为 resolve 后再继续向下执行。因此我们可以将异步逻辑,转化为同步的顺序来书写,并且这个函数可以自动执行。

6.diff算法如何比较节点

在这里插入图片描述

(1)新虚拟DOM和老虚拟DOM进行diff,算出应该如何最小量更新,最后反映到真正的DOM上。
(2)h函数用来得到虚拟DOM(带有“sel”,“data”,“text",“children”等属性)patch函数用来用来执行diff算法。
(3)DOM如何变为虚拟DOM属于模板编译原理范畴,本节暂不提及
(4)在这里插入图片描述

(5)diff算法的子节点更新策略
(如果顺序比较会有很多种情况比如更新一个节点/新增一个节点/删除一个节点会很混乱)
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小小林熬夜学编程/article/detail/637739
推荐阅读
相关标签
  

闽ICP备14008679号