文章結構
- prototype正常的定義方式
- 構造函數中定義prototype的異常現象
- (benz instanceof Car) 爲false 問題
- benz.printHistory is not a function 問題
- 總結
- 練習
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
屬性所指向的對象。
在代碼一中,兩者的關係如下圖,是相同的。
也就是說如果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()可以正常執行。
兩者的區別如下圖所示:
總結
在構造函數中對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私有。
參考鏈接更嚴格的繼承。