跳到主要內容
黯羽輕揚每天積累一點點

《JavaScript 語言精粹》之函數化

免費2015-05-07#JS#JavaScript语言精粹

學習筆記只差這一小塊兒,終於圓滿了。第一次看這部分,沒懂,第二次還沒懂,第三次有點感覺了,第四次發現感覺錯了。。現在差不多弄明白了,我們來捋一捋

寫在前面

看到好多書評和讀書筆記都說《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 语言精粹》学习笔记/)

評論

暫無評論,快來發表你的看法吧

提交評論