1. 数据类型

JS分两种类型,原始类型和对象类型。
原始类型:
1.boolean
2.number
3.string
4.undefined
5.null
6.symbol
7.bigint
对象类型:
1.Object(Array,RegExp,Math,Map,Set)
2.Function

2. 类型判断

2.1 typeof

原始类型中除了null,都可以用typeof判断。
typeof判断函数为function,其他均为object。

2.2 instanceof

常用于判断对象类型

2.3 Object.prototype.toString

最佳选择,能判断的类型最完整

2.4 isXXX API

isArray(),isNaN()

3. 类型转换

1
2
3
4
Number(false) // -> 0
Number('1')// -> 1
Number('zb')// -> NaN
(1).toString()// '1'

转布尔值规则:
1.undefined、null、false、NaN、’’、0、-0都转为false
2.其他所有值都转为true,包括所有对象
转数字规则:
1.true为1,false为0
2.null为0,undefined为NaN,symbol报错
3.字符串看内容,如果是数字或者进制值就正常转,否则就NaN
4.对象的规则隐式转换再讲

4. this

4.1 普通函数

谁调用this指向谁

1
2
3
var c = new foo()
c.a = 3
console.log(c.a) // this绑定到c上,不会被任何方式修改

使用apply,call,bind改变this,优先级仅次于new

4.2 箭头函数

箭头函数的this取决于定义时的环境

5. 闭包

定义:加入一个函数能访问外部的变量,那么这个函数它就是一个闭包。
闭包会将访问的变量存放在内部对象[[Scopes]]上,因此可以访问到本该销毁的变量
局部变量才是被存储在栈上,全局变量存在静态区域上,其他都存在堆上。(只针对Chrome)

6. new

new可以构建出一个实例,并绑定上this,执行步骤如下:
1.新生成一个对象
2.对象连接到构造函数原型上,并绑定this
3.执行构造函数代码
4.返回新对象
当构造函数中返回一个对象时,内部创建的新对象会被我们返回的对象所覆盖,所以构造函数一般来说不返回对象。

7. 作用域

作用域有三类:
1.全局作用域
2.函数作用域
3.块级作用域
[[Scopes]],定义时就被确定下来,后续不会改变

8. 原型

原型更重要引出继承,概念:
1.每个对象都有一个_proto_指向一个对象,就是原型
2.每个对象的原型都可以通过constructor访问构造函数,构造函数通过prototype访问原型
3.所有函数都可以通过_proto_找到Function对象
4.所有对象都可以通过_proto_找到Object对象
5.对象直接通过_proto_连接起来为原型链,顶层Object对象的原型为null

9. 继承

继承中的class,本质上还是一个函数。
ES5与6继承的区别:
1.ES6继承的子类需要调用super()才能拿到子类,ES5的话是通过apply这种绑定的方式
2.类声明不会提升,和let这些一致

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function Super() {}
Super.prototype.getNumber = function() {
return 1
}

function Sub() {}
Sub.prototype = Object.create(Super.prototype, {
constructor: {
value: Sub,
enumerable: false,
writable: true,
configurable: true
}
})
let s = new Sub()
s.getNumber()

10. 深浅拷贝

10.1 浅拷贝

浅拷贝:第一层引用不同
通过assign和扩展运算符实现浅拷贝

1
2
3
let a = {age:1}
let b = Object.assign({},a)
b = {...a}

10.2 深拷贝

深拷贝:所有引用不同
使用JSON.parse(JSON.stringify(object))可以实现,不过存在不少缺陷。
也可以通过递归实现深拷贝:

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
35
// 利用 WeakMap 解决循环引用
let map = new WeakMap()
function deepClone(obj) {
if (obj instanceof Object) {
if (map.has(obj)) {
return map.get(obj)
}
let newObj
if (obj instanceof Array) {
newObj = []
} else if (obj instanceof Function) {
newObj = function() {
return obj.apply(this, arguments)
}
} else if (obj instanceof RegExp) {
// 拼接正则
newobj = new RegExp(obj.source, obj.flags)
} else if (obj instanceof Date) {
newobj = new Date(obj)
} else {
newObj = {}
}
// 克隆一份对象出来
let desc = Object.getOwnPropertyDescriptors(obj)
let clone = Object.create(Object.getPrototypeOf(obj), desc)
map.set(obj, clone)
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
newObj[key] = deepClone(obj[key])
}
}
return newObj
}
return obj
}

11. Promise

高频考点,涉及API,问到all,race。
then,catch,finally都为微任务。其他代码都是宏任务(同步执行)
这些微任务在Promise状态为非pending时加入队列。
同级then下,前一个then执行完后,后面的才会加入任务队列。
最开始调用的then会首先依次加入任务队列。
同一个promise的每个链式调用的开端会首先依次进入队列。

11.1 async/await

async/await(ES8)
阻塞await后面的内容。

12. 事件循环

异步只是延迟执行同步代码。其他线程不影响(Web worker)。
Task(宏任务):同步代码、setTimeout回调、setInterval回调、IO、UI交互事件、postMessage、MessageChannel。
MicroTask(微任务):Promise的回调,Mutation observer回调,queueMicrotask回调。
执行顺序如下:
1.执行同步代码
2.执行完所有同步代码后且执行栈为空,判断是否有微任务需要执行
3.执行所有微任务且微任务队列为空
4.是否有必要渲染页面
5.执行一个宏任务

13. 模块化

CommonJS,ES6的ESM

14. 垃圾回收

分两个空间,新生代和老生代。

14.1 新生代

使用Scavenge GC算法
也分两个部分,From空间,To空间。
From占满时,启动算法检查存活的对象复制到To空间中,会和To空间互换。

14.2 老生代

使用标记清除和标记压缩算法
经历过Scavenge算法后还存活,To空间对象占比大小超过25%,会将新生代移到老生代。
启动标记清除算法条件:
1.某一个空间没有分块
2.空间中被对象超过一定限制
3.空间不能保证新生代中的对象移动到老生代中
标记活对象,销毁没被标记的对象

15. 其他

0.1+0.2 !== 0.3
浮点数用二进制表示的时候是无穷的,因为精度的问题,两个浮点数相加会造成截断丢失精度,因此再转换为十进制就出了问题。

16. 手写题

16.1 防抖

16.2 节流

16.3 Event Bus

16.4 instanceof

16.5 call,bind,apply