前端基础查漏补缺(一):内存空间

首先说一下这一系列基本上都是整理引用自大大这波能反杀的文章《前端基础进阶系列,中途会加上一些自己的扩展延伸,以便加深自己对底层js的理解。若有侵权请指正。

一、内存空间

JavaScript中并没有严格意义上区分栈内存与堆内存。因此我们可以简单粗暴的理解为JavaScript的所有数据都保存在堆内存中。但是在某些场景,我们仍然需要基于堆栈数据结构的思维来实现一些功能,因此理解栈数据结构的原理与特点十分重要。

三种数据结构:堆(heap),栈(stack)、队列(queue)。

堆数据结构:key-value,按值查找。就像图书馆按书名查。(如变量对象的存储)
栈数据结构:先进后出,后进先出,就像乒乓球盒子。(如执行上下文执行顺序,也就是函数调用栈)

栈数据结构图示

队列:先进先出(FIFO)。就像排队过安检
队列图示

变量对象与基础数据类型

JavaScript的执行上下文生成之后,会创建一个叫做变量对象的特殊对象,JavaScript的基础数据类型往往都会保存在变量对象中。

变量对象也是存放于堆内存中,但是由于变量对象的特殊职能,我们在理解时仍然需要将其于堆内存区分开来。

基础数据类型:Undefined、Null、Boolean、Number、String、Symbol(ES6)。按值访问,这些类型可以直接操作保存在变量中的实际的。
基础类型使用声明式赋值,永远都是去常量池中查找Number、null 、undefined、String、Boolean,没有再次声明就放在常量池中,若声明的变量名已存在就使用同一个值,不重复分配地址。

//虽然字面就很好理解,但从堆内存存储的角度重新理解下
var a=1;
var a;
console.log(a);//1 同名即是同一个地址,直接能访问其中的值。
var b=2;
var b=3
console.log(b);//3 因为访问的是同一个变量地址,改变就能直接改变这个值
引用数据类型与堆内存

Object(在JS中除了基本数据类型以外的都是对象,数据是对象,函数是对象,正则表达式也是对象)

var a1 = 0;   // 变量对象
var a2 = 'this is string'; // 变量对象
var a3 = null; // 变量对象

var b = { m: 20 }; // 变量b存在于变量对象中,{m: 20} 作为对象存在于堆内存中,b为存储{m:20}对象的地址(指针)
var c = [1, 2, 3]; // 变量c存在于变量对象中,[1, 2, 3] 作为对象存在于堆内存中
上例图解
// demo01
var a = 20;
var b = a;
b = 30;
// 这时a的值是多少?
console.log(a);//20
console.log(b);//30
// demo02
var m = { a: 10, b: 20 }
var n = m;
n.a = 15;
// 这时m.a的值是多少
console.log(m.a);//15

demo1的a是常量变量,b是另一个声明的变量,在执行阶段把a复制了一份给b,b就直接获得了a的值。而这两个变量的值相互独立互不影响。
而在demo02中,m变量存的指向堆内存中{a: 10, b: 20}的地址,因此var n = m执行的是一次复制引用类型的操作。同样也是为新的变量自动分配一个新的值保存在n中,不同的是,这个新的值只是引用类型的一个地址指针。尽管他们相互独立,但是在变量对象中访问到的具体对象实际上是同一个。如图所示。


demo02图解

基础数据类型的比较是值的比较

var a = 1;
var b = 1;
console.log(a === b);//true

引用类型的比较是引用的比较(堆的空间)

 var a = [1,2,3];
var b = [1,2,3];
console.log(a === b); // false
共享传递,深拷贝和浅拷贝

当数据作为实参传递函数的形参时,也是分数据类型的。
按值传递就是把实参在内存栈中的数据传递给形参, 然后在方法内部就可以使用形参的值了, 而引用传递(共享传递)是把实参的内存栈的地址编号传递给形参。

//demo3
var value = 1;
function foo(v) {
    v = 2;
    console.log(v); //2
}
foo(value);
console.log(value) // 1

demo3 foo(value)执行时,v得到value的拷贝,在内存中为形参v分配了一个地址, 其中保存了1这个值, 这时给v赋值,是直接修改形参v的值,而value的值不会变。

//demo4
var obj = {
    value: 1
};
function foo(o) {
    console.log(o);//{value:1}
    o.value = 2;
    console.log(o.value); //2
}
foo(obj);
console.log(obj.value) // 2

demo4中形参o拷贝得到是obj的指针地址,指向堆内存中的同一个对象,因此改变o的属性时,obj的属性也会改变。

//demo5
var obj = {
    value: 1
};
function foo(o) {
    o = 2;
    console.log(o); //2
}
foo(obj);
console.log(obj) // {value: 1}

共享传递: 在传递对象的时候,拷贝了一份对象引用地址的副本,然后对这个引用进行操作。demo4也是共享传递(引用传递)
demo5直接改变了形参o的数据类型,让它不再指向obj所指向的地址,因此obj地址中的对象内容自然不会变。
浅拷贝
前面已经提到,在定义一个对象或数组时,变量存放的往往只是一个地址。当我们使用对象拷贝时,如果属性是对象或数组时,这时候我们传递的也只是一个地址。因此子对象在访问该属性时,会根据地址回溯到父对象指向的堆内存中,即父子对象发生了关联,两者的属性值会指向同一内存空间。

var a={key1:"11111"}
function Copy(p){
   var c ={};
   for (var i in p){
      c[i]=p[i]
   }    
   return c;
}
a.key2 = ["小辉","小辉"]
var b = Copy(a);
b.key3 = "33333"
alert(b.key1)//11111
alert(b.key3)//33333
alert(a.key3);//undefined
b.key2.push("大辉")
alert(a.key2);//小辉,小辉,大辉

本来b只是一个新的对象,return的c得到的形参p的每一个属性,和a指向的地址毫无关联,但是当修改的属性变为对象或数组时,那么父子对象之间就发生关联,原因是key1的值属于基本类型,所以拷贝的时候传递的就是该数据段;但是key2的值是堆内存中的对象,所以key2在拷贝的时候传递的是指向key2对象的地址,无论复制多少个key2,其值始终是指向父对象的key2对象的内存空间。

//ES6实现浅拷贝的方法
var a = {name:"暖风"} var b= Object.assign({},a);
b.age = 18;
console.log(a.age);//undefined
----------------------------------
//数组
var a = [1,2,3]; var b = a.slice();
b.push(4);
b//1,2,3,4
a//1,2,4
----------------------------------
var a = [1,2,3]; var b = a.concat();
b.push(4);
b//1,2,3,4
a//1,2,4
----------------------------------
var a = [1,2,3]; var b = [...a]
b//1,2,3,4
a//1,2,4</pre>

深拷贝
或许浅拷贝并不是我们在实际编码中想要的结果,我们不希望父子对象之间产生关联并且父对象的内容被改变,那么这时候可以用到深拷贝。既然属性值类型是数组和或象时只会传址,那么我们就用递归来解决这个问题,把父对象中所有属于对象的属性类型都遍历赋给子对象即可。测试代码如下:

var a={key1:"11111"}
function Copy(p,c){
   var c =c||{};
   for (var i in p){
    if(typeof p[i]==="object"){
      c[i]=(p[i].constructor ===Array)?[]:{}
      Copy(p[i],c[i]);
    }else{
       c[i]=p[i]
    }
   }    
   return c;
}
a.key2 = ["小辉","小辉"]
var b = {}
b = Copy(a,b); 
b.key2.push("大辉");
b.key2//小辉,小辉,大辉
a.key2//小辉,小辉   
内存空间管理

JavaScript的内存生命周期

  1. 分配你所需要的内存
  2. 使用分配到的内存(读、写)
  3. 不需要时将其释放、归还
var a = 20;  // 在内存中给数值变量分配空间
alert(a + 100);  // 使用内存
a = null; // 使用完毕之后,释放内存空间

JavaScript的自动垃圾收集机制就是找出那些不再继续使用的值,然后释放其占用的内存。垃圾收集器会每隔固定的时间段就执行一次释放操作。
在JavaScript中,最常用的是通过标记清除的算法来找到哪些对象是不再继续使用的,因此a = null其实仅仅只是做了一个释放引用的操作,让 a 原本对应的值失去引用,脱离执行环境,这个值会在下一次垃圾收集器执行操作时被找到并释放。而在适当的时候解除引用,是为页面获得更好性能的一个重要方式。

扩展一下null数据类型
null值表示一个空对象指针(引用),undefined 值是派生自 null 值的,所以typeof null ==> object(特殊)
undefined 是表示已声明但没有被赋值(未经初始化)的变量.
利用null和undefined就可以区分对象空指针和的变量
要定义的变量是将来保存对象用的,就应该明确地给该变量初始化为null。这样做不仅可以体现null作为空对象指针的惯例,而且也有助于进一步区分null和undefined。只要直接检查null值就可以知道相应的变量是否已经保存了一个对象的引用。

//准确判断
var exp = null; 
!exp && typeof exp != "undefined" && exp != 0  ==>true
exp === null  ==>true

//有误区的判断
!exp  ==>true
//如果 exp 为 undefined,或数字零,或 false,也会得到与 null 相同的结果。
//同时判断 null、undefined、数字零、false 时可使用本法。
exp == null  ==>true
//exp为 undefined 时,也会得到与 null 相同的结果。
//要同时判断 null 和 undefined 时可使用本法。
typeof exp == "null"
//为了向下兼容,exp 为 null 时,typeof null 返回 object,所以不能这样判断。

在局部作用域中,当函数执行完毕,局部变量也就没有存在的必要了,因此垃圾收集器很容易做出判断并回收。但是全局变量什么时候需要自动释放内存空间则很难判断,因此在我们的开发中,需要尽量避免使用全局变量。

参考文章之一:前端基础进阶(一):内存空间详细图解

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,451评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,172评论 3 394
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,782评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,709评论 1 294
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,733评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,578评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,320评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,241评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,686评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,878评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,992评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,715评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,336评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,912评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,040评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,173评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,947评论 2 355