Prototype
与
__proto__
我们先写下一行代码:
function Parent {}
复制代码
当我们写下这简单的一行代码时,实际上发生了两件事情
-
创建了一个构造函数
Parent
-
创建了一个原型对象
prototype
如下图:
构造函数
Parent
中 有一个
prototype
的属性指向
Parent
的 原型对象
prototype
原型对象
prototype
则有一个
constructor
的属性 指向回 构造函数
Parent
紧接着,我们又写下一行代码:
var parent = new Parent()
复制代码
此时,图片上多出一个新成员
注意到图中的
Parent
的实例
parent
里,有一个
[[prototype]]
,为什么这里不是
__proto__
呢?
其实,这里的
[[prototype]]
表示一种标准规范内置属性,被一些浏览器自己通过
__proto__
实现了,对于
Chrome
的实现来说,这个
__proto__
也并不存在于 实例
parent
中,而是
Object.prototype
的一个
存取描述符
,以下代码可以证明:
parent.hasOwnProperty('__proto__') // false
Object.prototype.hasOwnProperty('__proto__') // true
Object.getOwnPropertyDescriptor(Object.prototype, '__proto__')
/**
* {
* configurable: true,
* enumerable: false,
* get: f __proto__()
* set: f __proto__()
* }
*/
复制代码
我们之所以能通过
parent.__proto__
访问到,是因为通过原型链访问到了
Object.prototype
上的
__proto__
存取描述符。
ES5 的 6 种继承
以下内容更像是《JavaScript高级程序设计》的笔记,主要提炼出每个继承的特点以及例图。
原型链继承
function Parent() {}
function Child() {}
var parent = new Parent()
Child.prototype = parent
var child = new Child()
复制代码
此时,根据第一部分所描述的细节,我们很快可以画出这几行代码所做的事情:
这样
child
就可以通过原型链继承的方式访问到
parent
以及
Parent.prototype
上的属性和方法了。 这种方式的特点是:
- 引用类型的属性为所有实例共享
- 无法向父类构造函数传值
借用构造函数继承(经典继承)
function Parent(name){
this.name = name
}
function Child(name){
Parent.call(this, name)
}
var child1 = new Child('child1')
var child2 = new Child('child2')
复制代码
可以看到,这种方式和 原型 没有任何关系,所以画出的图也很纯粹:
这种方式的特点是:
- 每个实例上的属性都是独立的
- 可以向父类构造函数传参
- 每次创建实例都会创建一遍方法
组合继承
顾名思义,就是讲上述两种继承方式有机结合,通过将方法定义在
prototype
中,属性通过借用构造函数继承的方式实现继承。
function Parent(name) {
this.name = name
}
Parent.prototype.talk = function () {}
function Child(name) {
Parent.call(this, name)
}
var parent = new Parent('parent')
Child.prototype = parent
Child.prototype.constructor = Child
var child = new Child('child')
复制代码
此时,关系图有了一些变化:
我们可以从图中看到,实例
child
和 实例
parent
各自拥有独立的
namne
,但是共享
Parent.prototype
中的
talk()
方法。这种方式的特点是:
- 拥有以上两种方式的优点
-
执行了两次 父类构造函数
Parent
原型式继承
function createObject(o) {
function F() {}
F.prototype = o
return new F()
}
function Parent() {}
var parent = new Parent()
var child = object(parent)
复制代码
这里先创建了一个
createObject
函数,其实就是
ES5 Object.create
的模拟实现,将传入的对象作为创建的对象的原型。
和
原型链继承
对比一下,我们发现其实是一样的,除了可以不用创建一个自定义构造函数
Child
。所以特点和
原型链继承
相同:
- 引用类型的属性为所有实例共享
- 无法向父类构造函数传值
寄生式继承
在
原型式继承
的基础上,创建一个仅用于封装继承过程的函数,该函数在内部以某种形式来做增强对象,最后返回对象。
function createObject(o) {
function F() {}
F.prototype = o
return new F()
}
function enhanceObject(o) {
var clone = createObject(o)
clone.talk = function() {}
return clone
}
function Parent