underscore.js源码中关于对象合并方法的解析
- 源码解析
- MDN
- 用途
源码解析
这篇接着解析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); // 目标对象强转为object类型if (length < 2 || obj == null) return obj; // 大于两个参数才进行合并for (var index = 1; index < length; index++) {
//从第二个参数开始遍历,第一个参数是目标对象var source = arguments[index],keys = keysFunc(source), // !这里是关键,不同的keysFunc,生成不同功能的函数l = keys.length;for (var i = 0; i < l; i++) {
//只遍历一层var key = keys[i]; // defaults是true时,不覆盖同名属性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); //for in 会遍历原型链上的可枚举属性// Ahem, IE < 9.if (hasEnumBug) collectNonEnumProps(obj, keys); //忽略return keys;
}// 只返回自身属性
function keys(obj) {
if (!isObject(obj)) return [];if (nativeKeys) return nativeKeys(obj); //nativeKeys是Object.keysvar keys = [];for (var key in obj)if (has(obj, key)) keys.push(key); //has判断是不是自身属性// Ahem, IE < 9.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) // {a:1} 原型链上有b属性allKeys(o1) //原型上的可枚举属性也会包括 ['a', 'b']
keys(o1) // ['a']
用Object.setPrototypeOf对o1进行简单的原型链扩展,allKeys(o1)得到的就是a和b两个属性,而keys(o1)则只有a属性。
再回看到createAssigner函数,defaults方法的会合并源对象原型链上的可枚举属性,但是后面的源对象并不会覆盖同名属性,而extend和extendOwn的同名属性则会被最后面的源对象所覆盖。
简介而明了,三个功能不同的函数就这么生成了。
MDN
而原生的Object.assign方法其实就相当于extendOwn方法。
最后再看一下MDN上关于Object.assign方法的polyfill,就会发现它其实跟extendOwn是差不多的,同样都是只合并对象自身属性的第一层,并且会覆盖同名属性。
// mdn上Object.assign的 polyfill 从源码可以看出,Object.assign跟extendOwn的功能基本是一致的
if (typeof Object.assign !== 'function') {
// Must be writable: true, enumerable: false, configurable: trueObject.defineProperty(Object, "assign", {
value: function assign(target, varArgs) {
// .length of function is 2'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) {
// Avoid bugs when hasOwnProperty is shadowedif (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) {
to[nextKey] = nextSource[nextKey];}}}}return to;},writable: true,configurable: true});
用途
如果你想在代码中扩展对象合并的功能,又不想整个引入underscore.js,通过上面的源码分析,就可以只引入你想用到的功能了,如果能自己改写一下那就更厉害了。
欢迎来我的b站空间逛逛
https://space.bilibili.com/395672451