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