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); }; }); })();