javascript中数组、堆、栈浅析
数据结构---数组
数组是一个存储元素的线性集合,元素可以通过索引来任意存取,索引通常是数字,用来计算元素之间存储位置的偏移量。
javascript中的数组是一种特殊的对象,用来表示偏移量的索引是该对象的属性,索引可能是整数,然而,这些整数索引在javascript内部被转换成字符串类型了,这是因为javascript对象中的属性必须是字符串类型,javascript中的数组只是一种特殊的对象,所以在效率上不如其他语言中的数组高。
常用的数组创建方式有:
- []操作符
- Array构造函数
大多数的javascript砖家都一致推荐使用[]操作符,和使用Array构造函数相比,这种方式更快
当把一个数组赋值给另一个数组时,只是为被赋值的数组增加了一个新的引用(相当于指针),当你通过源引用修改了数组的值,另一个数组也会感知到这个变化(因为它们操作的是同一块内存空间),这种行为称之为浅复制,解决新数组依然指向原来的数组,有一个更好的方案就是使用深复制,将源数组中的元素逐个复制到新数组中!解除引用关系,创建新的引用地址!
这些概念相信大家应该很熟悉,但是在这个基础上可玩的东西还有很多,比如:模拟指针移动等等
既然在javascript数组是一种特殊的对象,那么数组和对象的特性就一样喽!都是按址引用,那么接下来咱们稍微了解下堆和栈的概念之后玩玩这个模拟指针移动
javascript堆和栈
在开始之前先用一个小例子引下下面的话题:
var a = {n:1};
var b = a;
a.x = a = {n:2};
alert(a.x);
alert(b.x);
按照js中连等从右往左复制的原则栈中的a和b都指向堆中{n:1},但是在js中点操作符优先执行。
所以,在遇到a.x = a = {n:2}时a.x先执行此时在堆中第一块空间中创建了一个x变成{n:1,x:undefined},
这个时候才轮到a={n:2}执行,那么此时的a就被指向了堆中的第二块空间a={n:2}
然后再是a.x = {n:2}由于此时的a.x被指向了堆中的第一块空间,所以此时堆中第一块空间的值是{n:1,x:{n:2}}
堆和栈的概念
假如你有一个函数,当程序读取函数里的局部变量时,会对函数中所有的变量全部进行压栈,栈内存向低位增长,栈空间大小是8M(可用ulimit -a查看),假设一个变量用了8B,一个函数中定义了10个变量,那么函数最多可以递归 8M / (8B*10) = 80万次就会出现栈溢出。
堆内存从低向高位增长,堆可用空间比栈大很多,效率比栈低。
那么大家都知道闭包可以导致内存的溢出,为什么呢?因为闭包会一直占用你8M的栈空间,由于一直存在引用GC不能将它释放!
ok,都说到闭包了,其实闭包从官方的说法就是:一个能记住上一个词法作用域的函数!词法作用域和堆栈的关系后续详细说,篇幅太大了!
在内存中除了堆和栈之外还有哪些呢?
- 栈区(stack):编译器自动分配、释放,存放函数的参数和局部变量的值等等,其操作方式类似于数据结构中的栈
- 堆区(heap):一般是由程序员分配释放,若程序员不释放的话,程序结束时可能由系统OS回收,它与数据结构的堆是两回事,分配方式倒是类似数据结构中的链表
- 全局区(static):也叫静态数据内存空间,存储全局变量和静态变量,初始化的全局变量和静态变量是放在一块区域的,但是没有初始化的在相邻的另一块区域,程序结束后由系统释放
- 文字常量区:常量字符串就放在这里,程序结束后由系统释放
- 程序代码区:存放函数体的二进制代码
一个有趣的玩法
var s = [];
var arr = s;
for(var i = 0; i < 3; i++){
var pusher = {
value:'item'+i
},
tmp;
if(i != 2){
tmp = [];
pusher.children = tmp
}
arr.push(pusher);
arr = tmp
}
console.log(s)