Javascript 数据类型
Javascript 数据类型分为两种,基本数据类型和引用类型。
- 基本数据类型: null, undefined, Number, String, Boolean
- 引用类型: 统称为 Object 类型,细分的话又分为 Array, Object, Data, Function 等
Javascript 中的变量是存储在内存中的,不同的数据类型的变量的存储结构是不一样的。基本数据类型的变量是存储在栈内存中,而引用类型的变量是存储在堆内存中。由于存储结构不一致,变量在复制过程中也存在差异。
变量复制
Javascript 中,基本数据类型的存储结构是这样的,栈内存中存储的是变量的标识以及变量的值。
也就是每一个基本数据类型的变量在内存中占用的内存资源都是相互独立相互分开的。
var a = 1; var b = a; console.log(a === b); // true a = 2; console.log(a); // 2 console.log(b); // 1
将变量 a 复制给 变量 b 后,改变 a 的值并不影响 b 的值。
引用类型的存储结构则是这样的,栈内存中存储的是变量的标识和变量值的引用地址,堆内存中存储的才是变量真正的值。
也就是引用类型的变量值类似于指针,指向的是一个引用地址,在变量复制的过程中,其实只是把一个变量的引用地址复制给了另一个变量,两个变量的值都是指向同一个引用地址,当其中的一个变量的值改变之后,其实改变了引用地址中的值,就等同于两个变量的值都发生了改变。
浅拷贝、深拷贝
- 所谓的浅拷贝和深拷贝都是针对引用类型而言的。
- 如果复制的是引用地址,原来的变量和新的变量的值指向同一地址,彼此之间相互影响,就是浅拷贝。
- 如果是在堆内存中分配新的内存资源,值相同但是引用地址是不一样,原来的变量和复制的变量之间相互不影响,就是深拷贝。
Javascript 内置 API 中的深浅拷贝
-
slice, concat
对于数组而言,可以使用 Array 内置 API: slice, concat 进行拷贝,但是两种方式都属于浅拷贝。
//数组元素为基本数据类型 var arr1 = [1,2,3,4,5]; var arr2 = arr1.slice(); console.log(arr2); // [1,2,3,4,5] arr1[0] = 9; console.log(arr1); // [9,2,3,4,5] console.log(arr2); // [1,2,3,4,5] //数组元素为引用类型 var arr3 = [{ name: '白展堂', age: 25 }]; var arr4 = arr3.slice(); console.log(arr4); // [{name: '白展堂', age: 25}] arr3[0].age = 38; console.log(arr3); // [{name: '白展堂', age: 38}] console.log(arr4); // // [{name: '白展堂', age: 38}]
可以看到,当数组元素为基本数据类型时,变量复制之后,确实时相互不影响,但是当数组的元素为引用类型时,变量复制的其实就是引用地址,彼此之间相互影响着,所以 slice 方法是浅拷贝, 同理 concat 也是一样。
-
JSON.parse(JSON.stringify(obj))
JOSN 对象中的 stringify 可以把一个 js 对象序列化为一个 JSON 字符串,parse 可以把 JSON 字符串反序列化为一个 js 对象,这两个方法实现的是深拷贝。
var arr = [{ name: '白展堂', age: 25 }]; var newArr = JSON.parse(JSON.stringify(arr)); console.log(newArr); // [{name: '白展堂', age: 25}] arr[0].age = 38; console.log(arr); // [{name: '白展堂', age: 38}] console.log(newArr); // [{name: '白展堂', age: 25}]
深浅拷贝的实现
-
浅拷贝的实现
只需要将对象的第一层属性进行复制就可以了。
function shallowCopy(obj) { if(typeof obj != 'object') return; var newObj = obj instanceof Array ? [] : {}; for(var key in obj) { if(obj.hasOwnProperty(key)) { newObj[key] = obj[key]; } } return newObj; }
-
深拷贝的实现
采用递归的方式
function deepCopy(obj) { if(typeof obj != 'object') return; var newObj = obj instanceof Array ? [] : {}; for(var key in obj) { if(obj.hasOwnProperty(key)) { newObj[key] = typeof obj[key] == 'object' ? deepCopy(obj[key]) : obj[key]; } } return newObj; }
问题
-
对象环拷贝。
环就是对象循环引用,对象的属性值是对象本身,或者 a 对象的属性值是 b 对象,b 对象的属性值是 a 对象,这种对象就是对象循环。
对这样的对象,浏览器执行深拷贝是会不通过且会抛出错误。
如何处理这种环问题,答案是在拷贝属性之前先判断该属性值是否等于对象本身
-
深拷贝存在的问题,不能复制 Date 和 RegExp 这种特殊类型的对象。
var obj = { name: '白展堂', date: new Date('1530'), reg: /男/ig, say: function() { console.log('葵花点穴手'); } };
JSON 方式的深拷贝这个对象的结果是这样的
可以看到 date 值是执行后的字符串,reg 值是空对象,而 say 函数是直接没有拷贝的。
再来看下使用递归方式的拷贝,结果会是怎样的
很显然,Date 类型值和 RegExp 类型值都没有拷贝,说明这样的方式不能拷贝特殊类型的对象。
参考