文章结构


prototype正常的定义方式

JavaScript一般构造函数与prototype的定义是分离的,正常的实现方式如下:

1
2
3
4
5
6
7
8
9
10
11
12
代码一:
function Car() {
};
Car.prototype = {
printHistory : function(){
console.log('print history...');
}
};
var benz = new Car();
benz.printHistory();

以上代码正常输出:

1
2
D:\desk\JavaScript>node prototype.js
print history...

构造函数中定义prototype的异常现象

但如果在构造函数中定义prototype,如下代码二所示,则会出现意外的情况:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
代码二:
function Car() {
// 在构造函数中定义prototype // sodino.com
Car.prototype = { // 第3行
printHistory : function(){
console.log('print history...');
}
};
};
var benz = new Car();
var bmw = new Car();
console.log('benz instanceof Car : '+ (benz instanceof Car));
console.log('bmw instanceof Car : '+ (bmw instanceof Car));
bmw.printHistory();
benz.printHistory();

运行如上代码,会发生如下异常:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
D:\desk\JavaScript>node prototype.01.js
benz instanceof Car : false
bmw instanceof Car : false // benz和bmw都不是Car的子类..诧异!
print history... // bmw能正常打印 print History...
D:\desk\JavaScript\prototype.01.js:18 // 但benz却打印失败了...
benz.printHistory();
^
TypeError: benz.printHistory is not a function
at Object.<anonymous> (D:\desk\JavaScript\prototype.01.js:18:6)
at Module._compile (module.js:413:34)
at Object.Module._extensions..js (module.js:422:10)
at Module.load (module.js:357:32)
at Function.Module._load (module.js:314:12)
at Function.Module.runMain (module.js:447:10)
at startup (node.js:141:18)
at node.js:933:3

由上控制台打印可见异常现象有两处:

  • 为什么benz和bmw都不是Car的子类?
  • 为什么benz.printHistory() is not a function??

(benz instanceof Car) 为false 问题

先来解决为什么benz和bmw不是Car子类的问题吧。

需要先了解 instanceof 关键字的工作原理,当调用 benz instanceof Car 时,解释器是判断benz.__proto__是否为Car.prototype属性所指向的对象。
在代码一中,两者的关系如下图,是相同的。
proto

也就是说如果benz.__proto__ == Car.prototype就认为benz是Car的对象,当然在判断时并不是只判断benz的原型,而是判断benz的原型链中的每个对象,例如:

1
2
3
var str = new String('abc');
str instanceof String; // true
str instanceof Object; // true

上面的两条语句都会返回true,因为str的原型链中包含这两个对象。

既然benz instanceof Car 值为false,即表明benz.__proto__Car.prototype不相等,或者说是Car.prototype的值前后发生了变化。
看代码,很明显发现未执行new操作前,与执行new操作后,Car.prototype在代码二的第3行处被重新赋值给了一个新对象。
而且构造函数每执行一次,Car.prototype都会被重新赋值一次,虽然赋值的对象是完全相同的结构。
但解释器判断两个对象是否相等的条件为两个对象是否引用同一个地址块。
通过以下代码三可以判断构造函数被执行后,Car.prototype的值每次都被改变了..

1
2
3
4
5
6
7
8
9
10
11
12
13
14
代码三:
var last;
function Car() {
last = Car.prototype;
Car.prototype = {
printHistory : function(){
console.log('print history...');
}
};
var changed = (last != Car.prototype); // 每次都是 true
console.log('Car.prototype changed : ', changed);
};

所以benz的原型链中已经与运行后的Car.prototype没有任何关系了,自然benz instanceof Car值为false。


benz.printHistory is not a function 问题

benz是最先执行Car构造函数的,然后是bmw,但bmw.printHistory()正常执行,说明benz的类方法中并未声明printHistory()函数。
原因是解释器的构造对象时,是先将最开始的Car.prototype设置为benz的原型,然后再执行构造函数,而构造函数中对Car.prototype的篡改并不会反映到benz的原型中去。
所以benz的原型是个名为Car的空函数而已。
而bmw在初始化时,Car.prototype已经被赋值为一个新的匿名的Object对象,并带有printHistory()的函数声明,所以bmw.printHistory()可以正常执行。
两者的区别如下图所示:
difference


总结

在构造函数中对prototype重新定义是一个非常严重的错误。


练习

代码一连同以下两种方式,都能正常执行printHistory()方法,但这三种实现方式各有什么区别呢?

1
2
3
4
5
6
7
8
9
10
11
代码四:
function Car() {
this.printHistory = function() {
console.log('print history...');
};
};
var benz = new Car();
benz.printHistory();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
代码五:
var Car = (function(){
function Car1() {
}
Car1.prototype = {
printHistory : function () {
console.log('print history..');
}
}
return Car1;
}());
var benz = new Car();
benz.printHistory();

代码一和代码五的功能及效果是一致的,没有区别;只是代码五的实现方式在代码结构上更加紧凑,易于维护。
代码四的区别在于printHistory()并不具备可继承性,因为this.printHistory()指定了该方法为Car私有。
参考链接更严格的继承


About Sodino