Proxy 可以实现什么功能?
在 Vue3.0 中通过 Proxy 来替换原本的 Object.defineProperty 来实现数据响应式。
Proxy 是 ES6 中新增的功能,它可以用来自定义对象中的操作。
 let p = new Proxy(target, handler)
   复制代码
 
target 代表需要添加代理的对象,handler 用来自定义对象中的操作,比如可以用来自定义 set 或者 get 函数。
下面来通过 Proxy 来实现一个数据响应式:
 let onWatch = (obj, setBind, getLogger) => {  let handler = {    get(target, property, receiver) {      getLogger(target, property)      return Reflect.get(target, property, receiver)    },    set(target, property, value, receiver) {      setBind(value, property)      return Reflect.set(target, property, value)    }  }  return new Proxy(obj, handler)}let obj = { a: 1 }let p = onWatch(  obj,  (v, property) => {    console.log(`监听到属性${property}改变为${v}`)  },  (target, property) => {    console.log(`'${property}' = ${target[property]}`)  })p.a = 2 // 监听到属性a改变p.a // 'a' = 2
   复制代码
 
在上述代码中,通过自定义 set 和 get 函数的方式,在原本的逻辑中插入了我们的函数逻辑,实现了在对对象任何属性进行读写时发出通知。
当然这是简单版的响应式实现,如果需要实现一个 Vue 中的响应式,需要在 get 中收集依赖,在 set 派发更新,之所以 Vue3.0 要使用 Proxy 替换原本的 API 原因在于 Proxy 无需一层层递归为每个属性添加代理,一次即可完成以上操作,性能上更好,并且原本的实现有一些数据更新不能监听到,但是 Proxy 可以完美监听到任何方式的数据改变,唯一缺陷就是浏览器的兼容性不好。
script 标签中 defer 和 async 的区别
如果没有 defer 或 async 属性,浏览器会立即加载并执行相应的脚本。它不会等待后续加载的文档元素,读取到就会开始加载和执行,这样就阻塞了后续文档的加载。
defer 和 async 属性都是去异步加载外部的 JS 脚本文件,它们都不会阻塞页面的解析,其区别如下:
- 执行顺序: 多个带 async 属性的标签,不能保证加载的顺序;多个带 defer 属性的标签,按照加载顺序执行; 
- 脚本是否并行执行:async 属性,表示后续文档的加载和执行与 js 脚本的加载和执行是并行进行的,即异步执行;defer 属性,加载后续文档的过程和 js 脚本的加载(此时仅加载不执行)是并行进行的(异步),js 脚本需要等到文档所有元素解析完成之后才执行,DOMContentLoaded 事件触发执行之前。 
Promise.all
描述:所有 promise 的状态都变成 fulfilled,就会返回一个状态为 fulfilled 的数组(所有promise 的 value)。只要有一个失败,就返回第一个状态为 rejected 的 promise 实例的 reason。
实现:
 Promise.all = function(promises) {    return new Promise((resolve, reject) => {        if(Array.isArray(promises)) {            if(promises.length === 0) return resolve(promises);            let result = [];            let count = 0;            promises.forEach((item, index) => {                Promise.resolve(item).then(                    value => {                        count++;                        result[index] = value;                        if(count === promises.length) resolve(result);                    },                     reason => reject(reason)                );            })        }        else return reject(new TypeError("Argument is not iterable"));    });}
   复制代码
 ES6 新特性
 1.ES6引入来严格模式    变量必须声明后在使用    函数的参数不能有同名属性, 否则报错    不能使用with语句 (说实话我基本没用过)    不能对只读属性赋值, 否则报错    不能使用前缀0表示八进制数,否则报错 (说实话我基本没用过)    不能删除不可删除的数据, 否则报错    不能删除变量delete prop, 会报错, 只能删除属性delete global[prop]    eval不会在它的外层作用域引入变量    eval和arguments不能被重新赋值    arguments不会自动反映函数参数的变化    不能使用arguments.caller (说实话我基本没用过)    不能使用arguments.callee (说实话我基本没用过)    禁止this指向全局对象    不能使用fn.caller和fn.arguments获取函数调用的堆栈 (说实话我基本没用过)    增加了保留字(比如protected、static和interface)
2.关于let和const新增的变量声明
3.变量的解构赋值
4.字符串的扩展    includes():返回布尔值,表示是否找到了参数字符串。    startsWith():返回布尔值,表示参数字符串是否在原字符串的头部。    endsWith():返回布尔值,表示参数字符串是否在原字符串的尾部。5.数值的扩展    Number.isFinite()用来检查一个数值是否为有限的(finite)。    Number.isNaN()用来检查一个值是否为NaN。6.函数的扩展    函数参数指定默认值7.数组的扩展    扩展运算符8.对象的扩展    对象的解构9.新增symbol数据类型
10.Set 和 Map 数据结构     ES6 提供了新的数据结构 Set。它类似于数组,但是成员的值都是唯一的,没有重复的值。 Set 本身是一个构造函数,用来生成 Set 数据结构。
    Map它类似于对象,也是键值对的集合,但是“键”的范围不限于字符串,各种类型的值(包括对象)都可以当作键。11.Proxy    Proxy 可以理解成,在目标对象之前架设一层“拦截”,外界对该对象的访问    都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。    Proxy 这个词的原意是代理,用在这里表示由它来“代理”某些操作,可以译为“代理器”。    Vue3.0使用了proxy12.Promise    Promise 是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更合理和更强大。    特点是:        对象的状态不受外界影响。        一旦状态改变,就不会再变,任何时候都可以得到这个结果。13.async 函数     async函数对 Generator 函数的区别:    (1)内置执行器。    Generator 函数的执行必须靠执行器,而async函数自带执行器。也就是说,async函数的执行,与普通函数一模一样,只要一行。    (2)更好的语义。    async和await,比起星号和yield,语义更清楚了。async表示函数里有异步操作,await表示紧跟在后面的表达式需要等待结果。    (3)正常情况下,await命令后面是一个 Promise 对象。如果不是,会被转成一个立即resolve的 Promise 对象。    (4)返回值是 Promise。    async函数的返回值是 Promise 对象,这比 Generator 函数的返回值是 Iterator 对象方便多了。你可以用then方法指定下一步的操作。14.Class     class跟let、const一样:不存在变量提升、不能重复声明...    ES6 的class可以看作只是一个语法糖,它的绝大部分功能    ES5 都可以做到,新的class写法只是让对象原型的写法更加清晰、更像面向对象编程的语法而已。15.Module    ES6 的模块自动采用严格模式,不管你有没有在模块头部加上"use strict";。    import和export命令以及export和export default的区别
   复制代码
 偏函数
什么是偏函数?偏函数就是将一个 n 参的函数转换成固定 x 参的函数,剩余参数(n - x)将在下次调用全部传入。举个例子:
 function add(a, b, c) {    return a + b + c}let partialAdd = partial(add, 1)partialAdd(2, 3)
   复制代码
 
发现没有,其实偏函数和函数柯里化有点像,所以根据函数柯里化的实现,能够能很快写出偏函数的实现:
 function partial(fn, ...args) {    return (...arg) => {        return fn(...args, ...arg)    }}
   复制代码
 
如上这个功能比较简单,现在我们希望偏函数能和柯里化一样能实现占位功能,比如:
 function clg(a, b, c) {    console.log(a, b, c)}let partialClg = partial(clg, '_', 2)partialClg(1, 3)  // 依次打印:1, 2, 3
   复制代码
 
_ 占的位其实就是 1 的位置。相当于:partial(clg, 1, 2),然后 partialClg(3)。明白了原理,我们就来写实现:
 function partial(fn, ...args) {    return (...arg) => {        args[index] =         return fn(...args, ...arg)    }}
   复制代码
 数组扁平化
ES5 递归写法 —— isArray()、concat()
 function flat11(arr) {    var res = [];    for (var i = 0; i < arr.length; i++) {        if (Array.isArray(arr[i])) {            res = res.concat(flat11(arr[i]));        } else {            res.push(arr[i]);        }    }    return res;}
   复制代码
 
如果想实现第二个参数(指定“拉平”的层数),可以这样实现,后面的几种可以自己类似实现:
 function flat(arr, level = 1) {    var res = [];    for(var i = 0; i < arr.length; i++) {        if(Array.isArray(arr[i]) || level >= 1) {            res = res.concat(flat(arr[i]), level - 1);        }        else {            res.push(arr[i]);        }    }    return res;}
   复制代码
 ES6 递归写法 — reduce()、concat()、isArray()
 function flat(arr) {    return arr.reduce(        (pre, cur) => pre.concat(Array.isArray(cur) ? flat(cur) : cur), []    );}
   复制代码
 ES6 迭代写法 — 扩展运算符(...)、some()、concat()、isArray()
ES6 的扩展运算符(...) 只能扁平化一层
 function flat(arr) {    return [].concat(...arr);}
   复制代码
 
全部扁平化:遍历原数组,若arr中含有数组则使用一次扩展运算符,直至没有为止。
 function flat(arr) {    while(arr.some(item => Array.isArray(item))) {        arr = [].concat(...arr);    }    return arr;}
   复制代码
 toString/join & split
调用数组的 toString()/join() 方法(它会自动扁平化处理),将数组变为字符串然后再用 split 分割还原为数组。由于 split 分割后形成的数组的每一项值为字符串,所以需要用一个map方法遍历数组将其每一项转换为数值型。
 function flat(arr){    return arr.toString().split(',').map(item => Number(item));    // return arr.join().split(',').map(item => Number(item));}
   复制代码
 使用正则
JSON.stringify(arr).replace(/[|]/g, '') 会先将数组arr序列化为字符串,然后使用 replace() 方法将字符串中所有的[ 或 ] 替换成空字符,从而达到扁平化处理,此时的结果为 arr 不包含 [] 的字符串。最后通过JSON.parse() 解析字符串。
 function flat(arr) {    return JSON.parse("[" + JSON.stringify(arr).replace(/\[|\]/g,'') + "]");}
   复制代码
 类数组转化为数组
类数组是具有 length 属性,但不具有数组原型上的方法。常见的类数组有 arguments、DOM 操作方法返回的结果(如document.querySelectorAll('div'))等。
扩展运算符(...)
注意:扩展运算符只能作用于 iterable 对象,即拥有 Symbol(Symbol.iterator) 属性值。
Array.from()
 let arr = Array.from(arrayLike);
   复制代码
 Array.prototype.slice.call()
 let arr = Array.prototype.slice.call(arrayLike);
   复制代码
 Array.apply()
 let arr = Array.apply(null, arrayLike);
   复制代码
 concat + apply
 let arr = Array.prototype.concat.apply([], arrayLike);
   复制代码
 
参考 前端进阶面试题详细解答
代码输出结果
 console.log('1');
setTimeout(function() {    console.log('2');    process.nextTick(function() {        console.log('3');    })    new Promise(function(resolve) {        console.log('4');        resolve();    }).then(function() {        console.log('5')    })})process.nextTick(function() {    console.log('6');})new Promise(function(resolve) {    console.log('7');    resolve();}).then(function() {    console.log('8')})
setTimeout(function() {    console.log('9');    process.nextTick(function() {        console.log('10');    })    new Promise(function(resolve) {        console.log('11');        resolve();    }).then(function() {        console.log('12')    })})
   复制代码
 
输出结果如下:
(1)第一轮事件循环流程分析如下:
- 整体 script 作为第一个宏任务进入主线程,遇到- console.log,输出 1。
 
- 遇到- setTimeout,其回调函数被分发到宏任务 Event Queue 中。暂且记为- setTimeout1。
 
- 遇到- process.nextTick(),其回调函数被分发到微任务 Event Queue 中。记为- process1。
 
- 遇到- Promise,- new Promise直接执行,输出 7。- then被分发到微任务 Event Queue 中。记为- then1。
 
- 又遇到了- setTimeout,其回调函数被分发到宏任务 Event Queue 中,记为- setTimeout2。
 
上表是第一轮事件循环宏任务结束时各 Event Queue 的情况,此时已经输出了 1 和 7。发现了process1和then1两个微任务:
- 执行- process1,输出 6。
 
- 执行- then1,输出 8。
 
第一轮事件循环正式结束,这一轮的结果是输出 1,7,6,8。
(2)第二轮时间循环从**setTimeout1**宏任务开始:
第二轮事件循环宏任务结束,发现有process2和then2两个微任务可以执行:
第二轮事件循环结束,第二轮输出 2,4,3,5。
(3)第三轮事件循环开始,此时只剩 setTimeout2 了,执行。
第三轮事件循环宏任务执行结束,执行两个微任务process3和then3:
第三轮事件循环结束,第三轮输出 9,11,10,12。
整段代码,共进行了三次事件循环,完整的输出为 1,7,6,8,2,4,3,5,9,11,10,12。
说一下原型链和原型链的继承吧
 function Person(name) {  this.name = name;}
Person.prototype.constructor = Person
   复制代码
 
- 在发生 new 构造函数调用时,会将创建的新对象的 [[Prototype]] 链接到 Person.prototype 指向的对象,这个机制就被称为原型链继承 
- 方法定义在原型上,属性定义在构造函数上 
- 首先要说一下 JS 原型和实例的关系:每个构造函数 (constructor)都有一个原型对象(prototype),这个原型对象包含一个指向此构造函数的指针属性,通过 new 进行构造函数调用生成的实例,此实例包含一个指向原型对象的指针,也就是通过 [[Prototype]] 链接到了这个原型对象 
- 然后说一下 JS 中属性的查找:当我们试图引用实例对象的某个属性时,是按照这样的方式去查找的,首先查找实例对象上是否有这个属性,如果没有找到,就去构造这个实例对象的构造函数的 prototype 所指向的对象上去查找,如果还找不到,就从这个 prototype 对象所指向的构造函数的 prototype 原型对象上去查找 
- 什么是原型链:这样逐级查找形似一个链条,且通过  [[Prototype]] 属性链接,所以被称为原型链 
- 什么是原型链继承,类比类的继承:当有两个构造函数 A 和 B,将一个构造函数 A 的原型对象的,通过其 [[Prototype]] 属性链接到另外一个 B 构造函数的原型对象时,这个过程被称之为原型继承。 
** 标准答案更正确的解释**
什么是原型链?
当对象查找一个属性的时候,如果没有在自身找到,那么就会查找自身的原型,如果原型还没有找到,那么会继续查找原型的原型,直到找到 Object.prototype 的原型时,此时原型为 null,查找停止。这种通过 通过原型链接的逐级向上的查找链被称为原型链
什么是原型继承?
一个对象可以使用另外一个对象的属性或者方法,就称之为继承。具体是通过将这个对象的原型设置为另外一个对象,这样根据原型链的规则,如果查找一个对象属性且在自身不存在时,就会查找另外一个对象,相当于一个对象可以使用另外一个对象的属性和方法了。
手写题:Promise 原理
 class MyPromise {  constructor(fn) {    this.callbacks = [];    this.state = "PENDING";    this.value = null;
    fn(this._resolve.bind(this), this._reject.bind(this));  }
  then(onFulfilled, onRejected) {    return new MyPromise((resolve, reject) =>      this._handle({        onFulfilled: onFulfilled || null,        onRejected: onRejected || null,        resolve,        reject,      })    );  }
  catch(onRejected) {    return this.then(null, onRejected);  }
  _handle(callback) {    if (this.state === "PENDING") {      this.callbacks.push(callback);
      return;    }
    let cb =      this.state === "FULFILLED" ? callback.onFulfilled : callback.onRejected;    if (!cb) {      cb = this.state === "FULFILLED" ? callback.resolve : callback.reject;      cb(this.value);
      return;    }
    let ret;
    try {      ret = cb(this.value);      cb = this.state === "FULFILLED" ? callback.resolve : callback.reject;    } catch (error) {      ret = error;      cb = callback.reject;    } finally {      cb(ret);    }  }
  _resolve(value) {    if (value && (typeof value === "object" || typeof value === "function")) {      let then = value.then;
      if (typeof then === "function") {        then.call(value, this._resolve.bind(this), this._reject.bind(this));
        return;      }    }
    this.state === "FULFILLED";    this.value = value;    this.callbacks.forEach((fn) => this._handle(fn));  }
  _reject(error) {    this.state === "REJECTED";    this.value = error;    this.callbacks.forEach((fn) => this._handle(fn));  }}
const p1 = new Promise(function (resolve, reject) {  setTimeout(() => reject(new Error("fail")), 3000);});
const p2 = new Promise(function (resolve, reject) {  setTimeout(() => resolve(p1), 1000);});
p2.then((result) => console.log(result)).catch((error) => console.log(error));
   复制代码
 代码输出结果
 var myObject = {    foo: "bar",    func: function() {        var self = this;        console.log(this.foo);          console.log(self.foo);          (function() {            console.log(this.foo);              console.log(self.foo);          }());    }};myObject.func();
   复制代码
 
输出结果:bar bar undefined bar
解析:
- 首先 func 是由 myObject 调用的,this 指向 myObject。又因为 var self = this;所以 self 指向 myObject。 
- 这个立即执行匿名函数表达式是由 window 调用的,this 指向 window 。立即执行匿名函数的作用域处于 myObject.func 的作用域中,在这个作用域找不到 self 变量,沿着作用域链向上查找 self 变量,找到了指向 myObject 对象的 self。 
setInterval 模拟 setTimeout
描述:使用setInterval模拟实现setTimeout的功能。
思路:setTimeout的特性是在指定的时间内只执行一次,我们只要在setInterval内部执行 callback 之后,把定时器关掉即可。
实现:
 const mySetTimeout = (fn, time) => {    let timer = null;    timer = setInterval(() => {        // 关闭定时器,保证只执行一次fn,也就达到了setTimeout的效果了        clearInterval(timer);        fn();    }, time);    // 返回用于关闭定时器的方法    return () => clearInterval(timer);}
// 测试const cancel = mySetTimeout(() => {    console.log(1);}, 1000);  // 一秒后打印 1
   复制代码
 为什么 0.1+0.2 ! == 0.3,如何让其相等
在开发过程中遇到类似这样的问题:
 let n1 = 0.1, n2 = 0.2console.log(n1 + n2)  // 0.30000000000000004
   复制代码
 
这里得到的不是想要的结果,要想等于 0.3,就要把它进行转化:
 (n1 + n2).toFixed(2) // 注意,toFixed为四舍五入
   复制代码
 
toFixed(num) 方法可把 Number 四舍五入为指定小数位数的数字。那为什么会出现这样的结果呢?
计算机是通过二进制的方式存储数据的,所以计算机计算 0.1+0.2 的时候,实际上是计算的两个数的二进制的和。0.1 的二进制是0.0001100110011001100...(1100 循环),0.2 的二进制是:0.00110011001100...(1100 循环),这两个数的二进制都是无限循环的数。那 JavaScript 是如何处理无限循环的二进制小数呢?
一般我们认为数字包括整数和小数,但是在 JavaScript 中只有一种数字类型:Number,它的实现遵循 IEEE 754 标准,使用 64 位固定长度来表示,也就是标准的 double 双精度浮点数。在二进制科学表示法中,双精度浮点数的小数部分最多只能保留 52 位,再加上前面的 1,其实就是保留 53 位有效数字,剩余的需要舍去,遵从“0 舍 1 入”的原则。
根据这个原则,0.1 和 0.2 的二进制数相加,再转化为十进制数就是:0.30000000000000004。
下面看一下双精度数是如何保存的:
- 第一部分(蓝色):用来存储符号位(sign),用来区分正负数,0 表示正数,占用 1 位 
- 第二部分(绿色):用来存储指数(exponent),占用 11 位 
- 第三部分(红色):用来存储小数(fraction),占用 52 位 
对于 0.1,它的二进制为:
 0.00011001100110011001100110011001100110011001100110011001 10011...
   复制代码
 
转为科学计数法(科学计数法的结果就是浮点数):
 1.1001100110011001100110011001100110011001100110011001*2^-4
   复制代码
 
可以看出 0.1 的符号位为 0,指数位为-4,小数位为:
 1001100110011001100110011001100110011001100110011001
   复制代码
 
那么问题又来了,指数位是负数,该如何保存呢?
IEEE 标准规定了一个偏移量,对于指数部分,每次都加这个偏移量进行保存,这样即使指数是负数,那么加上这个偏移量也就是正数了。由于 JavaScript 的数字是双精度数,这里就以双精度数为例,它的指数部分为 11 位,能表示的范围就是 0~2047,IEEE 固定双精度数的偏移量为 1023。
- 当指数位不全是 0 也不全是 1 时(规格化的数值),IEEE 规定,阶码计算公式为 e-Bias。 此时 e 最小值是 1,则 1-1023= -1022,e 最大值是 2046,则 2046-1023=1023,可以看到,这种情况下取值范围是- -1022~1013。
 
- 当指数位全部是 0 的时候(非规格化的数值),IEEE 规定,阶码的计算公式为 1-Bias,即 1-1023= -1022。 
- 当指数位全部是 1 的时候(特殊值),IEEE 规定这个浮点数可用来表示 3 个特殊值,分别是正无穷,负无穷,NaN。 具体的,小数位不为 0 的时候表示 NaN;小数位为 0 时,当符号位 s=0 时表示正无穷,s=1 时候表示负无穷。 
对于上面的 0.1 的指数位为-4,-4+1023 = 1019 转化为二进制就是:1111111011.
所以,0.1 表示为:
 0 1111111011 1001100110011001100110011001100110011001100110011001
   复制代码
 
说了这么多,是时候该最开始的问题了,如何实现 0.1+0.2=0.3 呢?
对于这个问题,一个直接的解决方法就是设置一个误差范围,通常称为“机器精度”。对 JavaScript 来说,这个值通常为 2-52,在 ES6 中,提供了Number.EPSILON属性,而它的值就是 2-52,只要判断0.1+0.2-0.3是否小于Number.EPSILON,如果小于,就可以判断为 0.1+0.2 ===0.3
 function numberepsilon(arg1,arg2){                     return Math.abs(arg1 - arg2) < Number.EPSILON;        }        
console.log(numberepsilon(0.1 + 0.2, 0.3)); // true
   复制代码
 代码输出结果
 function fn1(){  console.log('fn1')}var fn2
fn1()fn2()
fn2 = function() {  console.log('fn2')}
fn2()
   复制代码
 
输出结果:
 fn1Uncaught TypeError: fn2 is not a functionfn2
   复制代码
 
这里也是在考察变量提升,关键在于第一个 fn2(),这时 fn2 仍是一个 undefined 的变量,所以会报错 fn2 不是一个函数。
setTimeout 模拟 setInterval
描述:使用setTimeout模拟实现setInterval的功能。
实现:
 const mySetInterval(fn, time) {    let timer = null;    const interval = () => {        timer = setTimeout(() => {            fn();  // time 时间之后会执行真正的函数fn            interval();  // 同时再次调用interval本身        }, time)    }    interval();  // 开始执行    // 返回用于关闭定时器的函数    return () => clearTimeout(timer);}
// 测试const cancel = mySetInterval(() => console.log(1), 400);setTimeout(() => {    cancel();}, 1000);  // 打印两次1
   复制代码
 代码输出结果
 function foo() {  console.log( this.a );}
function doFoo() {  foo();}
var obj = {  a: 1,  doFoo: doFoo};
var a = 2; obj.doFoo()
   复制代码
 
输出结果:2
在 Javascript 中,this 指向函数执行时的当前对象。在执行 foo 的时候,执行环境就是 doFoo 函数,执行环境为全局。所以,foo 中的 this 是指向 window 的,所以会打印出 2。
代码输出结果
 function runAsync (x) {  const p = new Promise(r => setTimeout(() => r(x, console.log(x)), 1000))  return p}Promise.race([runAsync(1), runAsync(2), runAsync(3)])  .then(res => console.log('result: ', res))  .catch(err => console.log(err))
   复制代码
 
输出结果如下:
then 只会捕获第一个成功的方法,其他的函数虽然还会继续执行,但是不是被 then 捕获了。
单行、多行文本溢出隐藏
 overflow: hidden;            // 溢出隐藏text-overflow: ellipsis;      // 溢出用省略号显示white-space: nowrap;         // 规定段落中的文本不进行换行
   复制代码
 
 overflow: hidden;            // 溢出隐藏text-overflow: ellipsis;     // 溢出用省略号显示display:-webkit-box;         // 作为弹性伸缩盒子模型显示。-webkit-box-orient:vertical; // 设置伸缩盒子的子元素排列方式:从上到下垂直排列-webkit-line-clamp:3;        // 显示的行数
   复制代码
 
注意:由于上面的三个属性都是 CSS3 的属性,没有浏览器可以兼容,所以要在前面加一个-webkit- 来兼容一部分浏览器。
如何解释 React 的渲染流程
- React 的渲染过程大致一致,但协调并不相同,以 - React 16为分界线,分为- Stack Reconciler和- Fiber Reconciler。这里的协调从狭义上来讲,特指 React 的 diff 算法,广义上来讲,有时候也指 React 的- reconciler模块,它通常包含了- diff算法和一些公共逻辑。
 
- 回到 - Stack Reconciler中,- Stack Reconciler的- 核心调度方式是递归。- 调度的基本处理单位是事务,它的事务基类是- Transaction,这里的- 事务是 React 团队从后端开发中加入的概念。在 React 16 以前,- 挂载主要通过 ReactMount 模块完成,更新通过- ReactUpdate模块完成,模块之间相互分离,落脚执行点也是事务。
 
- 在 - React 16及以后,协调改为了- Fiber Reconciler。它的调度方式主要有两个特点,- 第一个是协作式多任务模式,在这个模式下,线程会定时放弃自己的运行权利,交还给主线程,通过- requestIdleCallback实现。- 第二个特点是策略优先级,调度任务通过标记- tag的方式分优先级执行,比如动画,或者标记为- high的任务可以优先执行。- Fiber Reconciler的基本单位是- Fiber,- Fiber基于过去的- React Element提供了二次封装,提供了指向父、子、兄弟节点的引用,为- diff工作的双链表实现提供了基础。
 
- 在新的架构下,整个生命周期被划分为 - Render 和 Commit 两个阶段。- Render 阶段的执行特点是可中断、可停止、无副作用,主要是通过构造- workInProgress树计算出- diff。以- current树为基础,将每个- Fiber作为一个基本单位,自下而上逐个节点检查并构造 workInProgress 树。这个过程不再是递归,而是基于循环来完成
 
- 在执行上通过 - requestIdleCallback来调度执行每组任务,每组中的每个计算任务被称为- work,每个- work完成后确认是否有优先级更高的- work需要插入,如果有就让位,没有就继续。优先级通常是标记为动画或者- high的会先处理。每完成一组后,将调度权交回主线程,直到下一次- requestIdleCallback调用,再继续构建- workInProgress树
 
- 在 - commit阶段需要处理- effect列表,这里的- effect列表包含了根据- diff 更新 DOM 树、- 回调生命周期、- 响应 ref等。
 
- 但一定要注意,这个阶段是同步执行的,不可中断暂停,所以不要在 - componentDidMount、- componentDidUpdate、- componentWiilUnmount中去执行重度消耗算力的任务
 
- 如果只是一般的应用场景,比如管理后台、H5 展示页等,两者性能差距并不大,但在动画、画布及手势等场景下,- Stack Reconciler的设计会占用占主线程,造成卡顿,而- fiber reconciler的设计则能带来高性能的表现
 
水平垂直居中的实现
 .parent {    position: relative;} .child {    position: absolute;    left: 50%;    top: 50%;    transform: translate(-50%,-50%);}
   复制代码
 
 .parent {    position: relative;}
.child {    position: absolute;    top: 0;    bottom: 0;    left: 0;    right: 0;    margin: auto;}
   复制代码
 
 .parent {    position: relative;}
.child {    position: absolute;    top: 50%;    left: 50%;    margin-top: -50px;     /* 自身 height 的一半 */    margin-left: -50px;    /* 自身 width 的一半 */}
   复制代码
 
 .parent {    display: flex;    justify-content:center;    align-items:center;}
   复制代码
 代码输出结果
 function Person(name) {    this.name = name}var p2 = new Person('king');console.log(p2.__proto__) //Person.prototypeconsole.log(p2.__proto__.__proto__) //Object.prototypeconsole.log(p2.__proto__.__proto__.__proto__) // nullconsole.log(p2.__proto__.__proto__.__proto__.__proto__)//null后面没有了,报错console.log(p2.__proto__.__proto__.__proto__.__proto__.__proto__)//null后面没有了,报错console.log(p2.constructor)//Personconsole.log(p2.prototype)//undefined p2是实例,没有prototype属性console.log(Person.constructor)//Function 一个空函数console.log(Person.prototype)//打印出Person.prototype这个对象里所有的方法和属性console.log(Person.prototype.constructor)//Personconsole.log(Person.prototype.__proto__)// Object.prototypeconsole.log(Person.__proto__) //Function.prototypeconsole.log(Function.prototype.__proto__)//Object.prototypeconsole.log(Function.__proto__)//Function.prototypeconsole.log(Object.__proto__)//Function.prototypeconsole.log(Object.prototype.__proto__)//null
   复制代码
 
这道义题目考察原型、原型链的基础,记住就可以了。
评论