underscore.js源码中关于对象合并方法的解析
源码解析
这篇接着解析underscore.js源码
关于对象合并方法我们首先想到的肯定是Object.assign方法。 在underscore中根据功能不同,分别有extend,extendOwn,defaults三种方法与Object.assign类似,下面通过源码进行解析。
extend,extendOwn,defaults这三个函数其实都是由源码中的createAssigner函数生成的,每个函数的功能略有差异,代码简洁而精妙,非常值得学习。
以下代码的所有汉字部分为我加的注释
createAssigner源码如下:
function createAssigner(keysFunc, defaults) {
return function(obj) {
var length = arguments.length;
if (defaults) obj = Object(obj);
if (length < 2 || obj == null) return obj;
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];
if (!defaults || obj[key] === void 0) obj[key] = source[key];
}
}
return obj;
};
}
其中最重要的地方就是参数keysFunc,直接看这里肯定会比较懵逼,可以先往下看。
var extend = createAssigner(allKeys);
var extendOwn = createAssigner(keys);
var defaults = createAssigner(allKeys, true);
以上就是生成三种不同功能函数的方式,大家肯定又懵逼了,allKeys和keys又是个啥。 接着往下看。
function allKeys(obj) {
if (!isObject(obj)) return [];
var keys = [];
for (var key in obj) keys.push(key);
if (hasEnumBug) collectNonEnumProps(obj, keys);
return keys;
}
function keys(obj) {
if (!isObject(obj)) return [];
if (nativeKeys) return nativeKeys(obj);
var keys = [];
for (var key in obj)
if (has(obj, key)) keys.push(key);
if (hasEnumBug) collectNonEnumProps(obj, keys);
return keys;
}
allKeys和keys就是返回一个对象上所有键的数组。 区别是allKeys可以返回对象原型链上的可枚举属性。keys方法的效果和Object.keys方法一致,只返回自身属性。
所以extendOwn方法就是只合并源对象上的自身属性,extend方法就是合并源对象的自身属性以及原型链上的可枚举属性。
下面举一个简单的例子来看一下,一个普通对象原型链上的可枚举属性是怎么一回事。
let o1 = {
a: 1
}
let o2 = {
b: 2
}
Object.setPrototypeOf(o1, o2)
allKeys(o1)
keys(o1)
用Object.setPrototypeOf对o1进行简单的原型链扩展,allKeys(o1)得到的就是a和b两个属性,而keys(o1)则只有a属性。
再回看到createAssigner函数,defaults方法的会合并源对象原型链上的可枚举属性,但是后面的源对象并不会覆盖同名属性,而extend和extendOwn的同名属性则会被最后面的源对象所覆盖。
简介而明了,三个功能不同的函数就这么生成了。
MDN
而原生的Object.assign方法其实就相当于extendOwn方法。
最后再看一下MDN上关于Object.assign方法的polyfill,就会发现它其实跟extendOwn是差不多的,同样都是只合并对象自身属性的第一层,并且会覆盖同名属性。
if (typeof Object.assign !== 'function') {
Object.defineProperty(Object, "assign", {
value: function assign(target, varArgs) {
'use strict';
if (target === null || target === undefined) {
throw new TypeError('Cannot convert undefined or null to object');
}
var to = Object(target);
for (var index = 1; index < arguments.length; index++) {
var nextSource = arguments[index];
if (nextSource !== null && nextSource !== undefined) {
for (var nextKey in nextSource) {
if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) {
to[nextKey] = nextSource[nextKey];
}
}
}
}
return to;
},
writable: true,
configurable: true
});
用途
如果你想在代码中扩展对象合并的功能,又不想整个引入underscore.js,通过上面的源码分析,就可以只引入你想用到的功能了,如果能自己改写一下那就更厉害了。
|