js

js 事件循环

js 是单线程语言,事件循环是为了协调事件、用户交互、脚本、UI 渲染和网络处理等行为,防止主线程阻塞

主线程

一些具有回调函数的事件将进入执行栈中,等待主线程读取,等待主线程读取,遵循先进先出原则。主线程循环:即主线程会不停的从执行栈中读取事件,会执行完所有栈中的同步代码。当遇到一个异步事件后,并不会一直等待异步事件返回结果,而是会将这个事件挂在与执行栈不同的队列中,我们称之为任务队列(Task Queue)。当主线程将执行栈中所有的代码执行完之后,主线程将会去查看任务队列是否有任务。如果有,那么主线程会依次执行那些任务队列中的回调函数。

宏任务与微任务

异步任务分为 宏任务(macrotask) 与 微任务 (microtask)

  1. 宏任务(macrotask): script(整体代码)、setTimeout、setInterval、UI 渲染、 I/O、postMessage、 MessageChannel、setImmediate(Node.js 环境)

  2. 微任务(microtask): Promise、 MutaionObserver、process.nextTick(Node.js 环境)

Event Loop(事件循环)

  1. 执行栈选择最先进入队列的宏任务

  2. 然后执行微任务

  3. ...循环执行完全部任务(宏任务-微任务-宏任务)

数据类型

值类型(基本类型)

字符串(string)、数值(number)、布尔值(boolean)、undefined、空值(null)、symbol

引用类型

对象(Object)、数组(Array)、函数(Function)

diff 算法

计算出 Virtual DOM 中真正变化的部分,并只针对该部分进行原生 DOM 操作,而非重新渲染整个页面

tree diff

两棵树只对同一层级节点进行比较,只要该节点不存在了,那么该节点与其所有子节点会被完全删除,不在进行进一步比较

只需要遍历一次,便完成对整个 DOM 树的比较

component diff

同类型组件,组件 A 转化为了组件 B,如果 virtual DOM 无变化,可以通过 shouldComponentUpdate()方法优化

不同类型的组件,那么 diff 算法会把要改变的组件判断为 dirty component,从而替换整个组件的所有节点

element diff

key 往前移动的节点不进行任何操作,所以当把最后一个节点移动到头部时,性能损耗最大

  • 插入: 新的组件不在原来的集合中,而是全新的节点,则对集合进行插入操作

  • 删除: 组件已经在集合中,但集合已经更新,此时节点就需要删除

  • 移动: 组件已经存在于集合中,并且集合更新时,组件并没有发生更新,只是位置发生改变(同一层的节点添加唯一 key 进行区分,并且移动,当新集合中位置在旧集合之后时,需要移动)

原型链

事件

事件流

  1. 事件捕获阶段

  2. 目标阶段

  3. 事件冒泡阶段

事件冒泡

由内向外进行事件传播,直到根结点

事件捕获

从根结点开始,由外向内进行事件传播,直到目标元素

事件代理

利用事件冒泡的原理,将事件处理器添加到父元素,等待子元素事件冒泡,通过target属性能区分子元素,从而对不同子元素做不同处理

减少了事件处理器,节省内存

模块化

ES6 module

commonjs

V8垃圾回收机制

继承

原型链继承

子类的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 = o
return new F()
}
const c1 = clone(obj)
console.log(c1.a) // 1

缺点

  • 子类实例共享父类引用属性

  • 无法传递参数

寄生式继承

在原型式继承基础上增强

const obj = { a: 1 }
function clone(o) {
function F() {}
F.prototype = o
const f = new F()
f.say = function () {
console.log('hello')
}
return f
}
const c1 = clone(obj)
c1.a // 1
c1.say()

寄生组合式继承(perfect)

  1. 替换子类原型为父类原型,构造函数改为子类构造函数,实现原型属性的继承

  2. 子类构造函数中调用父类构造函数,实现父类属性继承

function Parent(age) {
this.age = age
this.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 = o
return new F()
}
function inherit(child, parent) {
let prototype = clone(parent.prototype)
prototype.constructor = child
child.prototype = prototye
}
inherit(Child, Parent)
const c1 = new Child('xjq')
const c2 = new Child('xjq')