什么是原型链?
定义的函数、类或对象会自动创建原型对象,并通过prototype和constructor互相指向对方,原型之间通过__proto__构成原型链,实例调用某个方法时,会沿原型链寻找该方法,直到找到指定方法,或遍历完整个原型链后抛出异常;
借助单链表理解:针对实例调用某方法的角度来讲,原型链可以看作一个链表,每个节点都是原型对象,以__proto__属性指向下一节点,头节点是实例本身,尾节点是Object.prototype;
不同情况下的__proto__如下:
- function 函数,箭头函数,其
__proto__一定是Function.prototype,包括Function; - 实例的
__proto__一定是其构造函数的原型; - 原型的
__proto__一定是Object.prototype;
举个例子,画出下列代码执行过程中的所有相关对象,如下:
1 | function Person() {} // or like this: class Person {} |

箭头函数与 function 函数
“An ArrowFunction does not define local bindings for arguments, super, this, or new.target. “ —— ECMAScript Language Specification
- 没有 new.target,不能通过 new 实例化,也没有 super;
- 没有 prototype,不能通过 Object.create(prototype)实例化(虽然可以将参数设为箭头函数本身,但无意义,相当于箭头函数做返回值的原型,返回值的父级原型是 Function.prototype,而非 Object.prototype,);
- 没有 arguments;
- 不能通过 call、apply、bind 改变 this 指向;
注:非箭头函数,其 new.target 指向函数原型的 constructor;
什么是属性描述符(property descriptor)
属性描述符是属性的配置,分为:
- 数据描述符(configurable、enumerable、value、writable)
- 存取描述符(configurable、enumerable、set、get)
configurable 标识描述符是否可配置,即属性是否可以通过 delete 删除,描述符是否可修改,默认为 false,即不可再次修改描述符,且不能删除属性;
enumerable 标识属性是否可枚举,默认为 false;
value 标识属性值,默认为 undefined;
writable 标识 value 是否可以修改,默认为 false,即不可再次修改属性值;
get/set 标识属性的 getter/setter 函数,默认为 undefined;
什么是闭包?
简单回答:读取其他函数内部变量的函数;
详细回答:从现象上看,对于一个返回函数的函数,其第一次执行后,内部定义的变量将只能被返回的函数访问,这就形成了一个闭包;
作用:
- 闭包内的变量无法被外部访问,可以实现私有属性;
- for 循环内,闭包及变量提升导致的回调函数无法正确捕捉到自增变量:
1 | // 修改前 |
注:闭包需要维护一个词法作用域,将带来额外的性能开销,谨慎使用;
JS 有几种数据类型?
JS 是一种动态类型(变量可以动态赋予任意类型的值)、弱类型(不检查类型一致性,允许隐式类型转换)语言;
原始类型有七种:
- null,
typeof null显示object; - undefined;
- boolean;
- number(Infinity, NaN);
- bigint;
- string;
- symbol;
引用类型有一种:
- object,
typeof function () {}、typeof class Person {}结果都为function,但函数实际上是 object 类型;
注:原始值就是语言底层的不可变值,共有七种类型,称为原始类型;
typeof 有几种结果
typeof null is objecttypeof undefined is undefinedtypeof boolean is booleantypeof number is numbertypeof bigint is biginttypeof string is stringtypeof symbol is symboltypeof function is function
null 和 undefined 的区别
- typeof null 结果为 object,typeof undefined 结果为 undefined;
- null 是关键字,undefined 不是;
- null 表示空对象或空指针,undefined 表示空值,用途不同:
- 原型链的终点是 null;
- 未赋值的变量或属性隐式初始化为 undefined,没有 return 语句的函数隐式返回 undefined;
instanceof 作用与模拟
instanceof 用于判断右值是否在左值的原型链上;
for in 与 for of
- for in 遍历键(一般用于对象),for of 遍历值(一般用于数组);
- for of 本质是遍历 iterator,被遍历的目标必须实现
Symbol.iterator方法,for in 则不必; - for in 遍历自身及原型链上的可枚举属性名,顺序无法保证,for of 循环调用 iterator,顺序由 iterator 的实现决定;
Promise 用途与机制
Promise 用于解决 ES5 中的回调地狱问题,将层级嵌套结构改为链式结构,以提高代码可读性,降低维护成本;
Promise 本质上是采用观察者模式,通过 then 收集回调函数,监听的是 Promise 实例化时传入的 executor 函数,executor 在 Promise 实例化时执行,并且会获得两个参数 resolve 和 reject,通过执行 resolve/reject 来触发 then 收集的回调函数;
Promise 具有三种状态:pending,fulfilled,rejected;按照规范,只能由 pending 向 fulfilled 或 rejected 转化;
Promise 能够链式调用是由于 then 方法会返回一个新的 Promise 实例;
常用静态方法:
- Promise.all(iterable)在 iterable 全部 fulfill 或其中一个 reject 时,返回一个 fulfilled 或 rejected Promise;
- Promise.any(iterable),任意一个 fulfill 或全部 reject 时,返回 fulfilled 或 rejected Promise;
- Promise.race(iterable),第一个 fulfill 或 reject 时,返回 fulfilled 或 rejected Promise;
- Promise.resolve(value),返回 fulfilled Promise;
- Promise.reject(error),返回 rejected Promise;
注:iterable 是指可迭代对象,如数组;
Generator Function 用途和机制
Generator Function 用 function*创建,执行后返回一个 Iterator,Iterator 的 next 方法用于执行被 yield 分割的函数体的代码,并返回一个对象,该对象的 value 属性就是 yield 右侧的值,done 标识迭代是否完成;
Generator Function 常见于执行异步操作,yield 交出控制权,next 交还控制权,并且 next 接受的参数会作为 yield 表达式的值,这都是执行异步操作的关键点;
因为需要把 next 方法传递给异步操作,以便完成后调用 next 交还控制权,异步操作的流程控制不太方便,代码也不直观;
yield*用于 Generator Function 内调用 Generator Function
1 | function* a() { |
Async Function 用途和机制
Async Function 可以视为 Generator 函数与 Promise 的语法糖,内置实现了 Generator 的自动执行,并将 Generator 的最终执行结果封装进 Promise,下一步处理更方便;
await 可以看作 yield 的语法糖;
Async Function 与 Generator Function、Promise 的关系可以从伪造的代码中理解:
1 | function boo() { |
相当于
1 | function boo() { |
ES6 新特性汇总
- const,let
- 解构赋值
- 模板字符串
- 参数默认值,剩余参数
- Arrow Function
- Generator Function
- Async Function
- 新增数组方法:entries,keys,values,includes,from,find,findIndex 等
- 新增 Object 方法:assign,keys,values,entries 等
- 表达式定义的属性名
- Symbol
- Set,Map,WeakSet,WeakMap
- Promise
- Iterator
Iterator 用途和机制
Iterator 就是符合迭代器协议(Iterator protocol)的对象,实现了如下方法(必须实现 next):
- next:用于遍历 Iterator 的一系列值,返回一个具有 value 和 done 属性的对象;
- return:结束迭代;
- throw:结束迭代并返回一个 Error 实例;
Symbol.Iterator 用途和机制
Symbol.Iterator 就是可迭代对象必须实现的一个 Symbol 类型的方法名,实现该方法的对象可以:
- 通过 for…of 遍历;
- 解构赋值;
- 通过 Array.from(iterable)转化为数组;
- 初始化 Set,Map;
- 作为 Promise.all,Promise.race 的参数;
- 通过 yield*把值导入到 Generator Function;
该方法名标识的方法要符合可迭代协议(Iterable protocol),该方法返回一个 Iterator;
显然,可以通过 Generator Function 实现该方法,因为 Generator Function 会返回一个 Iterator;
WeakSet 和 WeakMap
键只能存非空对象,且键与对象地址是一种弱引用关系,GC 机制不关注弱引用,所以在没有强引用(如通过变量指向一个对象地址)存在的情况下,弱引用的对象将被回收;
典型场景:保存 Dom 节点,被删除的节点会被 GC 正常回收,避免强引用导致内存泄漏;
Decorator
Decorator 是编译时执行的函数,目前提案处于 Stage 3(2023 年 7 月 5 日),但 ts 或 babel 等类库已经支持。
语法如下:
1 | // tsconfig.json 中配置 compileOptions.experimentalDecorators = true |
Proxy 和 Reflect
Proxy 用于代理目标对象的操作,例如读、写、添加属性、删除属性等,语法如下:
1 | const target = {}; |
Proxy 提供了元编程的能力,即针对编程的编程,基本可以看作是代理模式,代理属性读写(get、set、赋值、.)、delete、new、in 等;
Reflect 用于搭配 Proxy 使用,Reflect 有 Proxy 构造函数中的参数 handler 上的所有方法,通过 Reflect 执行这些方法等同于执行被代理对象的对应原生操作;
Proxy + Reflect 可以方便地实现对操作符的代理,改变默认行为或增加自定义行为,例如每次赋值时在控制台打印值:
1 | const target = { |
细节:
- Proxy 代理是在 Proxy 实例上,直接操作被代理对象 target 将不会触发代理行为;
const {proxy, revoke} = Proxy.revokable(target, handler),revoke 调用后将销毁 proxy 实例,后续调用将抛出错误;
class
class 用于定义类,细节如下:
- 不提供 constructor 则将隐式地提供一个
constructor() { return this; }; - 类名指向其构造函数,typeof 结果为
function; - constructor 只能通过 new 指令调用,直接调用将抛出异常;
- 依赖于 this 的方法必须定义为实例方法,通过箭头函数或 bind 绑定 this 指向;
原型方法是定义在 prototype 上方法,静态方法是定义在 constructor 上的方法,实例方法是定义在实例上的方法;
1 | class Person { |

0.1 + 0.2 = 0.30000000000000004
首先,十进制数在计算机底层只能以二进制形式存储:
- 整数部分转二进制:每次除 2 取余入栈,除数为 0 时顺序出栈;
- 小数部分转二进制:每次乘 2 取整入队,小数部分为 0 时顺序出队;
其次,js 中数字存储遵循 IEEE 754 标准——64 位双精度浮点数,1 符号位,11 位指数位,52 位尾数位,数字以科学计数法表示:符号 尾数 * 基数^指数。
该问题的关键就在于尾数位是有限的(52 位),十进制小数经过上述转换后,尾数长度可能远超 52 位,此时就要舍去 52 位之后的二进制数,0.1 和 0.2 就是这样,它们在计算机中只是一个近似数,加运算实际是两个近似数之和,也就是0.30000000000000004.
解决方案:
- 先乘上 10 的倍数 x,把小数转换成整数,计算后结果除以 x 还原为小数;
- Number.prototype.toFixed(digits)指定计算结果的小数精度;
- Number.prototype.toPrecision(precision)指定计算结果的整体精度;
- 目前仍处于 stage 1 的Ecma TC39 JavaScript Decimal proposal,即显式十进制数运算(对应的,js 默认为隐式二进制数运算);