作用域、闭包、内存泄露

简介:

 作用域

  作用域指的是变量的有效访问范围。作用域对Javascript有重要意义,了解作用域的工作原理是在性能角度和功能角度理解Javascript的关键。

  每一个JavaScript函数都被表示为对象,是一个函数实例。以下两种定义函数的方式是等价的。


var sayName = function(){
    alert('hello world!');
}

var sayName = new Function('alert("hello world!")');

  函数对象正如其他对象那样,拥有可以被Javascript代码访问的属性,和一系列不能被Javascript代码访问,仅供JavaScript引擎使用的内部属性。其中一个内部属性是[[Scope]]。

  内部[[Scope]]属性包含一个函数被创建的作用域中对象的集合。此集合被称为函数的作用域链,它决定哪些数据可由函数访问。此函数作用域链中的每个对象被称为一个可变对象,每个可变对象都以“键值对”的形式存在。当一个函数创建后,它的作用域链被填充以对象,这些对象代表创建此函数的环境中可访问的数据。例如下面这个全局函数:


function add(num1, num2) {
    var sum = num1 + num2;
    return sum;
}

 当add()函数创建后,它的内部属性指向一个作用域链,该作用域链中被填入一个单独的可变对象,这个可变对象是一个全局对象。此全局对象包含诸如窗口、浏览器和文档之类的访问接口。

  

  而当add函数运行时会建立一个内部对象,称作“运行期上下文”。一个运行期上下文定义了一个函数运行时的环境。对函数的每次运行而言,每个运行期上下文都是独一的,所以多次调用同一个函数就会导致多次创建运行期上下文(execution context)。当函数执行完毕,运行期上下文就被销毁。

  一个运行期上下文有它自己的作用域链,用于标识符解析。当运行期上下文被创建时,它的作用域链被初始化。包括函数的[[Scope]]属性中所包含的对象会按照它们出现在作用域链中的顺序,被复制到运行期上下文的作用域链中。这项工作一旦完成,一个被称作“激活对象”的新对象就为运行期上下文创建好了。此激活对象作为函数执行期的一个可变对象,包含访问所有局部变量,命名参数,参数集合,和this的接口。然后,此对象被推入作用域链的前端。当作用域链被销毁时,激活对象也一同销毁。下图显示了add函数运行时所对应的运行期上下文和它的作用域链。


var total = add(5, 10);

  

  需要记住的是两点:

  1. [[scope]]属性是函数创建到运行时一直存在的,知道函数被销毁后占用的内存才会被释放
  2. 运行期上下文只存在于函数运行期间,函数运行结束后该对象被销毁

  

  闭包、内存泄露

  闭包是JavaScript最强大的一个方面,它允许函数访问局部范围之外的数据。


function assignEvents() {
    var id = "xdi9592";
    document.getElementById('save').onclick = function(event){
        saveDocument(id);
    }
}

  当assignEvents()被执行时,一个激活对象被创建,并包含了该函数作用域内所有可访问的变量和函数,其中包括id变量。它将成为运行期上下文作用域链上的第一个对象,全局对象是第二个。当闭包创建时,[[Scope]]属性包含了作用域内所有对象的集合(等于assignEvents运行期上下文的作用域链,即assignEvents的激活对象,全局对象)。

  

  由于闭包的[[Scope]]属性包含与运行期上下文作用域链相同的对象引用,会产生副作用。通常,一个函数的激活对象与运行期上下文一同销毁。当涉及闭包时,运行期上下文对象,以及他的作用域链被销毁,但激活对象就无法销毁,因为引用仍然存在于闭包的[[Scope]]属性中。除非手动接触所有对匿名函数的引用,等到垃圾收集器下次运行时,assignEvents的激活对象才会随着匿名函数一同被销毁。


document.getElementById('save').onclick = null;

  所以在Javascript代码中闭包与非闭包函数相比,需要更多内存开销。在大型网页应用中,这可能会导致内存泄露与难以排查的性能问题。

  随着浏览器的升级,大部分浏览器对于闭包引起的循环引用问题都能够顺利解决。但IE9之前使用非本地JavaScript对象实现DOM对象,对于Javascript对象跟DOM对象使用不同的垃圾收集器。所以闭包在IE的这些版本中发生循环引用时便会导致内存泄露。


目录
相关文章
|
3月前
|
存储 JavaScript 前端开发
|
9月前
|
存储 JavaScript 前端开发
JS进阶(三) 闭包,作用域链,垃圾回收,内存泄露
闭包,作用域链,垃圾回收,内存泄露 1、函数创建 创建函数 1、开辟一个堆内存(16进制的内存地址) 2、声明当前函数的作用域(再哪个上下文创建的,它的作用域就是谁) 3、把函数体内的代码当作字符串存储在堆内存当中(所以不执行没有意义) 4、把函数的堆内存地址类似对象一样放到栈中供对象调用 执行函数 1、会形成一个全新的私有上下文(目的是供函数中的代码执行),然后进栈执行 2、在私有上下文中有一个存放私有变量的变量对象 AO(xx) 3、在代码执行之前要做的事情 - 初始化它的作用域链<自己的上下文,函数的作用域> - 初始化this (箭头函数没有this) - 初始化Arguments实参
64 0
|
JavaScript Java
面试题:闭包、作用域链、内存泄漏
面试题:闭包、作用域链、内存泄漏
|
存储 自然语言处理 JavaScript
JavaScript中的闭包是什么?JavaScript中闭包造成的内存泄漏又怎么解决?
JavaScript中的闭包是什么?JavaScript中闭包造成的内存泄漏又怎么解决?
85 0
|
存储
4、闭包(含义、内存空间、作用)
4、闭包(含义、内存空间、作用)
80 0
|
JavaScript 前端开发 Java
解析面试常问题之JavaScript中的闭包概念及应用,顺便普及一下大家口中常说的内存泄漏问题
JavaScript中的闭包是一个面试中经常被考到的问题,大家可能都对这个概念多多少少都有一些模糊的概念或者一点都不了解,那么今天就来给大家讲解一下。
98 0
解析面试常问题之JavaScript中的闭包概念及应用,顺便普及一下大家口中常说的内存泄漏问题
|
29天前
|
存储 JSON 监控
Higress Controller**不是将配置信息推送到Istio的内存存储里面的**。
【2月更文挑战第30天】Higress Controller**不是将配置信息推送到Istio的内存存储里面的**。
14 1
|
1月前
|
存储 C语言
C语言--------数据在内存中的存储
C语言--------数据在内存中的存储
26 0