prototype && inheritance

prototype 是哪里来的?

1
function Person() {}

这是一个简单的函数声明,除了声明函数,还发生了什么?

1
Person.prototype = { constructor: Person };

这里我们发现函数 Person 声明完以后,多了一个叫 prototype 的属性,它也是一个对象,包含一个 constructor 属性,constructor 也是一个引用,指向了 Person 函数本身

..proto.. 是什么鬼?

1
2
3
var xwill = new Person();

console.log(xwill); // { __proto__: Object}

我们使用 new 关键字生成一个 Person 的实例对象 xwill,然后打印了这个对象,我们发现,xwill 对象中出现了一个 proto 属性,而这个 proto 属性的值是一个对象。展开这个对象

很清楚了吧,xwill 中的 proto 对象和 Person.prototype 对象实际上是同一个,xwill 的 proto 对象指向了 Person.prototype 对象

我们来验证一下

嗯,确实是这样

沿着 prototype 寻找属性

思考一个问题,当我们去获取 xwill 对象的某个属性的时候,在 js 引擎中,到底发生了什么?

1
2
// what happend in js engine ???
console.log(xwill.name);

这个问题有点高级,我们先来回答一个简单的问题,console.log(xwill.name) 会输出什么?
毫无疑问, xwill 暂时只有一个 proto 属性,并没有 name 属性,所以输出 undefined

如果我们给 xwill.name 赋值,那么显然 xwill.name 就会等于所赋的值

实际上,当我们在获取 xwill.name 的值的时候,js 引擎做了两件事

  1. 寻找 xwill 对象上有没有 name 属性,有,则返回
  2. 如果没有,则寻找 xwill 的 proto 对象中有没有 name 属性(由于 xwill 的 proto 对象等于 Person 的 prototype 对象,所以就相当于寻找 Person 的 prototype 对象中是否有 name 属性),有,则返回

现在我们可以回答一个常见的问题,为什么使用 prototype 定义方法 ?

1
2
3
4
5
6
7
8
9
10
11
12
13
function Person(name) {
this.name = name;
}

Person.prototype.say = function() {
console.log('hello');
}

var p1 = new Person('xwill');
var p2 = new Person('jack');

p1.say(); // hello
p2.say(); // hello

p1 和 p2 对象并没有 say 方法,但是它们的 proto 对象中有 say 方法(因为 Person 的 prototype 对象定义了 say 方法,而 p1,p2 的 proto 对象又都等于 Person 的 prototype 对象),所以,相当于实现了方法共享,这样做的好处就是如果有100个对象,那么这100个对象共享一个方法,而不是各自有一个一样的方法

prototype 继承 (classic inheritance)

有了上面的基础,我们就可以通过改变 prototype 的指向,从而达到继承的效果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
function Person(name, jobName) {
this.name = name
this.jobName = jobName
}

Person.prototype.getPersonName = function() {
console.log(this.name)
}

function Job(jobName) {
this.jobName = jobName
}

Job.prototype.getJobName = function() {
console.log(this.jobName)
}

var p1 = new Person('xwill', 'worker')

// 如果这样做,只会改变 p1 单个实例的 __proto__ 指向 Job.prototype,很明显,p1 将不会再有 Person.prototype 上的所有方法,而创建的其他 Person 实例仍然没有 getJobName 的方法
p1.__proto__ = Job.prototype // not work

// 下面几种改变 prototype 指向的方法都可以使 Person 对象继承到 Job 对象的 getJobName 方法
p1.__proto__.__proto__ = Job.prototype // works
Person.prototype.__proto__ = Job.prototype // works too

// Object.create
Person.prototype = Object.create(Job.prototype, {
constructor: {
value: Person,
enumerable: false,
writable: true,
configurable: true
}
})

// ES6 特性
Object.setPrototypeOf(Person.prototype, Job.prototype); // also works

console.log(Person.prototype.__proto__ === Job.prototype); //true
p1.getPersonName();
p1.getJobName();

var p2 = new Person('xwill2', 'developer');
p2.getPersonName();
p2.getJobName();

这时候看看 Person.prototype 的 proto 属性,指向的对象已经是 Job 的 prototype 了

现在 p1 和 p2 既是 Person 的实例,又是 Job 的实例

1
2
3
4
console.log(p1 instanceof Person); //true
console.log(p1 instanceof Job); //true
console.log(p2 instanceof Person); //true
console.log(p2 instanceof Job); // true

Prototypal Inheritance

这是另一种原型继承模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// Prototypal Pattern
var human = {
species: 'human',
}

var xwill = {};

var xwill = Object.create(human);
// xwill.__proto__ = human;
xwill.species = 'superman';
console.log(xwill.species); // superman

delete xwill.species;
console.log(xwill.species); // human

console.log(Object.getPrototypeOf(xwill) === human); // true

console.log(xwill.hasOwnProperty('species')); // false

连续继承:

1
2
3
4
5
6
7
8
9
10
11
// prototypal
var human = {
species: 'human',
}

var musician = Object.create(human);
musician.instrument = 'drum';

var xwill = Object.create(musician);
console.log(xwill.instrument);
console.log(xwill.species);

这里 xwill 继承了 musician,而 musician 继承了 human,所以 xwill 即可以使用 musician 上的属性,也可以使用 human 上的属性

Prototypal Pattern Video
Prototypal inheritance

Others

nodejs 0.1.0 使用的继承工具方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function inherits(ctor, superCtor) {
ctor.super_ = superCtor;
ctor.prototype = Object.create(superCtor.prototype, {
constructor: {
value: ctor,
enumerable: false,
writable: true,
configurable: true
}
});
};

function Person(name) {
this.name = name;
}

function Musician(name) {
Musician.super_.call(this, name);
}

inherits(Musician, Person);

node 5.9.1 使用的继承方法已经改成了使用 Object.setPrototypeOf(ES6)

1
2
3
4
5
6
7
8
9
10
11
12
13
function inherits(ctor, superCtor) {
if (ctor === undefined || ctor === null)
throw new TypeError('The constructor to `inherits` must not be null or undefined.');

if (superCtor === undefined || superCtor === null)
throw new TypeError('The super constructor to `inherits` must not be null or undefined.');

if (superCtor.prototype === undefined)
throw new TypeError('The super constructor to `inherits` must have a prototype.');

ctor.super_ = superCtor;
Object.setPrototypeOf(Musician.prototype, Person.prototype);
};