JavaScript 壞掉優良部份
這本書當初是某 ptt 鄉民跟小弟推薦的,作者是 Yahoo! JavaScript 架構師,那裡面應該有黃金,讓我們看看 Page.49:
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,你會得到同樣的例子:
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 被呼叫(同上述)。來看一下作者建議的正確使用方式:
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);
看起來可以打完收工了,真的嗎?
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 屬性時,將會移除預設的建構式屬性」。所以作者建議你這麼做:
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 之後我們終於有了答案:
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 看看能不能找到我們要的答案:
//--- 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 的瀏覽器。
沒有留言:
張貼留言