在Vue
和Vuex
的源码中,作者都使用了Object.create(null)
来初始化一个新对象。为什么不用更简洁的{}
呢?
Object.create()
1 | Object.create(proto[, propertiesObject]) |
- proto:新创建对象的原型对象。
- propertiesObject:可选。要添加到新创建对象的可枚举属性(即其自身定义的属性,而不是其原型链上的枚举属性)的属性描述符以及相应的属性名称。这些属性对应
Object.defineProperties()
的第二个参数。
也就是返回一个新对象,并将传入的参数作为该对象的原型对象。
1 | const o = Object.create(proto) |
new 一个函数 和 Object.create 都发生了什么
new 一个构造函数时相当于:
- 新生成了一个对象
- 链接到原型
- 绑定 this
- 返回新对象
1 | // new Father() |
Object.create()
创建一个新对象,达到对传入第一个参数对象浅拷贝的效果(是浅拷贝的效果,但其实不是浅拷贝,而是将传入的参数作为新对象的原型对象,所以新对象可以通过原型链访问参数对象上的属性和方法):
1 | Object.create = function (obj) { |
{} 和 Object.create() 的区别
{}
相当于 new Object()
:
1 | let obj = {} |
新创建的对象继承了Object
自身的方法,如hasOwnProperty、toString
等,在新对象上可以直接使用。
再看看使用Object.create()
创建对象:
1 | let obj = Object.create(null) |
打印出来obj
没有任何属性,连_proto_
属性也没有,也就是说没有原型。参考上一段,因为在创建过程中 F.prototype = null
原型链被切断了。
如果把上面例子改一改:
1 | let obj = Object.create({}) |
打印出来obj
是有_proto_
属性的。
那么再改一下:
1 | let obj = Object.create(Object.prototype) |
则结果和使用{}
创建对象的结果一样了。所以:
{}
或new Object()
相当于Object.create(Object.prototype)
。{}
或new Object()
是将新创建的对象的_proto_
指向构造函数的原型对象Object.prototype
;而Object.create()
是将新创建的对象的_proto_
指向传入的对象;所以Object.create()
如果传入的对象本身没有任何属性,比如null
连_proto_
也没有,则新创建的对象则是一个没有任何属性的对象。{}
或new Object()
过程中构造函数会被调用;而Object.create()
即使传入的对象为构造函数,也不会调用该构造函数。
所以如果let obj = Object.create(Father)
,则obj
只是指向父类构造函数Father
而不能继承Father.prototype
的任何属性和方法。想要继承Father.prototype
上的属性方法需要通过let obj = Object.create(Father.prototype)
实现。
1 | function Father () { |
再回到文章开头的问题,为何使用Object.create(null)
来初始化一个新对象,而不用{}
:
Sure you can create an object that seems empty with {}, but that object still has a __proto__
and the usual hasOwnProperty and other object methods. So if you aren’t subclassing another object, then Object.create()
would be a new option to create a pure “dictionary” object by passing a null value to the function.
Object.create 实现类式继承
1 | function Car (desc) { |
为什么desc
打印出来是undefined
?因为传入Object.create()
的是Car.prototype
,car._proto_ === Car.prototype
,只引用prototype
而不引用constructor
。
所以, Object.create()
在创建新对象的同时,可以避免调用父类的构造函数。
所以,不像组合继承Son.prototype = new Father()
那样父类的constructor
还要被执行一便,使用Son.prototype = Object.create(Father.prototype)
实现继承不会重复调用父类的构造函数。而子类的实例是可以沿原型链找到父类的,可以共享父类原型上的属性方法。
下面的例子演示了如何使用Object.create()
来实现类式继承:
1 | // 父类 |
如果你希望能继承到多个对象,则可以使用混入的方式:
1 | function MyClass() { |