这是学习源码总体架构系列
第三篇。总体架构这词语好像有点大,姑且就算是源码总体结构吧,主要就是学习是代码总体结构,不深究其余不是主线的具体函数的实现。文章学习的是打包整合后的代码,不是实际仓库中的拆分的代码。html
上上篇文章写了jQuery源码总体架构
,学习 underscore 源码总体架构,打造属于本身的函数式编程类库前端
上一篇文章写了underscore源码总体架构
,学习 jQuery 源码总体架构,打造属于本身的 js 类库vue
感兴趣的读者能够点击阅读。git
underscore
源码分析的文章比较多,而lodash
源码分析的文章比较少。缘由之一多是因为lodash
源码行数太多。注释加起来一万多行。github
分析lodash
总体代码结构的文章比较少,笔者利用谷歌、必应、github
等搜索都没有找到,多是找的方式不对。因而打算本身写一篇。日常开发大多数人都会使用lodash
,并且都或多或少知道,lodash
比underscore
性能好,性能好的主要缘由是使用了惰性求值这一特性。面试
本文章学习的lodash
的版本是:v4.17.15
。unpkg.com
地址 https://unpkg.com/lodash@4.17...编程
文章篇幅可能比较长,能够先收藏再看,因此笔者使用了展开收缩的形式。segmentfault
导读:数组
文章主要学习了runInContext()
导出_
lodash
函数使用baseCreate
方法原型继承LodashWrapper
和LazyWrapper
,mixin
挂载方法到lodash.prototype
、后文用结合例子解释lodash.prototype.value(wrapperValue)
和Lazy.prototype.value(lazyValue)
惰性求值的源码具体实现。
;(function() { }.call(this));
暴露 lodash浏览器
var _ = runInContext();
这里的简版源码,只关注函数入口和返回值。
var runInContext = (function runInContext(context) { // 浏览器中处理context为window // ... function lodash(value) {}{ // ... return new LodashWrapper(value); } // ... return lodash; });
能够看到申明了一个runInContext
函数。里面有一个lodash
函数,最后处理返回这个lodash
函数。
再看lodash
函数中的返回值 new LodashWrapper(value)
。
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
这两个函数。
// 当即执行匿名函数 // 返回一个函数,用于设置原型 能够理解为是 __proto__ var baseCreate = (function() { // 这句放在函数外,是为了避免用每次调用baseCreate都重复申明 object // underscore 源码中,把这句放在开头就申明了一个空函数 `Ctor` function object() {} return function(proto) { // 若是传入的参数不是object也不是function 是null // 则返回空对象。 if (!isObject(proto)) { return {}; } // 若是支持Object.create方法,则返回 Object.create if (objectCreate) { // Object.create return objectCreate(proto); } // 若是不支持Object.create 用 ployfill new object.prototype = proto; var result = new object; // 还原 prototype object.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;
笔者画了一张图,表示这个关系。
判断typeof value
不等于null
,而且是object
或者function
。
function isObject(value) { var type = typeof value; return value != null && (type == 'object' || type == 'function'); }
面试官问:可否模拟实现JS的new操做符 以前这篇文章写过的一段,因此这里收缩起来了。
笔者以前整理的一篇文章中也有讲过,能够翻看JavaScript 对象全部API解析
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([object=lodash], source, [options={}])
添加来源对象自身的全部可枚举函数属性到目标对象。 若是 object 是个函数,那么函数方法将被添加到原型链上。注意: 使用 _.runInContext 来建立原始的 lodash 函数来避免修改形成的冲突。
添加版本
0.1.0
参数
[object=lodash] (Function|Object): 目标对象。source (Object): 来源对象。
[options={}] (Object): 选项对象。
[options.chain=true] (boolean): 是否开启链式操做。
返回
(*): 返回 object.
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
函数中 其实最终调用的就是 Object.keys
function keys(object) { return isArrayLike(object) ? arrayLikeKeys(object) : baseKeys(object); }
返回函数数组集合
function baseFunctions(object, props) { return arrayFilter(props, function(key) { return isFunction(object[key]); }); }
判断参数是不是函数
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; }
相似 [].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; }
相似 [].push
function arrayPush(array, values) { var index = -1, length = values.length, offset = array.length; while (++index < length) { array[offset + index] = values[index]; } return array; }
拷贝数组
function copyArray(source, array) { var index = -1, length = source.length; array || (array = Array(length)); while (++index < length) { array[index] = source[index]; } return array; }
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
代入到源码解析以下
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 赋值为 source options = source; // 把 source 赋值为 object source = 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 上,一般是lodash object.prototype[methodName] = function() { // 实例上的__chain__ 属性 是否支持链式调用 // 这里的 this 是 new LodashWrapper 实例 相似以下 /** { __actions__: [], __chain__: true __index__: 0 __values__: undefined __wrapped__: [] } **/ var chainAll = this.__chain__; // options 中的 chain 属性 是否支持链式调用 // 二者有一个符合链式调用 执行下面的代码 if (chain || chainAll) { // 一般是 lodash var 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)); }; } }); // 最后返回对象 object return object; }
小结:简单说就是把lodash
上的静态方法赋值到lodash.prototype
上。分两次第一次是支持链式调用(lodash.after
等 153
个支持链式调用的方法),第二次是不支持链式调用的方法(lodash.add
等152
个不支持链式调用的方法)。
再来看下lodash
究竟挂载在_
函数对象上有多少静态方法和属性,和挂载_.prototype
上有多少方法和属性。
使用for in
循环一试便知。看以下代码:
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, 3 return 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 methodName lodashFunc = lodash[isTaker ? ('take' + (methodName == 'last' ? 'Right' : '')) : methodName], // 根据isTaker 和 是 find 判断结果是否 包装 retUnwrapped = isTaker || /^find/.test(methodName); // 若是不存在这个函数,就不往下执行 if (!lodashFunc) { return; } // 把 lodash.prototype 方法赋值到lodash.prototype lodash.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) 支持链式调用 返回结果的第一个参数 不然 返回result return (isTaker && chainAll) ? result[0] : result; }; // useLazy true 而且 函数checkIteratee 且迭代器是函数,且迭代器参数个数不等于1 if (useLazy && checkIteratee && typeof iteratee == 'function' && iteratee.length != 1) { // Avoid lazy use if the iteratee has a "length" value other than `1`. // useLazy 赋值为 false // isLazy 赋值为 false isLazy = useLazy = false; } // 取实例上的 __chain__ var chainAll = this.__chain__, // 存储的待执行的函数 __actions__ 二次取反是布尔值 也就是等于0或者大于0两种结果 isHybrid = !!this.__actions__.length, // 是否不包装 用结果是否不包装 且 不支持链式调用 isUnwrapped = retUnwrapped && !chainAll, // 是否仅Lazy 用isLazy 和 存储的函数 onlyLazy = isLazy && !isHybrid; // 结果不包装 且 useLazy 为 true if (!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 或者nll result.__actions__.push({ 'func': thru, 'args': [interceptor], 'thisArg': undefined }); // 返回实例 lodashWrapper return new LodashWrapper(result, chainAll); } // 不包装 且 onlyLazy 为 true if (isUnwrapped && onlyLazy) { // 执行函数 return func.apply(this, args); } // 上面都没有执行,执行到这里了 // 执行 thru 函数,回调函数 是 interceptor result = this.thru(interceptor); return isUnwrapped ? (isTaker ? result.value()[0] : result.value()) : result; }; });
小结一下,写了这么多注释,简单说:其实就是用LazyWrapper.prototype
改写原先在lodash.prototype
的函数,判断函数是否须要使用惰性求值,须要时再调用。
读者能够断点调试一下,善用断点进入函数功能,对着注释看,可能会更加清晰。
链式调用最后都是返回实例对象,实际的处理数据的函数都没有调用,而是被存储存储下来了,最后调用value
方法,才执行这些函数。
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
。
function LazyWrapper(value) { // 参数 value this.__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, // 数组的长度。若是不是数组,则是0 arrLength = isArr ? array.length : 0, // 获取 take(3) 上述例子中 则是 start: 0,end: 3 view = getView(0, arrLength, this.__views__), start = view.start, end = view.end, // 长度 3 length = end - start, // 若是是是从右开始 index = isRight ? end : (start - 1), // 存储的迭代器数组 iteratees = this.__iteratees__, // 迭代器数组长度 iterLength = iteratees.length, // 结果resIndex resIndex = 0, // 最后获取几个值,也就是 3 takeCount = 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 赋值给value value = 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
连接
学习 underscore 源码总体架构,打造属于本身的函数式编程类库
学习 jQuery 源码总体架构,打造属于本身的 js 类库
面试官问:JS的继承
面试官问:JS的this指向
面试官问:可否模拟实现JS的call和apply方法
面试官问:可否模拟实现JS的bind方法
面试官问:可否模拟实现JS的new操做符
前端使用puppeteer 爬虫生成《React.js 小书》PDF并合并
做者:常以若川为名混迹于江湖。前端路上 | PPT爱好者 | 所知甚少,惟善学。
我的博客-若川-本文连接地址,使用vuepress
重构了,阅读体验可能更好些
掘金专栏,欢迎关注~segmentfault
前端视野专栏,欢迎关注~
知乎前端视野专栏,欢迎关注~
github blog,相关源码和资源都放在这里,求个star
^_^~
可能比较有趣的微信公众号,长按扫码关注。也能够加微信 lxchuan12
,注明来源,拉您进【前端视野交流群】。