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 的瀏覽器。
沒有留言:
張貼留言