词法环境[[environment]]–闭包
闭包定义
一些书籍中对于闭包的定义:
- 闭包是指有权访问另一个函数作用域中的变量的函数—《JavaScript高级程序设计》
- 闭包允许函数访问并操作函数外部的变量—《JavaScript忍者秘籍》
- 闭包使得函数可以继续访问定义时的词法作用域—《你不知道的JavaScript》
- 所有JavaScript函数都是闭包—《JavaScript权威指南》
MDN:
- 函数和对其周围状态(词法环境)的引用捆绑在一起构成闭包。
- 也就是说,闭包可以让你从内部函数访问外部函数作用域(作用域链)。
- 在JavaScript中,每当函数被创建,就会在函数生成时生成闭包。
[[Environment]]
[[Environment]]
:内见属性
- 每个函数都有;
- 存放词法环境的作用;
- v8引擎中无法读写;
对比之前学过的另一个内见属性[[Prototype]]
- 每个函数(构造函数)都有;
- 存放对象原型的作用;
- 可以使用
__proto__
读写;
[[Environment]]
叫词法环境对象:
- 整个脚本文件执行前会产生一个
- 函数实例创建后会产生一个
[[Environment]]
属性记录了:当前函数的词法环境对象==>外层函数的词法环境对象==>全局的词法环境对象;这样就形成了作用域链。
举例说明:
function fn() {
const a = 1;
function fo() {
console.log('hi')
}
}
fn()
//此时fn函数的[[Environment]]属性包括:作用域链
//1.当前环境的记录
a:1
fo:()=>console.log('hi')
//2.对外部词法环境的引用(全局词法环境)
闭包的例子:函数与词法环境捆绑
function fn() {
let a = 1;
return function fo() {
console.log(a++)
}
}
let x = fn()
x();//1
x();//2
//下面两种情况相当于创建了两个实例,
//当前面的函数执行完之后里面的变量和函数就被垃圾回收掉,
//所以不影响后面函数的执行依然打印的是a=1
fn()()//1
fn()()//1
我们分析一下上面函数不考虑词法环境的情况下的执行调用栈:
fn()
入栈;变量a入栈;fn()
和a一起出栈;x()
入栈;(此时出现问题了,x()
在执行过程中需要找到a,但是a又没在执行栈中,并不符合我们要的预期)
此时就需要在函数与词法环境捆绑,形成闭包来实现这个问题:函数执行前就形成词法环境写入[[Environment]]
属性中:此时fo函数内部词法环境是只有一个console.log函数,外层函数词法环境是a:1
,全局词法环境为空。
函数中在执行过程中的内部变量,并不是同函数一样被加入到调用栈中,而是在函数执行前形成词法环境写入[[Environment]]
属性中保存。只要有函数就会有闭包,只是v8中有垃圾回收机制,当一个函数执行后不被其他变量所引用,这个函数中的变量就不会被垃圾回收掉。但是当函数执行后有变量引用它时,就这个函数里面的变量就永远不会被垃圾回收掉,需要我们手动操作回收变量。
下面的例子中const a
这个变量就是被存放在词法环境对象[[Environment]]
中。(下图可以解释为什么要有闭包。)
理解垃圾回收问题:
js变量回收规则:
- 在js中定义的全局变量是不会被销毁的,因为随时都可能会用到这个变量,所以不能被销毁。
- 具体引用关系的不会被销毁。
- 如果一个对象不被引用,那么这个对象就会被回收;
- 如果两个对象互相引用,但是没有被第3个对象所引用,那么这两个互相引用的对象也会被回收。
通过下面两段代码进行对比分析:
function a(){
var b= 10;
return function(){
b++;
console.log(num);
}
}
a()(); //11
a()(); //11
分析:
在函数a中返回了一个匿名函数,在这个匿名函数中我们num++了一下,然后在函数外面执行了这个匿名函数函数,现在num是11,然后又执行了一次这个函数,你们应该是12吧,为什么不是呢?
原因:
js为了让没有必要的变量保存在内存中,(我们写的任何变量都是需要内存空间的)在不需要这个变量的时候它就会被销毁。所以每次执行一遍 a()() 则变量b就会被销毁。下次再执行,就会重新声明变量b,所以两次输出都是11。
function a(){
var b = 0;
return function(){
b ++;
console.log(b);
}
}
var d = a();
d();//1
d();//2
原因分析:
- 函数a 被 变量d 引用,更准确的说是 函数a 里面的 匿名函数 被变量d所引用。
- 因为变量d 保存的是函数a执行完成后的值,而函数a执行完,返回了那个匿名函数,所以变量d等于匿名函数。
-匿名函数因为使用了 函数a 中的 变量b 并且还被 变量 d所引用,所以就形成了一个闭包。 - 只要这个变量d不等于null的话,那么那个变量b会一直保存到变量d中不会被销毁。
- 所以两次执行的结果不一样
垃圾回收问题
1.什么是js的垃圾回收机制?
答:javascript中垃圾收集机制是自动回收的,不用人工操作,这让我们更专注于编辑代码上。 回收垃圾机制是定时执行的,具有周期性。
2.什么时候会有垃圾?
答:在作用域中当整个作用域中的代码执行完后,作用域中的变量和方法都会没用,此时就是被当做垃圾了。比如局部作用域,一个函数执行完,里面的变量就可以被销毁,其占用内存被释放。
3.垃圾回收方式?
答:常用的是标记清除:这样操作:一个变量–>进入环境(被标记,有此标记为不能被清除)–>执行–>离开环境(被标记,这个标记告诉机制能被清除)–>回收机制一段周期后,变量被清除。
4.为什么还要管理内存?
答:分配给web浏览器的内存比桌面的应用的内存少,这是出于安全考虑,为了防止运行js的网页耗尽系统内存导致系统崩溃。所以,开发者发现一旦数据不再用时,就将其值设为null来释放其引用(解除引用),这做法适用于全局变量和全局对象属性。
参考资料: