基本类型与应用类型
我相信对于Javascript有一些了解同学知道,在Javascript的变量里面包含两种类型---基本类型跟应用类型。
基本类型
Javascript中的基本类型有Null,Undefined,String,Boolean,Number还有ES6中引入的Symbol。总共6种基本类型。
什么叫基本类型呢,它是如何定义的呢?在Javascript中上面所说的6种基本类型是按值直接访问的,一般存放于内存中的栈区域。存取的速度相对来说会比等下提及的引用类型要快,但是存放量小。
let str = 'test';
let str2 = str;
console.log(str2); //test
console.log(str); // test
str2 = 'a';
console.log('after:' + str2); //a
console.log('after:' + str); //test
引用类型
在Javascript中 Array,Object,Function就是引用类型。它们不同于基本类型,在内存的栈空间里面只会存放引用类型的变量的存放地址,变量实际上是存在内存的堆空间里面的。相对于基本类型,引用类型的存取速度会相对慢一点,但是存放量会更大。

由于引用类型的变量存放的是变量的地址,那么显而易见会有一个问题。就是如果两个变量指向同一块地址,那么就会引发两个不同变量之间的互相干扰。这也就是我们平时所说的深拷贝与浅拷贝的问题。(如果嫌文字表达不够清楚,请看下图的示例)
let obj1 = {name:'kidd',age:18};
let obj2= obj1;
console.log(obj1); // {name:'kidd',age:18}
console.log(obj2); // {name:'kidd',age:18}
obj2.name = 'KiddAfter';
console.log(obj1); // {name: "KiddAfter", age: 18}
console.log(obj2); // {name: "KiddAfter", age: 18}
上面代码描述的问题就是浅拷贝。那么总结一下:
浅拷贝:指两个js对象指向同一块内存地址,导致其中一个变化会影响另外一个。
深拷贝:指复制后的js对象指向一块全新的内存地址,可以避免一个对象改变影响另一个对象的问题。
浅拷贝
1.简单赋值
let arr = [1,2,3];
let newArr = arr;
newArr[0] = 'one';
console.log(arr); //['one',2,3]
console.log(newArr); //['one',2,3]
console.log(arr===newArr); //true
上面是最简单赋值操作,对于基本类型的数据这种操作没有任何的问题,会将变量的值赋值拷贝给另外的变量。但是对于引用类型的变量,这种操作只会将地址拷贝给新的变量。
2.Object.assign()
Object.assign() 是ES6新引入的函数。官方对它的描述是用于将所有可枚举属性的值从一个或多个源对象复制到目标对象。它将返回目标对象。
关于这个函数的具体用法,这里就不多说了,有兴趣的朋友可以自行百度了解一下。
let obj = {name: 'zhijie', age: 18};
let obj2 = Object.assign({},obj);
console.log(obj2); // {name:'zhijie',age: 18};
obj2.name = 'Kidd';
console.log(obj); // {name:'zhijie',age: 18};
console.log(obj2); // {name:'Kidd',age: 18};
看到上面这段代码,就很容易令人误解以为Object.assign()是深拷贝。然而事实却不是如此的,大家可以看下下面的例子:
let obj1 = {name:'zhijie', prop: {nickName:'Kidd'}};
let obj2 = Object.assign({},obj1);
console.log(obj2); // {name:'zhijie', prop: {nickName:'Kidd'}}
obj2.prop.nickName = 'Kidd2';
console.log(obj1); // {name:'zhijie', prop: {nickName:'Kidd2'}}
console.log(obj2); // {name:'zhijie', prop: {nickName:'Kidd2'}}
由上面的例子可以看出Object.assign() 并不是真正意义上的深拷贝。而只是实现了一层的深拷贝。所以我们对于属性都是基本类型的对象可以通过Object.assign()实现深拷贝。
深拷贝
1.JSON.parse(JSON.stringify(obj))
这种方式是最简单粗暴的深拷贝方法了。但是这种转换方式有个很大的问题就是转换的时候可能会导致对象属性的丢失。(具体可以看下下面这个例子)
let obj = {
name:undefined,
nickName:null,
age:18,
test:function test(){}
};
let obj2 = JSON.parse(JSON.stringify(obj));
console.log(obj2); // 结果:{nickName: null, age: 18}
由上面的例子可以看出如果对象的属性为undefined,或者是函数在转换的时候就会丢失。所以我们在开发过程中要特别注意这一点。
2.自己实现deepClone
先贴出代码,这只是一个简单的实现,请大家轻喷:
function deepClone(obj) {
if(!obj) {
return;
}
let result = Array.isArray(obj) ? [] : {};
for(key in obj) {
if(obj.hasOwnProperty(key)) {
if(typeof obj[key] === 'object' && obj[key] !== null) {
result[key] = deepCopy(obj[key]); //递归复制
} else {
result[key] = obj[key];
}
}
}
return result;
}
实现的思路大概就是先判断一下要拷贝的是对象还是数组。然后通过递归的思想,先遍历要拷贝对象的属性,如果属性也是对象则递归调用拷贝方法,否则直接写入。
下面是测试结果:
let arr = [1,2,3];
let arr2 = deepClone(arr);
arr2[0] = 0;
console.log(arr); // [1,2,3]
console.log(arr2); //[0,2,3]
let obj = {name:'zhijie',person:{nickName:'Kidd'},age:undefined};
let obj2 = deepClone(obj);
obj2.name = 'zhijie2';
obj2.person.nickName = 'Kidd2';
console.log(obj); // {name:'zhijie',person:{nickName:'Kidd'},age:undefined}
console.log(obj2); // {name:'zhijie2',person:{nickName:'Kidd2'},age:undefined}
性能比较
function testJSON(obj) {
let t1 = performance.now();
let result = JSON.parse(JSON.stringify(obj));
let t2 = performance.now();
console.log('JSON',t2 - t1);
}
function testAssign(obj) {
let t1 = performance.now();
let result = Object.assign({},obj);
let t2 = performance.now();
console.log('Assign',t2 - t1);
}
let t0 = performance.now();
let obj = {name:'zhijie',person:{nickName:'Kidd'},age:undefined};
let obj2 = deepClone(obj);
let t1 = performance.now();
console.log('deepClone',t1 - t0);
let obj3 = testJSON(obj);
let obj4 = testAssign(obj);
deepClone 0.1050001010298729
JSON 0.014999881386756897
Assign 0.00500003807246685
根据上面的测试可以看出通过Object.assign()的效率是最高的,其次就是JSON.parse(JSON.stringify())方法,递归的方法效率是最低的。
总结
深拷贝在日常开发中是一个比较常遇到的问题。特别是在Vue,React这些框架中。由于单向数据流的模式,如果我们不注意浅拷贝这个问题,那么很容易会导致开发中遇到问题。希望这篇文章能帮到大家~~