Javascript/js 的浅拷贝与深拷贝(复制)学习随笔
js变量的数据类型值分基本类型值和引用类型值。
在es6(ecmascript6)以前,基本数据类型包括string、number、boolean、undefined、null。
基本类型值的复制(拷贝)
从一个变量向另一个变量复制基本类型的值。使用赋值操作符 ' = ' 即可。
如:
1 var num1 = 1, num2; 2 num2 = num1;
上述代码定义了两个变量num1、num2。num1初始化值是1,num2是undefined。接着把num1赋值给num2。
num1的值与num2的值增删改减完全独立、互不影响。
1 ++num1; 2 num2 = null; 3 // 2 null
拓展:基于基本类型值,es提供了三个特殊的引用类型。string、number、boolean。(基本包装类型)
1 var num3 = 3; 2 var num4 = num3.tofixed(2); 3 console.log(num3, num4); // 3 3.00
如上,变量num3包含一个数字值,数字当然属于基本类型值啦,接着num3调用了tofixed()方法。并将返回结果保存在num4中。最后在控制台输出下。结果是3 3.00。当然了,没有报错。。
一般来理解,基本类型值不是对象,不应该有方法。(但是它们确实有方法.。查看它们有哪些方法的一个办法是在chrome控制台console.log(new number(1))。其他基本类型值同理。baidu/翻书/强记。do whatever you want)
当第二行代码访问num3时,访问过程处于一种读取模式,也就是从内存中读取这个变量的值。此时,在后台,大概是执行了下列的es代码:
1 var _num3 = new number(3);// 创建number类型的一个实例 2 var _num4 = _num3.tofixed(2);// 在实例上调用指定的方法 3 _num3 = null;// 销毁这个实例 4 return _num4;// 可以想象成在一个函数里执行这里的4行代码,函数返回_num4。接着被num4接收。
这也意味着我们可以对基本类型值做一些扩展。比如:
1 var num5 = 1; 2 number.prototype.addten = function () { 3 var res = this + 10; 4 return res; 5 }; 6 console.log(num5.addten());// 11
如上,在number原型上定义addten()方法,所有number类型值都可以调用这个方法。
其他基本类型值同理。
es6规范引入了一项新特性--symbol,它也是一种基本数据类型,它的功能类似于一种标识唯一性的id。
调用symbol函数来创建一个symbol实例:
1 const s1 = symbol(); 2 // 可以在调用symbol函数时传入一个参数,相当于给你创建的symbol实例一个描述信息。参数可选,可以是任意可转化成字符串的值。 3 const s2 = symbol('id9527');
引用类型的复制(拷贝)
常见的引用类型包括 object、aarry、date、function、regexp...
引用类型值是引用类型的一个实例。
通过赋值操作符‘=’复制的引用类型值。实际上复制的是一个指针(地址)。该指针指向存储在堆中的对象。
1 const obj1 = new object(); 2 const obj2 = obj1; 3 obj1.name = 'xm'; 4 console.log(obj2.name);// xm
obj1与obj2指向同一个对象,对obj1的修改,同样作用于obj2。
多数时候这不是我们想要的结果。我们需要的是两个相互独立而又长得一模一样的对象。
由于引用类型值也可能包含引用类型值。由此就派生出了浅拷贝和深拷贝。
浅拷贝
数组的浅拷贝常用方法:
(1)concat方法
1 const arr1 = ['a', 'b', ['c', 4]]; 2 const arr2 = arr1.concat([]); 3 console.log(arr2, arr1 == arr2);// ['a', 'b', ['c', 4]] false
(2)slice方法
1 const arr1 = ['a', 'b', ['c', 4]]; 2 const arr2 = arr1.slice(0); 3 console.log(arr2, arr1 == arr2);// ['a', 'b', ['c', 4]] false
(3)扩展运算符...方法
1 const arr1 = ['a', 'b', ['c', 4]]; 2 const arr2 = [...arr1]; 3 // const [...arr2] = arr1; // 等同于上一行 4 console.log(arr2, arr1 == arr2);// ['a', 'b', ['c', 4]] false
(4)map方法
1 const arr1 = ['a', 'b', ['c', 4]]; 2 const arr2 = arr1.map(item => item); 3 console.log(arr2, arr1 == arr2);// ['a', 'b', ['c', 4]] false
(5)filter方法 把上面的map改成filter即可。
...for循环、foreach、for of、splice、object.values等方法均可。
对象的浅拷贝常用方法:
1、for in遍历方法
1 const obj = { 2 say(){ 3 console.log('hello'); 4 } 5 }; 6 const obj1 = object.create(obj); 7 obj1.a = '对象'; 8 obj1.b = [1, 2, 3]; 9 10 // const obj2 = object.create(obj); // 继承obj的属性、方法 11 const obj2 = {}; 12 for (let p in obj1) { 13 if (obj1.hasownproperty(p)) { 14 obj2[p] = obj1[p]; 15 } 16 }
如上,obj1的原型对象是obj,浅拷贝一般不需要拷贝原型上的属性和方法,而for in循环可以枚举原型上的属性和方法。使用hasownproperty()方法过滤掉原型的属性和方法。
结果如下:
(2)object.entries()方法
1 const obj = { 2 say(){ 3 console.log('hello'); 4 } 5 }; 6 7 const obj1 = object.create(obj); 8 obj1.a = '对象'; 9 obj1.b = [1, 2, 3]; 10 11 // const obj2 = object.create(obj); // 继承obj的属性、方法 12 const obj2 = {}; 13 object.entries(obj1).foreach(([key, value]) => obj2[key] = value);
之所以称为浅拷贝,其原因在于如果引用类型值里包含引用类型值,上述的所有方法,在对里层的引用类型值复制操作时,使用的还是赋值操作符'='。如下所示:
如果修改了obj1.b的值,同样会作用于obj2。
深拷贝
以下是实现对数组、对象深拷贝的一种方法。
采用递归的方式,层层遍历。
1 const deepclone = function handledeepclone(obj) { 2 if (typeof obj !== 'object' || obj === null) { 3 return obj; 4 } 5 6 let _obj; 7 if (obj instanceof array) { 8 _obj = []; 9 obj.foreach((item, i) => _obj[i] = handledeepclone(item)); 10 } else { 11 _obj = {}; 12 object.entries(obj).map(([key, value]) => _obj[key] = handledeepclone(value)); 13 } 14 15 return _obj; 16 };
结果如下: