对象深复制

clone

extend

对象引用

这里主要举例 jQuery 的$.extend方法,首先是对象的深复制的背景:对象和数组是引用的,所以如果你用

var arr = [1, 2, 3, 4, 5]
var arr2 = arr
arr2.push(6)
console.log(arr) //[1, 2, 3, 4, 5, 6]
console.log(arr2) //[1, 2, 3, 4, 5, 6]
1
2
3
4
5

但是有时候这不是我们要的结果,我们要将 arr 缓存起来,在某个时候重新还原 arr 为原来的值。这个时候就坑爹了,要复制整个对象或者数组。这个时候深复制就来了。

如果你不用 jQuery 的,你要自己写的话,还真的挺麻烦的。 jQuery 的$.extend方法的几种调用方法

  1. $.extend(object1)直接扩展 jQuery
  2. $.extend(target [,object1][,object2])复制不递归
  3. $.extend([deep], target, object1 [,objectN])递归复制

这里的deep是一个Boolean的值

$.extend(object1/function)直接扩展 jQuery

var object1 = {
  apple: 0,
  banana: { weight: 52, price: 100 },
  cherry: 97
}

// Merge object2 into object1
$.extend(object1)

// Assuming JSON.stringify - not available in IE<8
console.log($.apple) //0
console.log($.banana) //Object {weight: 52, price: 100}
console.log($.fn.cherry) //undefined
console.log($.banana === object1.banana) //true
1
2
3
4
5
6
7
8
9
10
11
12
13
14

把对象的属性 copy 到$下了,这里的$可能是Zepto或者jQuery,一样的。 默认不会递归复制,这里可以看到banana还是复制的引用。 $.extend这种写法 jquery 自己也用的很多,可以看到其实代码的后面一堆的$.extend或者是$.fn.extend这种写法,包括插件也用的很多。

$.extend(target [,object1][,object2])复制不递归

var object1 = {
  apple: [0],
  banana: { weight: 52, price: 100 },
  cherry: 97
}
var object2 = {
  banana: { price: 200 },
  durian: 100,
  egg: [1, 2, 3, 4]
}

// Merge object2 into object1
$.extend(object1, object2)

console.log(JSON.stringify(object1))
//{"apple":[0],"banana":{"price":200},"cherry":97,"durian":100,"egg":[1,2,3,4]}

console.log(object1.egg === object2.egg)
//true
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

这里将object2的属性合并到了object1中,object2object1共有的属性会直接覆盖object1的属性,没有的会合并到object1里面。没有递归深层的属性,就是简单的复制属性而已。

$.extend([deep], target, object1 [,objectN])递归复制

var object1 = {
  apple: [0],
  banana: { weight: 52, price: 100 },
  cherry: 97
}
var object2 = {
  banana: { price: 200 },
  durian: 100,
  egg: [1, 2, 3, 4]
}

// Merge object2 into object1
$.extend(true, object1, object2)

console.log(JSON.stringify(object1))
//{"apple":[0],"banana":{"weight":52,"price":200},"cherry":97,"durian":100,"egg":[1,2,3,4]}

console.log(object1.egg === object2.egg)
//false
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

这里传入了一个true的参数开启递归深复制,object1object2的属性不会直接拿个引用过去,而是检查不同的地方,将相同的地方覆盖,没有的地方复制,引用的对象数组也被拷贝了一份过去,而不是拷贝引用而已。

没有源码你说啥呢?

// 对$.fn.extend添加方法,之后将引用赋给$.extend
jQuery.extend = jQuery.fn.extend = function() {
  var options,
    name,
    src,
    copy,
    copyIsArray,
    clone,
    target = arguments[0] || {}, //target是最后返回的对象,如果没有就创建一个对象
    i = 1,
    length = arguments.length,
    deep = false

  // Handle a deep copy situation
  if (typeof target === 'boolean') {
    deep = target

    // skip the boolean and the target
    target = arguments[i] || {}
    i++
  }

  // Handle case when target is a string or something (possible in deep copy)
  if (typeof target !== 'object' && !jQuery.isFunction(target)) {
    target = {}
  }

  // extend jQuery itself if only one argument is passed
  if (i === length) {
    target = this
    i--
  }

  for (; i < length; i++) {
    // Only deal with non-null/undefined values
    if ((options = arguments[i]) != null) {
      // Extend the base object
      for (name in options) {
        src = target[name]
        copy = options[name]

        // Prevent never-ending loop
        if (target === copy) {
          continue
        }

        // Recurse if we're merging plain objects or arrays
        if (
          deep &&
          copy &&
          (jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)))
        ) {
          if (copyIsArray) {
            copyIsArray = false
            clone = src && jQuery.isArray(src) ? src : []
          } else {
            clone = src && jQuery.isPlainObject(src) ? src : {}
          }

          // Never move original objects, clone them
          target[name] = jQuery.extend(deep, clone, copy)

          // Don't bring in undefined values
        } else if (copy !== undefined) {
          target[name] = copy
        }
      }
    }
  }

  // Return the modified object
  return target
}
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
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73

对象的属性复制是分开的,boolean,number,string 这三个和 object,array 处理是不一样的。

Object.assign

这个方法会拷贝源对象自身可枚举的方法到目标对象. 类似 extend,这个方法可以扩展对象属性.并返回扩展后的对象

var obj = { a: 1 }
var copy = Object.assign({}, obj, { b: 123 })
console.log(copy) //{a:1,b:123}
1
2
3