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. 类型转换

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指向谁

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这些一致

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和扩展运算符实现浅拷贝

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

10.2 深拷贝

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

// 利用 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