本文参加了由公众号@若川视野 发起的每周源码共读活动, 点击了解详情一起参与。
这是
学习源码整体架构系列第三篇,链接: juejin.cn/post/684490… 。
导读:
runInContext()导出_,lodash函数使用baseCreate方法原型继承LodashWrapper和LazyWrapper,mixin挂载方法到lodash.prototype、后文用结合例子解释lodash.prototype.value(wrapperValue)和Lazy.prototype.value(lazyValue)惰性求值的源码实现
匿名函数执行
;(function(){
}.call(this))
暴露lodash
var _ = runInContext();
runInContext函数
这里简版源码,只关注函数入口和返回值。
// 浏览器处理context为window
// ...
function lodash(value) {
// ...
return new LodashWrapper(value);
}
// ...
return lodash;
});
可以看到声明了一个runInContext函数。里面有一个lodash函数,以后处理返回这个lodash函数。 再看lodash函数中返回值new LodashWrapper(value)。
LodashWrapper函数
function LodashWrapper(value, chainAll) {
this.__wrapped__ = value;
this.__action__ = [];
this.__chain__ = !!chainAll;
this.__index__ = 0;
this.__values__ = undefined;
}
设置了这些属性:
- wrapped:存放参数value
- action:存放待执行的函数体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.create
if (objectCreate) {
return objectCreate(proto);
}
// 如果不支持object.create,用ployfill new
object.prototype = proto;
var result = new object;
// 还原object.prototype
object.prototype = null;
return result;
};
}())
// 空函数
function baseLodash() {
// no operation performed.
}
// Ensure wrappers are instances of `baseLodash`.
lodash.prototype = baseLodash.prototype;
// 由于上一句将lodash.prototype.constructor 也设置为Object。这句为了修正contructor
lodash.prototype.constructor = lodash;
LodashWapper.prototype = baseCreate(baseLodash.prototype);
LodashWapper.prototype.constructor = LodashWrapper;
衍生的isObject函数
function isObject(value) {
var type = typeof value;
return value !== 'null' && (type === 'object' || type === 'function')
}
Object.create()用法列举
- 可以参考 JavaScript 对象所有API解析
- MDN Object.create()
Object.create(proto, [propertiesObject])方法创建一个新对象,使用现有对象来提供新创建的对象的__proto__。它接受两个参数,第二个参数可选,是属性描述符,默认undefiend。
var personObejct = {
name: 'sam'
}
var myObject = Object.create(personObject, {
age: {
value: 1
}
});
// 获得它的原型
Obejct.getPrototypeOf(personObject) === Object.prototype; // true 说明personObject的原型是Obejct.prototype
Object.getPrototype(MyObject) // {name: 'sam'} 说明myObject的原型是personObject
myObject.hasOwnProperty('name') // false, 说明name是原型上的
myObject.hasOwnProperty('age') // true,说明age是自身的
myObject.name // sam
myObject.age // 1
对于不支持ES5的浏览器,MDN上提供了ployfill方案
lodash上有很多方法和属性,但在lodash.prototype上也有很多相同的方法。肯定不是在lodash.prototype上重新写一遍。而是通过mixin挂载的。
mixin
_.mixin([object=lodash], source, [options={}])
添加来源自身的所有可枚举函数属性到目标对象。如果这个object是个函数,那么将函数添加到原型链上。
注意:使用_.runInContext来创建原始的lodash函数来避免修改造成的冲突。
添加版本 0.1.0
参数
- object=lodash: 目标对象
- source(Object): 来源对象
- options={}: 选项对象
- options.chain=true: 是否开启链式操作 返回 (*):返回object
mixin源码
衍生的函数
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;
}
var tag = baseGetTag(value);
return tag == funcTag || tag == genTag || tag == asyncTag || tag == proxyTag;
}
mixin衍生的函数arrayEach
类似[].forEach
function arrayFeach(array, iteratee) {
var index = -1,
length = array === null ? 0 : array.length
while (++index < length) {
if(ineratee(array[index], index, aray) === false) {
break
}
return aray;
}
}
mixin衍生的函数arrayPush
类似[].push
function arrayPush(array, values) {
var index = -1,
langth = values.length,
offest = array.length;
while (++index < length) {
array[offset + index] = values[index];
}
return rray;
}
mixin衍生的函数copyArray
拷贝数组
function copyArray(source, array) {
var index = -1,
length = array.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 = {};
// baseOwn 这里其实就是遍历lodash上的静态方法,执行回调函数
baseOwn(lodash, function(func, methodName) {
// 第一次 mixin 调用了所以赋值到了lodash.prototype
// 所以这里用Object.hasOwnPrototype 排除不在lodash.prototype 上的方法。也就是 add 等152个不支持链式调用方法
if (!hasOwnPrototype.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__),
// 复制 实例上 __actions__到result.__actions__ 和 actions 上
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挂载了多少方法和属性
再看下lodash究竟挂载在_函数对象上有多少静态属性和方法,和挂载在_.prototype上有多少个方法和属性。
使用for...in...循环
var staticMethods = [];
var staticPrototyep = [];
for (var name in _) {
if (typeof _[name] === 'function') {
staticMethods.push(name);
} else {
staticPrototype.push(name);
}
}
console.log(staticPrototype); // ["templateSettings", "VERSION"] 2个
console.log(staticMethods); // ["after", "ary", "assign", "assignIn", "assignInWith", ...] 305个
staticMethods其实就是上文提及的 lodash.after 等153个支持链式调用的函数 、lodash.add 等 152不支持链式调用的函数赋值而来
var prototypeMethods = []
var prototypePrototype = []
for (var name in _.prototype) {
if (typeof _.prototype[name] === 'function') {
prototypeMethods.push(name);
} else {
prototypePrototype.push(name);
}
}
console.log(prototypePrototype); // ["templateSettings", "VERSION"] 2个
console.log(prototypeMethods); // // ["after", "ary", "assign", "assignIn", "assignInWith", ...] 305个
相比lodash上静态方法多了12个,说明除了mixin外,还有12个其他形式赋值而来。
支持链式调用的方法最后返回的是实例对象,获取最后的处理的结果值,最后需要调用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
// by the way, ()是捕获分组,?:是非捕获分组。也就是说不用于其他操作
isTaker = /^(?:head|last)$/.test(methodName),
// lodashFunc 是根据 isTake组合 takeRight take methodName
lodashFunc = lodash[isTaker ? ('take' + (methodName == 'last' ? 'Right' : '')) : methodName],
// 根据isTake 和 是find 判断结果 是否包装
retUnwrapped = isTaker || /^find/.test(methodName);
// 如果不存在合格函数,就不往下执行
if (!lodashFunc) {
return;
}
// 把lodash.prototype 方法赋值到lodash.prototype
lodash.prototype[methodName] = function() {
// 取实例中的__wrappered__值,例子则是[1,2,3,4,5]
var value = this.__wrapped__,
// 如果head 和 last 方法, 方法isTaker 返回[1],否则是arguments对象
args = isTaker ? [1] : arguments,
// 如果value是lazyWrapper的实例
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(isTake) 支持链式调用,返回结果的第一个参数,否则返回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 LodashWapper()
value = onlyLazy ? value : new LazyWrapper(this);
// result 执行函数的结果
var result = func.apply(value, args);
/*
*
// _.thru(value, interceptor)
// 这个方法类似于_.tab, 除了他返回interceptor的返回结果。该方法的目的是“传递” 值到一个方法链序列以取代中间结果。
// _([1,2,3])
.tab(function (array) {
// 该变传入的数组
array.pop()
})
.reverse()
.value() // => [2,1]
*
*/
// thisArg 指向undefined或者null,非严格模式下指向window,严格模式下指向undefined或null
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的函数,判断函数是否需要使用惰性求值,需要是再调用。
Lodash.prototype.value 即wrapperValue
function baseWrapperValue(value, actions) {
var result = value;
if (result instanceof LazyWrapper) {
result = result.value();
}
return arrayReduce(actions, function(result, action) {
return action.func.apply(action.thisArg, arrayPush([result], action.args));
}, result);
}
如果是惰性求值,则调用LodashWrapper.prototype.value即lazyValue
lazyWrapper.prototype.value即lazyValue惰性求值
function LazyWrapper(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() {
var array = this.__wrapped__.value(),
dir = this.__dir__,
isArr = isArray(array),
isRight = dir < 0,
arrLength = isArr ? array.length : 0,
view = getView(0, arrLength, this.__views__),
start = view.start,
end = view.end,
length = end - start,
index = isRight ? end : (start - 1),
iteratees = this.__iteratees__,
iterLength = iteratees.length,
resIndex = 0,
takeCount = nativeMin(length, this.__takeCount__);
if (!isArr || (!isRight && arrLength == length && takeCount == length)) {
return baseWrapperValue(array, this.__actions__);
}
var result = [];
outer:
while (length-- && resIndex < takeCount) {
index += dir;
var iterIndex = -1,
value = array[index];
while (++iterIndex < iterLength) {
var data = iteratees[iterIndex],
iteratee = data.iteratee,
type = data.type,
computed = iteratee(value);
if (type == LAZY_MAP_FLAG) {
value = computed;
} else if (!computed) {
if (type == LAZY_FILTER_FLAG) {
continue outer;
} else {
break outer;
}
}
}
result[resIndex++] = value;
}
return result;
}
小结:lazyValue简单来说实现的功能就是把之前的记录的需要执行几次,把记录存储的函数执行几次,不会有多少次执行多少次,而是根据需要几项,执行几项。如下例子:
var result = _.chain([1, 2, 3, 4, 5])
.map(el => el + 1)
.take(3)
.value();
// 执行3次
总结
此文章为2024年04月Day1源码共读,每一次脑海里闪过努力的念头,都是未来的你在向你求救。