内存堆栈
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
}