JavaScript MVC 學習筆記(四)類的使用(下)

JingDing發表於2015-02-04

控制“類”庫的作用域

在類和例項中都新增proxy函式,可以在事件處理程式之外處理函式的時候保持類的作用域。下面是不用proxy的辦法:

var Class = function(parent){
    var klass = function(){
        this.init.apply(this, arguments);
    };

    klass.prototype.init = function(){};
    klass.fn = klass.prototype;

    // 新增一個proxy 函式
    klass.proxy = function(func){
        var self = this;
        return(function(){
            return func.apply(self, arguments);
        });
    }

    // 在例項中也新增這個函式
    klass.fn.proxy = klass.proxy;
    return klass;
};

下面是用proxy()函式來包裝函式,以確保它們在正確的作用域中被呼叫:

var Button = new Class;
    Button.include({
    init: function(element){
        this.element = jQuery(element);

        // 代理了這個click 函式
        this.element.click(this.proxy(this.click));
    },
    click: function(){ /* ... */ }
});

如果沒有使用proxyclick()的回撥包裝起來,它就會基於上下文this.element來呼叫,而不是Button,這會造成各種問題。在新版本的JavaScript——ECMAScript 5(ES5)——中同樣加入了bind()函式用以控制呼叫的作用域。bind()基於函式進行呼叫的,用來確保函式是在指定的this值所在的上下文中呼叫的。例如:

Button.include({
    init: function(element){
        this.element = jQuery(element);
        // 繫結這個click 函式
        this.element.click(this.click.bind(this));
    },
    click: function(){ /* ... */ }
});

這個例子和proxy()函式是等價的,它能確保click() 函式基於正確的上下文進行呼叫。老版本的瀏覽器不支援bind(),但可以手動實現它,相容性也不錯,直接擴充套件相關物件的原型,這樣就可以像在ES5 中使用bind(),在任意瀏覽器中呼叫它。下面是一段實現了bind() 函式的程式碼:

if (!Function.prototype.bind) {
    Function.prototype.bind = function (obj) {
        var slice = [].slice,
        args = slice.call(arguments, 1),
        self = this,
        nop = function () {},
        bound = function () {
        return self.apply( this instanceof nop ? this : (obj || {}),
            args.concat(slice.call(arguments)));
        };

        nop.prototype = self.prototype;
        bound.prototype = new nop();
        return bound;
    };
}

如果瀏覽器原生不支援bind(),則僅僅重寫了Function 的原型。現代瀏覽器則可以繼續使用內建的實現。

對於陣列來說這種“打補丁”式的做法非常有用,因為在新版本的JavaScript 中,陣列增加了很多新的特性。可以使用es5-shi專案,它涵蓋了ES5 中新增的儘可能多的特性。

新增私有函式

目前上面為“類”庫新增的屬性都是“公開的”,可以被隨時修改。關於給“類”新增私有屬性,JavaScript支援不可變屬性,但在主流瀏覽器中並未實現,沒辦法直接利用這個特性。可以利用JavaScript 匿名函式來建立私有作用域,這些私有作用域只能在內部訪問:

var Person = function(){};

(function(){
    var findById = function(){ /* ... */ };
    Person.find = function(id){

    if (typeof id == "integer")
        return findById(id);
    };
})();

上面的程式碼將類的屬性都包裝進一個匿名函式中,然後建立了區域性變數(findById),這些區域性變數只能在當前作用域中被訪問到。Person變數是在全域性作用域中定義的,可以在任何地方都能訪問到。定義變數的時候不要丟掉var運算子,如果丟掉var就會建立全域性變數。如果需要定義全域性變數,可以在全域性作用域中定義,或者定義為window的屬性:

(function(exports){
    var foo = "bar";

    // 將變數暴露出去
    exports.foo = foo;
})(window);

assertEqual(foo, "bar");

“類”庫

jQuery 本身並不支援類,但通過外掛的方式可以輕易引入類的支援,比如HJS。HJS 允許通過給$.Class.create傳入一組屬性來定義類:

var Person = $.Class.create({

    // 建構函式
    initialize: function(name) {
        this.name = name;
    }
});

可以在建立類的時候傳入父類作為引數,這樣就實現了類的繼承:

var Student = $.Class.create(Person, {
    price: function() { /* ... */ }
});

var alex = new Student("Alex");
alex.pay();

可以直接給類掛載屬性:

Person.find = function(id){ /* ... */ };

HJS 的API 中同樣包含一些工具函式,比如clone() 和equal() :

var alex = new Student("Alex");
var bill = alex.clone();
assert( alex.equal(bill) );

還有 Spine 同樣實現了類,通過直接在頁面中引入spine.js(http://maccman.github.com/spine/spine.js)來使用它:

    <script src="http://maccman.github.com/spine/spine.js"> </script>
    <script>
        var Person = Spine.Class.create();

        Person.extend({
            find: function() { /* ... */ }
        });

        Person.include({
            init: function(atts){
            this.attributes = atts || {};
            }
        });

        var person = Person.init();
    </script>

jQuery 的作者John Resig 在他的部落格中寫過一篇文章:專門講解如何實現經典的類繼承


公開記錄學習JS MVC,不知道能堅持多久= =。以《基於MVC的JavaScript web富應用開發》為主要學習資料。類的部分終於看完了,再慢慢消化。JS真是博大精深!

相關文章