extend的基本用法
extend:merge the contents of two or more objects together into the first object
extend的用法
1 | jQuery.extend(target [, object] [, objectN]) |
第一个参数target表示要拓展的目标,后面的参数都是对象,对象的内容会复制到目标对象中
打个比方
1 | var obj1 = { |
后面传入的参数相同时,后者会把前者覆盖掉,而不是合并。
extend 第一版
1 | function extend(){ |
如何进行深拷贝的复制呢?
1 | jQuery.extend([deep], target, object1 [, objectN]) |
也就是说通过第一参数值的状态来判断是否进行深拷贝,target被推后了一位。
同样的例子
1 | var obj1 = { |
extend 第二版
在实现深拷贝的功能之前,需要注意的是:
- 需要根据第一个参数的类型,确定target和要合并的对象的下标起始值。
- 如果是深拷贝,根据copy的类型递归extend。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44function extend() {
var deep = false;
var name, options, src, copy;
var length = arguments.length;
var i = 1;
var target = arguments[0] || {};
//如果是布尔值,target就应该是第二位
if(typeof target == 'boolean') {
deep = target;
target = arguments[i] || {};
i++;
}
// 校验第二个参数是否是个对象
if(typeof target !== 'object') {
target = {}
}
for(; i < length; i++) {
//获取到target之后的各个参数
options = arguments[i];
//参数不能为空, 避免 extend(a,,b)这种情况
if(options != null) {
//遍历参数
for(name in options) {
//添加了新属性的target的副本,用作遍历的暂存对象
src = target[name];
//得到参数里的参数值
copy = options[name];
//是深拷贝 参数值不为空 参数的值又是一个对象
if(deep && copy && typeof copy == 'object') {
//递归更新target
target[name] = extend(deep, src, copy);
}
//浅拷贝直接赋值就可以了
else if(copy !== undefined) {
target[name] = copy;
}
}
}
}
return target;
}
target 是函数
在实现中,typeof target
必须等于object,我们才会在这个target基础上进行拓展,但是typeof判断一个函数时,会返回function,也就是说,无法在一个函数上进行拓展,但是真实场景下,函数是能被扩展的
1 | function a(){} |
实际上,在underscore的实现中,underscore的各种方法便是挂在了函数上!
我们可以修改成如下:
1 | var class2type = {}; |
类型不一致
目前实现的extend函数有一个问题
1 | var obj1 = { |
我们期待的输出应该是这么一个对象
1 | { |
但是得到的结果确是:
1 | { |
在函数开始添加一行 console.log(1)
发现1打印了三次,这是怎么回事呢
我们来看看每次参数是怎么变化的
第一次遍历
1 | src = {c: 2}, target = {a: 1, b: {...}} name = "b" |
第二次遍历
1 | scr = 2 , target = {c: 2}, name = "c" |
第三遍进行最终的赋值,因为 src 是一个基本类型,我们默认使用一个空对象作为目标值,所以最终的结果就变成了对象的属性!
也就是这一段代码
1 | // 校验第二个参数是否是个对象 |
为了解决这个问题,我们就需要同时对目标属性和待复制对象的属性值进行判断:
判断目标属性值要跟复制的对象的属性值类型是否一致:
- 如果待复制对象属性值是一个数组,目标属性值类型不为数组的话,目标属性值就设置为 []
- 如果待复制对象属性值类型为对象,目标属性值类型不为对象的话,目标属性值就设置为{}
结合javaScript之类型判断中的isPlainObject函数,我们可以对类型进行更细致的划分:
1 | var clone, copyIsArray; |
完整代码如下:
1 | function extend() { |
循环引用
在实际中可能会遇到一个循环引用的问题
var a = {name : b};
var b = {name : a};
var c = extend(a, b);
console.log(c);
结果就成了下图所示:
为了避免这个问题,我们需要判断要复制的对象属性是否等于target,如果等于,就跳过
1 | src = target[name]; |
如果加上这句话,结果就会是:
1 | {name: undefined} |
最终代码
1 | // isPlainObject 函数来自于 [JavaScript专题之类型判断(下) ](https://github.com/mqyqingfeng/Blog/issues/30) |