function Person() {}
Person.prototype.dance = function () {}
function Ninja() {}
Ninja.prototype = new Person()
为了实现Ninja继承Person,且能用Person的dance方法
两种继承方式:
- Ninja.prototype = new Person()
- util.inherits(Ninja, Person)
两种都是Ninja继承Person,区别是什么?
1. Ninja.prototype = new Person()
这种方式就是让 Ninja的原型指向Person的实例对象
如图显示了当定义一个Person函数时,同时也创建了Person原型,
该原型通过constructor属性引用函数本身。
正常来说,我们可以使用附加属性扩展Person原型,
在本例中,我们在Person的原型上添加了一个dance方法,
因此每个Person的实例对象也都有dance方法:
function Person() {}
Person.prototype.dance = function() {}
我们也定义了一个Ninja函数。
该函数的原型也具有一个constructor属性指向函数本身
function Ninja() {}
接下来为了实现继承,将Ninja的原型赋值为Person的实例。
现在,每当创建一个新的Ninja对象时, 新创建的Ninja对象的原型(proto)将设置为 Ninja的原型属性(prototype)所指向的对象,即Person实例:
Ninja.prototype = new Person()
var ninja = new Ninja()
尝试通过Ninja对象访问dance方法,JavaScript运行时将会首先查找Ninja对象本身
由于Ninja对象本身不具有dance方法,接下来搜索Ninja对象的原型即Person对象。
Person对象也不具有dance方法,所以再接着查找Person对象的原型,
最终找到了dance方法。
这就是在JavaScript中实现继承的原理!
问题:
如果我们仔细观察上面的图,
通过设置Person实例对象作为Ninja的构造器的原型时,
我们已经丢失了Ninja与Ninja初始原型之间的关系
这是一个问题,
因为constructor属性可用于检测一个对象是否由某一个函数创建的。
我们代码的使用者有这样一个非常合理的假设,运行一下测试会通过
console.log(ninja.constructor === Ninja)
但是在目前的程序状态中,这个测试无法通过,
因为其无法找到Ninja对象的constructor属性。
回到原型上,原型上也没有constructor属性,继续在原型链上追溯,
在Person对象的原型上具有指向Person本身的constructor属性。
事实上,如果我们询问Ninja对象的构造函数,我们得到的答案是Person,
但这个答案是错误的,这可能是某些严重缺陷的来源。
即这种new方式的继承又一个问题就是: 我们丢失了Ninja与Ninja初始原型之间的关系 随即影响了Ninja实例与Ninja初始原型之间的关系 解决方式是: Object.defineProperty
'use strict'
function Person() {}
Person.prototype.dance = function () {}
function Ninja() {}
Ninja.prototype = new Person()
Object.defineProperty(Ninja.prototype, 'constructor', {
enumerable: false,
value: Ninja,
writable: true
})
var ninja = new Ninja()
2. util.inherits(Ninja, Person)
先看俺Object.create的源码
Object.create = function (o) {
var F = function () {};
F.prototype = o;
return new F();
};
可以看出来。
Object.create是内部定义一个对象,
并且让F.prototype对象 赋值为引进的对象/函数 o,
并return出一个新的对象。
即Ob*ject.create出来的 是一个实例对象(空对象),
其__proto__指向 引进的对象/函数o
再看看inherits源码
function inherits(ctor, superCtor) {
ctor.super_ = superCtor
ctor.prototype = Object.create(superCtor.prototype, {
constructor: {
value: ctor,
enumerable: false,
writable: true,
configurable: true
}
});
};
这里就是
Ninja 多了一个属性 _super 指向 Person
Ninja原型 的prototype 指向了一个 新创建的对象
这里的新创建是:
Ninja.prototype = Object.create(Person.prototype, {
constructor: {
value: Ninja,
enumerable: false,
writable: true,
configurable: true
}
});
就是说 Ninja的原型 指向了一个新创建的对象,
该对象是空对象{},
但该空对象的原型 指向 Person的原型对象
而且该空对象有一个constructor属性 指向 Ninja
总结
第二种继承方法
近乎等于
第一种继承方法 + Object.defineProperty
这里是近乎等于是因为,他们之间还有一个区别:
-
第一种继承方式,其对象是 Person的实例对象(所以它拥有Person的属性及方法)
-
第二种继承方式,其对象是 一个空对象( {} )
但它们都拥有Person原型的属性方法(因为它们的原型指向Person的原型)
它们以不同的方式使得
Ninja的原型 指向该 对象
该对象的[[prototype]] 指向 Person原型对象
该对象的constructor 指向 Ninja函数
例子
第一个实例
'use strict'
function Person() {
this.name = 'linzx'
}
Person.prototype.dance = function () {
return 'linzx'
}
function Ninja() {}
Ninja.prototype = new Person()
Object.defineProperty(Ninja.prototype, 'constructor', {
enumerable: false,
value: Ninja,
writable: true
})
var ninja = new Ninja()
console.log(ninja.name) //拥有Person上的属性、方法
console.log(ninja.dance()) //拥有Person原型上的属性、方法
/*
linzx
linzx
*/
第二个例子
'use strict'
var util = require('util')
function Person() {
this.name = 'linzx'
}
Person.prototype.dance = function () {
return 'linzx'
}
function Ninja() {}
// Ninja.prototype = new Person()
//
// Object.defineProperty(Ninja.prototype, 'constructor', {
// enumerable: false,
// value: Ninja,
// writable: true
// })
util.inherits(Ninja, Person)
var ninja = new Ninja()
console.log(ninja.name) //不拥有Person上的属性、方法
console.log(ninja.dance()) //拥有Person原型上的属性、方法
/*
undefined
linzx
*/