女神镇楼javascript
想写好前端,先练好内功。前端
栈内存与堆内存 、浅拷贝与深拷贝,能够说是前端程序员的内功,要知其然,知其因此然。java
笔者写的 JavaScript 数据结构与算法之美 系列用的语言是 JavaScript ,旨在入门数据结构与算法和方便之后复习。git
定义程序员
栈
结构。栈顶
,另外一端就叫栈底
。操做受限
的线性表,只容许在一端插入和删除数据。空栈
。栈也被用在编程语言的编译器和内存中保存变量、方法调用等,好比函数的调用栈。es6
定义github
堆与栈比较算法
JavaScript 中的变量分为基本类型和引用类型。编程
基本类型是保存在栈内存中的简单数据段,它们的值都有固定的大小,保存在栈空间,经过按值访问,并由系统自动分配和自动释放。 这样带来的好处就是,内存能够及时获得回收,相对于堆来讲,更加容易管理内存空间。 JavaScript 中的 Boolean、Null、Undefined、Number、String、Symbol
都是基本类型。segmentfault
引用类型(如对象、数组、函数等)是保存在堆内存中的对象,值大小不固定,栈内存中存放的该对象的访问地址指向堆内存中的对象,JavaScript 不容许直接访问堆内存中的位置,所以操做对象时,实际操做对象的引用。 JavaScript 中的 Object、Array、Function、RegExp、Date
是引用类型。
结合实例说明
let a1 = 0; // 栈内存
let a2 = "this is string" // 栈内存
let a3 = null; // 栈内存
let b = { x: 10 }; // 变量 b 存在于栈中,{ x: 10 } 做为对象存在于堆中
let c = [1, 2, 3]; // 变量 c 存在于栈中,[1, 2, 3] 做为对象存在于堆中
复制代码
当咱们要访问堆内存中的引用数据类型时
基本类型发生复制
let a = 20;
let b = a;
b = 30;
console.log(a); // 20
复制代码
在栈内存中的数据发生复制行为时,系统会自动为新的变量分配一个新值,最后这些变量都是 相互独立,互不影响的。
引用类型发生复制
let a = { x: 10, y: 20 }
let b = a;
b.x = 5;
console.log(a.x); // 5
复制代码
结合下图理解
总结
栈内存 | 堆内存 |
---|---|
存储基础数据类型 | 存储引用数据类型 |
按值访问 | 按引用访问 |
存储的值大小固定 | 存储的值大小不定,可动态调整 |
由系统自动分配内存空间 | 由代码进行指定分配 |
空间小,运行效率高 | 空间大,运行效率相对较低 |
先进后出,后进先出 | 无序存储,可根据引用直接获取 |
上面讲的引用类型的复制
就是浅拷贝,复制获得的访问地址都指向同一个内存空间
。因此修改了其中一个的值,另一个也跟着改变了。
深拷贝:复制获得的访问地址指向不一样的内存空间,互不相干
。因此修改其中一个值,另一个不会改变。
平时使用数组复制时,咱们大多数会使用 =
,这只是浅拷贝,存在不少问题。好比:
let arr = [1,2,3,4,5];
let arr2 = arr;
console.log(arr) //[1, 2, 3, 4, 5]
console.log(arr2) //[1, 2, 3, 4, 5]
arr[0] = 6;
console.log(arr) //[6, 2, 3, 4, 5]
console.log(arr2) //[6, 2, 3, 4, 5]
arr2[4] = 7;
console.log(arr) //[6, 2, 3, 4, 7]
console.log(arr2) //[6, 2, 3, 4, 7]
复制代码
很明显,浅拷贝下,拷贝和被拷贝的数组会相互受到影响。
因此,必需要有一种不受影响的方法,那就是深拷贝。
深拷贝的的复制过程
let a = { x: 10, y: 20 }
let b = JSON.parse(JSON.stringify(a));
b.x = 5;
console.log(a.x); // 10
console.log(b.x); // 5
复制代码
1、for 循环
//for 循环 copy
function copy(arr) {
let cArr = []
for(let i = 0; i < arr.length; i++){
cArr.push(arr[i])
}
return cArr;
}
let arr3 = [1,2,3,4];
let arr4 = copy(arr3) //[1,2,3,4]
console.log(arr4) //[1,2,3,4]
arr3[0] = 5;
console.log(arr3) //[5,2,3,4]
console.log(arr4) //[1,2,3,4]
复制代码
2、slice 方法
//slice实现深拷贝
let arr5 = [1,2,3,4];
let arr6 = arr5.slice(0);
arr5[0] = 5;
console.log(arr5); //[5,2,3,4]
console.log(arr6); //[1,2,3,4]
复制代码
3、concat 方法
//concat实现深拷贝
let arr7 = [1,2,3,4];
let arr8 = arr7.concat();
arr7[0] = 5;
console.log(arr7); //[5,2,3,4]
console.log(arr8); //[1,2,3,4]
复制代码
4、es6 扩展运算
//es6 扩展运算实现深拷贝
let arr9 = [1,2,3,4];
let [...arr10] = arr9;
arr9[0] = 5;
console.log(arr9) //[5,2,3,4]
console.log(arr10) //[1,2,3,4]
复制代码
5、JSON.parse 与 JSON.stringify
let arr9 = [1,2,3,4];
let arr10 = JSON.parse(JSON.stringify(arr9))
arr9[0] = 5;
console.log(arr9) //[5,2,3,4]
console.log(arr10) //[1,2,3,4]
复制代码
注意:该方法在数据量比较大时,会有性能问题。
1、对象的循环
// 循环 copy 对象
let obj = {
id:'0',
name:'king',
sex:'man'
}
let obj2 = copy2(obj)
function copy2(obj) {
let cObj = {};
for(var key in obj){
cObj[key] = obj[key]
}
return cObj
}
obj2.name = "king2"
console.log(obj) // {id: "0", name: "king", sex: "man"}
console.log(obj2) // {id: "0", name: "king2", sex: "man"}
复制代码
2、JSON.parse 与 JSON.stringify
var obj1 = {
x: 1,
y: {
m: 1
},
a:undefined,
b:function(a,b){
return a+b
},
c:Symbol("foo")
};
var obj2 = JSON.parse(JSON.stringify(obj1));
console.log(obj1) //{x: 1, y: {m: 1}, a: undefined, b: ƒ, c: Symbol(foo)}
console.log(obj2) //{x: 1, y: {m: 1}}
obj2.y.m = 2; //修改obj2.y.m
console.log(obj1) //{x: 1, y: {m: 1}, a: undefined, b: ƒ, c: Symbol(foo)}
console.log(obj2) //{x: 2, y: {m: 2}}
复制代码
可实现多维对象的深拷贝。
注意:进行JSON.stringify() 序列化的过程当中,undefined、任意的函数以及 symbol 值,在序列化过程当中会被忽略(出如今非数组对象的属性值中时)或者被转换成 null(出如今数组中时)。
3、es6 扩展运算
let obj = {
id:'0',
name:'king',
sex:'man'
}
let {...obj4} = obj
obj4.name = "king4"
console.log(obj) //{id: "0", name: "king", sex: "man"}
console.log(obj4) //{id: "0", name: "king4", sex: "man"}
复制代码
4、Object.assign()
Object.assign() 只能实现一维对象的深拷贝。
var obj1 = {x: 1, y: 2}, obj2 = Object.assign({}, obj1);
console.log(obj1) // {x: 1, y: 2}
console.log(obj2) // {x: 1, y: 2}
obj2.x = 2; // 修改 obj2.x
console.log(obj1) // {x: 1, y: 2}
console.log(obj2) // {x: 2, y: 2}
var obj1 = {
x: 1,
y: {
m: 1
}
};
var obj2 = Object.assign({}, obj1);
console.log(obj1) // {x: 1, y: {m: 1}}
console.log(obj2) // {x: 1, y: {m: 1}}
obj2.y.m = 2; // 修改 obj2.y.m
console.log(obj1) // {x: 1, y: {m: 2}}
console.log(obj2) // {x: 2, y: {m: 2}}
复制代码
简单版
let clone = function (v) {
let o = v.constructor === Array ? [] : {};
for(var i in v){
o[i] = typeof v[i] === "object" ? clone(v[i]) : v[i];
}
return o;
}
// 测试
let obj = {
id:'0',
name:'king',
sex:'man'
}
let obj2 = clone(obj)
obj2.name = "king2"
console.log(obj) // {id: "0", name: "king", sex: "man"}
console.log(obj2) // {id: "0", name: "king2", sex: "man"}
let arr3 = [1,2,3,4];
let arr4 = clone(arr3) // [1,2,3,4]
arr3[0] = 5;
console.log(arr3) // [5,2,3,4]
console.log(arr4) // [1,2,3,4]
复制代码
但上面的深拷贝方法遇到循环引用,会陷入一个循环的递归过程,从而致使爆栈,因此要避免。
let obj1 = {
x: 1,
y: 2
};
obj1.z = obj1;
let obj2 = clone(obj1);
console.log(obj2)
复制代码
结果以下:
总结:深入理解 javascript 的深浅拷贝,能够灵活的运用数组与对象,而且能够避免不少 bug。
JavaScript 数据结构与算法之美 的系列文章,坚持 3 - 7 天左右更新一篇,暂定计划以下表。
标题 | 连接 |
---|---|
时间和空间复杂度 | github.com/biaochenxuy… |
线性表(数组、链表、栈、队列) | github.com/biaochenxuy… |
实现一个前端路由,如何实现浏览器的前进与后退 ? | github.com/biaochenxuy… |
栈内存与堆内存 、浅拷贝与深拷贝 | github.com/biaochenxuy… |
非线性表(树、堆) | 精彩待续 |
递归 | 精彩待续 |
冒泡排序 | 精彩待续 |
插入排序 | 精彩待续 |
选择排序 | 精彩待续 |
归并排序 | 精彩待续 |
快速排序 | 精彩待续 |
计数排序 | 精彩待续 |
基数排序 | 精彩待续 |
桶排序 | 精彩待续 |
希尔排序 | 精彩待续 |
堆排序 | 精彩待续 |
十大经典排序汇总 | 精彩待续 |
若是有错误或者不严谨的地方,请务必给予指正,十分感谢。
欢迎 star github,对做者也是一种鼓励。
关注个人公众号,第一时间接收最新的精彩博文。
文章能够转载,但须注明做者及出处,须要转载到公众号的,喊我加下白名单就好了。
参考文章: