Javascript 传值传址&深浅拷贝

前言

因为公司前端资料已经处理成单层结构,所以都没注意到浅拷贝、深拷贝的实际差别。
在读完高手文章後,才发现和自己想的不一样。
也顺手将文章重点整理,分享给大家,别枉费自己写这麽多Code/images/emoticon/emoticon02.gif

重点

  • 基本型别 ⇒ 传值。stringnumberbooleannullundefined
  • 物件型别 ⇒ 传址
  • 浅拷贝 Shallow Copy ⇒ 物件若有多层,第一层之下的物件会以传址拷贝。
    • 物件的浅拷贝方法
      • Object.assign()
      • ...
    • 阵列的浅拷贝方法
      • array.slice()
      • array.concat()
      • array.map()
      • array.filter()
      • forEach +push()
      • ...
  • 深拷贝 Deep Copy ⇒ 返回一个全新的物件且不与被复制物件有关系。
    • 物件、阵列深拷贝方式
      • JSON.parse(JSON.stringify())
      • 第三方函示库,Lodash ⇒ _.cloneDeep() ...等等

传值

var a = 10;
var b = 10;

console.log(a === b); // true

ab皆为基本型别,故互相比较时回传true

传址

var obj1 = { a : 1 };
var obj2 = { a : 1 };

console.log(obj1 === obj2); // false

虽然物件内容相等,但是物件的记忆体位置却不同,故比较结果为false

不同的例子

var obj1 = { a : 1 };
var obj2 = obj1;

obj1.a = 0;
console.log(obj1.a); // 0
console.log(obj2.a); // 0

obj2.a = 2;
console.log(obj1.a); // 2
console.log(obj2.a); // 2

console.log(obj1 === obj2); // true

当我们修改任何一边的属性时,会一起变动,这是因为物件透过传址的方式指派,所以两个物件会指向同一个记忆体位置,实际上也意味着并未产生新的物件。

但若是这样呢?

var obj1 = { a : 1 };
var obj2 = obj1;

obj1.a = 0;
console.log(obj1.a); // 0
console.log(obj2.a); // 0

obj2.a = 2;
console.log(obj1.a); // 2
console.log(obj2.a); // 2

obj1 = {}; // 指派新的物件

console.log(obj1 === obj2); // false

obj1 被指向新的记忆体位置,但是obj2 依然保持原本的记忆体位置,因此这时obj1obj2 彼此就毫无关系了。

复制物件

复制物件可以分为两种

  • 浅拷贝 Shallow Copy
  • 深拷贝 Deep Copy

浅拷贝

  1. 物件的浅拷贝

    • Object.assign()
    • 展开运算子...

    Object.assign(target, source) 能复制一个或多个物件自身所有可数的属性到另一个目标物件。

    将原本的obj 内容复制到另一个空物件

    var obj = { a : 1, b : 2 };
    
    var obj2 = Object.assign({}, obj);
    
    console.log(obj2); // { a : 1, b : 2 }
    console.log(obj == obj2); // false
    

    展开运算子

    var obj = { a : 1, b : 2 };
    var obj2 = { ...obj1 };
    
    console.log(obj2); // { a : 1, b : 2 }
    console.log(obj === obj2); // false
    
  2. 阵列的浅拷贝

    • array.slice()
    • array.concat()
    • array.map()
    • array.filter()
    • forEach +push()
    • ...

    slice() 原本是用在分割阵列,但用参数为0或不传入的话,相当於浅拷贝

    var arr = [1, 2, 3, 4];
    
    var arr2 = arr;
    console.log(arr === arr2); // true
    
    var arr3 = arr.slice(0); 
    
    console.log(arr === arr3); // false
    console.log(arr3); // [1, 2, 3, 4]
    

    concat() 原本是用在组合阵列,但可以使用空阵列合并,也相当於浅拷贝

    var arr = [1, 2, 3, 4];
    
    var arr2 = [].concat(arr);
    
    console.log(arr === arr2); // false
    consoloe.log(arr2); // [1, 2, 3, 4]
    

    map() 是将执行结果会存至新阵列,若回传原本的元素,也相於浅拷贝

    var arr = [1, 2, 3, 4];
    
    var arr2 = arr.map(x => x);
    
    console.log(arr === arr2); // false
    console.log(arr2); // [1, 2, 3, 4]
    

    filter 是将符合条件的值存至新阵列,若条件皆为true ,也相当於浅拷贝

    var arr = [1, 2, 3, 4];
    var arr2 = arr.filter(x => { return true; } );
    
    console.log(arr === arr2); // false
    console.log(arr2); // [1, 2, 3, 4]
    

    ... 也可用於阵列,效果也是浅拷贝

    var arr = [1, 2, 3, 4];
    
    var arr2 = [ ...arr ];
    
    console.log(arr === arr2); // false
    console.log(arr2); // [1, 2, 3, 4]
    

    至於为什麽叫浅拷贝,可以看以下程序码

    var obj1 = {
      foo : 10,
      bar : {
        baz : 20,
      },
    };
    
    var obj2 = { ...obj1 };
    
    console.log(obj1 === obj2); // false
    
    console.log(obj1.bar === obj2.bar); // true
    

    浅拷贝後,obj1obj2 已经是两个不同的物件,但是第二层的物件却是相同的记忆体位置。

    再看看阵列

    var arr = [{a : 1}, {b : 2}];
    
    var arr2 = [...arr];
    
    console.log(arr === arr2); // false
    
    console.log(arr[0] === arr2[0]); // true
    

    浅拷贝後,arrarr2 已经是不同的阵列,但是里面的物件却是同一个。

    上述只是有容器不同,若有巢状或多维的状况,仍然是传址。因为深层的物件或阵列还是传址,不会完全复制一份,所以称为『浅拷贝』。

    深拷贝

    深拷贝是完全复制一份新的,不会共用记忆体位置。

    • Json.parse(Json.stringify())
    • 第三方函示库,Lodash ⇒ _.cloneDeep() ...等等

    利用JSON

    var obj1 = {
    	a : 1,
    	b : {
    		val: 5
    	}
    };
    
    var obj2 = JSON.parse(JSON.stringify(obj1));
    
    console.log(obj1 === obj2); // false
    console.log(obj1.b === obj2.b); // false
    

    但JSON的转法须注意

    • 函式、undefinedSymbol 会被忽略转换
    • NaNInfinity 会被转换成 null
    var obj1 = {
      a: function() {},
      b: undefined,
      c: Symbol(''),
      d: NaN,
      e: Infinity,
      f: -Infinity,
    };
    
    var obj2 = JSON.parse(JSON.stringify(obj1));
    
    console.log(obj2);  // {d: null, e: null, f: null}
    

参考

参考文章


<<:  缓冲区溢出和记忆体泄漏(Buffer Overflow and Memory Leak)

>>:  Python 演算法 Day 1 - 程序基础 & 简介

1. 新Leader不该事必躬亲

前言 这篇适合给first time leader,特别是刚被promote成team leade...

Day 18 - Android Studio 如何切换Activity(分页)

Day 18 - Android Studio 如何切换Activity(分页) 昨天我们讲了如何使...

Day 14:RecyclerView 进阶项目布局

本篇文章同步发表在 HKT 线上教室 部落格,线上影音教学课程已上架至 Udemy 和 Youtu...

Day24-Vuex核心概念与结构(part2)

接续前面的内容,还有几个东西没有研究到... 3. mutations 前面是学到的向仓库取资料的办...

每个人都该学的30个Python技巧|番外篇:第31天但不是第31个技巧(字幕、衬乐)

今天是铁人赛的最後一天了(虽然我已经完赛了),这部影片也会是这系列的最後一部了,真的要结束了(´༎ຶ...