乐趣区

关于javascript:清晰易懂讲解JS赋值浅拷贝和深拷贝

一些问题

// 赋值 1
let obj1 = {
    a: 1,
    b: 2
}

let obj2 = obj1
obj2.a = 3

console.log(obj1) // ?
console.log(obj2) // ?

// 赋值 2
let obj1 = {
    a: 1,
    b: 2,
    c: {q: 6}
}

let obj2 = obj1
obj2.a = 3
obj2.c.q = 7

console.log(obj1) // ?
console.log(obj2) // ?

// 浅拷贝 1
let obj1 = {
    a: 1,
    b: 2
}
let obj2 = Object.assign({}, obj1)
obj2.a = 3

console.log(obj1) // ?
console.log(obj2) // ?

// 浅拷贝 2
let obj1 = {
    a: 1,
    b: 2,
    c: {q: 6}
}

let obj2 = Object.assign({}, obj1)
obj2.a = 3
obj2.c.q = 7

console.log(obj1) // ?
console.log(obj2) // ?

// 深拷贝
let _ = require('lodash');
let obj1 = {
    a: 1,
    b: 2,
    c: {q: 6}
};
let obj2 = _.cloneDeep(obj1);
obj2.a = 3
obj2.c.q = 7

console.log(obj1) // ?
console.log(obj2) // ?

JS 数据类型及相干概念

数据类型

JS 有两种数据类型:根本类型、援用类型。
根本类型:String Number Boolean undefined null symbol
援用类型:Object Array Function Date

数据在内存中的存储

JS 定义变量的时候,变量会被存入内存栈中,但根本类型和援用类型的存储有区别:
根本类型存储在栈内存中;
援用类型存储在堆内存中,栈中存储了指向数据的指针(内存地址)。
即:定义一个新变量拷贝数据时,根本类型是拷贝原始数据,援用类型则不是。

正是因为这个问题,老手容易遇到一个问题:拷贝一个对象(援用类型)后,对新对象进行批改,再应用原对象后会发现原对象也变了。
解决这个问题的方法是学习赋值、浅拷贝和深拷贝的应用场景,不同场景灵活处理援用类型。

对象的赋值、浅拷贝、深拷贝

赋值没有创立新对象,仅仅是拷贝了原对象的指针
浅拷贝是创立一个新对象,这个对象仅对原对象的属性进行拷贝,属性值是根本类型时,拷贝的是原数据,属性值是援用类型时,拷贝的是指针。因而,如果原对象的属性有援用类型数据,无论批改新对象还是原对象的援用类型数据,另一个都会随之扭转
深拷贝也是创立一个新对象,但不仅对原对象的属性进行拷贝,还在堆内存中开拓一个新的地址用来存储新对象,所以 新对象和原对象没有关联,批改其中一个另一个不会变动

问题解答

// 赋值 1
let obj1 = {
    a: 1,
    b: 2
}

let obj2 = obj1
obj2.a = 3

console.log(obj1) // {a: 3, b: 2}
console.log(obj2) // {a: 3, b: 2}

// 赋值 2
let obj1 = {
    a: 1,
    b: 2,
    c: {q: 6}
}

let obj2 = obj1
obj2.a = 3
obj2.c.q = 7

console.log(obj1) // {a: 3, b: 2, c: {q: 7}}
console.log(obj2) // {a: 3, b: 2, c: {q: 7}}

// 浅拷贝 1
let obj1 = {
    a: 1,
    b: 2
}
let obj2 = Object.assign({}, obj1)
obj2.a = 3

console.log(obj1) // {a: 1, b: 2}
console.log(obj2) // {a: 3, b: 2}

// 浅拷贝 2
let obj1 = {
    a: 1,
    b: 2,
    c: {q: 6}
}

let obj2 = Object.assign({}, obj1)
obj2.a = 3
obj2.c.q = 7

console.log(obj1) // {a: 1, b: 2, c: {q: 7}}
console.log(obj2) // {a: 3, b: 2, c: {q: 7}}

// 深拷贝
let _ = require('lodash');
let obj1 = {
    a: 1,
    b: 2,
    c: {q: 6}
};
let obj2 = _.cloneDeep(obj1);
obj2.a = 3
obj2.c.q = 7

console.log(obj1) // {a: 1, b: 2, c: {q: 6}}
console.log(obj2) // {a: 3, b: 2, c: {q: 7}}

应用场景

原对象仅应用一次(被批改也不产生副作用),用赋值。

原对象需屡次应用(被批改会导致后续应用数据不精确),且属性只有根本类型,无援用类型,用浅拷贝。

原数据需屡次应用(被批改会导致后续应用数据不精确),且属性含有援用类型,用深拷贝。

浅拷贝的实现

Object.assign()

如下面的例子,Object.assign()是浅拷贝

concat

es5 的浅拷贝办法

扩大运算符

es6 的浅拷贝办法

slice

JS 数组的办法

let arr1 = [1, 2, { username: 'Kobe'}];
let arr2 = arr.slice();
arr2[2].username = 'Wade'

console.log(arr1[2]); // {username: 'Kobe'}
console.log(arr2[2]); // {username: 'Wade'}

lodash 的 clone()

let _ = require('lodash');
let obj1 = {
    a: 1,
    b: 2,
};

var obj2 = _.clone(obj1);

手写实现

function shallowClone(source) {var target = {};
    for(var i in source) {if (source.hasOwnProperty(i)) {target[i] = source[i];
        }
    }
    return target;
}

let obj1 = {...}
let obj2 = shallowClone(obj1)

深拷贝的实现

JSON.parse(JSON.stringfy())

let obj1 = {a: 1, b: 2}
let obj2 = JSON.parse(JSON.stringify(obj1))

留神:慎用,因为如果对象中有办法,这样深拷贝会失落办法。如:


let obj1 = {a: 1, b: 2, c: function() {return '看办法在不在'}}
let obj2 = JSON.parse(JSON.stringify(obj1))

console.log(obj1); // {a: 1, b: 2, c: ƒ}
console.log(obj2); // {a: 1, b: 2}

lodash 的 deepClone()

let _ = require('lodash');
let obj1 = {
    a: 1,
    b: 2,
    c: {q: 6}
};
let obj2 = _.cloneDeep(obj1);

jquery.extend()

// 用法:$.extend(deepCopy, target, object1, [objectN]) // 第一个参数为 true, 就是深拷贝

let $ = require('jquery');
let obj1 = {
    a: 1,
    b: 2,
    c: {q: 6}
};
let obj2 = $.extend(true, {}, obj1);

手写实现

深拷贝的原理是递归遍历对象,直到只有根本类型时再拷贝。

点击浏览手写深拷贝代码

退出移动版