underscore对象浅拷贝核心
核心函数: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
方法进行对象的浅拷贝扩展