[JavaScript]構造函數中定義prototype的異常現象及研究

文章結構


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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章