JavaScript 壞掉優良部份
這本書當初是某 ptt 鄉民跟小弟推薦的,作者是 Yahoo! JavaScript 架構師,那裡面應該有黃金,讓我們看看 Page.49:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | var Mammal = function (name){ console.log( 'Mammal' ); this .name = name; }; Mammal.prototype.get_name = function (){ return this .name; } Mammal.prototype.says = function (){ return this .saying || '' ; } var Cat = function (name){ this .name = name; this .saying = 'meow' ; } Cat.prototype = new Mammal(); |
我在 Mammal constructor 加了一行 console.log('Mammal'),這樣當 constructor 被呼叫時就可以在 Browser console (Chrome -> key F12)觀察到。問題出在最後一行:
- Mammal() 需要參數,但作者忽略了他
- 建立 sub class prototype 時呼叫 super class constructor 是正確行為嗎?
這本書的出版日期是 2008 年,或許不該再拿明朝的劍來斬清朝的官了,請繼續往下看。
Secrets of JavaScript Ninja
作者之一是大名鼎鼎 jQuery 之父 John Resig,翻開 Page.150,你會得到同樣的例子:
1 2 3 4 5 6 | function Person(){} Person.prototype.dance = function (){} function Ninja(){} Ninja.prototype = new Person(); |
搞什麼!?難道真的這樣寫才對嗎?先不用跟小弟一樣開始懷疑自己的智力,其實後面作者有附一個真正的解法,不過複雜到你覺得我還是放棄當忍者,回頭當足輕好了。
Effective JavaScript
Effective 系列一向有口皆碑,據說始祖 Scott Meyers 靠此就建立了他的事業。身為 Effective 一員,當然對此也要發表一下意見,翻開 Page.110:
「super class constructor 應該只在 sub class constructor 中被調用,而非在建立 sub class prototype 時被調用」
作者認為你在建立 sub class prototype 時呼叫 super class constructor 根本就給不出有意義的參數,而且 super class constructor 應該是在建立 sub class instance 被呼叫(同上述)。來看一下作者建議的正確使用方式:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | function Actor(scene, x, y){ this .scene = scene; this .x = x; this .y = y; scene.register( this ); } //...... function SpaceShip(scene, x, y){ Actor.call( this , scene, x, y); this .point = 0; } SpaceShip.prototype = Object.create(Actor.prototype); |
看起來可以打完收工了,真的嗎?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | function Actor(scene, x, y){ this .scene = scene; this .x = x; this .y = y; //scene.register(this); } Actor.prototype.moveTo = function (){ this .x = x; this .y = y; this .scene.draw(); } function SpaceShip(scene, x, y){ Actor.call( this , scene, x, y); this .point = 0; } SpaceShip.prototype = Object.create(Actor.prototype); var ss = new SpaceShip( null , 1, 2); console.log(ss.constructor == SpaceShip); //false console.log(ss instanceof SpaceShip); //true |
慢著,ss 是 SpaceShip 的實體,但 constructor 是 Actor?WTF?好像還差一味耶?
JavaScript 深入精要
Page.92: 「以新物件來取代 prototype 屬性時,將會移除預設的建構式屬性」。所以作者建議你這麼做:
1 2 3 4 5 6 7 | var Foo = function Foo(){ } Foo.prototype = {constructor:Foo}; var FooInstance = new Foo(); console.log(FooInstance.constructor === Foo); //true console.log(FooInstance.constructor); //function Foo() |
不過離我們的目標還有一小段距離,作者沒解釋是否在 prototype 繼承時也該這麼做。
JavaScript Cookbook 2nd
最後,付了一堆錢給 O'Reilly 之後我們終於有了答案:
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 | function origObject() { this .val1 = 'a' ; this .val2 = 'b' ; } origObject.prototype.returnVal1 = function () { return this .val1; }; origObject.prototype.returnVal2 = function () { return this .val2; }; function newObject() { this .val3 = 'c' ; origObject.call( this ); } newObject.prototype = Object.create(origObject.prototype); newObject.prototype.constructor = newObject; newObject.prototype.getValues = function () { return this .val1 + " " + this .val2 + " " + this .val3; }; var obj = new newObject(); |
程式設計的大數法則
「訴諸權威」是種邏輯繆誤,大師也會犯錯,當書上的東西模稜兩可不可信時,小弟的作法是去找一個最多人用的 open source project 來研究,如果大家都會錯,那也比較多人跟著一起陪葬。
我們下載一個知名 library - backbone.js,試著 grep 看看能不能找到我們要的答案:
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 | //--- underscore.js nativeCreate = Object.create; //(1) // An internal function for creating a new object that inherits from another. var baseCreate = function (prototype) { if (!_.isObject(prototype)) return {}; if (nativeCreate) return nativeCreate(prototype); Ctor.prototype = prototype; var result = new Ctor; Ctor.prototype = null ; return result; }; // Creates an object that inherits from the given prototype object. // If additional properties are provided then they will be added to the // created object. _.create = function (prototype, props) { var result = baseCreate(prototype); if (props) _.extendOwn(result, props); return result; }; //--- backbone.js // Set the prototype chain to inherit from `parent`, without calling // `parent`'s constructor function and add the prototype properties. child.prototype = _.create(parent.prototype, protoProps); child.prototype.constructor = child; |
由(1)可以發現,他的作法的確是跟 JavaScript Cookbook 一樣的,會弄得這麼複雜是為了相容不支援 ES5 的瀏覽器。
沒有留言:
張貼留言