一个对象和另一个对象之间2_赋值和克隆

"Hello World, Hello Blog"

Posted by wudimingwo on December 15, 2018

当我们操作两个对象的时候 1.0 赋值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
var obj = {
        age: 123,
        name : 'mike',
        son : {
          age : 18,
          name : 'peter'
        }
      }
      var newObj = obj;
      
      newObj.name = 'kaisa';
      console.log(obj);

赋值操作,两个变量指向同一个地址,
除非 newObj = newnewObj,
否则 obj和 newObj是互相影响的.

我们也可以看成 数据的流向是双向的.
我们有时希望这样,
但有时我们也希望能毫无关系.互不影响.

2.0深度克隆

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
function clone (origin, target) {
      	var target = target || {};
      	  for(var key in origin){
      	    if(origin[key] !== null && origin.hasOwnProperty(key)){
      	    if(typeof(origin[key]) == 'object'){
      	        console.log(origin.hasOwnProperty(key),key);
      	        if(Object.prototype.toString.call(origin[key]) == '[object Array]'){
      	          target[key] = [];
      	        }else{
      	          target[key] = {};
      	        }
      	        clone(origin[key],target[key]);
      	    }else {
      	      target[key] = origin[key];
      	    }
      	  }
      	 }
      	return target;
      }
      var newObj = [];
      clone(obj,newObj);
      console.log(newObj);
      newObj.name = 'kaisa';
      console.log(obj);
      console.log(newObj);

你发现,两个对象一样,但互不影响.
但我们知道一个对象不只是他自己,还包括他的原型链

3.0 对象间继承

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
最简单的是这样
function Obj () {}
var obj = new Obj();
var newObj = Object.create(obj);
这样就能继承obj以及obj上的原型链
或者其实1.0版本赋值也可以做到,因为本质上是同一个对象.

那我们有没有办法深度克隆的同时,还能继承?
最简单的方法是
var newObj = new Obj()
这样的话,出来的基本上一样,又能继承.

那我们就想克隆obj的同时,想要继承呢?

也许可以
var newObj = Object.create(obj);
然后调用2.0
clone(obj,newObj);

      Person.prototype.name = 'mike';
      Person.prototype.aaa = 'bbb';
      
      function Person () {
      	
      }
      
      var obj = new Person();
      obj.age = 18;
      obj.son = {
        name : 'peter',
        age : 5
      }
      var newObj = Object.create(obj);
      clone(obj,newObj);
      console.log(newObj);
      newObj.age = 99;
      console.log(obj);
      console.log(newObj);
      
      console.log(obj.name);
      console.log(newObj.name);//竟然可以

我本来以为不可以,后来发现,clone函数里从头到尾都没有对 target = 赋值,所以target地址是没有变化的,所以可以继承.这就把我给逗乐了.

既然如此,能不能把两个步骤写在一起?

4.0 继承版深度克隆

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
Person.prototype.name = 'mike';
      
      function Person () {
      	
      }
      
      var obj = new Person();
      obj.age = 18;
      obj.son = {
        name : 'peter',
        age : 5
      }
      
      function clone (origin,target,flag) {
        if(!flag){
          var target = Object.create(origin);
          flag = true;
        }else{
          var target = target;
        }
      	  for(var key in origin){
      	    if(origin[key] !== null && origin.hasOwnProperty(key)){
      	    if(typeof(origin[key]) == 'object'){
      	        if(Object.prototype.toString.call(origin[key]) == '[object Array]'){
      	          target[key] = [];
      	        }else{
      	          target[key] = {};
      	        }
      	        clone(origin[key],target[key],flag);
      	    }else {
      	      target[key] = origin[key];
      	    }
      	  }
      	 }
      	return target;
      }
      var newObj = clone(obj,newObj);
      console.log(newObj);
      newObj.age = 99;
      console.log(obj);
      console.log(newObj);
      console.log(obj.name);
      console.log(newObj.name);

但这个版本有个问题, 那就是 必须要接收返回值,newobj = clone(obj,newObj);//括号里的newOjb实际上不用传,因为不会起效果

最近看学习q源码 有个extend继承跟本篇内容有很大的相关性 jq extend 源码笔记 https://www.jianshu.com/p/6d033fa5c39e

第一个细节是,我们的深度克隆没有解决一个可能的bug 版本2.1

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
var obj = {
            name : 'mike',
            age : 18,
            son : {
              name : 'kaile',
              age : 4
            }
          }
          obj.obj2 = obj;// 语句1
          var obj1 = {
            name : 'peter',
            son : 'yellow'
          }
          console.log(obj);
          console.log(clone(obj1,obj));

因为语句一的存在,所以 clone的执行会无限死循环.
根据2.0 修改一下


function clone (target,origin) {
          	var target = target || {};
          	for(var prop in origin) {
          	  if(typeof(origin[prop]) == 'object') {
          	    if(origin == origin[prop]) {//加上这一块之后就好多了.
          	      continue;
          	    }
          	    target[prop] = Object.prototype.toString.call(origin[prop]) == 'object Array' ? [] : {};
          	    clone(target[prop],origin[prop]);
          	  }else {
          	    target[prop] = origin[prop];
          	  }
          	}
          	return target;
          }

看完extend 后还有两个问题思考.

第一个是,有可能会发生 origin[prop] 覆盖 target[prop] 的情况. 问题是,保不保留没被覆盖的情况. 实际上,版本2.0 能够保留第一层子属性当中未被重名的属性. 但到了孙子及以下属性,就无法保留,原因在于, 重名时,我们直接用{ } 或者 [ ] 进行了覆盖. 逻辑很不清晰,直接上代码吧.

版本2.2 保留不重名属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function clone (target,origin) {
          	var target = target || {};
          	for(var prop in origin) {
          	  if(typeof(origin[prop]) == 'object') {
          	    if(origin == origin[prop]) {
          	      continue;
          	    }
          	    target[prop] = Object.prototype.toString.call(origin[prop]) == '[object Array]' ? 
          	       (Object.prototype.toString.call(target[prop]) == '[object Array]' ? target[prop] : []) : //语句1
          	       (Object.prototype.toString.call(target[prop]) == '[object Object]' ? target[prop] : {});// 语句2
          	    clone(target[prop],origin[prop]);
          	  }else {
          	    target[prop] = origin[prop];
          	  }
          	}
          	return target;
          }

语句1和语句2 的作用就是,当 origin[prop] 和target[prop] 都是 对象/数组时,
传的是原来的 target[prop] 而不是覆盖.
这样就能保留不重名的属性了.

第二个是继承问题, 版本3.0 和版本4.0 也考虑了继承, 但考虑的是,让target 继承 origin.prototype, 而extend 当中,很明显是想要让target 保持原来的继承.

其实到这里的时候,就会发现, 深度克隆的目的涉及三个部分, 第一部分是,实现target和origin 两个对象之间互相不影响. 第二部分是,继承部分.extend想要保留原来的,而4.0 想要继承origin的. 第三部分是,保留属性部分,extend是想要保留未被重名的部分. 其实我们也可以选择要完全不保留原来的属性的版本. 也就是,完全克隆一份与origin相同的target,target 原来的属性统统不要. 所以一度,我在原型链上封装深度克隆的时候,认为target根本就是没有用的.

我们实现以下. 版本2.3

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
下面的flag让整个代码变得更丑,但我主要是想用这个来实现,保留target原来的继承.
function clone (target,origin,flag) {
            if(!flag){
              var target = Object.create(target.__proto__);
              flag = true;
            }else{
              target = target;
            }
          	for(var prop in origin) {
          	  if(typeof(origin[prop]) == 'object') {
          	    if(origin == origin[prop]) {
          	      continue;
          	    }
          	    target[prop] = Object.prototype.toString.call(origin[prop]) == '[object Array]' ? [] : {};
          	    clone(target[prop],origin[prop],flag);
          	  }else {
          	    target[prop] = origin[prop];
          	  }
          	}
          	return target;
          }