一步一步學習underscore的封裝和擴展方式

前言

underscore雖然有點過期,這些年要慢慢被Lodash給淘汰或合併。
但經過看它的源碼,仍是能學到一個庫的封裝和擴展方式。javascript

第一步,不污染全局環境。

ES5中的JS做用域是函數做用域。
函數內部能夠直接讀取全局變量,固然函數外部沒法讀取函數內的局部變量。
因此,咱們在匿名函數裏啪啪啪寫代碼,媽媽不再會擔憂修改到全局變量。java

(funtion(){

    var _ = function(obj) {
        return new wrapper(obj);
    };

    var wrapper = function(obj) {
        this._wrapped = obj;
    };


    window._ = _;
})()

第二步,擴展實例方法

首先,咱們要知道,
聲明在_.prototype的方法是專門給_實例用。
聲明在wrapper.prototype的方法是給wrapper方法實例用。
underscore的_方法是一個工廠方法,_方法返回的是私有wrapper方法實例。
那麼如何把_的靜態方法賦予給wrapper方法實例?且看如下代碼。數組

(function(){

    var _ = function(obj) {
        return new wrapper(obj);
    };

    var wrapper = function(obj) {
        this._wrapped = obj;
    };

    var result = function(obj) {
        return obj;
    };


    var ArrayProto = Array.prototype,
        forEach = ArrayProto.forEach,
        push = ArrayProto.push;
    _.each = forEach;

    _.type = function(obj){
        return Object.prototype.toString.call(obj).slice(8,-1).toLowerCase();
    }

    _.isFunction = function(fn){
        return (_.type(fn) == "function");
    }

    _.functions = function(obj) {
        var names = [];
        for (var key in obj) {
            if (_.isFunction(obj[key])) names.push(key);
        }
        return names.sort();
    }

    _.mixin = function(obj) {
        forEach.call(_.functions(obj), function(name) {
            var func = _[name] = obj[name];
            _.prototype[name] = function() {
                var args = [this._wrapped];
                push.apply(args, arguments);
                return result( func.apply(_, args));
            };
        });
    };
    
    // _.prototype 指向  wrapper.prototype
    _.prototype = wrapper.prototype;
    // 修復_實例的原型鏈
    _.prototype.constructor = _;
    // 這裏經過mixin方法把_的靜態方法,賦值給wrapper實例
    _.mixin(_);
    

    window._ = _;
})();

測試代碼緩存

var str = _("str");
str.type(); //"String"
str instanceof _; //true

第三步,支持鏈式調用。

鏈式調用使得操做同一個對象時很是方便。
實現的思路是,從新包裝調用的函數,緩存函數調用結果,使其返回的值是wrapper方法實例。app

(function(){
    var _ = function(obj) {
        return new wrapper(obj);
    };

    var wrapper = function(obj) {
        this._wrapped = obj;
    };

    // 鏈式包裝函數
    var result = function(obj, chain) {
        return chain ? _(obj).chain() : obj;
    };

    // 觸發可鏈式函數
    wrapper.prototype.chain = function() {
        // this._chain用來標示當前對象是否使用鏈式操做
        this._chain = true;
        return this;
    };

    // 當觸發可鏈式後,用這個來取結果值
    wrapper.prototype.value = function() {
        return this._wrapped;
    };
    var ArrayProto = Array.prototype,
        forEach = ArrayProto.forEach;

    // 這些數組方法須要包裝如下才能夠鏈式調用
    forEach.call(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], function(name) {

        var method = ArrayProto[name];
        wrapper.prototype[name] = function() {
            var wrapped = this._wrapped;
            // 調用Array對應的方法並返回結果
            method.apply(wrapped, arguments);
            var length = wrapped.length;
            if ((name == 'shift' || name == 'splice') && length === 0) {
                delete wrapped[0];
            }
            return result(wrapped, this._chain);
        };
    });


    // 這些數組方法自己可鏈式調用
    forEach.call(['concat', 'join', 'slice'], function(name) {
        var method = ArrayProto[name];
        wrapper.prototype[name] = function() {
            return result(method.apply(this._wrapped, arguments), this._chain);
        };
    });

    window._ = _;

})();

測試代碼模塊化

var a =_([1, 2]).chain().push(3).push(4).push(5);
a.value(); // [1, 2, 3, 4, 5]
[1,2].push(3).push(4).push(5); // Uncaught TypeError: [1,2].push(...).push is not a function(…)

第四步,模塊化支持。

ES6 Modules以前,UMD很盛行,咱們要支持。函數

(function(){
    var _ = function(obj) {
        return new wrapper(obj);
    };

    var wrapper = function(obj) {
        this._wrapped = obj;
    };

    if (typeof define === 'function' && define.amd) {
        define('underscore', [], function() {
            return _;
        });
    } else if (typeof exports !== 'undefined') {
        if (typeof module !== 'undefined' && module.exports) {
            exports = module.exports = _;
        }
        exports._ = _;
    } else {
        window['_'] = _;
    }
)();

第五步,綜合代碼

(function() {

    var _ = function(obj) {
        return new wrapper(obj);
    };

    if (typeof define === 'function' && define.amd) {
        define('underscore', [], function() {
            return _;
        });
    } else if (typeof exports !== 'undefined') {
        if (typeof module !== 'undefined' && module.exports) {
            exports = module.exports = _;
        }
        exports._ = _;
    } else {
        window['_'] = _;
    }

    var wrapper = function(obj) {
        this._wrapped = obj;
    };

    var result = function(obj, chain) {
        return chain ? _(obj).chain() : obj;
    };

    wrapper.prototype.chain = function() {
        this._chain = true;
        return this;
    };

    wrapper.prototype.value = function() {
        return this._wrapped;
    };

    var ArrayProto = Array.prototype,
        forEach = ArrayProto.forEach,
        push = ArrayProto.push;
    _.each = forEach;

    _.type = function(obj){
        return Object.prototype.toString.call(obj).slice(8,-1).toLowerCase();
    }

    _.isFunction = function(fn){
        return (_.type(fn) == "function");
    }

    _.functions = function(obj) {
        var names = [];
        for (var key in obj) {
            if (_.isFunction(obj[key])) names.push(key);
        }
        return names.sort();
    }

    _.mixin = function(obj) {
        forEach.call(_.functions(obj), function(name) {
            var func = _[name] = obj[name];
            _.prototype[name] = function() {
                var args = [this._wrapped];
                push.apply(args, arguments);
                return result( func.apply(_, args),this._chain);
            };
        });
    };

    _.prototype = wrapper.prototype;
    _.prototype.constructor = _;
    _.mixin(_);

    forEach.call(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], function(name) {

        var method = ArrayProto[name];
        wrapper.prototype[name] = function() {
            var wrapped = this._wrapped;
            method.apply(wrapped, arguments);
            var length = wrapped.length;
            if ((name == 'shift' || name == 'splice') && length === 0) {
                delete wrapped[0];
            }
            return result(wrapped, this._chain);
        };
    });


    forEach.call(['concat', 'join', 'slice'], function(name) {
        var method = ArrayProto[name];
        wrapper.prototype[name] = function() {
            return result(method.apply(this._wrapped, arguments), this._chain);
        };
    });



})();
相關文章
相關標籤/搜索