一些问题

  1. // 赋值1
  2. let obj1 = {
  3. a: 1,
  4. b: 2
  5. }
  6. let obj2 = obj1
  7. obj2.a = 3
  8. console.log(obj1) // ?
  9. console.log(obj2) // ?
  1. // 赋值2
  2. let obj1 = {
  3. a: 1,
  4. b: 2,
  5. c: {
  6. q: 6
  7. }
  8. }
  9. let obj2 = obj1
  10. obj2.a = 3
  11. obj2.c.q = 7
  12. console.log(obj1) // ?
  13. console.log(obj2) // ?
  1. // 浅拷贝1
  2. let obj1 = {
  3. a: 1,
  4. b: 2
  5. }
  6. let obj2 = Object.assign({}, obj1)
  7. obj2.a = 3
  8. console.log(obj1) // ?
  9. console.log(obj2) // ?
  1. // 浅拷贝2
  2. let obj1 = {
  3. a: 1,
  4. b: 2,
  5. c: {
  6. q: 6
  7. }
  8. }
  9. let obj2 = Object.assign({}, obj1)
  10. obj2.a = 3
  11. obj2.c.q = 7
  12. console.log(obj1) // ?
  13. console.log(obj2) // ?
  1. // 深拷贝
  2. let _ = require('lodash');
  3. let obj1 = {
  4. a: 1,
  5. b: 2,
  6. c: {
  7. q: 6
  8. }
  9. };
  10. let obj2 = _.cloneDeep(obj1);
  11. obj2.a = 3
  12. obj2.c.q = 7
  13. console.log(obj1) // ?
  14. console.log(obj2) // ?

JS数据类型及相关概念

数据类型

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

数据在内存中的存储

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

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

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

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

问题解答

  1. // 赋值1
  2. let obj1 = {
  3. a: 1,
  4. b: 2
  5. }
  6. let obj2 = obj1
  7. obj2.a = 3
  8. console.log(obj1) // {a: 3, b: 2}
  9. console.log(obj2) // {a: 3, b: 2}
  1. // 赋值2
  2. let obj1 = {
  3. a: 1,
  4. b: 2,
  5. c: {
  6. q: 6
  7. }
  8. }
  9. let obj2 = obj1
  10. obj2.a = 3
  11. obj2.c.q = 7
  12. console.log(obj1) // {a: 3, b: 2, c: {q: 7}}
  13. console.log(obj2) // {a: 3, b: 2, c: {q: 7}}
  1. // 浅拷贝1
  2. let obj1 = {
  3. a: 1,
  4. b: 2
  5. }
  6. let obj2 = Object.assign({}, obj1)
  7. obj2.a = 3
  8. console.log(obj1) // {a: 1, b: 2}
  9. console.log(obj2) // {a: 3, b: 2}
  1. // 浅拷贝2
  2. let obj1 = {
  3. a: 1,
  4. b: 2,
  5. c: {
  6. q: 6
  7. }
  8. }
  9. let obj2 = Object.assign({}, obj1)
  10. obj2.a = 3
  11. obj2.c.q = 7
  12. console.log(obj1) // {a: 1, b: 2, c: {q: 7}}
  13. console.log(obj2) // {a: 3, b: 2, c: {q: 7}}
  1. // 深拷贝
  2. let _ = require('lodash');
  3. let obj1 = {
  4. a: 1,
  5. b: 2,
  6. c: {
  7. q: 6
  8. }
  9. };
  10. let obj2 = _.cloneDeep(obj1);
  11. obj2.a = 3
  12. obj2.c.q = 7
  13. console.log(obj1) // {a: 1, b: 2, c: {q: 6}}
  14. console.log(obj2) // {a: 3, b: 2, c: {q: 7}}

使用场景

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

原对象需多次使用(被修改会导致后续使用数据不准确),且属性只有基本类型,无引用类型,用浅拷贝。

原数据需多次使用(被修改会导致后续使用数据不准确),且属性含有引用类型,用深拷贝。

浅拷贝的实现

Object.assign()

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

concat

es5的浅拷贝方法

扩展运算符

es6的浅拷贝方法

slice

JS数组的方法

  1. let arr1 = [1, 2, { username: 'Kobe' }];
  2. let arr2 = arr.slice();
  3. arr2[2].username = 'Wade'
  4. console.log(arr1[2]); // { username: 'Kobe' }
  5. console.log(arr2[2]); // { username: 'Wade' }

lodash的clone()

  1. let _ = require('lodash');
  2. let obj1 = {
  3. a: 1,
  4. b: 2,
  5. };
  6. let obj2 = _.clone(obj1);

手写实现

  1. function shallowClone(source) {
  2. var target = {};
  3. for(var i in source) {
  4. if (source.hasOwnProperty(i)) {
  5. target[i] = source[i];
  6. }
  7. }
  8. return target;
  9. }
  10. let obj1 = {...}
  11. let obj2 = shallowClone(obj1)

深拷贝的实现

JSON.parse(JSON.stringfy())

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

注意:慎用,因为如果对象中有方法,这样深拷贝会丢失方法。如:

  1. let obj1 = {a: 1, b: 2, c: function() {return '看方法在不在'}}
  2. let obj2 = JSON.parse(JSON.stringify(obj1))
  3. console.log(obj1); // {a: 1, b: 2, c: ƒ}
  4. console.log(obj2); // {a: 1, b: 2}

lodash的deepClone()

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

jquery.extend()

  1. // 用法:$.extend(deepCopy, target, object1, [objectN]) // 第一个参数为true,就是深拷贝
  2. let $ = require('jquery');
  3. let obj1 = {
  4. a: 1,
  5. b: 2,
  6. c: {q: 6}
  7. };
  8. let obj2 = $.extend(true, {}, obj1);

手写实现

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

点击阅读手写深拷贝代码