寫在前面
看到好多書評和讀書筆記都說《JavaScript 語言精粹》字字珠璣,名不虛傳。。當然,要看得懂才行
其實個人認為函數化部分不是很好,舉的例子不是十分恰當,之前看不懂是因為被成功誤導了,就像 《Head First》設計模式第一章《策略模式》 一樣,作者有些偏離章節主題,讀者容易被誤導
聲明:姑且把函數化部分給出的用來創建對象的函數稱為*「創造函數」*吧,為了與「構造函數」區分開。。不是很好聽,將就著用吧
一。源碼中需要注意的點
很容易就能拿到源碼,和中文版書上的代碼一樣,仔細看了一遍發現了一個很精妙的地方,當然,不是很好理解
P.S. 源碼有點小問題:「創造函數」cat 花括號不匹配,中文版 54 頁,return that; 之前少了};
Object 的原型函數 superior 內部有一個很精妙的地方
Object.method('superior', function (name) {
var that = this,
method = that[name];
return function ( ) {
return method.apply(that, arguments);
};
});
亮點就是最後一句的 arguments,看似無心,其實是有意為之的,表明用 superior 調用父類方法時也可以傳參。當然,傳參的話需要修改調用方式,測試代碼如下:
Function.prototype.method = function (name, func) {
this.prototype[name] = func;
return this;
};
var mammal = function (spec) {
var that = {};
that.get_name = function ( ) {
return spec.name;
};
that.says = function ( ) {
return spec.saying || '';
};
return that;
};
var cat = function (spec) {
spec.saying = spec.saying || 'meow';
var that = mammal(spec);
that.purr = function (n) {
var i, s = '';
for (i = 0; i < n; i += 1) {
if (s) {
s += '-';
}
s += 'r';
}
return s;
};
that.get_name = function ( ) {
alert("cat.get_name :" + arguments.length);///
return that.says( ) + ' ' + spec.name +
' ' + that.says( ) + "[" + arguments.length + "]";
};
return that;
};
Object.method('superior', function (name) {
var that = this,
method = that[name];
return function ( ) {
alert("superior :" + arguments.length);///
return method.apply(that, arguments);
};
});
var coolcat = function (spec) {
var that = cat(spec),
super_get_name = that.superior('get_name');
that.get_name = function () {
alert("coolcat.get_name :" + arguments.length);///
return 'like ' + super_get_name.apply(this, arguments) + ' baby';
};
return that;
};
var myCoolCat = coolcat({name: 'Bix'});
var name = myCoolCat.get_name(1, 2, 3);
// 'like meow Bix meow baby'
alert(name); // 'like meow Bix meow[3] baby'
P.S. 開始以為 superior 函數最後的 arguments 是作者的錯誤,覺得應該需要把外面的 arguments 對象傳給 method 而不是裡面的,繞了一大圈發現是自己錯了,道行不夠,沒能秒懂道格拉斯大爺的意思。。
二。函數化的初衷
函數化部分開篇就說明了初衷:為了實現私有屬性,創建最後提到的「防偽對象」
目的無可厚非,實現私有屬性太有必要了。但舉的例子 mammal -> cat -> coolcat 太不合適了,作者想說明用函數化的方式可以實現繼承
當然,不是嚴格意義上的繼承,因為函數化方式沒有用到自定義類型,子類實例與父類實例的 is-a 關係也就無從談起了
P.S. 看第一遍的時候 cat 的例子就把我帶到溝裡去了,以為函數化就是要拋棄 new,完全用函數來實現繼承。。自然是在溝裡越走越深了
三。函數化的思想
直接看代碼,代碼自己會說話:
/*
* 函數化的思想:
* 1. 創建對象
* 2. 添加私有屬性
* 3. 公開接口(添加公有屬性)
* 4. 返回該對象
*/
/*
* method: getSuper
* @param spec 規格說明對象,提供創建對象所需的基本數據
* @param my「創造函數」之間共享數據的容器
*/
function getSuper(spec, my){
var obj; // 要返回的對象
var my = my || {}; // 沒傳入就創建一個
// 私有屬性
var attr = spec.value; // 從規格說明對象取數據
var fun = function(){
alert(attr);
}
// [可選] 把需要與其它「創造函數」共享的數據裝入 my
// 創建對象,可以用任意方式,比如 new、字面量、調用其它「創造函數」
obj = {
name: "SuperObject"
};
// 公開接口
obj.fun1 = fun;
// 返回 obj
return obj;
}
/*
* method: getSub
* 參數同上
*/
function getSub(spec, my){
var obj;
var my = my || {};
// 私有屬性
var attr = spec.value + 1;
var fun = function(){
alert(attr);
}
// [可選] 共享
// 創建對象
obj = getSuper(spec, my); // 可以直接傳過去,當然也可以改一改再傳,或者傳別的什麼
// 公開接口
obj.fun2 = fun;
// 返回 obj
return obj;
}
// 測試
var spec = {
value: 1
};
var sub = getSub(spec); // 不用傳入 my,my 只應該在「創造函數」之間用
sub.fun1(); // 1
sub.fun2(); // 2
P.S. 又是「創建對象 -> 增強 -> 返回新對象」這個套路,不就是尼古拉斯所說的由道格拉斯發明的「模塊模式」嗎?
四。防偽對象(持久性的對象)
函數化部分的核心就是它了,注意上面例子中公開接口的方式:
// 私有屬性
var myFun = function(){/* ... */};
// 公開接口
obj.fun = myFun;
而不直接用:
// 公開接口
obj.fun = function(){/* ... */};
第一種方式更安全,因為即便從外界修改了 fun,內部其它調用了 myFun 的方法仍然可以正常工作,這樣的函數對象就是所謂的防偽對象了
完整定義:
防偽對象的屬性可以被替換或者刪除,但該對象的完整性不會受到損害
也被稱為持久性的對象,一個持久性對象就是一個簡單功能函數的集合
後話
到這裡《JavaScript 語言精粹》的學習筆記就告一段落了,補好了 [函數化] 的空缺,學習筆記的其它部分請查看 [黯羽輕揚:《JavaScript 語言精粹》學習筆記](http://ayqy.net/blog/《JavaScript 语言精粹》学习笔记/)
暫無評論,快來發表你的看法吧