js内存

本文出自https://juejin.im/post/5b10ba336fb9a01e66164346
 
每种编程语言都有它的内存管理机制,比如简单的C有低级的内存管理基元,像malloc(),free()。同样我们在学习JavaScript的时候,很有必要了解JavaScript的内存管理机制。 JavaScript的内存管理机制是:内存基元在变量(对象,字符串等等)创建时分配,然后在他们不再被使用时“自动”释放。后者被称为垃圾回收。这个“自动”是混淆并给JavaScript(和其他高级语言)开发者一个错觉:他们可以不用考虑内存管理。 对于前端开发来说,内存空间并不是一个经常被提及的概念,很容易被大家忽视。当然也包括我自己。在很长一段时间里认为内存空间的概念在JS的学习中并不是那么重要。可是后我当我回过头来重新整理JS基础时,发现由于对它们的模糊认知,导致了很多东西我都理解得并不明白。比如最基本的引用数据类型和引用传递到底是怎么回事儿?比如浅复制与深复制有什么不同?还有闭包,原型等等。 但其实在使用JavaScript进行开发的过程中,了解JavaScript内存机制有助于开发人员能够清晰的认识到自己写的代码在执行的过程中发生过什么,也能够提高项目的代码质量。

内存模型

JS内存空间分为栈(stack)、堆(heap)、池(一般也会归类为栈中)。 其中栈存放变量,堆存放复杂对象,池存放常量。

 

JS中的基础数据类型,这些值都有固定的大小,往往都保存在栈内存中(闭包除外),由系统自动分配存储空间。我们可以直接操作保存在栈内存空间的值,因此基础数据类型都是按值访问 数据在栈内存中的存储与使用方式类似于数据结构中的堆栈数据结构,遵循后进先出的原则。 基础数据类型: Number String Null Undefined Boolean 复习一下,此问题常常在面试中问到,然而答不出来的人大有人在 ~ ~ 要简单理解栈内存空间的存储方式,我们可以通过类比乒乓球盒子来分析。

这种乒乓球的存放方式与栈中存取数据的方式如出一辙。处于盒子中最顶层的乒乓球5,它一定是最后被放进去,但可以最先被使用。而我们想要使用底层的乒乓球1,就必须将上面的4个乒乓球取出来,让乒乓球1处于盒子顶层。这就是栈空间先进后出,后进先出的特点。

 

引用数据类型与堆内存

与其他语言不同,JS的引用数据类型,比如数组Array,它们值的大小是不固定的。引用数据类型的值是保存在堆内存中的对象。JS不允许直接访问堆内存中的位置,因此我们不能直接操作对象的堆内存空间。在操作对象时,实际上是在操作对象的引用而不是实际的对象。因此,引用类型的值都是按引用访问的。这里的引用,我们可以粗浅地理解为保存在栈内存中的一个地址,该地址与堆内存的实际值相关联。 堆存取数据的方式,则与书架与书非常相似。 书虽然也有序的存放在书架上,但是我们只要知道书的名字,我们就可以很方便的取出我们想要的书,而不用像从乒乓球盒子里取乒乓一样,非得将上面的所有乒乓球拿出来才能取到中间的某一个乒乓球。好比在JSON格式的数据中,我们存储的key-value是可以无序的,因为顺序的不同并不影响我们的使用,我们只需要关心书的名字。

为了更好的搞懂栈内存与堆内存,我们可以结合以下例子与图解进行理解。
var a1 = 0; // 栈
var a2 = 'this is string'; // 栈
var a3 = null; // 栈
var b = { m: 20 }; // 变量b存在于栈中,{m: 20} 作为对象存在于堆内存中
var c = [1, 2, 3]; // 变量c存在于栈中,[1, 2, 3] 作为对象存在于堆内存中

 

 

[栈内存空间] ------->

        堆内存空间
        [1,2,3]           
                    {m:20}      

 

 

var a = 20;
var b = a;
b = 30; // 这时a的值是多少?
var m = { a: 10, b: 20 };
var n = m;
n.a = 15; // 这时m.a的值是多少
 
在栈内存中的数据发生复制行为时,系统会自动为新的变量分配一个新值。var b = a执行之后,ab虽然值都等于20,但是他们其实已经是相互独立互不影响的值了。具体如图。所以我们修改了b的值以后,a的值并不会发生变化。
 
在demo02中,我们通过var n = m执行一次复制引用类型的操作。引用类型的复制同样也会为新的变量自动分配一个新的值保存在栈内存中,但不同的是,这个新的值,仅仅只是引用类型的一个地址指针。当地址指针相同时,尽管他们相互独立,但是在堆内存中访问到的具体对象实际上是同一个。 




 

 

 

 

 

 

 

 

 

JavaScript 变量可以用来保存两种类型的值:基本类型值和引用类型值。基本类型的值源自以下 5 种基本数据类型:Undefined、Null、Boolean、Number 和 String。基本类型值和引用类型值具 有以下特点: 11

  •   基本类型值在内存中占据固定大小的空间,因此被保存在栈内存中;

  •   从一个变量向另一个变量复制基本类型的值,会创建这个值的一个副本;

  •   引用类型的值是对象,保存在堆内存中;

  •   包含引用类型值的变量实际上包含的并不是对象本身,而是一个指向该对象的指针;

  •   从一个变量向另一个变量复制引用类型的值,复制的其实是指针,因此两个变量最终都指向同

    一个对象;

  •  确定一个值是哪种基本类型可以使用 typeof 操作符,而确定一个值是哪种引用类型可以使用 instanceof 操作符。

     

     

    所有变量(包括基本类型和引用类型)都存在于一个执行环境(也称为作用域)当中,这个执 行环境决定了变量的生命周期,以及哪一部分代码可以访问其中的变量。以下是关于执行环境的几 点总结:

    •   执行环境有全局执行环境(也称为全局环境)和函数执行环境之分;

    •   每次进入一个新执行环境,都会创建一个用于搜索变量和函数的作用域链;

    •   函数的局部环境不仅有权访问函数作用域中的变量,而且有权访问其包含(父)环境,乃至全

      局环境;

    •   全局环境只能访问在全局环境中定义的变量和函数,而不能直接访问局部环境中的任何数据;

    •   变量的执行环境有助于确定应该何时释放内存。

    JavaScript 是一门具有自动垃圾收集机制的编程语言,开发人员不必关心内存分配和回收问题。可 以对 JavaScript 的垃圾收集例程作如下总结。

    •   离开作用域的值将被自动标记为可以回收,因此将在垃圾收集期间被删除。

    •   “标记清除”是目前主流的垃圾收集算法,这种算法的思想是给当前不使用的值加上标记,然

      后再回收其内存。

    •   另一种垃圾收集算法是“引用计数”,这种算法的思想是跟踪记录所有值被引用的次数。JavaScript

      引擎目前都不再使用这种算法;但在 IE 中访问非原生 JavaScript 对象(如 DOM 元素)时,这种

         算法仍然可能会导致问题。
      
    •   当代码中存在循环引用现象时,“引用计数”算法就会导致问题。

    •   解除变量的引用不仅有助于消除循环引用现象,而且对垃圾收集也有好处。为了确保有效地回

         收内存,应该及时解除不再使用的全局对象、全局对象属性以及循环引用变量的引用
    • 
      

posted on 2018-06-04 19:42  南果梨  阅读(123)  评论(0编辑  收藏  举报