underscore.js源码中关于对象合并方法的解析(extend,extendOwn,defaults)

7 篇文章 0 订阅

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);   // 目标对象强转为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.keys
	var 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: true
  Object.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 shadowed
            if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) {
              to[nextKey] = nextSource[nextKey];
            }
          }
        }
      }
      return to;
    },
    writable: true,
    configurable: true
  });

用途

如果你想在代码中扩展对象合并的功能,又不想整个引入underscore.js,通过上面的源码分析,就可以只引入你想用到的功能了,如果能自己改写一下那就更厉害了。

欢迎来我的b站空间逛逛
https://space.bilibili.com/395672451

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值