文章出處
文章列表
前言
underscore雖然有點過時,這些年要慢慢被Lodash給淘汰或合并。
但通過看它的源碼,還是能學到一個庫的封裝和擴展方式。
第一步,不污染全局環境。
ES5中的JS作用域是函數作用域。
函數內部可以直接讀取全局變量,當然函數外部無法讀取函數內的局部變量。
所以,我們在匿名函數里啪啪啪寫代碼,媽媽再也不會擔心修改到全局變量。
(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方法實例。
(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);
};
});
})();
文章列表
全站熱搜