Object.create() 是 E5 的一个新特性,其实可以理解为继承一个对象,官方的定义为 Object.create() 方法可以创建一个拥有指定原型和若干个指定属性的对象
它的基本语法为
1 | Object.create(proto, [ propertiesObject ]) |
参数
proto,一个对象,作为新创建对象的原型propertiesObject,可选,该参数对象是一组属性与值,该对象的属性名称将是新创建的对象的属性名称,值是属性描述符
需要注意的是,该参数对象不能是
undefined,另外只有该对象中自身拥有的可枚举的属性才有效,也就是说该对象的原型链上属性是无效的
下面我们就来看看如何使用 Object.create() 当中的 propertyObject 参数
参数
1 | // 创建一个原型为 null 的空对象 |
另外一个示例
1 | function Constructor() { } |
不过有几个需要注意的地方,第一种情况
1 | var a = { x: 1 } |
第二种情况,注意区分 __proto__ 和 prototype
1 | // 如果用的是 |
实例
一般来说,Object.create() 的使用还是在继承当中使用较多,下面我们就来看一个使用它来实现类式继承的具体实现
1 | function Foo(name) { |
这段代码的核心部分就是语句 Bar.prototype = Object.create(Foo.prototype),调用 Object.create() 会凭空创建一个新对象并把新对象内部的 [[Prototype]] 关联到你指定的对象(本例中是 Foo.prototype),换句话说,这条语句的意思是,创建一个新的 Bar.prototype 对象并把它关联到 Foo.prototype
声明函数 Bar() 的时候,和其他函数一样,Bar 会有一个 .prototype 关联到默认的对象,但是这个对象并不是我们想要的 Foo.prototype,因此我们创建了一个新对象并把它关联到我们希望的对象上,直接把原始的关联对象抛弃掉,注意下面这两种方式是常见的错误做法,实际上它们都存在一些问题
1 | // 和你想要的机制不一样 |
第一种情况的 Bar.prototype = Foo.prototype 并不会创建一个关联到 Bar.prototype 的新对象,它只是让 Bar.prototype 直接引用 Foo.prototype 对象,因此当你执行类似 Bar.prototype.myLabel = ... 的赋值语句时会直接修改 Foo.prototype 对象本身
而第二种情况的 Bar.prototype = new Foo() 的确会创建一个关联到 Bar.prototype 的新对象,但是它使用了 new 操作符来进行调用,如果函数 Foo 有一些副作用(比如写日志、修改状态、注册到其他对象、给 this 添加数据属性,等等)的话,就会影响到 Bar() 的后代
因此,要创建一个合适的关联对象,我们必须使用 Object.create() 而不是使用具有副作用的 new 操作,这样做唯一的缺点就是需要创建一个新对象然后把旧对象抛弃掉,不能直接修改已有的默认对象
ES6 中的 Object.setPrototypeOf()
如果能有一个标准并且可靠的方法来修改对象的 [[Prototype]] 关联就好了,在 ES6 之前,我们只能通过设置 .__proto__ 属性来实现,但是这个方法并不是标准并且无法兼容所有浏览器,ES6 添加了辅助函数 Object.setPrototypeOf(),可以用标准并且可靠的方法来修改关联,我们来对比一下两种把 Bar.prototype 关联到 Foo.prototype 的方法
1 | // ES6 之前需要抛弃默认的 Bar.prototype |
如果忽略掉 Object.create() 方法带来的轻微性能损失(抛弃的对象需要进行垃圾回收),它实际上比 ES6 及其之后的方法更短而且可读性更高,不过无论如何,这是两种完全不同的语法
最后我们再来看一种特殊的使用方式,它就是 Object.create(null)
Object.create(null)
通过 Object.create(null) 创建出来的对象,就没有 Object.prototype 上的一些方法(需要注意与 {} 进行区分),我们可以在控制台进行简单的测试
1 | {} // {} __proto__: Object |
所以并不是所有的对象都继承有 Object.prototype 上的一些方法,看下面代码
1 | var obj = Object.create(null) |
Object.create(null)会创建一个拥有空(或者说null)[[Prototype]]链接的对象,这个对象无法进行委托- 由于这个对象没有原型链,所以
instanceof操作符无法进行判断,因此总是会返回false
这些特殊的空 [[Prototype]] 对象通常被称作字典,它们完全不会受到原型链的干扰,因此非常适合用来存储数据,另外一个需要注意的地方,那就是并不是所有的函数对象都会有 prototype 属性,代码如下
1 | function abc() {} |