##深入赋值、拷贝问题
要说赋值的问题的话就必须先说基本数据类型和引用数据类型;
1.基本数据类型和引用数据类型
@1 基本数据类型指的是简单的数据段;
@2 引用数据类型指的是那些可能由多个值构成的对象
- @1@2出自于JavaScript高级程序设计第三版
@3 截止今年(2019)的ES10,js的基本数据类型已经来到了七种;
string(字符串)、number(数字)、boolean(布尔)、null(对空)、undefined(未定义)、Symbol(独一无二的值-ES6)、BigInt(任意大的整数-ES10)
@4 常用的引用类型有
Object(对象)、Array(数组)、Date(日期)、RegExp(实例)、Function(函数);还有许多,这些只是常用的。
!!!string在ECMAScript中不属于对象形式不是引用类型。
2.赋值
@1 在将一个值赋给变量时,解析器必须确定这个值是基本类型还是引用类型
@2 赋值分为两部分
基本数据类型:赋值,赋值之后两个变量互不影响
引用数据类型:赋址,两个变量具有相同的引用,指向同一个对象,相互之间有影响
let num1 = 5;
let num2 = num1
- | - |
---|---|
num2 | 5(number类型) |
num1 | 5(number类型) |
let obj1 = new Object()
let obj2 = obj1
obj1.name = 'tom'
console.log(obj2.name) // 'tom'
- | - |
---|---|
obj2 | (Object类型) |
obj1 | (Object类型) |
!!!为了解决此问题影响,就要使用浅拷贝或深拷贝。
3.浅拷贝
浅拷贝是按位拷贝对象,它会创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值;如果属性是引用类型,拷贝的就是它的内存地址 ,因此如果其中一个对象改变了这个地址,就会影响到另一个对象。即默认拷贝构造函数只是对对象进行浅拷贝复制(逐个成员依次拷贝),即只复制对象空间而不复制资源。
实现浅拷贝
@1 Object.assign()
用于将所有可枚举属性的值从一个或多个源对象复制到目标对象。它将返回目标对象。
const obj = { a: 1,test:{sys:2} };
const copy = Object.assign({}, obj);
obj.test.sys = 3
console.log(copy); // { a: 1, test: Object { sys: 3 } }
@2 展开语法(Spread syntax) - MDN
可以在函数调用/数组构造时, 将数组表达式或者string在语法层面展开;还可以在构造字面量对象时, 将对象表达式按key-value的方式展开。
- 函数调用
myFunction(...iterableObj);
- 字面量数组构造或字符串:
[...iterableObj, '4', ...'hello', 6];
- 构造字面量对象时,进行克隆或者属性拷贝ES9(ECMAScript 2018)规范新增特性:
let objClone = { ...obj };
eg:
const values = this.state.value
values.push(this.state.detailed)
上面利用赋值然后直接修改新变量会影响this.state.value因此造成页面渲染的bug
const values = [...this.state.value]
values.push(this.state.detailed)
利用数组扩展运算符则可避免此情况
@3 部分数组方法
Array.prototype.copyWithin() // 方法浅复制数组的一部分到同一数组中的另一个位置,并返回它,不会改变原数组的长度
Array.prototype.slice() // 返回一个新的数组对象,这一对象是一个由 begin 和 end 决定的原数组的浅拷贝
Array.prototype.concat() // 用于合并两个或多个数组。此方法不会更改现有数组,而是返回一个新数组。
Array.from() // 从一个类似数组或可迭代对象创建一个新的,浅拷贝的数组实例
4.深拷贝
一个引用对象一般来说由两个部分组成:一个具名的Handle,也就是我们所说的声明(如变量)和一个内部(不具名)的对象,也就是具名Handle的内部对象。它在Manged Heap(托管堆)中分配,一般由新增引用对象的New方法是进行创建。深拷贝是指源对象与拷贝对象互相独立,其中任何一个对象的改动都不会对另外一个对象造成影响。
实现深拷贝
@1 JSON方法
JSON.parse(JSON.stringify(object))
该方法有缺陷:会忽略 undefined、symbol、函数、正则
let obj = {
name: 'tom',
a: undefined,
b: Symbol('tom'),
c: function() {},
d: /'123'/
}
let b = JSON.parse(JSON.stringify(obj));
console.log(b);
// {name: "tom"}
@2 递归函数
let deepClone = function (obj) {
let copy = Object.create(Object.getPrototypeOf(obj));
let propNames = Object.getOwnPropertyNames(obj);
propNames.forEach(function (items) {
let item = Object.getOwnPropertyDescriptor(obj, items);
Object.defineProperty(copy, items, item);
});
return copy;
};
let obj ={name:'tom'}
console.log(deepClone(obj))
// {name: "tom"}
###@3 其他
jQuery.extend()、lodash.cloneDeep() 也可以实现深拷贝
5.总结
- | |||
---|---|---|---|
堆内存指向 | 基本数据类型 | 引用数据类型 | |
赋值(引用类型) | 是 | 改变会使基本数据类型一同改变 | 改变会使引用数据类型一同改变 |
浅拷贝 | 是(引用类型) | 改变不会使基本数据类型一同改变 | 改变会使引用数据类型一同改变 |
深拷贝 | 否 | 改变不会使基本数据类型一同改变 | 改变不会使引用数据类型一同改变 |