JavaScript:引用传递/按值传递 与 深拷贝/浅拷贝

207 阅读5分钟

内存堆栈

JS中数据类型分为两大类,原始类型对象(也叫引用类型)。

其内存存储上的区别在于:
原始类型:只在栈内存中有对应的空间,直接存放值
对象:在栈内存和堆内存中都有对应的空间,栈内存堆内存的地址堆内存中存放值

栈内存被称作堆内存的引用,所以叫引用类型。其实一个变量名也是一个栈内存的引用。

堆栈内存的存储特点不同:
栈内存中数据不可变:

var a = 123;
a = 456;

存放123和456是两块不同的内存。给一个变量赋予新值的时候,不是在原来的内存空间里去修改,而是新开辟一块内存存储新值,旧的内存空间交给垃圾回收处理。

堆内存中数据可变

数据比较都是在比较栈内存

=== 和 == 的区别是:在于是否有类型转换,不是在于比较的是栈内存还是堆内存

原始类型的比较:

var a = 1;
var b = 1;
console.log(a == b);// true
console.log(a === b);// true

对象的比较:

var a = [1,2,3];
var b = [1,2,3];
console.log(a == b);// false!因为在栈内存中两个地址不同,即使是双等号返回的也是false
console.log(a === b);// false

赋值运算符(=)

赋值运算符操作的是栈内存

基本类型的赋值:

var a = 1;
var b = a;
b ++;
console.log(a);// 1
console.log(b);// 2

对象的赋值:

var a = {};
var b = a;
b.name = 'brynn'
console.log(a);// { name: 'brynn' }
console.log(b);// { name: 'brynn' }

var a = {};
var b = a;
b = {name: 'brynn'}
console.log(a);// {}
console.log(b);// { name: 'brynn' }

在前面说过,每一个对象类型都有一个地址存储着直接量(真正的值)存放的地方。
赋值操作符的特点是:先检查操作的主体是否有“地址”,如果有“地址”,操作的就是地址,如果没有地址,操作的就是直接量。

var a = 1; var b = a;在将a的值赋给b时,因为a不是对象,没有对应的地址,拷贝的是真正的值1,后面b改变时,a不会跟着改变。

var a = {}; var b = a;在将a的值赋给b时,因为a是对象,有对应的地址,拷贝的是地址。此时a和b指向同一块内存空间。

b.name = 'brynn'此时操作的‘brynn’不是对象,没有对应的地址,操作的是真正的值‘brynn’。

b = {name: 'brynn'}此时操作的是对象,有对应的地址,此时b的指向发生变化了。

深拷贝和浅拷贝

首先需要注意的是:

  • 深拷贝/浅拷贝的概念只存在于对象。
  • 下面这种操作,既不是深拷贝,也不是浅拷贝。

非深/浅拷贝:

var a = {
    name: 'brynn',
    address: {
        provi: 'HN',
        city: 'CS'
    }
};
var b = a;

浅拷贝:

var a = {
    name: 'brynn',
    address: {
        provi: 'HN',
        city: 'CS'
    }
};
var b = {};
b.name = a.name;
b.address = a.address;

此时,a和b在栈中存储的是两个不同的堆内存空间,改变b.name,a.name不会随之改变。
但尝试改变浅拷贝对象的属性:

var a = {
    name: 'brynn',
    address: {
        provi: 'HN',
        city: 'CS'
    }
};
var b = {};
b.name = a.name;
b.address = a.address;
console.log(a);
console.log(b);

b.name = 'boolean'//改变浅拷贝对象属性中的 原始数据类型
console.log(a);
console.log(b);
b.address.city = 'ChangSha'//改变浅拷贝对象中的 对象中的原始数据类型
console.log(a);
console.log(b);
b.address = {//改变浅拷贝对象中的 对象
    provi: 'SX',
    city: 'XA'
}
console.log(a);
console.log(b);

发现

  • 改变浅拷贝对象属性中的 原始数据类型 时:原来的对象没有联动
  • 改变浅拷贝对象中的 对象中的原始数据类型 时:原来的对象发生联动
  • 改变浅拷贝对象中的 对象 时:原来的对象没有联动

原因可以用这张图解释 赋值操作符的特点是:先检查操作的主体是否有“地址”,如果有“地址”,操作的就是地址,如果没有地址,操作的就是直接量。

深拷贝

var a = {
    name: 'brynn',
    address: {
        provi: 'HN',
        city: 'CS'
    }
};
var b = {};
b.name = a.name;
b.address = {};
b.address.provi = a.address.provi;
b.address.city = a.address.city;

尝试改变深拷贝对象中的属性:

var a = {
    name: 'brynn',
    address: {
        provi: 'HN',
        city: 'CS'
    }
};
var b = {};
b.name = a.name;
b.address = {};
b.address.provi = a.address.provi;
b.address.city = a.address.city;
console.log(a);
console.log(b);

b.name = 'boolean'//改变浅拷贝对象属性中的 原始数据类型
console.log(a);
console.log(b);
b.address.city = 'ChangSha'//改变浅拷贝对象中的 对象中的原始数据类型
console.log(a);
console.log(b);
b.address = {//改变浅拷贝对象中的 对象
    provi: 'SX',
    city: 'XA'
}
console.log(a);
console.log(b);

发现

  • 改变深拷贝对象属性中的 原始数据类型 时:原来的对象没有联动
  • 改变深拷贝对象中的 对象中的原始数据类型 时:原来的对象没有联动
  • 改变深拷贝对象中的 对象 时:原来的对象没有联动

深拷贝时递归复制到每一层,自然无论如何改变都不会联动

实现

简单的深拷贝和浅拷贝可以通过以下方式来实现

浅拷贝

  • Object.assign({},obj)
  • {...obj}
const a = {
  name: 'brynn',
  address: {
    province: 'hunan',
    city: 'changsha',
  },
}
const b = Object.assign({}, a)
// const b = {
//   ...a,
// }
b.name = 'boolean' // a不会随之变化
b.address.province = 'shanxi' // a会随之变化
console.log(a)
console.log(b)

深拷贝

  • JSON.parse(JSON.Stringify(Obj))
const a = {
  name: 'brynn',
  address: {
    province: 'hunan',
    city: 'changsha',
  },
}
const b = JSON.parse(JSON.stringify(a))
b.name = 'boolean'
b.address.province = 'shanxi'
console.log(a)
console.log(b)

但是这种方法无法处理函数和正则

const a = {
  name: 'brynn',
  address: {
    province: 'hunan',
    city: 'changsha',
  },
  func: () => {                // 拷贝完后不存在
    console.log('i am function')
  },
  function() {                 // 拷贝完后不存在
    console.log('i am function2')
  },
  reg: /brynn/,                // 拷贝完后是空对象
}
const b = JSON.parse(JSON.stringify(a))
b.name = 'boolean'
b.address.province = 'shanxi'
console.log(a)
console.log(b)

场景

在Redux中,要求reducer是一个纯函数,即不能修改传入的参数,这时候就需要用深拷贝将参数赋值一份再去做修改。

import { GET_LIST } from './actionTypes'
const defaultState = {
  list: [],
}
export default function Reducer(state = defaultState, action) {
  if (action.type === GET_LIST) {
    //根据type值,编写业务逻辑
    let newState = JSON.parse(JSON.stringify(state)) // 深拷贝
    newState.list = action.data.result //复制性的List数组进去
    return newState
  }
  return state
}