最近打算好好看看underscore原始碼,乙個是因為自己確實水平不夠,另乙個是underscore原始碼比較簡單,比較易讀。
本系列打算對underscore1.8.3中關鍵函式原始碼進行分析,希望做到最詳細的原始碼分析。
今天是underscore原始碼剖析系列第一篇,主要對underscore整體架構和基礎函式進行分析。
首先,我們先來簡單的看一下整體的**:
這段**整體比較簡單,不過我看後來的underscore版本有一些小改動,主要是將var root = this;替換為下面這句:// 這裡是乙個立即呼叫函式,使用call繫結了外層的this(全域性物件)
(function() ;
// 內部實現省略
var _ = function(obj) {};
// 這裡是各種方法的實現(省略)
// 匯出underscore方法,如果有exports則用exports匯出,如果 沒有,則將其設為全域性變數
if (typeof exports !== 'undefined')
exports._ = _;
} else
// 版本號
_.version = '1.8.3';
// 用amd的形式匯出
if (typeof define === 'function' && define.amd) );
}}.call(this))
var root = typeof self == 'object' && self.self === self && self || typeof global == 'object' && global.global === global && global || this;
這裡增加了對self和global的判斷,self屬性可以返回對視窗自身的引用,等價於window,這裡主要是為了相容web worker,因為web worker中是沒有window的,global則是為了相容node,而且在嚴格模式下,立即執行函式內部的this是undefined。
掃一眼原始碼,我們會發現在原始碼中並沒有見到undefined的出現,反而是用void(0)或者void 0來代替的,那麼這個void到底是什麼?為什麼不能直接用undefined呢?
關於void的解釋,我們可以看這裡:mdn
void 運算子通常只用於獲取 undefined的原始值,一般使用void(0),因為undefined不是保留字,在低版本瀏覽器或者區域性作用域中是可以被當做變數賦值的,這樣就會導致我們拿不到正確的undefined值,在很多壓縮工具中都是將undefined用void 0來代替掉了。
其實這裡不僅是void 0可以拿到undefined,還有其他很多方法也可以拿到,比如0["ygy"]、object.__undefined__、object.__ygy__,這些原理都是訪問乙個不存在的屬性,所以最後一定會返回undefined
也許有時候我們會碰到這樣一種情況,_已經被當做乙個變數宣告了,我們引入underscore後會覆蓋這個變數,但是又不想這個變數被覆蓋,還好underscore提供了noconflict這個方法。
顯而易見,這裡正常保留原來的_變數,並返回了underscore這個方法(this就是_方法)_.noconflict = function() ;
var underscore = _.noconflict();
接下來講到了本文的重點,關於_方法的分析,在看原始碼之前,我們先熟悉一下_的用法。
這裡總結的是我日常的用法,如果有遺漏,希望大家補充。
一種是直接呼叫_上的方法,比如_.map([1, 2, 3]),另一種是通過例項訪問原型上的方法,比如_([1, 2, 3]).map(),這裡和jquery的用法很像,$.extend呼叫jquery物件上的方法,而$("body").click()則是呼叫jquery原型上的方法。
既然_可以使用原型上面的方法,那麼說明執行_函式的時候肯定會返回乙個例項。
這裡來看原始碼:
我先從原始碼上來解釋,這裡可以看出來_是乙個建構函式,我們都知道,我既可以在建構函式上面增加方法,還可以在原型上面增加方法,前者只能通過建構函式本身訪問到,後者由於原型鏈的存在,可以在建構函式的例項上面訪問到。// instanceof 運算子用來測試乙個物件在其原型鏈中是否存在乙個建構函式的 prototype 屬性。
// 我這裡有個不夠準確但容易理解的說法,就是檢查乙個物件是否為另乙個建構函式的例項,為了更容易理解,下面將全部以***是***的例項的方式來說。
var _ = function(obj) ;
所以我們平時用的_.map就是person.say()這種用法,而_([1, 2, 3]).map則是ygy.say()這種用法。var person = function()
person.say = function()
person.prototype.say = function()
var ygy = new person();
person.say(); // hello
ygy.say(); // world
在繼續講這個之前,我們再來複習一下原型的知識,當我們new乙個例項的時候到處發生了什麼?
首先,這裡會先建立乙個空物件,這個空物件繼承了建構函式的原型(或者理解為空物件上增加乙個指向建構函式原型的指標__proto__),之後會根據例項傳入的引數執行一遍建構函式,將建構函式內部的this繫結到這個新物件中,最後返回這個物件,過程和如下類似:
這樣就很好理解了,要是想呼叫原型上面的方法,必須先new乙個例項出來。我們再來分析_方法的原始碼:var ygy = {};
ygy.__proto__ = person.prototype
// 或者var ygy = object.create(person.prototype)
person.call(ygy);
_接收乙個物件作為引數,如果這個物件是_的乙個例項,那麼直接返回這個物件。(這種情況我倒是沒見過)
如果this不是_的例項,那麼就會返回乙個新的例項new _(obj),這個該怎麼理解?
我們需要結合例子來看這句話,在_([1, 2, 3])中,obj肯定是指[1, 2, 3]這個陣列,那麼this是指什麼呢?我覺得this是指window,不信你直接執行一下上面例子中的person()?你會發現在全域性作用域中是可以拿到name和age兩個屬性的。
那麼既然this指向window,那麼this肯定不是_的例項,所以this instanceof _必然會返回false,這樣的話就會return乙個new _([1, 2, 3]),所以_([1, 2, 3])就是new _([1, 2, 3]),從我們前面對new的解釋來看,這個過程表現如下:
var obj = {}
obj.__proto__ = _.prototype
// 此時_函式中this的是new _(obj),this instanceof _是true,所以就不會重新return乙個new _(obj),這樣避免了迴圈呼叫
這樣我們就理解了為什麼_([1, 2, 3]).map中map是原型上的方法,因為_([1, 2, 3])是乙個例項。
我這裡再提供乙個自己實現的_思路,和jquery的實現類似,這裡就不作解釋了:
underscore中所有方法都是在_方法上面直接掛載的,並且用mixin方法將這些方法再一次掛載到了原型上面。不過,由於篇幅有限,mixin方法的實現會在後文中給大家講解。var _ = function(obj)
_.prototype = ,
name: function(name)
}_.prototype.init.prototype = _.prototype;
var a = _([1, 2, 3])
a.name("ygy"); // ygy
如果本文有錯誤和不足之處,希望大家指出。
underscore原始碼閱讀整理
underscore是我閱讀的第乙份原始碼,這份 比較小巧,只有1500行,我閱讀的版本是1.8.3.underscore裡封裝了很多功能性的函式,和jquery不同,我覺得jquery的特點是對針對dom,而underscore裡都是基於js的功能性函式,比如each,map等等。以下內容僅是我閱...
underscore原始碼分析 1
underscore 版本1.83 最主要的乙個特性是鏈式呼叫 1,2,3 each console.log 1 0 3 1,2,3 2 1 3 1,2,3 3 2 3 1,2,3 我們先簡單的實現鏈式呼叫的功能 實現 each 1,2,3 console.log 是很簡單的 直接 each函式就搞...
underscore 原始碼閱讀 四
keys one two three 檢索object擁有的所有可列舉屬性的名稱。我們知道,在js中本就提供了幾個方法如for.in.object.keys來遍歷物件的屬性,為什麼underscore還是要封裝乙個api呢?這其實是為相容ie9版本下的乙個bug做的封裝 在ie9以下的版本中,以下 ...