1. 实现一个 new
new 一个对象会发生如下的步骤,基于这些步骤我们来尝试想一下怎么实现它们:
- 创建或者说事构造一个全新的对象(创建一个空对象);
- 这个全新的对象会进行 [[Prototype]] 连接(遍历构造函数的 prototype 连接到空对象的 proto 上);
- 这个对象会被绑定到函数调用的 this(使用 call、apply 来改变构造函数中的 this,并在 new 的阶段执行);
- 如果函数没有返回其它对象,那么 new 表达式中的函数调用会自动返回这个新对象(判断构造函数有没有返回值)。
同时,我们来回顾一下这张原型链连接图:
我们要实现一个 createNewObject
方法,让其可以达到如下效果:
js
1 | // 构造函数 |
实现1:
js
1 | function createNewObject(constructor, args) { |
(1) 其实在创建一个空对象时候,可以写为
result = {}
或者result = new Object()
,甚至如果你想创建一个真正意义上的纯空的对象的话,可以使用result = Object.create(null)
,但是要注意的是Object.create(null)
的方法在 Nodejs 环境下,创建的对象无法改写 proto,这就导致我们无法去连接构造函数的 prototype。
实现2:
我们其实可以利用 Object.create()
来实现,其含义为:
Object.create() 方法创建一个新对象,使用现有的对象来提供新创建的对象的proto
那么实现一个 new 方法就可以改写为:
js
1 | function createNewObject(constructor, args) { |
2. 实现 call apply 方法
js
1 | Function.prototype.call2 = function (context) { |
3. bind 的实现
js
1 | Function.prototype.bind_ = function (obj) { |
4. 实现一个深克隆
重点:
- 使用递归实现深拷贝
- 基础要实现 Object 和 Array 的拷贝
- 创建一个 Map 来存放已经拷贝过的对象,防止循环引用
- 使用 Object.prototype.toString 来判断拷贝对象的
- 对于 Map、Set 要遍历拷贝
- 对于 Boolean、Number、String、Error 要调用对应的构造函数来拷贝
- 对于 RegExp 和 Symbol 要单独特殊处理
- 对于函数来说,可以通过 toString 将函数转为字符串,然后使用目标函数是否有 prototype 来判断其是箭头函数还是普通函数:
- 箭头函数可以直接返回 eval(functionString) 的执行结果来拷贝函数
- 对于普通函数,需要利用正则解析函数的参数位以及函数体,再利用 new Function(…paramArr, functionBody) 来克隆一个函数
JSON.stringify 的局限性:
- 仅能正确克隆基础类行,以及克隆对象、数组
- 对于无法拷贝的对象(这些对象通常在 JSON 中没有有效概念),如:Map、Set、RegExp、Function,会返回一个空对象
{}
- 对于 undefined 会直接忽略该键值
- 会将 NaN 转为 null
- 会将 Date 转为时间字符串
- 无法序列化的对象,如 BigNumber,会直接报错
- 对于循环引用会报错
基础版(仅实现了 1~3):
js
1 | function clone(target, map = new Map()) { |
5. 实现继承
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/create
ES5 环境下,可以使用 SubClass.prototype = Object.create(ParentClass)
来连接父类的原型链:
js
1 | // Shape - 父类(superclass) |
如果你希望能继承到多个对象,则可以使用混入的方式。
js
1 | function MyClass() { |
更低版本的 ES 标准下,可以使用 new 关键字的特性来模拟 Object.create
:
js
1 | function objectCreate(o) { |