引用
学习就好比是座大山,人们沿着不同的路登山,分享着自己看到的风景。你不一定能看到别人看到的风景,体会到别人的心情。只有自己去登山,才能看到不一样的风景,体会才更加深刻。
最近在一个老项目上进行修改,遇见了自己的心里障碍,看别人的代码真的很难受,并且在不懂业务逻辑的情况下,所以在空闲时刻开始了学习,记录。
栈(stack)和堆(heap)
stack为自动分配的内存空间,它由系统自动释放;而heap则是动态分配的内存,大小也不一定会自动释放
数据存储以及类型
JS分两种数据类型:
-
基本数据类型:Number、String、Boolean、Null、Undefined、Symbol(ES6),这些类型可以直接操作保存在变量中的实际值。
-
引用数据类型:Object(在JS中除了基本数据类型以外的都是对象,引用类型是存放在堆内存中的对象,变量其实是保存的在栈内存中的一个指针(保存的是堆内存中的引用地址),这个指针指向堆内存。)
基本数据类型是指存放在栈中的简单数据段,数据大小确定,内存空间大小可以分配,它们是直接按值存放的,所以可以直接按值访问
var a = 10;
var b = a;
b = 20;
console.log(a); // 10值
console.log(b); // 20值
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堆内存中。
浅拷贝的实现方式
let demo = {
name : 'dayday',
book : {
title : 'Do you really Know JS',
price : "45"
}
}
let clone_demo = Object.assign({}, demo)
console.log(clone_demo);
demo.name = 'new name'
demo.book.price = '100'
console.log(clone_demo.name,clone_demo.book.price);
// dayday 100
修改上面代码demo变量之后,对象clone_demo基本属性没有改变,但是修改demo对象中book引用属性时,对象clone_demo相应位置属性值也发生改变,同样的接下来展开运算符也是一样效果
let demo = {
name : 'dayday',
book : {
title : 'Do you really Know JS',
price : "45"
}
}
let clone_demo = {...demo}
console.log(clone_demo);
demo.name = 'new name'
demo.book.price = '100'
console.log(clone_demo.name,clone_demo.book.price);
// dayday 100
可以看到展开运算… 效果跟Object.assign() 效果是一样的。
let a = [0, "1", [2, 3]];
let b = a.slice(1);
console.log(b);
// ["1", [2, 3]]
a[1] = "99";
a[2][0] = 4;
console.log(a);
// [0, "99", [4, 3]]
console.log(b);
// ["1", [4, 3]]
slice() 方法返回一个新的数组对象,这一对象是一个由 begin和 end(不包括end)决定的原数组的「浅拷贝」。原始数组不会被改变。可以看出,改变 a[1] 之后 b[0] 的值并没有发生变化,但改变 a[2][0] 之后,相应的 b[1][0] 的值也发生变化。说明 slice() 方法是浅拷贝,相应的还有concat等,在工作中面对复杂数组结构要额外注意。
深拷贝实现方式
深拷贝会拷贝所有的属性,并拷贝属性指向的动态分配的内存。当对象和它所引用的对象一起拷贝时即发生深拷贝。深拷贝相比于浅拷贝速度较慢并且花销较大。拷贝前后两个对象互不影响。
let demo = {
name : 'dayday',
book : {
title : 'Do you really Know JS',
price : "45"
}
}
let clone_demo = JSON.parse(JSON.stringify(demo))
console.log(clone_demo);
demo.name = 'new name'
demo.book.price = '100'
console.log(clone_demo.name,clone_demo.book.price);
// dayday 45
完全改变变量 demo 之后对 clone_demo 没有任何影响,这就是深拷贝的魔力。
同样的对于数组使用该方法也是可以达到深拷贝的。
注意的就是:
- 会忽略undefined Symbol
- 不能序列化函数
- 不能解决循环引用的对象
- 不能正确处理 new Date()
- 不能处理正则
对于undefined symbol 函数三种情况会直接忽略
let demo = {
name : 'dayday',
h1 : undefined,
h2 : Symbol('dayday'),
h3 : function () {},
}
let clone_demo = JSON.parse(JSON.stringify(demo))
console.dir(clone_demo)
// { name : 'dayday' }
循环引用情况下,会报错
let obj = {
a: 1,
b: {
c: 2,
d: 3
}
}
obj.a = obj.b;
obj.b.c = obj.a;
let b = JSON.parse(JSON.stringify(obj));
// Uncaught TypeError: Converting circular structure to JSON
new Date 情况下,转换结果不正确。
new Date();
// Wed Jul 01 2020 16:19:07 GMT+0800 (中国标准时间) {}
JSON.stringify(new Date());
// ""2020-07-01T08:19:19.860Z""
JSON.parse(JSON.stringify(new Date()));
// "2020-07-01T08:19:35.569Z"
正则情况下
let demo = {
name: "daydaylee",
a: /'123'/
}
console.log(demo);
// {name: "daydaylee", a: /'123'/}
let clone_demo = JSON.parse(JSON.stringify(obj));
console.log(clone_demo);
// {name: "daydaylee", a: {}}
除了上面介绍的深拷贝方法,常用的还有jQuery.extend() 和 lodash.cloneDeep(),由于文章篇幅的问题,这里就不多介绍了,有兴趣的可以自己去了解了解
其实写到这里还没很完善,后续在慢慢补充