栈(stack)和堆(heap)
stack为自动分配的内存空间,它由系统自动释放;(基本数据类型)
heap是动态分配内存,大小也不一定会自动释放。(引用数据类型 Object)
js的数据类型:
- 基本数据类型:String、Number、Boolean、Null、Undefine、Symbol
基本数据类型存放在栈中,数据大小确定,内存大小可以分配,按值存放,按值访问 - 引用数据类型:Object(除基本数据类型外都是对象,如:函数、数组、正则表达式等)
存放在堆内存中的对象,每个空间大小不一样,据情况而配置)
引用类型是存放在堆内存中的对象,变量其实是保存在栈内存中的一个指针(保存的是堆内存中的引用地址), 这个指针指向堆内存。 如:
var obj1 = new Object();
var obj2 = obj1;
obj2.name = "我有名字了";
console.log(obj1.name); // 我有名字了
说明这两个引用数据类型指向了同一个堆内存对象。obj1赋值给obj2,实际上这个堆内存对象在栈内存的引用 地址复制了一份给了obj2,但是实际上他们共同指向了同一个堆内存对象,所以修改obj2其实就是修改那个对 象,所以通过obj1访问也能访问的到。
var a = [1, 2, 3, 4, 5];
var b = a;//传址 ,对象中传给变量的数据是引用类型的,会存储在堆中;
var c = a[0];//传值,把对象中的属性/数组中的数组项赋值给变量,这时变量C是基本数据类型,存储在栈
内存中;改变栈中的数据不会影响堆中的数据
alert(b);//1,2,3,4,5
alert(c);//1
//改变数值
b[4] = 6;c = 7;
alert(a[4]);//6
alert(a[0]);//1
从上面我们可以得知,当我改变b中的数据时,a中数据也发生了变化;但是当我改变c的数据值时,a却没有发生改变。
这就是传值与传址的区别。因为a是数组,属于引用类型,所以它赋予给b的时候传的是栈中的地址(相当于新
建了一个不同名“指针”),而不是堆内存中的对象。而c仅仅是从a堆内存中获取的一个数据值,并保存在栈中。
所以b修改的时候,会根据地址回到a堆中修改,c则直接在栈中修改,并且不能指向a堆内存中。
深拷贝与浅拷贝:
创建一个新对象,如果属性是基本数据类型,拷贝的就是基本数据类型的值,如果是引用类型,拷贝的就是内存地址,如果其中一个对象改变了这个地址,就会影响到另一个对象。 如果修改了其中的数据,浅拷贝之后数据就会被修改
1. 浅拷贝:
1. Object.assign(): 将属性值从源对象复制到目标对象,拷贝的是(可枚举)属性值。
const target = { a: 1, b: 10 };
const source = { b: 4, c: 5 };
const returnedTarget = Object.assign(target, source);
console.log(target);
console.log(returnedTarget);
// Object { a: 1, b: 4, c: 5 }
// Object { a: 1, b: 4, c: 5 }
let a = {
name: 'sujingxin',
book: {
title: 'go',
price: 20
}
};
let b = Object.assign(a);
console.log(a)
console.log(b)
a.name = 'zhouxinbin';
a.book.price = 40;
console.log(a)
console.log(b)
// { name: 'sujingxin', book: { title: 'go', price: 20 } }
// { name: 'sujingxin', book: { title: 'go', price: 20 } }
// { name: 'zhouxinbin', book: { title: 'go', price: 40 } }
// { name: 'zhouxinbin', book: { title: 'go', price: 40 } }
拷贝之后基本数据类型与对象的值都改变
2. 扩展运算符
let a = {
name: 'sujingxin',
book: {
title: 'go',
price: 20
}
};
let b = {...a};
console.log(a)
console.log(b)
// 改变基本数据类型和对象的值之后:
a.name = 'zhouxinbin';
a.book.price = 40;
console.log(a)
console.log(b)
// { name: 'sujingxin', book: { title: 'go', price: 20 } }
// { name: 'sujingxin', book: { title: 'go', price: 20 } }
// { name: 'zhouxinbin', book: { title: 'go', price: 40 } }
// { name: 'sujingxin', book: { title: 'go', price: 40 } }
拷贝之后引用类型的值改变
3. .concat()
const a1 = [1, 2];
const a2 = a1.concat();
a2[0] = 2;
a1 // [1, 2]
4. slice
let a = [0, '1', [2, 3]];
let b = a.slice(1);
console.log(a)
console.log(b)
a[1] = '250';
a[2][0] = 44;
console.log(a)
console.log(b)
// [ 0, '1', [ 2, 3 ] ]
// [ '1', [ 2, 3 ] ]
// [ 0, '250', [ 44, 3 ] ]
// [ '1', [ 44, 3 ] ]
2. 深拷贝:
要完全复制又不能修改到原对象,拷贝最原始的数据JSON.parse(JSON.stringify(object))
let a = {
name: 'sujingxin',
book: {
title: 'go',
price: 20
}
};
let b = JSON.parse(JSON.stringify(a));
console.log(a)
console.log(b)
a.name = 'zhouxinbin';
a.book.price = 40;
console.log(a)
console.log(b)
// { name: 'sujingxin', book: { title: 'go', price: 20 } }
// { name: 'sujingxin', book: { title: 'go', price: 20 } }
// { name: 'zhouxinbin', book: { title: 'go', price: 40 } }
// { name: 'sujingxin', book: { title: 'go', price: 20 } }
拷贝了原始的数据
总结:浅拷贝在复制数据的时候,如果基本数据类型或者对象的值改变了之后复制的数据也会改变,如:assign,slice,扩展运算符
深拷贝复我作用数据类型制最底层的数据,即使原数据的基本数据类型或引用数据类型改变,也只复制最原始的数据,如:concat,JSON.parse(JSON.stringify(object))