js 是单线程语言,事件循环是为了协调事件、用户交互、脚本、UI 渲染和网络处理等行为,防止主线程阻塞
一些具有回调函数的事件将进入执行栈中,等待主线程读取,等待主线程读取,遵循先进先出原则。主线程循环:即主线程会不停的从执行栈中读取事件,会执行完所有栈中的同步代码。当遇到一个异步事件后,并不会一直等待异步事件返回结果,而是会将这个事件挂在与执行栈不同的队列中,我们称之为任务队列(Task Queue)。当主线程将执行栈中所有的代码执行完之后,主线程将会去查看任务队列是否有任务。如果有,那么主线程会依次执行那些任务队列中的回调函数。
异步任务分为 宏任务(macrotask) 与 微任务 (microtask)
宏任务(macrotask): script(整体代码)、setTimeout、setInterval、UI 渲染、 I/O、postMessage、 MessageChannel、setImmediate(Node.js 环境)
微任务(microtask): Promise、 MutaionObserver、process.nextTick(Node.js 环境)
执行栈选择最先进入队列的宏任务
然后执行微任务
...循环执行完全部任务(宏任务-微任务-宏任务)
字符串(string)、数值(number)、布尔值(boolean)、undefined、空值(null)、symbol
对象(Object)、数组(Array)、函数(Function)
计算出 Virtual DOM 中真正变化的部分,并只针对该部分进行原生 DOM 操作,而非重新渲染整个页面
两棵树只对同一层级节点进行比较,只要该节点不存在了,那么该节点与其所有子节点会被完全删除,不在进行进一步比较
只需要遍历一次,便完成对整个 DOM 树的比较
同类型组件,组件 A 转化为了组件 B,如果 virtual DOM 无变化,可以通过 shouldComponentUpdate()方法优化
不同类型的组件,那么 diff 算法会把要改变的组件判断为 dirty component,从而替换整个组件的所有节点
key 往前移动的节点不进行任何操作,所以当把最后一个节点移动到头部时,性能损耗最大
插入: 新的组件不在原来的集合中,而是全新的节点,则对集合进行插入操作
删除: 组件已经在集合中,但集合已经更新,此时节点就需要删除
移动: 组件已经存在于集合中,并且集合更新时,组件并没有发生更新,只是位置发生改变(同一层的节点添加唯一 key 进行区分,并且移动,当新集合中位置在旧集合之后时,需要移动)
事件捕获阶段
目标阶段
事件冒泡阶段
由内向外进行事件传播,直到根结点
从根结点开始,由外向内进行事件传播,直到目标元素
利用事件冒泡的原理,将事件处理器添加到父元素,等待子元素事件冒泡,通过target属性能区分子元素,从而对不同子元素做不同处理
减少了事件处理器,节省内存
子类的prototype设置为父类的实例
function Parent() {this.arr = [1, 2]}function Child(name) {this.name = name}Child.prototype = new Parent()const c1 = new Child('x1')const c2 = new Child('x2')console.log(c1.arr) // [1,2]c1.arr.push(3)console.log(c2.arr) // [1,2,3]
子类共享同一份实例,当父类实例中存在引用类型属性时,其中一个子类修改后,会影响所有子类
无法传参
将父类构造函数的内容复制给了子类的构造函数,子类不会相互影响,可以传参
function Parent() {this.arr = [1, 2]}function Child(name) {Parent.call(this)this.name = name}const c1 = new Child('x1')const c2 = new Child('x2')console.log(c1.arr) // [1,2]c1.arr.push(3)console.log(c2.arr) // [1,2]
父类方法不能复用
原型链继承与构造函数继承的组合
function Parent(age) {this.age = age}Parent.prototype.say = function () {console.log('hello')}function Child(name) {Parent.call(this, 40)this.name = name}Child.prototype = new Parent()const c1 = new Child('x1')const c2 = new Child('x2')
父类方法可以复用
父类引用属性不会被共享
子类构造实例可以向父类传参
调用了两次父类构造函数
父类属性存在子类实例以及子类实例原型中
父类原型中的属性会覆盖子类原型中的属性
浅复制参数对象
const obj = { a: 1 }function clone(o) {function F() {}F.prototype = oreturn new F()}const c1 = clone(obj)console.log(c1.a) // 1
子类实例共享父类引用属性
无法传递参数
在原型式继承基础上增强
const obj = { a: 1 }function clone(o) {function F() {}F.prototype = oconst f = new F()f.say = function () {console.log('hello')}return f}const c1 = clone(obj)c1.a // 1c1.say()
替换子类原型为父类原型,构造函数改为子类构造函数,实现原型属性的继承
子类构造函数中调用父类构造函数,实现父类属性继承
function Parent(age) {this.age = agethis.arr = [1]}Parent.prototype.c = 'c1'Parent.prototype.say = function () {console.log('hello')}function Child(name) {Parent.call(this, 22)this.name = name}function clone(o) {function F() {}F.prototype = oreturn new F()}function inherit(child, parent) {let prototype = clone(parent.prototype)prototype.constructor = childchild.prototype = prototye}inherit(Child, Parent)const c1 = new Child('xjq')const c2 = new Child('xjq')