摘要:所以,剛開始,我從源碼比較短的包含注釋只有行開始學習起。一般,在客戶端瀏覽器環境中,即為,暴露在全局中。學習以后判斷直接使用看起來也優雅一點滑稽臉。在的函數視線中,的作用執行一個傳入函數次,并返回由每次執行結果組成的數組。
前言
最近在社區瀏覽文章的時候,看到了一位大四學長在尋求前端工作中的面經,看完不得不佩服,掌握知識點真是全面,無論是前端后臺還是其他,都有涉獵。
在他寫的文章中,有這么一句話,大概意思是,沒有看過一個庫或者框架的源碼還敢出來混。然后自己心虛了一下,一直以來,都只是學習使用框架或庫,或者在過程中有學習框架的思想,但并不深入。例如,在學習Vue.js中,我曾經去探索過Vue中的雙向綁定是如何實現的,通過什么模式,什么API,作者的思想是什么,也曾經實現過簡單版的雙向綁定。
但是感覺自己在這方面并沒有什么提高,尤其在原生JavaScript的學習中,一些不常用的API經常忘,思維也不夠好。所以有了學習優秀的庫的源碼的想法,一方面能夠學習作者的思想,提高自己的分析能力,另一方面我覺得如果能好好分析一個庫的源碼,對自己的提升也是有的。
所以,剛開始,我從源碼比較短的underscore.js(包含注釋只有1.5k行)開始學習起。
什么是underscoreUnderscore一個JavaScript實用庫,提供了一整套函數式編程的實用功能,但是沒有擴展任何JavaScript內置對象。它是這個問題的答案:“如果我在一個空白的HTML頁面前坐下, 并希望立即開始工作, 我需要什么?“...它彌補了部分jQuery沒有實現的功能,同時又是Backbone.js必不可少的部分?!訳nderscore中文文檔
我的學習之路是基于Underscore1.8.3版本開始的。
// Current version. _.VERSION = "1.8.3";作用域包裹
與其他第三方庫一樣,underscore最外層是一個立即執行函數(IIFE),來包裹自己的業務邏輯。一般使用IIFE有如下好處,可以創建一個獨立的沙箱似的作用域,避免全局污染,還可以防止其他代碼對該函數內部造成影響。(但凡在立即執行函數中聲明的函數、變量等,除非是自己想暴露,否則絕無可能在外部獲得)
(function(){ // ...執行邏輯 }.call(this))
_對象學習的點,當我們要寫自己的庫或者封裝某個功能函數時,可以給自己的庫或函數在最外層包裹一個立即執行函數,這樣既不會受外部影響,也不會給外部添麻煩。
underscore有下劃線的意思,所以underscore通過一個下劃線變量_來標識自身,值得注意的是,_是一個函數對象或者說是一個構造函數,并且支持無new調用的構造的函數,所有API都會掛載在這個對象上,如_.each,_.map等
var _ = function(obj) { if(obj instanceof _) return obj; if(!(this instanceof _)) return new _(obj) //實例化 this._wrapped = obj }全局命名空間
underscore使用root變量保存了全局的this。
var root = this;
為了防止其他庫對_的沖突或影響,underscore做了如下處理,
var previousUnderscore = root._ _.noConflict = function() { root._ = perviousUnderscore; return this; }執行環境判斷
underscore 既能夠服務于瀏覽器,又能夠服務于諸如 nodejs 所搭建的服務端。
一般,在客戶端(瀏覽器)環境中,_即為window._=_,暴露在全局中。若在node環境中,_將被作為模塊導出,并且向后兼容老的API,即require。
if (typeof exports !== "undefined") { if (typeof module !== "undefined" && module.exports) { exports = module.exports = _; } exports._ = _ ; } esle { root._ = _; }緩存局部變量及快速引用
underscore本身用到了不少ES5的原生方法,在瀏覽器支持的條件下,underscore率先使用原生的ES5方法。如下代碼所示,underscore通過局部變量來保存一些常用到的方法或者屬性。
這樣做有幾個好處:
便于壓縮代碼
提高代碼性能,減少在原型鏈中的查找次數
同時也可減少代碼量,避免在使用時冗長的書寫
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;undefined處理
在underscore中,有很多函數都會有一個context函數,也就是當前函數的執行上下文,underscore對其進行了處理,如果沒有傳入context即context為undefined,則返回原函數。
這里判斷值為undefined用的是void 0,如下:
if (context === void 0) return func
作為一只涉獵尚淺的小白,查閱資料之后終于知道這里作者為什么要用void 0來做判斷了。
詳情可點鏈接了解,這樣做更加安全可靠。
在還沒看到這個代碼時, 如果我要判斷一個值是不是undefined,我會這樣寫
if (context === undefined) {}
但是,在發現作者的void 0之后,才發現這樣寫并不可靠,在JavaScript中,我們可以這樣寫:
args => { let undefined = 1 console.log(undefined) // => 1 if (args === undefined) { //... } }
如果這樣寫,undefined就被輕易地修改為了1,所以對于我們之后定義的undefined的理解有歧義。所以,在JavaScript中,把undefined直接解釋為“未定義”是有風險的,因為它可能被修改。
處理類數組學習:以后判斷undefined直接使用void 0, 看起來也優雅一點(滑稽臉)。
// getLength 函數 // 該函數傳入一個參數,返回參數的 length 屬性值 // 用來獲取 array 以及 arrayLike 元素的 length 屬性值 var getLength = property("length"); // 判斷是否是 ArrayLike Object // 類數組,即擁有 length 屬性并且 length 屬性值為 Number 類型的元素 // 包括數組、arguments、HTML Collection 以及 NodeList 等等 // 包括類似 {length: 10} 這樣的對象 // 包括字符串、函數等 var isArrayLike = function(collection) { var length = getLength(collection); return typeof length == "number" && length >= 0 && length <= MAX_ARRAY_INDEX; };對象創建的特殊處理
為了處理Object.create的跨瀏覽器的兼容性,underscore進行了特殊的處理。我們知道,原型是無法直接實例化的,因此我們先創建一個空對象,然后將其原型指向這個我們想要實例化的原型,最后返回該對象其一個實例。其代碼如下:
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; // 恢復Ctor的原型供下次使用 return result; // 返回該實例 };underscore中的迭代(iteratee)
在函數式編程中,使用更多的是迭代,而不是循環。
迭代:
var res = _.map([1,2], function(item){ return item * 2 })
循環:
var arr = [1,2] var res = [] for(var i = 0; i < arr.length; i++) { res.push(arr[i] * 2) }
在underscore中迭代使用非常巧妙,源碼也寫的非常好,通過傳入的數據類型不同而選擇不同的迭代函數。
首先,在underscore中_.map的實現如下:
_.map = _.collect = function(obj, iteratee, context) { iteratee = cb(iteratee, context); var keys = !isArrayLike(obj) && _.keys(obj), length = (keys || obj).length, results = Array(length); for (var index = 0; index < length; index++) { var currentKey = keys ? keys[index] : index; results[index] = iteratee(obj[currentKey], currentKey, obj) //(value, index, obj) } return results; }
可以看到,在_.map函數中的第二個參數iteratee,這個參數的格式可以是函數,對象,字符串。underscore會將其處理成一個函數,這將由回調函數cb來完成,我們來看一下cb的實現:
var cb = function(value, context, argCount) { // 是否用默認的迭代器 如果沒有傳入value 則返回當前迭代元素自身 if (value == null) return _.identity; // 如果value是一個回調函數, 則需要優化回調 優化函數為optimizeCb if (_.isFunction(value)) return optimizeCb(value, context, argCount); // 如果value是個對象, 則返回一個matcher進行對象匹配 if (_.isObject(value)) return _.matcher(value) // 否則, 如果value只是一個字面量, 則把value看做是屬性名稱, 返回一個對應的屬性獲得函數 return _.property(value); }
前面兩個比較容易理解,看看當傳入的數據格式為對象的情況,如果 value 傳入的是一個對象,那么返回iteratee(_.matcher)的目的是想要知道當前被迭代元素是否匹配給定的這個對象:
var results = _.map([{name:"water"},{name: "lzb",age:13}], {name: "lzb"}); // => results: [false,true]
如果傳入的是字面量,如數字,字符串等, 他會返回對應的key值,如下:
var results = _.map([{name:"water"},{name:"lzb"}],"name"); // => results: ["water", "lzb"];回調處理
在上面的cb函數中,我們可以看到,當傳入的數據格式是函數,則需要通過optimizeCb函數進行統一處理,返回對應的回調函數,下面是underscore中optimizeCb函數的實現:
// 回調處理 // underscore 內部方法 // 根據 this 指向(context 參數) // 以及 argCount 參數 // 二次操作返回一些回調、迭代方法 var optimizeCb = function(func, context, argCount) { // // void 0 會返回純正的undefined,這樣做避免undefined已經被污染帶來的判定失效 if (context === void 0) return func; switch (argCount == null ? 3 : argCount) { // 回調參數為1時, 即迭代過程中,我們只需要值 // _.times case 1: return function(value) { return func.call(context, value); }; case 2: return function(value, other) { return func.call(context, value, other); }; // 3個參數(值,索引,被迭代集合對象) // _.each、_.map (value, key, obj) case 3: return function(value, index, collection) { return func.call(context, value, index, collection); }; // 4個參數(累加器(比如reducer需要的), 值, 索引, 被迭代集合對象) // _.reduce、_.reduceRight case 4: return function(accumulator, value, index, collection) { return func.call(context, accumulator, value, index, collection); }; } // 如果都不符合上述的任一條件,直接使用apply調用相關函數 return function() { return func.apply(context, arguments); }; }
optimizeCb 的總體思路就是:傳入待優化的回調函數 func,以及迭代回調需要的參數個數argCount,根據參數個數分情況進行優化。
在underscore的_.times函數視線中,_times的作用執行一個傳入iteratee函數n次,并返回由每次執行結果組成的數組。它的迭代過程iteratee只需要1個參數(當前迭代的索引)
_.times函數在underscore中的實現:
_.times = function(n, iteratee, context) { vat accum = Array(Math.max(0, n)); iteratee = optimizeCb(iteratee, context, 1); for (var i = 0; i < n; i++) accum[i] = iteratee(i); return accum; }
_.times的使用
function getIndex(index) { return index; } var results = _.times(3, getIndex); // => [0,1,2]
optimizeCb函數中當argCount的個數為2的情況并不常見,在_.each,_.map等函數中,argCount的值為3(value, key, obj),當argCount需要四個參數時,這四個參數的格式為:
accumulator:累加器
value:迭代元素
index:迭代索引
collection:當前迭代集合
underscore中reduce的實現如下:
/** * reduce函數的工廠函數, 用于生成一個reducer, 通過參數決定reduce的方向 * @param dir 方向 left or right * @returns {function} */ function createReduce(dir) { function iterator(obj, iteratee, memo, keys, index, length) { for(; index >= 0 && index < length; index += dir) { var currentKey = keys ? keys[index] : index; // memo 用來記錄最新的 reduce 結果 // 執行 reduce 回調, 刷新當前值 memo = iteratee(memo, obj[currentKey], currentKey, obj); } return memo; } return function(obj, iteratee, memo, context) { // 優化回調 iteratee = optimizeCb(iteratee, context, 4); var keys = !isArrayLike(obj) && _.keys(obj), length = (keys || obj).length, index = dir > 0 ? 0 : length - 1; if (arguments.length < 3) { // 如果沒有傳入memo初始值 則從左第一個為初始值 從右則最后一個為初始值 memo = obj[keys ? keys[index] : index]; index += dir; } // return func return iterator(obj, iteratee, memo, keys, index, length); } }
例如在_.reduce、_.reduceRight中,argCount的值為4??纯磚nderscore中_.reduce的使用例子
var sum = _.reduce([1,2,3,4], function(accumulator, value, index, collection){ return accumulator + value; }, 0) // => 10
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/91906.html
摘要:所以它與其他系列的文章并不沖突,完全可以在閱讀完這個系列后,再跟著其他系列的文章接著學習。如何閱讀我在寫系列的時候,被問的最多的問題就是該怎么閱讀源碼我想簡單聊一下自己的思路。感謝大家的閱讀和支持,我是冴羽,下個系列再見啦 前言 別名:《underscore 系列 8 篇正式完結!》 介紹 underscore 系列是我寫的第三個系列,前兩個系列分別是 JavaScript 深入系列、...
摘要:本文同步自我得博客最近準備折騰一下,在事先了解了之后,我知道了對這個庫有著強依賴,正好之前也沒使用過,于是我就想先把徹底了解一下,這樣之后折騰的時候也少一點阻礙。 本文同步自我得博客:http://www.joeray61.com 最近準備折騰一下backbone.js,在事先了解了backbone之后,我知道了backbone對underscore這個庫有著強依賴,正好undersc...
摘要:譯立即執行函數表達式處理支持瀏覽器環境微信小程序。學習整體架構,利于打造屬于自己的函數式編程類庫。下一篇文章可能是學習的源碼整體架構。也可以加微信,注明來源,拉您進前端視野交流群。 前言 上一篇文章寫了jQuery整體架構,學習 jQuery 源碼整體架構,打造屬于自己的 js 類庫 雖然看過挺多underscore.js分析類的文章,但總感覺少點什么。這也許就是紙上得來終覺淺,絕知此...
摘要:所以經常會在一個源碼中看到寫法吧立即執行函數創建變量,保存全局根變量。 // ================立即執行函數================ // 使用(function(){}())立即執行函數,減少全局變量 // ----????----函數聲明 function (){} 與函數表達式 var funName = function(){}----????---- /...
閱讀 2843·2023-04-26 01:02
閱讀 1863·2021-11-17 09:38
閱讀 791·2021-09-22 15:54
閱讀 2899·2021-09-22 15:29
閱讀 888·2021-09-22 10:02
閱讀 3432·2019-08-30 15:54
閱讀 2007·2019-08-30 15:44
閱讀 1586·2019-08-26 13:46