原型继承

480 阅读2分钟

原型继承

思考如下代码:

function Foo (name) {
  this.name = name
}
Foo.prototype.myName = function () {
  return this.name
}
function Bar (name, label) {
  Foo.call(this, name)
  this.label = label
}
Bar.prototype = Object.create(Foo.prototype)
Bar.prototype.myLabel = function () {
  return this.label
}
const a = new Bar('limei', 'special')
a.myName() // 'limei'
a.myLabel() // 'special'

稍微说一下,在Bar构造函数中,存在的this属于绑定规则的显式绑定,call方法会将Bar函数执行上下文用于Foo函数的执行,在new Bar()执行时,Foo构造函数内的属性name也会被赋值。

在上述代码中,最重要的表达式是Bar.prototype = Object.create(Foo.prototype),意思是新创建一个对象,赋值给Bar.prototype,并将新对象的[[prototype]]属性指向Foo.prototype

我们对比下:

Bar.prototype = Foo.prototype

如果是直接赋值呢,不同的地方在哪?

其实有本质区别,通过create方法创建会生成一个新对象,只是新对象的[[prototype]]会与原对象进行关联;而直接赋值,不会创建新对象,只是把引用同时赋值给了Bar.prototype,这样,当有任何修改,都会同步,而创建的新对象则不会。

还有个问题,在const a = new Bar('limei', 'special')的时候,会给变量a一个默认的[[prototype]]Bar.prototype,这里就相当于手动赋值了,而放弃了原来的默认指向值。虽然上面那种方式完全不符合我们的要求,但通过new Foo()呢?就像刚才所说,执行new Foo()的时候会将新对象的[[prototype]]指向Foo.prototype,这样不就符合了嘛。

Bar.prototype = new Foo()

我们其实想要的是能最小副作用的修改原型属性的指向,通过这种方式可以,只是这样会调用Foo构造函数,如果在构造函数有些副作用操作,则不可控。

修改[[prototype]]

我们从对象api的方式来看,都存在一个属性__proto__,这个在有的浏览器是可以修改[[prototype]]值的,只是这个方法不标准,且兼容性不是很好,不常用。

在ES6中添加了辅助函数Object.setPrototypeOf()来实现:

Object.setPrototypeOf(Bar.prototype, Foo.prototype)

这两种方式,各有优劣:create方式性能稍微差一点,setPrototypeOf方法可读性不如create。

原型继承了什么

一句话总结:它继承了对象间的关联关系。