这是JS 原生方法原理探究系列的第二篇文章。本文会介绍如何实现 Object.create() 方法。关于这个方法的具体用法,MDN 已经描述得很清楚了,这里我们只做简单的介绍,具体的重点在于如何模拟实现。
语法简介
调用:Object.create ( proto , propertiesObject )
返回: 一个新的实例对象
调用这个方法的时候接受两个参数,第一个参数作为返回对象的 __proto__,这个参数只能是 null 或者对象(而且不能是基本类型的包装对象)。
第二个参数作为返回对象的属性描述,它和 Object.defineProperties() 的第二个参数形式是一样的:
{
    propertyA: {
        value: xxx,
        configurable: xxx,
        enumerable: xxx,
        writable: xxx    
    },
    propertyB: {...},
    propertyC: {...}    
}这个参数的每一个属性都会作为返回对象的属性,而属性值则是相应属性的特性描述(该属性的属性值、是否可读、是否可枚举、是否可配置)。第二个参数只能是对象或者 undefined(表示没有传第二个参数),不能是 null。
ES 规范
对于 Object.create() 的具体实现,规范中其实已经描述得很清楚,可以进入http://es5.github.io/#x15.2.3.5查看:

我简单翻译一下这段话:
create() 方法会创建一个具有指定原型的新对象,当调用该方法的时候,会有如下步骤:
- 如果传入的参数 
O不是对象也不是null,抛出 TypeError 错误 - 令 
obj作为调用new Object()方法所创建的新对象 - 将 
obj的内部属性[[prototype]]设置为O - 如果提供了第二个参数 
Properties,且不是undefined,则调用Object.defineProperties方法并传入obj和Properties作为参数,从而为obj添加它自己的属性 - 返回 
obj 
可以说,整个过程是一目了然的,我们实现的时候也只需要按照上述步骤实现即可。
代码实现
我们先看第一种实现:
Object.create = function(proto,propertiesObject){
    if(typeof proto != 'object' && proto !== null){
        throw new Error('the first param must be an object or null')
    }
    if(typeof propertiesObject === null){
        throw 'TypeError'
    }
    let obj = {}
    obj.__proto__ = proto
    if(propertiesObject){
        Object.defineProperties(obj,propertiesObject)
    }
    return obj
}基本上没有什么大问题。不过,我们要留意两个地方:
- 在这个实现中,没有检测第一个参数是不是基本类型的包装对象,只要传进来的参数是对象,我们就认为是合法的
 - 当传入 null 也即 
Object.create(null)的时候,我们实际上创建了一个很纯粹的空对象,这个对象的原型直接就是 null,Object.prototype甚至没有出现在该对象的原型链中,这意味这个对象不会继承 Object 的任何方法。 
此外,你还可能在其他地方看到类似下面这样的实现:
具体实现如下:
Object.create = function(proto,propertiesObject){
    if(typeof proto != 'object' && proto !== null){
        throw new Error('the first param must be an object or null')
    }
    if(propertiesObject === null){
        throw 'TypeError'
    }
    function F(){}
    F.prototype = proto
    const obj = new F()
    // 处理传参 null 的情况
    if(proto === null){
        obj.__proto__ = proto
    }
    if(propertiesObject){
        Object.defineProperties(obj,propertiesObject)
    }
    return obj
}这个实现和前面的实现有一个很关键的区别:代码中单独处理了传参 proto 为 null 的情况。可能你会觉得很奇怪:当 proto 为 null 的时候,F.prototype = proto 的效果和 obj.__proto__ = proto 应该是一样的,为什么还要在这种情况下执行一遍 obj.__proto__ = proto 呢?这似乎说明,用 null 重写 F 的原型后,新创建的实例的 __proto__ 并不是 null —— 事实上确实不是。
关于调用构造函数时会执行的操作,规范明确提到了这一点:
If Type(proto) is not Object, set the [[Prototype]] internal property of obj to the standard built-in Object prototype object as described in 15.2.4.
由于我们这里是通过 new 构造函数的方式创建新对象(而不是像之前那样通过对象字面量的形式),所以在 new F 的时候,内部会检测 F 的原型是不是对象,如果不是对象,那么会把实例的 __proto__ 链接到内建的 Object.prototype。因此,这里新创建的实例的 __proto__ 还真不是 null。
但根据 Object.create 的实现规范,这里必须让实例的 __proto__ 指向 null,所以才需要执行 obj.__proto__ = proto 去手动设置对象原型。
当然,如果我们像第一个实现那样,直接去设置对象的 __proto__,而不是采用构造函数的方式,就不存在这个问题了。