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)
属性值。
let arr = [...arrayLike]
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') })})
输入后果如下:
176824359111012
(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 | 微工作Event Queue |
---|---|
setTimeout1 | process1 |
setTimeout2 | then1 |
上表是第一轮事件循环宏工作完结时各Event Queue的状况,此时曾经输入了1和7。发现了process1
和then1
两个微工作:
- 执行
process1
,输入6。 - 执行
then1
,输入8。
第一轮事件循环正式完结,这一轮的后果是输入1,7,6,8。
(2)第二轮工夫循环从**setTimeout1**
宏工作开始:
- 首先输入2。接下来遇到了
process.nextTick()
,同样将其散发到微工作Event Queue中,记为process2
。 new Promise
立刻执行输入4,then
也散发到微工作Event Queue中,记为then2
。
宏工作Event Queue | 微工作Event Queue |
---|---|
setTimeout2 | process2 |
then2 |
第二轮事件循环宏工作完结,发现有process2
和then2
两个微工作能够执行:
- 输入3。
- 输入5。
第二轮事件循环完结,第二轮输入2,4,3,5。
(3)第三轮事件循环开始,此时只剩setTimeout2了,执行。
- 间接输入9。
- 将
process.nextTick()
散发到微工作Event Queue中。记为process3
。 - 间接执行
new Promise
,输入11。 - 将
then
散发到微工作Event Queue中,记为then3
。
宏工作Event Queue | 微工作Event Queue |
---|---|
process3 | |
then3 |
第三轮事件循环宏工作执行完结,执行两个微工作process3
和then3
:
- 输入10。
- 输入12。
第三轮事件循环完结,第三轮输入9,11,10,12。
整段代码,共进行了三次事件循环,残缺的输入为1,7,6,8,2,4,3,5,9,11,10,12。
说一下原型链和原型链的继承吧
- 所有一般的 [[Prototype]] 链最终都会指向内置的 Object.prototype,其蕴含了 JavaScript 中许多通用的性能
- 为什么能创立 “类”,借助一种非凡的属性:所有的函数默认都会领有一个名为 prototype 的共有且不可枚举的属性,它会指向另外一个对象,这个对象通常被称为函数的原型
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 fn2fn1()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))
输入后果如下:
1'result: ' 123
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
的设计则能带来高性能的体现
程度垂直居中的实现
- 利用相对定位,先将元素的左上角通过top:50%和left:50%定位到页面的核心,而后再通过translate来调整元素的中心点到页面的核心。该办法须要思考浏览器兼容问题。
.parent { position: relative;} .child { position: absolute; left: 50%; top: 50%; transform: translate(-50%,-50%);}
- 利用相对定位,设置四个方向的值都为0,并将margin设置为auto,因为宽高固定,因而对应方向实现平分,能够实现程度和垂直方向上的居中。该办法实用于盒子有宽高的状况:
.parent { position: relative;}.child { position: absolute; top: 0; bottom: 0; left: 0; right: 0; margin: auto;}
- 利用相对定位,先将元素的左上角通过top:50%和left:50%定位到页面的核心,而后再通过margin负值来调整元素的中心点到页面的核心。该办法实用于盒子宽高已知的状况
.parent { position: relative;}.child { position: absolute; top: 50%; left: 50%; margin-top: -50px; /* 本身 height 的一半 */ margin-left: -50px; /* 本身 width 的一半 */}
- 应用flex布局,通过align-items:center和justify-content:center设置容器的垂直和程度方向上为居中对齐,而后它的子元素也能够实现垂直和程度的居中。该办法要思考兼容的问题,该办法在挪动端用的较多:
.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
这道义题目考查原型、原型链的根底,记住就能够了。