作用域、闭包、内存泄露

简介:

 作用域

  作用域指的是变量的有效访问范围。作用域对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的这些版本中发生循环引用时便会导致内存泄露。


目录
相关文章
|
5月前
|
JavaScript 前端开发 Java
内存管理和内存泄露(闭包、作用域链)(三)
内存管理和内存泄露(闭包、作用域链)
61 0
|
5月前
|
自然语言处理 JavaScript 前端开发
内存管理和内存泄露(闭包、作用域链)(二)
内存管理和内存泄露(闭包、作用域链)
46 0
|
5月前
|
存储 JavaScript 前端开发
|
3月前
|
自然语言处理 前端开发 JavaScript
前端 JS 经典:闭包与内存泄漏、垃圾回收
前端 JS 经典:闭包与内存泄漏、垃圾回收
34 0
|
5月前
|
缓存 自然语言处理 JavaScript
JavaScript内存泄漏导致应用性能下降,常见于闭包使用不当
【5月更文挑战第14天】JavaScript内存泄漏导致应用性能下降,常见于闭包使用不当。闭包能记住并访问词法作用域,若函数返回后,其引用的对象未被释放,就会引发泄漏。例如,`createLeakyFunction`创建的闭包保留了对大型对象`someLargeObject`的引用,即使函数执行完毕,对象也无法被垃圾回收。避免泄漏的方法包括及时解除引用、清除事件监听器、使用WeakMap和WeakSet以及定期清理缓存。使用性能分析工具可检测和修复内存泄漏问题。
43 3
|
4月前
|
Web App开发 存储 JavaScript
如何避免闭包函数的内存泄漏
如何避免闭包函数的内存泄漏
28 0
|
5月前
|
存储 JavaScript 前端开发
内存管理和内存泄露(闭包、作用域链)(一)
内存管理和内存泄露(闭包、作用域链)
69 0
|
存储 JavaScript 前端开发
JS进阶(三) 闭包,作用域链,垃圾回收,内存泄露
闭包,作用域链,垃圾回收,内存泄露 1、函数创建 创建函数 1、开辟一个堆内存(16进制的内存地址) 2、声明当前函数的作用域(再哪个上下文创建的,它的作用域就是谁) 3、把函数体内的代码当作字符串存储在堆内存当中(所以不执行没有意义) 4、把函数的堆内存地址类似对象一样放到栈中供对象调用 执行函数 1、会形成一个全新的私有上下文(目的是供函数中的代码执行),然后进栈执行 2、在私有上下文中有一个存放私有变量的变量对象 AO(xx) 3、在代码执行之前要做的事情 - 初始化它的作用域链<自己的上下文,函数的作用域> - 初始化this (箭头函数没有this) - 初始化Arguments实参
90 0
|
JavaScript Java
面试题:闭包、作用域链、内存泄漏
面试题:闭包、作用域链、内存泄漏
|
存储 自然语言处理 JavaScript
JavaScript中的闭包是什么?JavaScript中闭包造成的内存泄漏又怎么解决?
JavaScript中的闭包是什么?JavaScript中闭包造成的内存泄漏又怎么解决?
109 0

热门文章

最新文章