文章目录
内存
内存分为四个区域:栈区(堆栈),堆区,全局静态区,只读区(常量区和代码区)。
-
栈区
- 数据:局部变量,形参,被调用的函数的地址等
- 特点:
- 读取速度快。存储和释放的思路是按照栈来进行的,存储是压栈,释放弹栈。
- 空间小:基本类型的数据占用空间的大小不会随着值的改变而改变。
-
堆区
- 数据:new出来的数据
- 特点:
- 读取速度慢
- 空间大:引用类型的数据大小是动态的,会随着数据的增加而改变大小。
-
全局静态区
- 数据:全局变量和静态变量
- 特点:程序运行过程中,数据会一直在内存中。
-
只读区
- 数据:常量区存放常量,代码区存放程序代码。
- 特点:在程序运行过程中数据不能改变。
基本类型和引用类型在内存上存储的区别
function test(){
var age = 250; // 基本类型
var arr = new Array(12, 23, 34); // 引用类型
}
test()
在调用时:
- 定义局部变量
age
。由于age
是局部变量,所以在栈中申请内存空间,起名为age
,又由于给age
赋的值 250 250 250是基本类型,所以,值直接存储在栈中。 - 定义局部变量
arr
。由于arr
是局部变量,所以在栈中申请空间,但是arr
的内存中存储的是引用类型(new出来的),所以,先在堆中申请空间存放数据 12 , 23 , 34 , 12,23,34, 12,23,34,。再把堆区的地址赋给arr
。
数据类型
基本类型和引用类型
- 基本类型:值类型。直接存储在栈(stack)中的数据,即在变量所对于的内存区域存储的是值,而不是地址。
- 引用类型:地址类型,存储的是该对象在栈中引用,真实的数据存放在堆内存里。
引用数据类型在栈中存储了指针,该指针指向堆中该实体的起始地址。当解释器寻找引用值时,会首先检索其在栈中的地址,取得地址后从堆中获得实体。
赋值时的内存变化
-
基本类型:
-
引用类型:
如果给
arr[0]
赋值的话,arr1[0]
的值也会发生变化,因为,arr
和arr1
保存着相同的地址,它门两个引用的数据是共享的。
深拷贝和浅拷贝
深拷贝浅拷贝只针对Object
和Array
这种引用类型。
- 浅拷贝只复制指向某个对象的指针,而不复制对象本身,新旧对象共享一块内存。
- 深拷贝会创建一个一模一样的对象,新对象和原对象不共享内存,修改新对象不会影响到原对象。
赋值与浅拷贝区别
把一个对象赋值给一个新的变量时,赋的其实是该对象在栈中的地址,而不是堆中的数据。两个对象指向同一个存储空间,无论哪个对象发生改变,其实都是改变存储空间中的内容。两个对象是联动的。
浅拷贝是按位拷贝,会创建一个新的对象,这个对象有着原始对象属性值的一份精准拷贝。如果属性是基本类型,拷贝的就是基本类型的值;如果是引用类型,则拷贝的是内存地址。如果其中一个对象改变了这个地址,就会影响到另一个对象。
默认拷贝构造函数这是对对象进行浅拷贝,只复制对象空间,不复制资源。
示例
// 赋值
var obj1 = {
name: 'a',
age: '18',
type: [1, [2, 3], [4, 5]],
};
var obj2 = obj1;
obj2.name = 'b';
obj2.type = ['一', '二'];
console.log('obj1:', obj1);
console.log('obj2:', obj2);
// 浅拷贝
var obj1 = {
name: 'a',
age: '18',
type: [1, [2, 3], [4, 5]],
};
var obj2 = shallowCopy(obj1);
obj2.name = 'b';
obj2.type[1] = ['一', '二'];
function shallowCopy(obj) {
var newObj = {};
for (var prop in obj) {
if (obj.hasOwnProperty(prop)) {
newObj[prop] = obj[prop];
}
}
return newObj;
}
console.log('obj1:', obj1);
console.log('obj2:', obj2);
是否和原数据指向同一对象 | 第一层数据为基本数据类型 | 原数据中包含子对象 | |
---|---|---|---|
赋值 | 是 | 改变——原数据改变 | 改变——原数据改变 |
浅拷贝 | 否 | 改变——原数据不变 | 改变——原数据改变 |
深拷贝 | 否 | 改变——原数据不变 | 改变——原数据不变 |
浅拷贝实现方式
1. Object.assign()
Object.assign()
方法可以把任意多个的源对象自身的可枚举属性拷贝给目标对象,然后返回目标对象。但是 Object.assign()
进行的是浅拷贝,拷贝的是对象的属性的引用,而不是对象本身。注意:当object
只有一层的时候,是深拷贝
var obj1 = { a: { a: 'aaa', b: 10 } };
var obj2 = Object.assign({}, obj1);
obj2.a.a = 'bbb';
console.log(obj1.a.a);
var obj3 = { username: 'zsy' };
var obj4 = Object.assign({}, obj3);
obj4.username = 'ZhShy';
console.log(obj3);
2. Array.prototype.concat()
let arr = [
1,
3,
{
username: 'zsy',
},
];
let arr2 = arr.concat();
arr2[2].username = 'ZhShy';
console.log(arr);
3. Array.prototype.slice()
let arr = [
1,
3,
{
username: 'zsy',
},
];
let arr2 = arr.slice();
arr2[2].username = 'ZhShy';
console.log(arr);
深拷贝的实现方法
1. JSON.parse(JSON.stringify())
用JSON.stringify
将对象转成 JSON 字符串,再用JSON.parse()
把字符串解析成对象,一去一来,新的对象产生了,而且对象会开辟新的栈,实现深拷贝。
let arr = [
1,
3,
{
username: 'zsy',
},
];
let arr2 = JSON.parse(JSON.stringify(arr));
arr2[2].username = 'ZhShy';
console.log(arr, arr2);
这种方法虽然可以实现数组或对象深拷贝,但不能处理函数
2. 手写递归方法
递归方法实现深度克隆原理:遍历对象、数组直到里边都是基本数据类型,然后再去复制,就是深度拷贝
function checkedType(target) {
return Object.prototype.toString.call(target).slice(8, -1);
}
function clone(target) {
let result,
targetType = checkedType(target);
if (targetType === 'object') {
result = {};
} else if (targetType === 'Array') {
result = [];
} else {
return target;
}
for (let i in target) {
let value = target[i];
if (checkedType(value) === 'object' || checkedType(value) === 'Array') {
result[i] = clone(value);
} else {
result[i] = value;
}
}
return result;
}
3. lodash
var _ = require('lodash');
var obj1 = {
a: 1,
b: { f: { g: 1 } },
c: [1, 2, 3],
};
var obj2 = _.cloneDeep(obj1);
console.log(obj1.b.f === obj2.b.f); // false