xlshen / underscore Goto Github PK
View Code? Open in Web Editor NEWunderscore【经历】
Home Page: https://github.com/xlshen/underscore
underscore【经历】
Home Page: https://github.com/xlshen/underscore
核心函数: restArgs
方法
var restArgs = function(func, startIndex) {
// 如果没有传入startIndex,startIndex默认为func函数形参个数的最后一个,也就说最后一个形参默认为rest对象
startIndex = startIndex == null ? func.length - 1 : +startIndex;
return function() {
// 获取实际参数个数和startIndex大小之差与0的最大值
var length = Math.max(arguments.length - startIndex, 0),
// 构建长度为length的rest数组
rest = Array(length),
index = 0;
// 初始化startIndex之后的所有元素为rest数组元素
for (; index < length; index++) {
rest[index] = arguments[index + startIndex];
}
// call性能加速处理常用0, 1, 2三种情况,剩余情况apply处理
switch (startIndex) {
case 0:
return func.call(this, rest);
case 1:
return func.call(this, arguments[0], rest);
case 2:
return func.call(this, arguments[0], arguments[1], rest);
}
// 如果startIndex超过2,则执行默认的方法:构建args数组,含有所有参数
var args = Array(startIndex + 1);
// 构建args数组,结构:[1, 3, 4, [2, 3, 5]]
for (index = 0; index < startIndex; index++) {
args[index] = arguments[index];
}
// args最后一个参数为rest对象
args[startIndex] = rest;
return func.apply(this, args);
};
};
_.restArgs(function(value, rest){
console.log(value, rest);
})(1, 2, 3);
// 1, [2, 3]
在源码中,_.restArgs = restArgs
,也就说直接调用的restArgs
方法。该方法目的就是通过分割传入函数的形式参数,达到动态可变参数的效果。
实例中:startIndex
没有传值,则默认为func.length - 1
;通过比较实Math.max(arguments.length - startIndex, 0)
来确定rest
数组长度。接下来循环生成rest数组元素,如果startIndex
的取值为0 || 1 || 2
的话,进行优化加速【优化原理】,否则调用默认执行。
调用默认执行:因为rest
对象总是最后一个,则args
数组长度为startIndex + 1
,初始化args
数组元素,最后一个位置留给rest
对象,返回执行结果。
总结一下:restArgs函数解决了动态参数分配问题,返回构建好的类似[1, 2, [3, 4, 5]]形式的数组方便其他函数使用
默认情况下,underscore
的方法都是挂载到_
对象下面。那在OOP中怎么实现实例对象方法的调用,也就说如何把_
下面方法挂载到_
的原型对象中。那就是_.mixin()
方法,源码实现:
// Add your own custom functions to the Underscore object.
// 混入函数
// ① 挂载_下面的函数到_.prototype原型上
// ② 扩展underscroe库函数,参数为对象,方法在对象的属性上
_.mixin = function(obj) {
// 遍历对象下面所有方法挂载到_上面和_.prototype上面
_.each(_.functions(obj), function(name) {
// 挂载到_上面【此处为扩展,用户可以自定义自己的对象方法混入到_对象上面】
var func = _[name] = obj[name];
// 挂载到_.prototype上面
_.prototype[name] = function() {
// 第一个参数为OOP的对象,例如:_([1, 2, 3]).each(function(){})中,this._warpped = [1, 2, 3];
var args = [this._wrapped];
// arguments为name需要的其他参数,例如:_([1, 2, 3]).each(function(){}, this)中,arguments为function(){}和this
push.apply(args, arguments);
// 执行func函数,支持链式操作
return chainResult(this, func.apply(_, args));
};
});
return _;
};
// Add all of the Underscore functions to the wrapper object.
// 混入_对象
_.mixin(_);
实例:
// 自定义对象方法,扩展到_对象
var _obj = {
tips: function(obj, msg){
console.log(obj, msg);
}
}
_.mixin(_obj);
_.tips([1,2,3], "underscore"); // [1, 2, 3] "underscore"
_([1,2,3]).tips("underscore"); // [1, 2, 3] "underscore"
解析:
_.mixin()
方法将目标对象中的方法扩展到_
和_.prototype
对象上,之后可以直接方法调用和OOP实例调用使用。在通过OOP实例调用时,内部函数获取到传入的this._wrapped
对象,然后和调用参数一起赋值给args
变量,最后在chainResult
函数中调用执行。
【注:对于chainResult
函数不清楚的移步:underscore链式操作】
涉及函数:_.chain(obj) === _(obj).chain()
// 非 OOP 链式调用
_.chain([1, 2, 3])
.map(function(a) {return a;})
.reverse()
.value(); // [3, 2, 1]
// OOP 链式调用
_([1, 2, 3])
.chain()
.map(function(a){return a;})
.first()
.value(); // 1
以上两种方式都可以达到链式调用的目的。在underscore内部实现中,这两种方式实现都是用了OOP的实现方法,具体来看实现函数:
_.chain = function(obj) {
var instance = _(obj);
instance._chain = true;
return instance;
};
_.chain(obj)
通过调用chain()
方法将_(obj)
先转化为_
的实例对象,然后将实例对象添加_chain
属性,返回该实例对象。
【注:对于_(obj)
不熟悉的可以移步
underscore中_是弄啥的】
而对于_(obj).chain()
来说,首先通过_(obj)
创建_
的实例对象,然后该实例调用_
对象原型中的chain()
方法【该方法通过_.mixin()
方法混入到_.prototype
中】详见:underscore中OOP**—实例对象方法调用实现机制
_.prototype['chain']
方法最后一步:
// _.mixin()方法中_.prototype[name] = function() {...}最后返回
return chainResult(this, func.apply(_, args));
// chainResult实现函数
var chainResult = function(instance, obj) {
return instance._chain ? _(obj).chain() : obj;
};
chainResult(this, func.apply(_, args))
方法中this
是_
对象,func.apply(_, args)
调用_.chain()
方法返回传入对象的实例,在上面例子中就是[1, 2, 3]
对象的实例对象,并给该对象添加_chain
属性,返回该实例对象。此时调用为chain(_对象, [1, 2, 3]添加了_chain属性的实例对象)
,_._chain === undefined
所以直接返回添加了_chain
属性的[1, 2, 3]
对象实例。
到这里是不是发现了什么?!Yes,_.chain(obj)
和_(obj).chain()
回到了同一起跑线!
最后再梳理一下其中逻辑:chainResult实现函数通过传入的obj实例和obj对象,判断如果obj实例中含有_chain属性则继续调用_(obj).chain()生成具有_chain属性的obj实例对象;如果没有,则说明obj本身已经有了_chain属性【因为调用该函数的都是在_.prototype[XXX]中,该函数的执行最后必然会调用上面说的chainResult方法,func.apply(this, args)最终返回的一定是带有_chain属性的实例对象】,直接返回obj即可。
_
在underscore
中是以函数形式定义的对象,看看它的定义:
var _ = function(obj) {
// 如果参数是"_"的实例,直接返回,相当于容错机制
if (obj instanceof _) return obj;
// 否则返回实例new _(obj), 继续调用该函数,下一次进来之后直接保存下一步的变量_wrapped
if (!(this instanceof _)) return new _(obj);
// 实例对象_wrapped属性中存储了接受的参数
this._wrapped = obj;
};
① _
是一个函数,支持OOP调用的构造函数
② underscore
中的属性和方法默认都挂载在_
下面:比如 _.each
、_.map
...等
// 直接调用
_.each([1, 2, 3], function(value, index){
console.log(value); // 1, 2, 3
});
// OOP方式调用
_([1, 2, 3]).each(function(value, index){
console.log(value); // 1, 2, 3
});
第一种直接调用就直接调用_.each()
方法即可,因为方法默认都是挂载在_
上面。
第二种就厉害了,第一个判断是否是_
实例,如果是直接返回该对象,_.chain
方法就用到了该判断。
第二个判断this
如果不是_
实例,生成_
对象实例并返回,继而继续从头执行该函数,前两个条件都不满足,执行最后一个,把传入的对象赋值给该实例_wrapped
属性,完成操作。
梳理一下:_在underscore中就是定义的一个函数,可以作为OOP中构造函数使用,然后把传入的对象添加到生成的实例对象的_wrapped属性中
optimizeCb
内部优化函数接收参数:原始函数、执行上下文、参数个数。
【注:为什么说是内部优化函数,具体可以参考最下方参考文献详解:Why is call so much faster than apply?】
简而言之就是call
比apply
快的原因就是在call内部执行中,内部处理所需要的参数都是格式化好的参数,不用特殊处理。
optimizeCb
源码:
// Internal function that returns an efficient (for current engines) version
// of the passed-in callback, to be repeatedly applied in other Underscore
// functions.
// 内部优化方法,参数:函数,执行上下文,参数个数
// 返回根据传入函数在不同执行上下文,不同参数个数的情况下执行的函数
var optimizeCb = function(func, context, argCount) {
// 如果没有指定执行上下文,返回该函数
if (context === void 0) return func;
switch (argCount) {
case 1:
return function(value) {
return func.call(context, value);
};
// The 2-parameter case has been omitted only because no current consumers made use of it.
// 针对下面的null的问题,已经在github提交了pull request(https://github.com/jashkenas/underscore/pull/2732),我认为应该是undefined而非null,具体在下面分析中会提到
// case void 0:
case null:
// 在执行上下文中,没有参数个数,执行下面
// 在undefined情况下_.each(), _.map()内部用到
case 3:
return function(value, index, collection) {
return func.call(context, value, index, collection);
};
// _.reduce(), _.reduceRight()
case 4:
return function(accumulator, value, index, collection) {
return func.call(context, accumulator, value, index, collection);
};
}
// 其实不用上面的 switch-case 语句,直接执行下面的 return 函数就行了
// 理由就是充分利用了call的参数格式化优化
return function() {
return func.apply(context, arguments);
};
};
// _.each()方法在内部调用时首先进行的就是optimizeCb()方法的优化,把_.each()方法源码放到下面
_.each([1, 2, 3], function(value, index){
console.log(value, index); // 1, 2, 3
}, this);
// _.each()方法源码
_.each = _.forEach = function(obj, iteratee, context) {
// 执行函数分发优化策略,即前面的optimizeCb函数,赋值给局部变量iteratee进行代理执行
iteratee = optimizeCb(iteratee, context);
// 执行闭包iteratee方法
var i, length;
if (isArrayLike(obj)) {
// 如果是类数组对象
for (i = 0, length = obj.length; i < length; i++) {
iteratee(obj[i], i, obj);
}
} else {
// 普通对象
var keys = _.keys(obj);
for (i = 0, length = keys.length; i < length; i++) {
iteratee(obj[keys[i]], keys[i], obj);
}
}
// 返回该对象,链式操作使用
return obj;
};
对于_.each()
方法,案例中的context
是window
对象,argCount
为undefined
,如果此处按照null
的话则直接执行最后通用返回调用,不会进入case 3
的情况,但按照该优化函数设立目的应该是要进入该条件执行,可以参考1.8.2
版本[optimizeCb] Combine null and 3 as multi-case for argCount switch statement #2613,已提交pull request
:undefined【大家觉得呢???】
按这样理解则执行
// iteratee(obj[i], i, obj);
return function(value, index, collection) {
// context == window,value是每个值,index是索引,collection是传入的数组本身,返回函数执行结果
return func.call(context, value, index, collection);
};
总结一下:optimizeCb是针对underscore内部参数确定方法的分类优化函数,原理就是call && apply执行调用
【参考文献】
核心函数:createAssigner()
// An internal function for creating assigner functions.
// 浅拷贝代码核心
// 内部调用函数:_.extend(), _.extendOwn(), _.defaults()
// 1. _.extend = createAssigner(_.allKeys);
// 2. _.extendOwn = _.assign = createAssigner(_.keys);
// 3. _.defaults = createAssigner(_.allKeys, true);
// 文章号:[underscore对象浅拷贝核心](https://github.com/xlshen/underscore/issues/5)
// keysFunc为_.key或者_.allKeys方法,返回对象的属性名组成的数组
var createAssigner = function(keysFunc, defaults) {
// 返回函数闭包,通过传入的对象执行以下函数
return function(obj) {
// 获取传入执行函数参数个数
var length = arguments.length;
// 如果defaults执行true,再对传入对象包装一下,此时如果obj === null || undefined
// 则obj也转换为对象,则执行下一步时就不会因为obj为null而返回null
if (defaults) obj = Object(obj);
// 如果参数只有原始对象一个或者没有的情况,或者obj值为null的话,直接返回该对象
// 1. _.extend()和_.extendOwn()调用是defaults为undefined,此时length<2或者obj值为null时,返回该对象
// 2. _.defaults()调用时,defaults为true,此时length<2时,返回该对象【注:此时obj不可能为null,因为即使原始传入的obj为null,经过上一步操作,null也会转换为Object对象】
if (length < 2 || obj == null) return obj;
// _.extendOwn(destination, source1, source2, ...) 外层循环从source1开始取值
// 外层循环参数取源对象source1,source2...,从source1开始,顺序往后执行
for (var index = 1; index < length; index++) {
// 获取源对象
var source = arguments[index],
// 返回源对象中的属性名数组
keys = keysFunc(source),
// 源对象属性名数组长度
l = keys.length;
// 内层循环源对象属性名数组
for (var i = 0; i < l; i++) {
// 获取属性名
var key = keys[i];
// 1. _.extendOwn(), _.extend()执行时defaults === false,直接覆盖目标对象相应的属性值
// 2. _.defaults()执行时defaults === true且目标对象当前属性为undefined时,赋值该对象该属性值;如果该对象该属性存在,不执行任何操作
if (!defaults || obj[key] === void 0) obj[key] = source[key];
}
}
// 返回目标对象
return obj;
};
};
内部调用函数:
_.extend() || _.extendOwn() || _.defaults()
// Fill in a given object with default properties.
// 扩展目标对象,但是禁止扩展修改目标对象已经存在的属性键值对
_.defaults = createAssigner(_.allKeys, true);
// Extend a given object with all the properties in passed-in object(s).
// 扩展目标对象,对于源对象及其原型链中所有可枚举属性对目标对象进行扩展修改
_.extend = createAssigner(_.allKeys);
// Assigns a given object with all the own properties in the passed-in object(s).
// (https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object/assign)
// 扩展目标对象,对于源对象仅获取自身可枚举属性扩展修改目标对象,但是源对象原型链属性不进行扩展修改
_.extendOwn = _.assign = createAssigner(_.keys);
var _proto = {name: "xlshen", userName: "13日星期五"};
var obj = Object.create(_proto, {
age: {
value: 20,
writable: true,
enumerable: true,
configurable: true
}
});
_.extend({age: 18}, obj); // {name: "xlshen", age: 20, userName: "13日星期五"}
_.extendOwn({name: "xlshen"}, {name: "XXX", age: 20}); // {name: "XXX", age: 20}
_.defaults({name: "xlshen"}, {name: "XXX", age: 20}); // {name: "xlshen", age: 20}
_.extend
执行中keysFunc
为_.allKeys
方法,返回的是对象本身及其原型链上所有可枚举属性名组成的数组;执行createAssigner
方法,在上例中目标对象为{age: 18}
,arguments.length
为2
,defaults
为undefined
;继续往下执行,length == 2 && obj != null
;继续往下执行,开始依次循环源对象,因源对象从第二个参数开始,所以index
取值从1
开始,在循环内部,获取源对象source
,执行keysFunc
方法获取source
自身和原型上所有属性组成的数组,得到属性个数l
;依次循环属性数组,!defaults
始终为true
,始终执行obj[key]=source[key]
向目标对象赋值操作
_.extendOwn
和_.extend
方法唯一不同的就是keysFunc
函数,在_.extendOwn
中,keysFunc
为_.keys
方法,返回的是对象自身所有可枚举属性组成的数组,剩下的都和_.extend
方法一样
_.defaults
执行和上面两个都略有区别:在_.defaults
中createAssigner
函数中keysFunc
函数为_.allKeys
方法,defaults
置为true
;执行createAssigner
方法,第一步获取传入对象个数;因为defaults
始终为true
,第二步执行Object
包装,此处可以兼容如果传入obj
为空或者null
的情况;如果参数个数<2
则直接返回目标对象【注:此时obj
不可能为null
,因为即使原始传入的obj
为null
,经过上一步操作,null
也会转换为Object
对象】;往下和_.extend
方法一样,依次执行外层循环、内层循环,在内层循环的最后,因为!defaults
始终为false
,此处就是判断,如果obj[key] === void 0
则赋值,否则如果该属性已经存在目标对象中,则不进行任何操作
_.extend || _.extendOwn || _.defaults
方法通过调用createAssigner
方法进行对象的浅拷贝扩展
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.