更新于 

JavaScript

数据类型

基本类型

numberstringundefinednullbooleansymbolbigint

引用类型

arrayobjectfunctiondate

判断

typeof

用来判断基本类型,对于null,则返回objectarrayobject等其他引用类型,返回object

instanceof

判断引用类型

其他

Object.prototype.toString.call([]) 也可以用来判断类型

this

一般情况下,this指向调用者,箭头函数的this,指向调用的父作用域

call、apply、bind

call:第二个参数之后是个列表
apply:第二个参数是个数组
bind:返回一个新的函数,并且只会改变一次this

作用域

函数内部可以访问外部数据,就形成了作用域链,但是外部没办法访问函数内部的数据

全局作用域

函数作用域

eval作用域

执行上下文

当js执行的时候,js会去创建一个全局执行上下文,添加到执行栈中,如果遇到函数的话,会创建一个函数的执行上下文(函数的执行上下文会包括当前的this,作用域等一些信息),也一并添加到栈中,如果该函数执行完成后,将从栈中移出,直到清空执行栈

闭包

原理

函数内部嵌套了一个新的函数,嵌套的函数对外部的函数造成了引用就形成了闭包

应用

  • 柯里化
  • 节流、防抖
  • compose 组合函数

垃圾回收

参考文章

[v8的堆空间]
[v8的堆空间]

新生代(副垃圾回收器)

作用

保存生存时间较短的对象

实现方式

新生代中用Scavenge算法来处理,所谓Scavenge算法,是把新生代空间对半划分为两个区域,一半是对象区域,一半是空闲区域。副垃圾回收器会把这些存活的对象复制到空闲区域中,同时它还会把这些对象有序地排列起来,所以这个复制过程,也就相当于完成了内存整理操作,复制后空闲区域就没有内存碎片了。完成复制后,对象区域与空闲区域进行角色翻转,也就是原来的对象区域变成空闲区域,原来的空闲区域变成了对象区域。这样就完成了垃圾对象的回收操作,同时这种角色翻转的操作还能让新生代中的这两块区域无限重复使用下去。

缺点

由于采用Scavenge算法,所以执行垃圾清理的时候,都需要把对象区域复制到空闲区域,每次都需要一定时间,所以一般新生代的空间会比较小

特点

因为新生代空间不大,所以很容易溢出,所以V8采用了对象晋升策略,如果2次垃圾回收之后依然存活,就会被移动到老生代区域

老生代(主垃圾回收器)

作用

保存时间较长的对象

特点

占用空间大、存活时间长

实现方式

主垃圾回收器是采用标记-清除(Mark-Sweep)的算法进行垃圾回收的。首先是标记过程阶段。标记阶段就是从一组根元素开始,递归遍历这组根元素,在这个遍历过程中,能到达的元素成为活动对象,没有到达的元素就可以判断为垃圾数据。对一块内存多次执行标记-清除算法之后,会产生大量不连续的内存碎片。而碎片过多会导致大对象无法分配到足够的连续内存,于是又产生了另一种算法 标记-整理(Mark-Compact),这个标记过程仍然与标记-清除算法里一样的,但后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动, 然后就直接清理掉端边界以外的内存。

代际假说

  • 大部分对象在内存中存在的时间很短,简单来说,就是很多对象已经分配内存,很快就变得不可访问;
  • 不死的对象,会活得更久。

工作流程

  1. 标记空间中活动对象和非活动对象。所谓活动对象就是还在使用的对象,非活动对象就是可以进行垃圾回收的对象
  2. 回收非活动对象所占据的内存。其实就是在所有的标记完成之后,统一清理内存中所有被标记为可回收的对象
  3. 做内存整理。一般来说,频繁回收对象后,内存中就会存在大量不连续空间,我们把这些不连续的内存空间称为内存碎片。当内存中出现了大量的内存碎片后,如果需要分配较大连续内存的时候,就有可能出现内存不足的情况,所以最后一步需要整理这些内存碎片,但这步其实是可选的,因为有的垃圾回收器不会产生内存碎片

全停顿

  • 每次执行垃圾回收的时候,都会暂停js的脚本执行,等垃圾回收完毕,再恢复执行,这种被称作全停顿
  • 新生代中,由于空间较小,且对象存活较少,所以影响不大,老生代的话,会容易出现卡顿
  • 为了降低老生代的垃圾回收而造成的卡顿,V8将标记过程分为一个个的子标记过程,同时让垃圾回收标记和JavaScript应用逻辑交替进行,直到标记阶段完成,我们把这个算法称为增量标记(Incremental Marking)算法

OOP

原型链

每个对象都有一个__proto__属性,表示自己的原型,每个对象的原型都指向该对象的原型对象prototype,而每个__proto__.constructor都指向该对象构造函数的实例,当我们查找的时候,会先沿着自身的属性去查找,找不到去原型上去查找,如果依然没有,则前往该对象上的原型对象进行查找,最终返回结果,如果没有,则返回null

继承

ES6继承

1
2
3
4
5
6
7
8
9
class Parent {

}

class Son extends Parent {
constructor () {
super()
}
}

原型链继承

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function SuperType() {
this.property = true
}

SuperType.prototype.getSuperTypeValue = function() {
return this.property
}

function SubType() {
this.subProperty = false
}

SubType.prototype = new SuperType()

SubType.prototype.getSubValue = function() {
return this.subProperty
}

const instance = new SubType()

console.log(instance.getSuperTypeValue) // true

缺点:多个继承的时候,不能对父类进行修改,否则多个子类会共享一个父类实例(因为所有子类的prototype都指向的是父类的实例)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function SuperType(){ 
this.colors = ["red", "blue", "green"];
}

function SubType(){}

SubType.prototype = new SuperType()

const instance1 = new SubType()

instance1.colors.push("black")

alert(instance1.colors); //"red, blue, green, black"

const instance2 = new SubType();

alert(instance2.colors); //"red, blue, green, black"

借用构造函数继承

通过在子类构造函数的内部调用父类的构造函数,从而实现继承

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//  借用构造函数继承
function SuperType () {
this.colors = ['red', 'blue', 'green']
}

function SubType () {
// 通过 call 来继承
SuperType.call(this)
}

const instance1 = new SubType()
const instance2 = new SubType()
instance1.colors.push('black')

console.log(instance1.colors) // ['red', 'blue', 'green', 'black']
console.log(instance2.colors) // ['red', 'blue', 'green']

缺点:子类只能拿到父类自身的属性和方法,无法拿到原型的方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
function SuperType(name) {
this.colors = ['red', 'blue', 'green']
this.name = name
this.run = function() {
console.log('run')
}
}

SuperType.prototype.test = function() {
console.log('test')
}

function SubType() {
// 通过 call 来继承
SuperType.call(this, 'test')
}

const instance1 = new SubType()
const instance2 = new SubType()
instance1.colors.push('black')

console.log(instance1.colors, instance1)
console.log(instance2.colors, instance2)
console.log(instance1.test()) // test is not a function

组合继承

组合继承是使用原型链实现对原型属性和方法的继承,从而通过借用构造函数来实现对实例属性的继承,这样,就可以在原型上实现了原型属性和方法的复用,也能保证每个实例都拥有自己的属性

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
// 组合继承
function SuperType(name) {
this.name = name
this.colors = ['red', 'blue', 'green']
}

SuperType.prototype.sayName = function() {
console.log(this.name)
}

function SubType(name, age) {
// 通过 call 来继承
SuperType.call(this, name)

this.age = age
}

SubType.prototype = new SuperType()
SubType.prototype.constructor = SubType
SubType.prototype.sayAge = function() {
console.log(this.age)
}

const instance1 = new SubType('father', 40)
instance1.colors.push('black')
instance1.sayName()

const instance2 = new SubType('son', 40)

instance2.sayName()

缺点:无论在什么情况下,都会调用2次父类构造函数,一次是在创建子类型的时候,另一次是在子类构造函数内部,子类会包含父类的全部实例属性,但不得不在调用子类从而重写这些属性。

原型式继承

在object函数内部,通过创建一个临时的实例,并返回出去,从而实现继承,也可以通Object.create() 代替object函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 原型式继承
// object = Object.create()
function object(obj) {
function F() {}
F.prototype = obj
return new F()
}

const person = {
name: 'Nicholas',
friends: ['Shelby', 'Court', 'Van']
}

const anotherPerson = object(person)
anotherPerson.name = 'Greg'
anotherPerson.friends.push('Rob')

const yetAnotherPerson = object(person)
yetAnotherPerson.name = 'Linda'
yetAnotherPerson.friends.push('Barbie')

alert(person.friends) //"Shelby,Court,Van,Rob,Barbie"

缺点

  • 原型链继承多个实例的引用类型属性指向相同,存在篡改的可能
  • 无法传递参数

寄生式继承

通过创建一个仅用于封装继承过程的函数,该函数在内部以某种方式来增强对象,最后再像真地是它做了所有工作一样返回对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function createAnother (original) {
const clone = Object.create(original)
clone.sayHi = function () {
console.log('hi')
}

return clone
}

const person = {
name: 'test',
friends: ['a', 'b', 'c']
}

const anotherPerson = createAnother(person)

anotherPerson.sayHi() // hi

缺点:通过寄生式继承来为对象添加函数,会由于不能做到函数复用,从而降低效率

寄生组合式继承

寄生组合式继承是通过借用构造函数来继承属性,通过原型链的混合形式来继承方法

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
function inheritPrototype (subType, superType) {
const prototype = Object.create(superType.prototype) // 创建对象
prototype.constructor = subType // 增强对象
subType.prototype = prototype // 指定对象
}

// 父类初始化实例属性和原型属性
function SuperType (name) {
this.name = name
this.colors = ["red", "blue", "green"]
}
SuperType.prototype.sayName = function () {
alert(this.name)
}

// 借用构造函数传递增强子类实例属性(支持传参和避免篡改)
function SubType (name, age) {
SuperType.call(this, name)
this.age = age
}

// 将父类原型指向子类
inheritPrototype(SubType, SuperType)

// 新增子类原型属性
SubType.prototype.sayAge = function () {
alert(this.age)
}

var instance1 = new SubType("xyc", 23)
var instance2 = new SubType("lxy", 23)

instance1.colors.push("2") // ["red", "blue", "green", "2"]
instance1.colors.push("3") // ["red", "blue", "green", "3"]

new

参考文章

事件循环

参考文章

宏任务

setTimeout、setInterval、script、setImmedate、IO、Promise.resolve

微任务

process.nextTick、Promise、async await、mutation observer

浏览器

执行宏任务,然后执行该宏任务产生的微任务,若微任务在执行过程中产生了新的微任务,则继续执行微任务,微任务执行完毕后,再回到宏任务中进行下一轮循环

node

参考文章

异步

promise

promise是回调地狱的一个解决方案,有3种状态:pending(等待)、onFulfilled(成功)、onRejected(失败)

参考文章

async、await

generator

调用之后会返回一个value、next和done,value表示当前的值,done表示是否结束,结束后值为true,每次执行都需要调用next才会继续执行

事件流

捕获

事件的传播顺序为从根节点到出发的具体元素

触发

冒泡

事件的传播顺序为从事件开始的具体元素直到根节点

迭代器 for of

  • 核心是基于 Symbol.iterator,Symbol.iterator 定义了一个接口,用于 遍历操作,这样的话,不管什么样的数据只要提供 Symbol.iterator 接口就可以通过 for of 进行遍历
  • Symbol.iterator 规定,需要返回一个对象,对象内有 next、value、done 共3个属性,value是当前调用返回后的值;done表示是否结束,如果结束值为true,否则为false;而next需要是一个函数,每次迭代都需要调用next函数,调用一次,内部指针加1,直到结束。

模块规范

esmodule

  • 值拷贝
  • 静态模块,可被tree shaking

commonjs

  • 值引用
  • 可动态加载
  • 可被缓存
  • 默认严格模式

浮点精度

参考文章

ES6

参考文章

Web Components

参考文章