學習 lodash 源碼整體架構,打造屬于自己的函數式編程類庫

前言

這是?學習源碼整體架構系列第三篇。整體架構這詞語好像有點大,姑且就算是源碼整體結構吧,主要就是學習是代碼整體結構,不深究其他不是主線的具體函數的實現。文章學習的是打包整合后的代碼,不是實際倉庫中的拆分的代碼。

上上篇文章寫了?jQuery源碼整體架構,學習 jQuery 源碼整體架構,打造屬于自己的 js 類庫

上一篇文章寫了?underscore源碼整體架構,學習 underscore 源碼整體架構,打造屬于自己的函數式編程類庫

感興趣的讀者可以點擊閱讀。

underscore源碼分析的文章比較多,而?lodash源碼分析的文章比較少。原因之一可能是由于?lodash源碼行數太多。注釋加起來一萬多行。

分析?lodash整體代碼結構的文章比較少,筆者利用谷歌、必應、?github等搜索都沒有找到,可能是找的方式不對。于是打算自己寫一篇。平常開發大多數人都會使用?lodash,而且都或多或少知道,?lodash比?underscore性能好,性能好的主要原因是使用了惰性求值這一特性。

本文章學習的?lodash的版本是:?v4.17.15。?unpkg.com地址 https://unpkg.com/lodash@4.17.15/lodash.js

文章篇幅可能比較長,可以先收藏再看。

導讀:

文章主要學習了?runInContext()?導出?_?lodash函數使用?baseCreate方法原型繼承?LodashWrapper和?LazyWrapper,?mixin掛載方法到?lodash.prototype、后文用結合例子解釋?lodash.prototype.value(wrapperValue)和?Lazy.prototype.value(lazyValue)惰性求值的源碼具體實現。

匿名函數執行

;(function() {
}.call(this));

暴露 lodash

var _ = runInContext();

runInContext 函數

這里的簡版源碼,只關注函數入口和返回值。

var runInContext = (function runInContext(context) {// 瀏覽器中處理context為window// ...function lodash(value) {}{// ...return new LodashWrapper(value);}// ...return lodash;
});

可以看到申明了一個?runInContext函數。里面有一個?lodash函數,最后處理返回這個?lodash函數。

再看?lodash函數中的返回值?newLodashWrapper(value)

LodashWrapper 函數

function LodashWrapper(value, chainAll) {this.__wrapped__ = value;this.__actions__ = [];this.__chain__ = !!chainAll;this.__index__ = 0;this.__values__ = undefined;
}

設置了這些屬性:

__wrapped__:存放參數?value

__actions__:存放待執行的函數體?func, 函數參數?args,函數執行的?this?指向?thisArg

__chain__、?undefined兩次取反轉成布爾值?false,不支持鏈式調用。和?underscore一樣,默認是不支持鏈式調用的。

__index__:索引值 默認 0。

__values__:主要?clone時使用。

接著往下搜索源碼,?LodashWrapper, 會發現這兩行代碼。

LodashWrapper.prototype = baseCreate(baseLodash.prototype);
LodashWrapper.prototype.constructor = LodashWrapper;

接著往上找?baseCreate、baseLodash這兩個函數。

baseCreate 原型繼承

//  立即執行匿名函數
// 返回一個函數,用于設置原型 可以理解為是 __proto__
var baseCreate = (function() {// 這句放在函數外,是為了不用每次調用baseCreate都重復申明 object// underscore 源碼中,把這句放在開頭就申明了一個空函數 `Ctor`function object() {}return function(proto) {// 如果傳入的參數不是object也不是function 是null// 則返回空對象。if (!isObject(proto)) {return {};}// 如果支持Object.create方法,則返回 Object.createif (objectCreate) {// Object.createreturn objectCreate(proto);}// 如果不支持Object.create 用 ployfill newobject.prototype = proto;var result = new object;// 還原 prototypeobject.prototype = undefined;return result;};
}());
// 空函數
function baseLodash() {// No operation performed.
}
// Ensure wrappers are instances of `baseLodash`.
lodash.prototype = baseLodash.prototype;
// 為什么會有這一句?因為上一句把lodash.prototype.construtor 設置為Object了。這一句修正constructor
lodash.prototype.constructor = lodash;
LodashWrapper.prototype = baseCreate(baseLodash.prototype);
LodashWrapper.prototype.constructor = LodashWrapper;

筆者畫了一張圖,表示這個關系。?

衍生的 isObject 函數

判斷?typeofvalue不等于?null,并且是?object或者?function

function isObject(value) {var type = typeof value;return value != null && (type == 'object' || type == 'function');
}

Object.create() 用法舉例

面試官問:能否模擬實現JS的new操作符?之前這篇文章寫過的一段。

筆者之前整理的一篇文章中也有講過,可以翻看JavaScript 對象所有API解析

MDN Object.create()

Object.create(proto,[propertiesObject])?方法創建一個新對象,使用現有的對象來提供新創建的對象的proto。它接收兩個參數,不過第二個可選參數是屬性描述符(不常用,默認是?undefined)。

var anotherObject = {name: '若川'
};
var myObject = Object.create(anotherObject, {age: {value:18,},
});
// 獲得它的原型
Object.getPrototypeOf(anotherObject) === Object.prototype; // true 說明anotherObject的原型是Object.prototype
Object.getPrototypeOf(myObject); // {name: "若川"} // 說明myObject的原型是{name: "若川"}
myObject.hasOwnProperty('name'); // false; 說明name是原型上的。
myObject.hasOwnProperty('age'); // true 說明age是自身的
myObject.name; // '若川'
myObject.age; // 18;

對于不支持?ES5的瀏覽器,?MDN上提供了?ployfill方案。

if (typeof Object.create !== "function") {Object.create = function (proto, propertiesObject) {if (typeof proto !== 'object' && typeof proto !== 'function') {throw new TypeError('Object prototype may only be an Object: ' + proto);} else if (proto === null) {throw new Error("This browser's implementation of Object.create is a shim and doesn't support 'null' as the first argument.");}if (typeof propertiesObject != 'undefined') throw new Error("This browser's implementation of Object.create is a shim and doesn't support a second argument.");function F() {}F.prototype = proto;return new F();};
}

lodash上有很多方法和屬性,但在?lodash.prototype也有很多與?lodash上相同的方法。肯定不是在?lodash.prototype上重新寫一遍。而是通過?mixin掛載的。

mixin

mixin 具體用法

_.mixin([object=lodash], source, [options={}])

添加來源對象自身的所有可枚舉函數屬性到目標對象。如果 object 是個函數,那么函數方法將被添加到原型鏈上。

注意: 使用 _.runInContext 來創建原始的 lodash 函數來避免修改造成的沖突。

添加版本

0.1.0

參數

[object=lodash] (Function|Object): 目標對象。

source (Object): 來源對象。

[options={}] (Object): 選項對象。

[options.chain=true] (boolean): 是否開啟鏈式操作。

返回

(*): 返回 object.

mixin 源碼

mixin源碼,后文注釋解析

function mixin(object, source, options) {var props = keys(source),methodNames = baseFunctions(source, props);if (options == null &&!(isObject(source) && (methodNames.length || !props.length))) {options = source;source = object;object = this;methodNames = baseFunctions(source, keys(source));}var chain = !(isObject(options) && 'chain' in options) || !!options.chain,isFunc = isFunction(object);arrayEach(methodNames, function(methodName) {var func = source[methodName];object[methodName] = func;if (isFunc) {object.prototype[methodName] = function() {var chainAll = this.__chain__;if (chain || chainAll) {var result = object(this.__wrapped__),actions = result.__actions__ = copyArray(this.__actions__);actions.push({ 'func': func, 'args': arguments, 'thisArg': object });result.__chain__ = chainAll;return result;}return func.apply(object, arrayPush([this.value()], arguments));};}});return object;
}

接下來先看衍生的函數。

其實看到具體定義的函數代碼就大概知道這個函數的功能。為了不影響主線,導致文章篇幅過長。具體源碼在這里就不展開。

感興趣的讀者可以自行看這些函數衍生的其他函數的源碼。

mixin 衍生的函數 keys

在?mixin?函數中 其實最終調用的就是?Object.keys

function keys(object) {return isArrayLike(object) ? arrayLikeKeys(object) : baseKeys(object);
}

mixin 衍生的函數 baseFunctions

返回函數數組集合

function baseFunctions(object, props) {return arrayFilter(props, function(key) {return isFunction(object[key]);});
}

mixin 衍生的函數 isFunction

判斷參數是否是函數

function isFunction(value) {if (!isObject(value)) {return false;}// The use of `Object#toString` avoids issues with the `typeof` operator// in Safari 9 which returns 'object' for typed arrays and other constructors.var tag = baseGetTag(value);return tag == funcTag || tag == genTag || tag == asyncTag || tag == proxyTag;
}

mixin 衍生的函數 arrayEach

類似 [].forEarch

function arrayEach(array, iteratee) {var index = -1,length = array == null ? 0 : array.length;while (++index < length) {if (iteratee(array[index], index, array) === false) {break;}}return array;
}

mixin 衍生的函數 arrayPush

類似 [].push

function arrayPush(array, values) {var index = -1,length = values.length,offset = array.length;while (++index < length) {array[offset + index] = values[index];}return array;
}

mixin 衍生的函數 copyArray

拷貝數組

function copyArray(source, array) {var index = -1,length = source.length;array || (array = Array(length));while (++index < length) {array[index] = source[index];}return array;
}

mixin 源碼解析

lodash?源碼中兩次調用?mixin

// Add methods that return wrapped values in chain sequences.
lodash.after = after;
// code ... 等 153 個支持鏈式調用的方法
// Add methods to `lodash.prototype`.
// 把lodash上的靜態方法賦值到 lodash.prototype 上
mixin(lodash, lodash);
// Add methods that return unwrapped values in chain sequences.
lodash.add = add;
// code ... 等 152 個不支持鏈式調用的方法
// 這里其實就是過濾 after 等支持鏈式調用的方法,獲取到 lodash 上的 add 等 添加到lodash.prototype 上。
mixin(lodash, (function() {var source = {};// baseForOwn 這里其實就是遍歷lodash上的靜態方法,執行回調函數baseForOwn(lodash, function(func, methodName) {// 第一次 mixin 調用了所以賦值到了lodash.prototype// 所以這里用 Object.hasOwnProperty 排除不在lodash.prototype 上的方法。也就是 add 等 152 個不支持鏈式調用的方法。if (!hasOwnProperty.call(lodash.prototype, methodName)) {source[methodName] = func;}});return source;
// 最后一個參數options 特意注明不支持鏈式調用
}()), { 'chain': false });

結合兩次調用?mixin?代入到源碼解析如下 mixin源碼及注釋

function mixin(object, source, options) {// source 對象中可以枚舉的屬性var props = keys(source),// source 對象中的方法名稱數組methodNames = baseFunctions(source, props);if (options == null &&!(isObject(source) && (methodNames.length || !props.length))) {// 如果 options 沒傳為 undefined  undefined == null 為true// 且 如果source 不為 對象或者不是函數// 且 source對象的函數函數長度 或者 source 對象的屬性長度不為0// 把 options 賦值為 sourceoptions = source;// 把 source 賦值為 objectsource = object;// 把 object 賦值為 this 也就是 _ (lodash)object = this;// 獲取到所有的方法名稱數組methodNames = baseFunctions(source, keys(source));}// 是否支持 鏈式調用// options  不是對象或者不是函數,是null或者其他值// 判斷options是否是對象或者函數,如果不是或者函數則不會執行 'chain' in options 也就不會報錯//  且 chain 在 options的對象或者原型鏈中// 知識點 in [MDN in :  https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Operators/in// 如果指定的屬性在指定的對象或其原型鏈中,則in 運算符返回true。// 或者 options.chain 轉布爾值var chain = !(isObject(options) && 'chain' in options) || !!options.chain,// object 是函數isFunc = isFunction(object);// 循環 方法名稱數組arrayEach(methodNames, function(methodName) {// 函數本身var func = source[methodName];// object 通常是 lodash  也賦值這個函數。object[methodName] = func;if (isFunc) {// 如果object是函數 賦值到  object prototype  上,通常是lodashobject.prototype[methodName] = function() {// 實例上的__chain__ 屬性 是否支持鏈式調用// 這里的 this 是 new LodashWrapper 實例 類似如下/**{__actions__: [],__chain__: true__index__: 0__values__: undefined__wrapped__: []}**/var chainAll = this.__chain__;// options 中的 chain 屬性 是否支持鏈式調用// 兩者有一個符合鏈式調用  執行下面的代碼if (chain || chainAll) {// 通常是 lodashvar result = object(this.__wrapped__),// 復制 實例上的 __action__ 到 result.__action__ 和 action 上actions = result.__actions__ = copyArray(this.__actions__);// action 添加 函數 和 args 和 this 指向,延遲計算調用。actions.push({ 'func': func, 'args': arguments, 'thisArg': object });//實例上的__chain__ 屬性  賦值給 result 的 屬性 __chain__result.__chain__ = chainAll;// 最后返回這個實例return result;}// 都不支持鏈式調用。直接調用// 把當前實例的 value 和 arguments 對象 傳遞給 func 函數作為參數調用。返回調用結果。return func.apply(object, arrayPush([this.value()], arguments));};}});// 最后返回對象 objectreturn object;
}

小結:簡單說就是把?lodash上的靜態方法賦值到?lodash.prototype上。分兩次第一次是支持鏈式調用(?lodash.after等?153個支持鏈式調用的方法),第二次是不支持鏈式調用的方法(?lodash.add等?152個不支持鏈式調用的方法)。

lodash 究竟在和.prototype掛載了多少方法和屬性

再來看下?lodash究竟掛載在?_函數對象上有多少靜態方法和屬性,和掛載?_.prototype上有多少方法和屬性。

使用?forin循環一試便知。看如下代碼:

var staticMethods = [];
var staticProperty = [];
for(var name in _){if(typeof _[name] === 'function'){staticMethods.push(name);}else{staticProperty.push(name);}
}
console.log(staticProperty); // ["templateSettings", "VERSION"] 2個
console.log(staticMethods); // ["after", "ary", "assign", "assignIn", "assignInWith", ...] 305個

其實就是上文提及的?lodash.after?等?153個支持鏈式調用的函數 、?lodash.add?等?152不支持鏈式調用的函數賦值而來。

var prototypeMethods = [];
var prototypeProperty = [];
for(var name in _.prototype){if(typeof _.prototype[name] === 'function'){prototypeMethods.push(name);}else{prototypeProperty.push(name);}
}
console.log(prototypeProperty); // []
console.log(prototypeMethods); // ["after", "all", "allKeys", "any", "assign", ...] 317個

相比?lodash上的靜態方法多了?12個,說明除了?mixin?外,還有?12個其他形式賦值而來。

支持鏈式調用的方法最后返回是實例對象,獲取最后的處理的結果值,最后需要調用?value方法。

筆者畫了一張表示?lodash的方法和屬性掛載關系圖。

請出貫穿下文的簡單的例子

var result = _.chain([1, 2, 3, 4, 5])
.map(el => {console.log(el); // 1, 2, 3return el + 1;
})
.take(3)
.value();
// lodash中這里的`map`僅執行了`3`次。
// 具體功能也很簡單 數組 1-5 加一,最后獲取其中三個值。
console.log('result:', result);

也就是說這里?lodash聰明的知道了最后需要幾個值,就執行幾次?map循環,對于很大的數組,提升性能很有幫助。
而?underscore執行這段代碼其中?map執行了5次。如果是平常實現該功能也簡單。

var result = [1, 2, 3, 4, 5].map(el => el + 1).slice(0, 3);
console.log('result:', result);

而相比?lodash這里的?map執行了?5次。

// 不使用 map、slice
var result = [];
var arr = [1, 2, 3, 4, 5];
for (var i = 0; i < 3; i++){result[i] = arr[i] + 1;
}
console.log(result, 'result');

簡單說這里的?map方法,添加?LazyWrapper?的方法到?lodash.prototype存儲下來,最后調用?value時再調用。具體看下文源碼實現。

添加?LazyWrapper?的方法到?lodash.prototype

主要是如下方法添加到到?lodash.prototype?原型上。

// "constructor"
["drop", "dropRight", "take", "takeRight", "filter", "map", "takeWhile", "head", "last", "initial", "tail", "compact", "find", "findLast", "invokeMap", "reject", "slice", "takeRightWhile", "toArray", "clone", "reverse", "value"]

具體源碼及注釋

// Add `LazyWrapper` methods to `lodash.prototype`.
// baseForOwn 這里其實就是遍歷LazyWrapper.prototype上的方法,執行回調函數
baseForOwn(LazyWrapper.prototype, function(func, methodName) {// 檢測函數名稱是否是迭代器也就是循環var checkIteratee = /^(?:filter|find|map|reject)|While$/.test(methodName),// 檢測函數名稱是否head和last// 順便提一下 ()這個是捕獲分組 而加上 ?:  則是非捕獲分組 也就是說不用于其他操作isTaker = /^(?:head|last)$/.test(methodName),// lodashFunc 是 根據 isTaker 組合 takeRight take methodNamelodashFunc = lodash[isTaker ? ('take' + (methodName == 'last' ? 'Right' : '')) : methodName],// 根據isTaker 和 是 find 判斷結果是否 包裝retUnwrapped = isTaker || /^find/.test(methodName);// 如果不存在這個函數,就不往下執行if (!lodashFunc) {return;}// 把 lodash.prototype 方法賦值到lodash.prototypelodash.prototype[methodName] = function() {// 取實例中的__wrapped__ 值 例子中則是 [1,2,3,4,5]var value = this.__wrapped__,// 如果是head和last 方法 isTaker 返回 [1], 否則是arguments對象args = isTaker ? [1] : arguments,// 如果value 是LayeWrapper的實例isLazy = value instanceof LazyWrapper,// 迭代器 循環iteratee = args[0],// 使用useLazy isLazy value或者是數組useLazy = isLazy || isArray(value);var interceptor = function(value) {// 函數執行 value args 組合成數組參數var result = lodashFunc.apply(lodash, arrayPush([value], args));// 如果是 head 和 last (isTaker) 支持鏈式調用 返回結果的第一個參數 否則 返回resultreturn (isTaker && chainAll) ? result[0] : result;};// useLazy true 并且 函數checkIteratee 且迭代器是函數,且迭代器參數個數不等于1if (useLazy && checkIteratee && typeof iteratee == 'function' && iteratee.length != 1) {// Avoid lazy use if the iteratee has a "length" value other than `1`.// useLazy 賦值為 false// isLazy 賦值為 falseisLazy = useLazy = false;}// 取實例上的 __chain__var chainAll = this.__chain__,// 存儲的待執行的函數 __actions__ 二次取反是布爾值 也就是等于0或者大于0兩種結果isHybrid = !!this.__actions__.length,// 是否不包裝 用結果是否不包裝 且 不支持鏈式調用isUnwrapped = retUnwrapped && !chainAll,// 是否僅Lazy 用isLazy 和 存儲的函數onlyLazy = isLazy && !isHybrid;// 結果不包裝 且 useLazy 為 trueif (!retUnwrapped && useLazy) {// 實例 new LazyWrapper 這里的this 是 new LodashWrapper()value = onlyLazy ? value : new LazyWrapper(this);// result 執行函數結果var result = func.apply(value, args);/**// _.thru(value, interceptor)// 這個方法類似 _.tap, 除了它返回 interceptor 的返回結果。該方法的目的是"傳遞" 值到一個方法鏈序列以取代中間結果。_([1, 2, 3]).tap(function(array) {// 改變傳入的數組array.pop();}).reverse().value();// => [2, 1]*/// thisArg 指向undefined 或者null 非嚴格模式下是指向window,嚴格模式是undefined 或者nllresult.__actions__.push({ 'func': thru, 'args': [interceptor], 'thisArg': undefined });// 返回實例 lodashWrapperreturn new LodashWrapper(result, chainAll);}// 不包裝 且 onlyLazy 為 trueif (isUnwrapped && onlyLazy) {// 執行函數return func.apply(this, args);}// 上面都沒有執行,執行到這里了// 執行 thru 函數,回調函數 是 interceptorresult = this.thru(interceptor);return isUnwrapped ? (isTaker ? result.value()[0] : result.value()) : result;};
});

小結一下,寫了這么多注釋,簡單說:其實就是用?LazyWrapper.prototype?改寫原先在?lodash.prototype的函數,判斷函數是否需要使用惰性求值,需要時再調用。

讀者可以斷點調試一下,善用斷點進入函數功能,對著注釋看,可能會更加清晰。

斷點調試的部分截圖

鏈式調用最后都是返回實例對象,實際的處理數據的函數都沒有調用,而是被存儲存儲下來了,最后調用?value方法,才執行這些函數。

lodash.prototype.value 即 wrapperValue

function baseWrapperValue(value, actions) {var result = value;// 如果是lazyWrapper的實例,則調用LazyWrapper.prototype.value 方法,也就是 lazyValue 方法if (result instanceof LazyWrapper) {result = result.value();}// 類似 [].reduce(),把上一個函數返回結果作為參數傳遞給下一個函數return arrayReduce(actions, function(result, action) {return action.func.apply(action.thisArg, arrayPush([result], action.args));}, result);
}
function wrapperValue() {return baseWrapperValue(this.__wrapped__, this.__actions__);
}
lodash.prototype.toJSON = lodash.prototype.valueOf = lodash.prototype.value = wrapperValue;

如果是惰性求值,則調用的是?LazyWrapper.prototype.value?即?lazyValue

LazyWrapper.prototype.value 即 lazyValue 惰性求值

lazyValue源碼及注釋

function LazyWrapper(value) {// 參數 valuethis.__wrapped__ = value;// 執行的函數this.__actions__ = [];this.__dir__ = 1;// 過濾this.__filtered__ = false;// 存儲迭代器函數this.__iteratees__ = [];// 默認最大取值個數this.__takeCount__ = MAX_ARRAY_LENGTH;// 具體取值多少個,存儲函數和類型this.__views__ = [];
}
/**
* Extracts the unwrapped value from its lazy wrapper.
*
* @private
* @name value
* @memberOf LazyWrapper
* @returns {*} Returns the unwrapped value.
*/
function lazyValue() {// this.__wrapped__ 是 new LodashWrapper 實例 所以執行.value 獲取原始值var array = this.__wrapped__.value(),//dir = this.__dir__,// 是否是函數isArr = isArray(array),// 是否從右邊開始isRight = dir < 0,// 數組的長度。如果不是數組,則是0arrLength = isArr ? array.length : 0,// 獲取 take(3) 上述例子中 則是 start: 0,end: 3view = getView(0, arrLength, this.__views__),start = view.start,end = view.end,// 長度 3length = end - start,// 如果是是從右開始index = isRight ? end : (start - 1),// 存儲的迭代器數組iteratees = this.__iteratees__,// 迭代器數組長度iterLength = iteratees.length,// 結果resIndexresIndex = 0,// 最后獲取幾個值,也就是 3takeCount = nativeMin(length, this.__takeCount__);// 如果不是數組,或者 不是從右開始 并且 參數數組長度等于take的長度 takeCount等于長度// 則直接調用 baseWrapperValue 不需要if (!isArr || (!isRight && arrLength == length && takeCount == length)) {return baseWrapperValue(array, this.__actions__);}var result = [];// 標簽語句 label// MDN label 鏈接// https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Statements/label// 標記語句可以和 break 或 continue 語句一起使用。標記就是在一條語句前面加個可以引用的標識符(identifier)。outer:while (length-- && resIndex < takeCount) {index += dir;var iterIndex = -1,// 數組第一項value = array[index];while (++iterIndex < iterLength) {// 迭代器數組 {iteratee: function{}, typy: 2}var data = iteratees[iterIndex],iteratee = data.iteratee,type = data.type,// 結果 迭代器執行結果computed = iteratee(value);if (type == LAZY_MAP_FLAG) {// 如果 type 是 map 類型,結果 computed 賦值給valuevalue = computed;} else if (!computed) {if (type == LAZY_FILTER_FLAG) {// 退出當前這次循環,進行下一次循環continue outer;} else {// 退出整個循環break outer;}}}// 最終數組result[resIndex++] = value;}// 返回數組 例子中則是 [2, 3, 4]return result;
}
// Ensure `LazyWrapper` is an instance of `baseLodash`.
LazyWrapper.prototype = baseCreate(baseLodash.prototype);
LazyWrapper.prototype.constructor = LazyWrapper;
LazyWrapper.prototype.value = lazyValue;

筆者畫了一張?lodash和?LazyWrapper的關系圖來表示。?

小結:?lazyValue簡單說實現的功能就是把之前記錄的需要執行幾次,把記錄存儲的函數執行幾次,不會有多少項數據就執行多少次,而是根據需要幾項,執行幾項。也就是說以下這個例子中,?map函數只會執行?3次。如果沒有用惰性求值,那么?map函數會執行?5次。

var result = _.chain([1, 2, 3, 4, 5])
.map(el => el + 1)
.take(3)
.value();

總結

行文至此,基本接近尾聲,最后總結一下。

文章主要學習了?runInContext()?導出?_?lodash函數使用?baseCreate方法原型繼承?LodashWrapper和?LazyWrapper,?mixin掛載方法到?lodash.prototype、后文用結合例子解釋?lodash.prototype.value(wrapperValue)和?Lazy.prototype.value(lazyValue)惰性求值的源碼具體實現。

分享一個只知道函數名找源碼定位函數申明位置的?VSCode?技巧:?Ctrl+p。輸入?@functionName?定位函數?functionName在源碼文件中的具體位置。如果知道調用位置,那直接按?alt+鼠標左鍵即可跳轉到函數申明的位置。

如果讀者發現有不妥或可改善之處,再或者哪里沒寫明白的地方,歡迎評論指出。另外覺得寫得不錯,對您有些許幫助,可以點贊、評論、轉發分享,也是對筆者的一種支持。萬分感謝。

推薦閱讀

lodash github倉庫
lodash 官方文檔
lodash 中文文檔
打造一個類似于lodash的前端工具庫
惰性求值——lodash源碼解讀
luobo tang:lazy.js 惰性求值實現分析
lazy.js github 倉庫
本文章學習的?lodash的版本?v4.17.15?unpkg.com鏈接

關于

作者:常以若川為名混跡于江湖。前端路上 | PPT愛好者 | 所知甚少,唯善學。
個人博客 http://lxchuan12.github.io?使用?vuepress重構了,閱讀體驗可能更好些
https://github.com/lxchuan12/blog,相關源碼和資源都放在這里,求個 star^_^~

微信交流群,加我微信lxchuan12,注明來源,拉您進前端視野交流群

下圖是公眾號二維碼:若川視野,一個可能比較有趣的前端開發類公眾號,目前前端內容不多

往期文章

工作一年后,我有些感悟(寫于2017年)

高考七年后、工作三年后的感悟

學習 jQuery 源碼整體架構,打造屬于自己的 js 類庫

學習underscore源碼整體架構,打造屬于自己的函數式編程類庫

由于公眾號限制外鏈,點擊閱讀原文,或許閱讀體驗更佳

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/276515.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/276515.shtml
英文地址,請注明出處:http://en.pswp.cn/news/276515.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

python數據庫模糊查詢_Python操作mongodb數據庫進行模糊查詢操作示例

本文實例講述了Python操作mongodb數據庫進行模糊查詢操作。分享給大家供大家參考&#xff0c;具體如下&#xff1a;# -*- coding: utf-8 -*-import pymongoimport refrom pymongo import MongoClient#創建連接#10.20.66.106client MongoClient(10.20.4.79,27017)#client Mong…

推薦一個快速反射調用的類

使用傳統的.net反射機制&#xff0c;調用類的方法時&#xff0c;在調用頻率大的情況下&#xff0c;會感覺速度很慢。最近瀏覽盧彥的博客時&#xff0c;找到一個他改進后的反射調用類。試用以后感覺效率明顯提高&#xff0c;特推薦給大家。作者重新實現了&#xff0c;反射調用方…

CMake 構建項目Android NDK項目基礎知識

本篇文章將介紹如何使用 CMake 構建實現你的第一個 NDK 項目。 ##前言 你好&#xff01;歡迎來到我的的學習筆記分享系列&#xff0c;第一次給大家分享的是 Android NDK 開發的學習筆記&#xff0c;讓我們先開始了解 NDK 的構建方式吧&#xff01; NDK 構建方式有兩種&#xff…

linux installaccess Nessus-5.2.4

1、Download: http://www.tenable.com/products/nessus/select-your-operating-system 2、Current version&#xff1a;Nessus-5.2.4-debian6_i386.deb 3、Install&#xff1a;dpkg -i Nessus-5.2.4-debian6_i386.deb # dpkg -i Nessus-5.2.4-debian6_i386.deb Selecting p…

面試官問:JS的繼承

原文作者若川&#xff0c;掘金鏈接&#xff1a;https://juejin.im/post/5c433e216fb9a049c15f841b寫于2019年2月20日&#xff0c;現在發到公眾號聲明原創&#xff0c;之前被《前端大全》公眾號等轉載閱讀量超1w&#xff0c;知乎掘金等累計閱讀量超過1w。導讀&#xff1a;文章主…

qt 快速按行讀取文件_這是知識點之Linux下分割文件并保留文件頭

點擊上方"開發者的花花世界"&#xff0c;選擇"設為星標"技術干貨不定時送達&#xff01;這是一個知識點方便快捷的給結構化數據文件分割大小并保留文件的表頭&#xff0c;幾十個G的結構化文件不僅閱讀編輯麻煩&#xff0c;而且使用受限&#xff0c;因此高效…

mono 調用windows webService

1. 實現linux mono Develop中調用windows 中的webService l linux 與 windows 在一個局域網的網段中 l windows 的IIs中發布webService 2. windows 中的設置 l webService 的代碼 using System; using System.Collections.Generic; using System.Linq; using S…

Linux 內存機制

轉載鏈接&#xff1a;http://blog.csdn.net/tianlesoftware/article/details/5463790 一. 內存使用說明 Free 命令相對于top 提供了更簡潔的查看系統內存使用情況&#xff1a; [rootrac1 ~]# free total used free shared buffers cached Mem: …

network中的請求信息,headers中的每一項分別是什么意義?

這里是修真院前端小課堂&#xff0c;每篇分享文從 【背景介紹】【知識剖析】【常見問題】【解決方案】【編碼實戰】【擴展思考】【更多討論】【參考文獻】 八個方面深度解析前端知識/技能&#xff0c;本篇分享的是&#xff1a; 【network中的請求信息&#xff0c;headers中的每…

學習 sentry 源碼整體架構,打造屬于自己的前端異常監控SDK

前言這是學習源碼整體架構第四篇。整體架構這詞語好像有點大&#xff0c;姑且就算是源碼整體結構吧&#xff0c;主要就是學習是代碼整體結構&#xff0c;不深究其他不是主線的具體函數的實現。文章學習的是打包整合后的代碼&#xff0c;不是實際倉庫中的拆分的代碼。其余三篇分…

巴西龜吃什么

1、活蝦&#xff0c;哈哈&#xff0c;巴西龜最喜歡的食物&#xff0c;超市很多雞尾蝦買的&#xff0c;就那種&#xff0c;要活的&#xff0c;鍛煉它們的天性&#xff0c;一次一只可以吃一、兩天&#xff1b; 2、蚶子&#xff0c;貝殼類&#xff0c;活的&#xff0c;整個扔進去&…

綁定dictionary 給定關鍵字不再字典中_VBA代碼集錦-利用字典做兩列數據的對比并對齊...

源數據&#xff1a;代碼&#xff1a;Sub 對比()Dim arr, brr, crrDim i, j, n, lastrowA, lastrowB As Integer建立字典對象Set d CreateObject("scripting.dictionary")獲取數據區域最后一行的行數lastrowA Sheets("對比對齊兩列數據").Cells(Rows.Coun…

linux啟動時掛載rootfs的幾種方式 .

轉載鏈接&#xff1a;http://blog.csdn.net/zuokong/article/details/9022707 根文件系統&#xff08;在樣例錯誤消息中名為 rootfs&#xff09;是 Linux 的最基本的組件。根文件系統包含支持完整的 Linux 系統所需的所有內容。它包含所有應用程序、配置、設備、數據等 Linux 中…

PHP 手冊

by:Mehdi AchourFriedhelm BetzAntony DovgalNuno LopesHannes MagnussonGeorg RichterDamien SeguyJakub Vrana其他貢獻者2018-06-19Edited By: Peter Cowburn中文翻譯人員&#xff1a;肖盛文洪建家穆少磊宋琪黃嘯宇王遠之肖理達喬楚戴劼褚兆瑋周夢康袁玉強段小強© 1997-…

前端使用puppeteer 爬蟲生成《React.js 小書》PDF并合并

前端也可以爬蟲&#xff0c;寫于2018年08月29日&#xff0c;現在發布到微信公眾號申明原創。掘金若川 本文章鏈接&#xff1a;https://juejin.im/post/5b86732451882542af1c80821、 puppeteer 是什么&#xff1f;puppeteer: Google 官方出品的 headless Chrome node 庫puppetee…

蜘蛛與佛的故事

最近閉關,空面四壁,窗外層巒疊嶂,窗臺上只有一盆花每日陪著我&#xff0c;朋友們都說我要成佛了,想想也是&#xff01; 于是在閉關即將結束的時候找了一篇佛的故事送給自己&#xff0c;希望自己能夠頓悟一些"禪"機。 從前&#xff0c;有一座圓音寺&#xff0c;每天都…

信息安全管理與評估_計算機工程學院教師參加“信息安全管理與評估賽項”說明會...

看了就要關注我&#xff0c;喵嗚~2019年3月15日下午&#xff0c;2019年陜西省高等職業院校技能大賽“信息安全管理與評估賽項說明會”在咸陽職業技術學院舉行。出席本次會儀的有咸陽職業技術學院教務處長楊新宇、神州數碼范永強經理、神州數碼信息安全工程師高峰和各院校指導教…

haproxy概念和負載均衡

https://pan.baidu.com/s/1Sq2aJ35zrW2Xn7Th9j7oOA //軟件百度網盤連接 在80.100虛擬機上 systemctl stop firewalld //關閉防火墻 setenforce 0 //關閉監控 yum install lrz* -y //安裝上傳軟件 tar xf haproxy-1.5.15.tar.gz -C /opt/ //解壓壓縮包到/opt/ cd /op…

PHP用戶注冊郵箱驗證激活帳號

轉載鏈接&#xff1a;http://www.helloweba.com/view-blog-228.html 本文將結合實例&#xff0c;講解如何使用PHPMysql完成注冊帳號、發送激活郵件、驗證激活帳號、處理URL鏈接過期的功能。 業務流程 1、用戶提交注冊信息。 2、寫入數據庫&#xff0c;此時帳號狀態未激活。 …

知乎問答:一年內的前端看不懂前端框架源碼怎么辦?

知乎問答&#xff1a;一年內的前端看不懂前端框架源碼怎么辦&#xff1f;以下是我的回答&#xff0c;閱讀量 1000。現在轉載到微信公眾號中。鏈接&#xff1a;https://www.zhihu.com/question/350289336/answer/910970733其他回答的已經很好了。剛好最近在寫學習源碼整體架構系…