学习 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.mzph.cn/news/276515.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

推荐一个快速反射调用的类

使用传统的.net反射机制&#xff0c;调用类的方法时&#xff0c;在调用频率大的情况下&#xff0c;会感觉速度很慢。最近浏览卢彦的博客时&#xff0c;找到一个他改进后的反射调用类。试用以后感觉效率明显提高&#xff0c;特推荐给大家。作者重新实现了&#xff0c;反射调用方…

面试官问: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…

学习 sentry 源码整体架构,打造属于自己的前端异常监控SDK

前言这是学习源码整体架构第四篇。整体架构这词语好像有点大&#xff0c;姑且就算是源码整体结构吧&#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…

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

前端也可以爬虫&#xff0c;写于2018年08月29日&#xff0c;现在发布到微信公众号申明原创。掘金若川 本文章链接&#xff1a;https://juejin.im/post/5b86732451882542af1c80821、 puppeteer 是什么&#xff1f;puppeteer: Google 官方出品的 headless Chrome node 库puppetee…

信息安全管理与评估_计算机工程学院教师参加“信息安全管理与评估赛项”说明会...

看了就要关注我&#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…

知乎问答:一年内的前端看不懂前端框架源码怎么办?

知乎问答&#xff1a;一年内的前端看不懂前端框架源码怎么办&#xff1f;以下是我的回答&#xff0c;阅读量 1000。现在转载到微信公众号中。链接&#xff1a;https://www.zhihu.com/question/350289336/answer/910970733其他回答的已经很好了。刚好最近在写学习源码整体架构系…

冷启动问题:如何构建你的机器学习组合?

作为即将告别大学的机器学习毕业狗的你&#xff0c;会不会有种迷茫的感觉&#xff1f;你知道 HR 最看重的是什么吗&#xff1f;在求职季到来之前&#xff0c;毕业狗要怎么做&#xff0c;才能受到 HR 的青睐、拿到心仪的 Offer 呢&#xff1f;负责帮助应届生找到机器学习工作的 …

JavaScript 对象所有API解析【2020版】

写于 2017年08月20日&#xff0c;虽然是2017年写的文章&#xff0c;但现在即将2020年依旧不过时&#xff0c;现在补充了2019年新增的ES10 Object.fromEntries()。发到公众号申明原创。若川顺便在此提前祝大家&#xff1a;2020年更上一层楼。近日发现有挺多人对对象基础API不熟悉…

PHP生成各种验证码和Ajax验证

转载链接&#xff1a;http://www.helloweba.com/view-blog-191.html 验证码在WEB应用中非常重要&#xff0c;通常用来防止用户恶意提交表单&#xff0c;如恶意注册和登录、论坛恶意灌水等。本文将通过实例讲解使用PHP生成各种常见的验证码包括数字验证码、数字字母验证码、中文…

若川的2019年度总结,波澜不惊

从2014年开始写年度总结至今已经六个年头了。正如孔子所说&#xff1a;逝者如斯夫&#xff0c;不舍昼夜。2019年的年度总结写得比较晚&#xff0c;都快农历新年了&#xff0c;此刻在家里继续写完这篇文章。往年基本是元旦之后几天就写完了。我的年度总结尽量写得非技术人员也能…

如何正确选择仓储物流供应商?

如何正确选择仓储物流供应商&#xff1f; 以前有做电商的朋友向我咨询过怎么去选择优质的仓储物流供应商&#xff1f;有哪些能做作为关键问题进行参考。作为一个优秀的合作伙伴是可以为客户提供超乎预期的服务的&#xff0c;上海维佳供应链服务专业提供物流外包解决仓储物流供应…

Realtime Ray Tracing RenderMan Point Cloud

这里演示的是演示的是光线与包围盒测试。在装备Winfast 8800GT 512M的台式机上可以进行每秒4.6亿次点到射线的距离计算计算&#xff0c;用于判断点是否真正的与射线相交。外部数据的填充与准备延迟依旧是GPGPU应用的一个巨大门槛。白色是命中的包围盒&#xff0c;绿色的就是射线…

如何制定有价值的目标

写于2017年07月09日23:29现在修改发布到公众号声明原创公司会制定一系列目标&#xff0c;个人也可以制定一些目标&#xff0c;有利于自我学习成长。那么看我这篇文章可以告诉你如何制定有价值的目标。会制定有价值的目标&#xff0c;绝对超越很多人。SMART原则王健林之前说定个…

清除dns缓存命令行_怎么防止移动dns劫持,防止移动dns劫持要先了解什么是dns劫持...

本人以网络技术出身&#xff0c;近两年接触CDN网络&#xff0c;处理了一些CDN方面的网络问题&#xff0c;大多数以运营商丢包&#xff0c;延迟抖动为主&#xff0c;也处理一些硬件故障&#xff0c;比如机械硬盘的读写io测试&#xff0c;内存条兼容性测试&#xff0c;服务器IPMI…

移动硬盘格式化(pc和mac共用)-菜鸟级解决方案[转]

用pc的时候买了一个320G的移动硬盘&#xff0c;从来没考虑过什么格式化的问题&#xff0c;插上就用了。 后来接触mac才发现pc和mac在移动存储设备的格式化上还是有不少冲突的。如果你的移动硬盘mac上不能修改&#xff0c;或者pc上找不到&#xff0c;那就尽情得批判万恶的资本主…

回答知乎问题:你写过什么自认为惊艳的诗?

首次整理于 2019-07-27 22:04:00&#xff0c;现在整理发布在公众号申明原创。整理了一下大学期间2012年&#xff5e;2016年发布在QQ空间&#xff0c;自己感觉写得还行的七首“诗词”。回答知乎问题&#xff1a;你写过什么自认为惊艳的诗&#xff1f;中国古诗词博大精深。小时候…