摘要:不過這樣子又回帶來另一個問題,對于函數,函數返回什么不重要,主要是處理過程,可以支持鏈式調用,對于函數,返回的是處理后的結果,可以不用鏈式,所以函數就是來判斷是否需要鏈式,而對返回值進行處理。然后后面還有一個函數,也是用來作為回調函數的。
其實,學習一個庫的源碼,最重要的就是先理清它的基本架構,jQuery 是這樣,Underscore 也應該是這樣。
Underscore 這個庫提供力很多有用的函數,這些函數部分已經在 es5 或 es6 中支持了,比如我們常用的 map、reduce、each,還有 es6 中的 keys 方法等,因為這些方法比較好用,所以被 javascript 的制定者采納了。
先過一遍源碼我看的版本是 1.8.3,網上很多舊版本的,貌似有很多函數都已經啟用或改變了,有點不一樣啦。
打開源碼,會看到函數的基本架構:
(function(){ ... }.call(this))
這和我們常見的閉包不太一樣啊,但是功能都是類似的,在函數內執行,防止對全局變量進行污染,然后在函數的最后調用 call 函數,把 函數內部的 this 和全局的 this 進行綁定。如果在瀏覽器里執行,this 會指向 window,在 node 環境下,會指向全局的 global。當厭倦使用閉包的時候,這種方法也是一種不錯的體驗。
那么接著向下看:
(function(){ var root = this; // 用 root 來保存當前的 this var previousUnderscore = root._; // 萬一 _ 之前被占用了,先備份 // 下面是一些原型,包括 數組,對象和函數 var ArrayProto = Array.prototype, ObjProto = Object.prototype, FuncProto = Function.prototype; var push = ArrayProto.push, slice = ArrayProto.slice, toString = ObjProto.toString, hasOwnProperty = ObjProto.hasOwnProperty; var nativeIsArray = Array.isArray, nativeKeys = Object.keys, nativeBind = FuncProto.bind, nativeCreate = Object.create; }.call(this))
在源碼中搜索 previousUnderscore,可以找到兩處,另外一處就是:
_.noConflict = function() { root._ = previousUnderscore; return this; };
noConflict 函數的用法是可以讓用戶自定義變量來替代 _,并且把之前保存的 _ 給還原,比如:
// 測試使用 var _ = "Hello World"; // _ 已經被占用 ... var us = _.noConflict(); us // 指向 underscore _ // ‘Hello World"
像 push slice 這些函數,原生都已經支持了,源碼里面直接把他們拿過來使用。
來看看 _ 是如何定義的_ 在 underscore 地位是非常核心的,而它的本質實際上還是函數,同樣也是一個對象:
var _ = function(obj) { if (obj instanceof _) return obj; if (!(this instanceof _)) return new _(obj); this._wrapped = obj; }; _.VERSION = "1.8.3"; _.map = _.collect = function(){...}; _.each = _.forEach = function(){...};
_ 是一個函數,但是在源碼中,它是被當作對象來使用,所有的屬性和函數都是直接綁定到 _ 對象上面的,所有最終的調用都是通過:
_.each([22,33,44], console.log); // 22 0 [22, 33, 44] // 33 1 [22, 33, 44] // 44 2 [22, 33, 44]
最終的返回值是處理的那個數組,而不是 _ 自己,下面將會討論,這個涉及到鏈式調用。
那如果,我就想通過函數來生成,這也是支持的:
_([1,2,3]).each(console.log) // 返回的結果都是一樣的
這個時候,就會疑惑,_ 的原型呢?我們再來搜索一下 _.prototype:
_.mixin = function(obj) { _.each(_.functions(obj), function(name) { // 調用 each 對每一個函數對象處理 var func = _[name] = obj[name]; // 綁定到 _ 上 _.prototype[name] = function() { // 綁定到 _ 的原型上 var args = [this._wrapped]; push.apply(args, arguments); // 參數對齊 return result(this, func.apply(_, args)); // 調用 result 查看是否鏈式 }; }); }; _.mixin(_); // 執行 // 相關的一些方法 _.functions = _.methods = function(obj) { var names = []; for (var key in obj) { if (_.isFunction(obj[key])) names.push(key); } return names.sort(); }; _.isFunction = function(obj){ return typeof obj == "function" || false; }
_.functions 是一個獲取目標所有函數對象的方法,并把這些方法淺拷貝傳遞給 _ 和 _的原型,因為原型方法,處理對象已經在 _wrapped 中了,而這些常用的方法參數都是固定的,如果直接調用,參數會出問題,所以:
var args = [this._wrapped]; push.apply(args, arguments);// args 已經拼接完成 func.apply(_, args);
那么 result 函數是用來做什么的?因為 underscore 有兩種調用方式,一種是通過 _.each(obj, func),另一種是通過 _(obj).each(func)。第一種方法很好理解,返回值要么是 obj 本身,要么是處理后的結果,而第二種調用方法和 jQuery 很像,先生成一個 new 實體,對實體的進行調用,也就有了上面的參數校準問題。
不過這樣子又回帶來另一個問題,對于 each、map 函數,函數返回什么不重要,主要是處理過程,可以支持鏈式調用,對于 reduce 函數,返回的是處理后的結果,可以不用鏈式,所以 result 函數就是來判斷是否需要鏈式,而對返回值進行處理。
介紹 result 之前,先來看一下 chain 函數:
_.chain = function(obj) { var instance = _(obj); instance._chain = true; // 設置一個 _chain 屬性,后面用于判斷鏈式 return instance; };
返回一個新的 _(obj),并且多了一個 _chain 屬性,且為 true,所以 result 函數:
var result = function(instance, obj) { return instance._chain ? _(obj).chain() : obj; };
如果當前是允許鏈式的,可以進行鏈式調用,不允許鏈式,就直接返回處理結果,比如:
var arr = [22, 33, 44]; _.chain(arr) .map(function(v){ return v + 1 }) .reduce(function(p, n){ return p + n }, 0) .value() // 102 // 如果不允許鏈式,返回結果是處理后的數組 _(arr) .map(function(v){ return v + 1 }) // [23, 34, 45]
現在返回來看一下 _ 函數,也非常的有意思,_(obj)實際上是執行兩次的,第二次才用到了 new:
var _ = function(obj) { if (obj instanceof _) return obj; // 如果 obj 繼承于 _,直接返回 if (!(this instanceof _)) return new _(obj); // 如果 this 不繼承 _,返回一個 new this._wrapped = obj; // 保存 obj 的值 };
現在應該就非常的明朗了吧。當調用 _([22,33,44]) 的時候,發現 obj 并不是繼承與 _,會用 new 來生成,又會重新跑一遍 _ 函數,然后將 _wrapped 屬性指向 obj。
由于在之前已經 root = this,Underscore 在不同的環境中都可以運行,需要將 _ 放到不同的環境中:
if (typeof exports !== "undefined") { // nodejs 模塊 if (typeof module !== "undefined" && module.exports) { exports = module.exports = _; } exports._ = _; } else { // window root._ = _; }接著看源碼
源碼再往下看,是一個 optimizeCb 函數,用來優化回調函數:
var optimizeCb = function(func, context, argCount) { // 這里沒有用 undefined,而是用 void 0 if (context === void 0) return func; // 只有一個參數,直接返回回調函數 switch (argCount == null ? 3 : argCount) { // call 比 apply 好? case 1: return function(value) { return func.call(context, value); }; case 2: return function(value, other) { return func.call(context, value, other); }; case 3: return function(value, index, collection) { return func.call(context, value, index, collection); }; case 4: return function(accumulator, value, index, collection) { return func.call(context, accumulator, value, index, collection); }; } // 最后走 apply 函數 return function() { return func.apply(context, arguments); }; };
所謂優化版的回調函數,就是用 call 來固定參數,1 個參數,2 個參數,3 個參數,4 個參數的時候,由于 apply 可以不用考慮參數,但是在性能上面貌似沒有 call 好。
然后后面還有一個 cb 函數,也是用來作為回調函數的。
var cb = function(value, context, argCount) { if (value == null) return _.identity; if (_.isFunction(value)) return optimizeCb(value, context, argCount); if (_.isObject(value)) return _.matcher(value); return _.property(value); }; _.iteratee = function(value, context) { return cb(value, context, Infinity); };
iteratee 可以用來對函數進行處理,給一個函數綁定 this 等等,最總還是調用到 cb,其實 cb 本身就很復雜,要么是一個 identity 函數,要么是一個優化到回調函數,要么是一個 property 獲取屬性函數。
再往下就是 createAssigner,搜了一下,發現全文有三處用到此函數,分別是 extend、extendOwn、default,可以看出來,此函數主要到作用是用來實現拷貝,算是拷貝到輔助函數吧,把拷貝公共到部分抽離出來:
var createAssigner = function(keysFunc, undefinedOnly) { return function(obj) { var length = arguments.length; if (length < 2 || obj == null) return obj; // 將第二個參數及以后的 object 拷貝到第一個 obj 上 for (var index = 1; index < length; index++) { var source = arguments[index], // keysFunc 是點睛所在 // 不同的 keysFunc 獲得的 keys 集合不同 // 分為兩種,所有 keys(包括繼承),自身 keys keys = keysFunc(source), l = keys.length; for (var i = 0; i < l; i++) { var key = keys[i]; // underfinedOnly 表示是否覆蓋原有 if (!undefinedOnly || obj[key] === void 0) obj[key] = source[key]; } } return obj; }; };
所以當 keyFunc 函數獲得所有 keys 時,包括繼承來的,這個時候就對應于 _.extend 函數,非繼承 keys 時,對應于 _.extendOwn。如果 underfinedOnly 設置為 true,則實現的是不替換原有屬性的繼承 _.defaults。
在 Underscore 中,原型的繼承用 baseCreate 函數:
var Ctor = function(){}; var baseCreate = function(prototype) { if (!_.isObject(prototype)) return {}; if (nativeCreate) return nativeCreate(prototype); Ctor.prototype = prototype; var result = new Ctor; Ctor.prototype = null; return result; };
nativeCreate 之前已經介紹來,就是 Object.create,所以,如果瀏覽器不支持,下面實現的功能就是在實現這個函數,方法也很常規,用了一個空函數 Ctor 主要是防止 new 帶來的多余屬性問題。
property 函數也是一個比較有意思的函數,使用了閉包的思路,比如判斷一個對象是否為類似數組結構的時候就用到了這個函數:
var property = function(key) { return function(obj) { return obj == null ? void 0 : obj[key]; }; }; var MAX_ARRAY_INDEX = Math.pow(2, 53) - 1; var getLength = property("length"); // 返回一個閉包韓式,用來檢測對象是非有 length 參數 var isArrayLike = function(collection) { var length = getLength(collection); return typeof length == "number" && length >= 0 && length <= MAX_ARRAY_INDEX; };
而且我搜索了一下,發現 getLength 函數使用的地方還是挺多的。
總結總的來說,這些開源的庫,都保持著自己的一種風格,jQuery 是這樣,Underscore 也是這樣,從 Underscore 的總體架構可以發現,它主要封裝了一些好用的方法。
參考Underscore.js (1.8.3) 中文文檔
Underscore源碼解析(一)
中文版 underscore 代碼注釋
歡迎來我的博客交流。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/82749.html
摘要:我這里有個不夠準確但容易理解的說法,就是檢查一個對象是否為另一個構造函數的實例,為了更容易理解,下面將全部以是的實例的方式來說。 underscore源碼分析之整體架構 最近打算好好看看underscore源碼,一個是因為自己確實水平不夠,另一個是underscore源碼比較簡單,比較易讀。本系列打算對underscore1.8.3中關鍵函數源碼進行分析,希望做到最詳細的源碼分析。今...
摘要:譯立即執行函數表達式處理支持瀏覽器環境微信小程序。學習整體架構,利于打造屬于自己的函數式編程類庫。下一篇文章可能是學習的源碼整體架構。也可以加微信,注明來源,拉您進前端視野交流群。 前言 上一篇文章寫了jQuery整體架構,學習 jQuery 源碼整體架構,打造屬于自己的 js 類庫 雖然看過挺多underscore.js分析類的文章,但總感覺少點什么。這也許就是紙上得來終覺淺,絕知此...
摘要:支持形式的調用這其實是非常經典的無構造,其實就是一個構造函數,的結果就是一個對象實例,該實例有個屬性,屬性值是。 前言 終于,樓主的「Underscore 源碼解讀系列」underscore-analysis 即將進入尾聲,關注下 timeline 會發現樓主最近加快了解讀速度。十一月,多事之秋,最近好多事情搞的樓主心力憔悴,身心俱疲,也想盡快把這個系列完結掉,也好了卻一件心事。 本文...
摘要:所以,剛開始,我從源碼比較短的包含注釋只有行開始學習起。一般,在客戶端瀏覽器環境中,即為,暴露在全局中。學習以后判斷直接使用看起來也優雅一點滑稽臉。在的函數視線中,的作用執行一個傳入函數次,并返回由每次執行結果組成的數組。 前言 最近在社區瀏覽文章的時候,看到了一位大四學長在尋求前端工作中的面經,看完不得不佩服,掌握知識點真是全面,無論是前端后臺還是其他,都有涉獵。 在他寫的文章中,有...
摘要:在上篇文章整體架構分析中,我們講過上面的方法有兩種掛載方式,一個是掛載到構造函數上以的形式直接調用在后文上統稱構造函數調用,另一種則是掛到上以的形式被實例調用在后文上統稱原型調用。 underscore源碼分析之基礎方法 本文是underscore源碼剖析系列的第二篇,主要介紹underscore中一些基礎方法的實現。 mixin 在上篇文章underscore整體架構分析中,我們講...
閱讀 623·2023-04-26 01:53
閱讀 2749·2021-11-17 17:00
閱讀 2880·2021-09-04 16:40
閱讀 1983·2021-09-02 15:41
閱讀 830·2019-08-26 11:34
閱讀 1222·2019-08-26 10:16
閱讀 1335·2019-08-23 17:51
閱讀 815·2019-08-23 16:50