1、js中的变量数据类型可分为两种类型:基本类型和引用类型。
基本类型值指的是简单的数据段:number、string、Boolean、null、undefined、symbol
- 引用类型值指那些可能由多个值构成的对象:Object
2、在将一个值赋值给变量时,解析器必须先确定这个值是基本类型值还是引用类型值。
- 基本数据类型是按值访问的,因为可以操作保存在变量中的实际的值。
- 引用类型的值是保存在内存中的对象,与其他的语言不通,js不允许直接访问内存中的位置,也就是说不能直接操作对象的内存空间,在操作对象是,实际上是在操作对象的引用还不是实际的对象。
3、js的变量存储方式——栈(stack)、堆(heap)
- 栈:自动分配内存空间,系统自动释放,里面存放的是基本类型的值和引用类型的地址
- 堆:动态分配的内存,大小不定,也不会自动释放,里面存放的是引用类型的值
4、js值传递与址传递
基本类型和引用类型最大的区别实际就是传值和传址的区别
- 值传递:基本类型采用的是值传递
var str = 'How are you';
var newStr = str; //两个变量都是基本类型,值传递
newStr = 10
console.log(str); // How are you
console.log(newStr); // 10
- 址传递:引用类型采用的是址传递
var data = [1, 2, 3, 4, 5]; //引用类型
var newData = data;
newData[0] = 100;
console.log(data[0]); // 100
console.log(newData[0]); // 100
由于data和newData都是引用类型,采用的是址传递,即data将地址传递给newData,那么data和newData指向同一个地址(引用类型的地址存放在栈内存中),而这个地址都指向了堆内存中引用类型的值,当newData改变了这个值的同时,因为data的地址也指向了这个值,他俩的地址是一个,所以data的值也跟着变化。
就好比data租了一间房子,将房子的地址给了newData,newData通过地址找到了房间,当newData对房间对了改变,对于data来说也是可见的。
如何解决上面的问题呢?就要利用浅拷贝或深拷贝了。
5、浅拷贝和深拷贝
浅拷贝只拷贝一层对象的属性,而深拷贝则递归拷贝了所有层级。
- 浅拷贝:
function shallowClone(source) {
var target = {};
for(var i in source) {
if (source.hasOwnProperty(i)) {
target[i] = source[i];
}
}
return target;
}
- 深拷贝:
function deepClone(source) {
//定义一个对象,用来确定当前的参数是数据还是对象
var target = Array.isArray(source) ? [] : {};
//如果source存在,且类型为对象
if(source && typeof source === 'object') {
for(var i in source) {
if (source.hasOwnProperty(i)) {
//如果source的子元素是对象,递归操作
if (source[i] && typeof source[i] === 'object') {
target[i] = deepClone(source[i]); // 注意这里
} else {
//如果不是,直接赋值
target[i] = source[i];
}
}
}
}
return target;
}
其他拷贝的方法:
(1)利用JSON的两个方法,JSON.stringify(),JSON.parse()
来实现最简洁的深拷贝
var arr = ['str', 1, true, [1, 2], {age: 23}]
var newArr = JSON.parse( JSON.stringify(arr) );
newArr[3][0] = 100;
console.log(arr); // ['str', 1, true, [1, 2], {age: 23}]
console.log(newArr); // ['str', 1, true, [100, 2], {age: 23}]
理解:
我们可以理解为,将原始数据转换为新字符串,再通过新字符串还原为一个新对象,这中改变数据类型的方式,间接的绕过了拷贝对象引用的过程,也就谈不上影响原始数据
限制:
这种方式成立的根本就是保证数据在“中转”时的完整性,而JSON.stringify()将值转换为相应的JSON格式时也有缺陷:
- undefined、任意的函数以及 symbol 值,在序列化过程中会被忽略(出现在非数组对象的属性值中时)或者被转换成 null(出现在数组中时)。
- 函数、undefined 被单独转换时,会返回 undefined,
- 如JSON.stringify(function(){}),将一个 JavaScript 对象或值转换为 JSON 字符串
- JSON.stringify(undefined),方法用来解析JSON字符串,构造由字符串描述的值或对象
- 对包含循环引用的对象(对象之间相互引用,形成无限循环)执行此方法,会抛出错误。
- NaN 和 Infinity 格式的数值及 null 都会被当做 null。
- 其他类型的对象,包括 Map/Set/WeakMap/WeakSet,仅会序列化可枚举的属性。
所以当我们拷贝函数、undefined等stringify转换有问题的数据时,就会出错,我们在实际开发中也要结合实际情况使用。
(2)Object.assign()
var obj = {a: 1, b: { c: 2 } }
var newObj = Object.assign({}, obj)
newObj.a = 100;
newObj.b.c = 200;
console.log(obj); // {a: 1, b: { c: 200 } }
console.log(newObj) // {a: 100, b: { c: 200 } }
第一层属性没有改变,但第二层却同步改变了。
因为 Object.assign()拷贝的是(可枚举)属性值,假如源值是一个对象的引用,它仅仅会复制其引用值。
(3)… 展开运算符
const originArray = [1,2,3,4,5,[6,7,8]];
const originObj = {a:1,b:{bb:1}};
const cloneArray = [...originArray];
cloneArray[0] = 0;
cloneArray[5].push(9);
console.log(originArray); // [1,2,3,4,5,[6,7,8,9]]
const cloneObj = {...originObj};
cloneObj.a = 2;
cloneObj.b.bb = 2;
console.log(originObj); // {a:1,b:{bb:2}}
… 实现的是对象第一层的深拷贝。后面的只是拷贝的引用值。
(4)jQuery.extend([deep], target, object1, [objectN]);
第一个参数设置为true,则jQuery返回一个深层次的副本,递归地复制找到的任何对象。
*** 参考链接:
1、JavaScript中的深拷贝和浅拷贝
2、JavaScript专题(五)深浅拷贝
3、JavaScript基础新发 深浅拷贝(浅拷贝和深拷贝)
4、深拷贝的终极探索(90%的人都不知道)