在开发中由于基本概念理解的不彻底,会出现一些令人头疼的bug
特别是当前变量和状态值有关联的时候,

eg:
// 在方法里面创建一个变量 该变量和当前状态值一样
// 然后把该变量传入回调中

const values = this.state.value
values.push(this.state.detailed)
// 采用直接赋值的方法,会导致状态值的改变从而影响视图渲染
const values = [...this.state.value]
values.push(this.state.detailed)
// 采用扩展运算符就能避免此问题

要说赋值的问题的话就必须先说基本数据类型和引用数据类型;下面就介绍下数据类型

1.基本数据类型和引用数据类型

1.1 基本数据类型指的是简单的数据段;

1.2 引用数据类型指的是那些可能由多个值构成的对象

1.3 截止今年(2019)的ES10,js的基本数据类型已经来到了七种;

string(字符串)、number(数字)、boolean(布尔)、null(对空)、undefined(未定义)、Symbol(独一无二的值-ES6)、BigInt(任意大的整数-ES10)

1.4 常用的引用类型有

Object(对象)、Array(数组)、Date(日期)、RegExp(实例)、Function(函数);还有许多,这些只是常用的。

!!!string在ECMAScript中不属于对象形式不是引用类型。

2.赋值

2.1 在将一个值赋给变量时,解析器必须确定这个值是基本类型还是引用类型

2.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类型) => 同一个堆内存(Object)
obj1 (Object类型) => 同一个堆内存(Object)

!!!为了解决此问题影响,就要使用浅拷贝或深拷贝。

3.浅拷贝

浅拷贝是按位拷贝对象,它会创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值;如果属性是引用类型,拷贝的就是它的内存地址 ,因此如果其中一个对象改变了这个地址,就会影响到另一个对象。即默认拷贝构造函数只是对对象进行浅拷贝复制(逐个成员依次拷贝),即只复制对象空间而不复制资源。

实现浅拷贝

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 } }

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.3 部分数组方法

Array.prototype.copyWithin() // 方法浅复制数组的一部分到同一数组中的另一个位置,并返回它,不会改变原数组的长度

Array.prototype.slice()     //  返回一个新的数组对象,这一对象是一个由 begin 和 end 决定的原数组的浅拷贝

Array.prototype.concat()     //  用于合并两个或多个数组。此方法不会更改现有数组,而是返回一个新数组。

Array.from()   // 从一个类似数组或可迭代对象创建一个新的,浅拷贝的数组实例

4.深拷贝

一个引用对象一般来说由两个部分组成:一个具名的Handle,也就是我们所说的声明(如变量)和一个内部(不具名)的对象,也就是具名Handle的内部对象。它在Manged Heap(托管堆)中分配,一般由新增引用对象的New方法是进行创建。深拷贝是指源对象与拷贝对象互相独立,其中任何一个对象的改动都不会对另外一个对象造成影响。

实现深拷贝

4.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"}

4.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"}

4.3 其他

jQuery.extend()、lodash.cloneDeep() 也可以实现深拷贝

5.总结

-
堆内存指向 基本数据类型 引用数据类型
赋值(引用类型) 改变会使基本数据类型一同改变 改变会使引用数据类型一同改变
浅拷贝 是(引用类型) 改变不会使基本数据类型一同改变 改变会使引用数据类型一同改变
深拷贝 改变不会使基本数据类型一同改变 改变不会使引用数据类型一同改变

wyd_4219
1 声望0 粉丝