1. 作用域
與其他第三方庫一樣,Underscore也通過立即執(zhí)行函數(shù)來包裹自己的業(yè)務(wù)邏輯。
目的:
- 避免全局污染:所有庫中的邏輯,庫所定義和使用的變量全部被封裝到了該函數(shù)的作用域中。
- 隱私保護(hù):但凡在立即執(zhí)行函數(shù)中聲明的函數(shù)、變量等,除非是自己想暴露,否則絕無可能在外部獲得。
(function(root){
var _ = function(){
}
root._ = _
})(this)
2. _ 對象
underscore有下劃線的意思,所以underscore通過一個下劃線變量 _ 來標(biāo)識自身。
注: _ 是一個函數(shù)對象,之后所有的api都會被掛載到這個對象上,例如_.map()
(funtion(root){
var _ = function(obj){
}
_.map = function(){
//balalala
}
// ......
root._ = _
})(this)
3. _()
雖然Underscore推崇函數(shù)式編程,但也支持面向?qū)ο箫L(fēng)格的函數(shù)調(diào)用,僅需要通過_()來包裹對象即可。
當(dāng)我們進(jìn)行如下調(diào)用時:_([1,2,3])會創(chuàng)建一個新的underscore對象實例(從而能夠調(diào)用underscore提供的方法),并在this._wrapped中存儲傳入的數(shù)據(jù)。
var _ = function(obj){
if(obj instanceof _){
return obj
}
if(!(this instanceof _)){
return new _(obj)
}
this._wrapped = obj
}
當(dāng)使用面向?qū)ο蟮姆绞秸{(diào)用時,例如傳入[1,2,3],函數(shù)首先會判斷是不是_的實例,如果是直接返回這個實例對象,如果不是則創(chuàng)建一個實例對象new _(obj),然后將參數(shù)[1,2,3]賦值給_wrapped,以備后用。
4. mixin
- mixin(混入)模式是增加代碼復(fù)用度的一個廣泛的設(shè)計模式。
- _.mixin(obj):為underscore對象混入obj具有的功能
- mixin主要實現(xiàn)了 _ 的原型對象的方法調(diào)用
源碼如下:
_.mixin = function(obj) {
_.each(_.functions(obj), function(name) {
var func = obj[name];
_.prototype[name] = function() {
var args = [this._wrapped];
push.apply(args, arguments);
return result(this, func.apply( _ , args));
}
});
}
_.mixin(_)
最后一句是執(zhí)行這個mixin函數(shù),執(zhí)行完后會將 _ 上掛載的方法都掛載到原型對象prototype上,以方便面向?qū)ο髸r的調(diào)用
5. 鏈?zhǔn)秸{(diào)用 _.chain()
- _.chain(obj):為underscore對象的方法增加鏈?zhǔn)秸{(diào)用能力
- _.chain源碼如下:
_.chain = function(obj){
var instance = _(obj)
instance._chain = true
return instance
}
比較簡單,就是拿到這個實例對象,賦了一個屬性 _chain為true,再返回這個實例對象
- underscore還提供了一個幫助函數(shù)result,該函數(shù)將會判斷方法調(diào)用結(jié)果,如果該方法的調(diào)用者被標(biāo)識了需要鏈化,則鏈化當(dāng)前的方法執(zhí)行結(jié)果。
- 源碼如下:
var chainResult = function(instance , obj){
return instance._chain ? _(obj).chain() : obj ;
}
============這是分割線============
接下來我們就看看,當(dāng)調(diào)用_.unique([1,2,3,1])的時候怎么執(zhí)行的
先附上源碼:
(function(root){
var _ = function(){}
_.unique = function(arr,callback){
var ret = [];
var target, i = 0;
for (; i < arr.length; i++) {
var target = callback ? callback(arr[i]) : arr[i];
if (ret.indexOf(target) === -1) {
ret.push(target);
}
}
return ret;
}
root._ = _
})(this)
代碼就不解釋了,比較簡單,就是在 _ 對象上新建一個unique方法直接調(diào)用就行。
如果使用面向?qū)ο蟮恼{(diào)用方式呢?就需要這樣寫了 _([1,2,3,1]).chain().unique()
先附上源碼:
(function(root){
var push = Array.prototype.push;
//面向?qū)ο笳{(diào)用時返回_的實例,然后將參數(shù)存儲在_wrapped中
var _ = function(obj) {
if (obj instanceof _) {
return obj;
}
if (!(this instanceof _)) {
return new _(obj);
}
this._wrapped = obj;
}
//給_定義一個去重的函數(shù)
_.unique = function(arr, callback) {
var ret = [];
var target, i = 0;
for (; i < arr.length; i++) {
var target = callback ? callback(arr[i]) : arr[i];
if (ret.indexOf(target) === -1) {
ret.push(target);
}
}
return ret;
}
//定義一個遍歷函數(shù)
_.each = function(target, callback) {
console.log(target)
var key, i = 0;
if (_.isArray(target)) {
var length = target.length;
for (; i < length; i++) {
callback.call(target, target[i], i);
}
} else {
for (key in target) {
callback.call(target, key, target[key]);
}
}
}
//開啟鏈接式的調(diào)用
_.chain = function(obj) {
var instance = _(obj);
instance._chain = true;
return instance;
}
//輔助函數(shù) obj 數(shù)據(jù)結(jié)果
var result = function(instance, obj) {
//_(obj).chain() 會對傳入的obj創(chuàng)建一個新的實例對象,返回出去
return instance._chain ? _(obj).chain() : obj;
}
//獲取_上所有綁定的函數(shù)
_.functions = function(obj) {
var result = [];
var key;
for (key in obj) {
result.push(key);
}
return result;
}
//類型檢測
_.isArray = function(array) {
return toString.call(array) === "[object Array]";
}
//將函數(shù)加載到
_.mixin = function(obj) {
_.each(_.functions(obj), function(name) {
var func = obj[name];
_.prototype[name] = function() {
var args = [this._wrapped];
push.apply(args, arguments);
return result(this, func.apply(this, args));
}
});
}
root._ = _
})(this)
以上基本就是underscore的源碼框架了。
- 先解釋
_.mixin(_)
- 函數(shù)一開始就會調(diào)用這個函數(shù),傳入的參數(shù)就是我們自定義的參數(shù),例如[1,2,3],然后調(diào)用each方法,each方法傳入兩個參數(shù),第一個參數(shù)_.functions(obj),就是獲取綁定在 _ 上面的所有函數(shù),返回的是一個數(shù)組。
- 繼續(xù)往里走,第二個參數(shù)回去調(diào)用數(shù)組中各個函數(shù)名,拿到這個函數(shù)。
- 繼續(xù),之后會給 _ 的prototype綁定函數(shù),
var args = [this._wrapped]就是獲取我們傳進(jìn)來的參數(shù)[1,2,3,1]
push.apply(args,arguments)如果后面調(diào)用方法時,還有參數(shù),則合并push進(jìn)args里。(如果不了解apply的可以看看我之前寫的call apply bind的理解這篇文章) - 最后調(diào)用result這個函數(shù),第一個參數(shù)this就是當(dāng)前的實例對象,第二個參數(shù)就是調(diào)用unique的方法。
- 最后返回的結(jié)果,是調(diào)用chain后的實例對象
_([1,2,3,1]).chain().unique()
- 首先_([1,2,3,1])會生成一個實例對象,instance
- 調(diào)用chain方法,給instance創(chuàng)建一個屬性chain并且設(shè)置為true,表示可以鏈?zhǔn)秸{(diào)用
- 調(diào)用unique方法,此處的unique方法不是 _ 的方法,而是_.prototype的方法,也就是mixin中的函數(shù)。
- 最后返回result的結(jié)果,也就是執(zhí)行unique方法后的實例對象。