可访问标识符:指在当前作用域内可以被访问到的标识符;引用:在 JavaScript 中对象、数组和函数,它们的值实际上是存储在内存中的对象,值赋值给变量,变量中存储的是该对象的引用;顶层声明:在全局作用域中声明的变量、函数或类,也就是不在任何函数或代码块中声明的语句。函数实例:函数实例指的是一个函数对象的实例,也就是通过一个构造函数创建的函数对象。函数实例可以像普通函数一样被调用,并且它们可以有自己的属性和方法。在 JavaScript 中,函数只是一段静态代码、脚本文本,它在一段代码中,属于是「编译期、静态」的概念;闭包则是函数代码在运行过程中一个动态环境,是一个「运行期、动态」的概念。在 JavaScript 中还存在「对象闭包」,而我们所说的闭包指的是「函数闭包」,而我们在面试中遇到的闭包面试题,其实主要谈的是闭包的持有。在 JavaScript 中,执行结构无非就是三种:脚本(Script)、模块(Module)、函数(Function),在任何文件中,要么是全局语句(Script),要么是模块中语句(Module),要么是语句之外的函数声明(Function)。因为 JavaScript 引擎通过「闭包」来为每个函数维护其「运行期」的信息,所以当函数再次执行或者通过某种方法进入函数体内时,就可以通过闭包来得到这些信息,得到当前函数运行期的数据。在我们写代码的过程中,函数只是一段代码文本,在真正的运行环境中需要先将它们变成可处理的数据对象,这个处理过程就是函数类的实例,从文本到数据对象的这个行为:无论怎样称呼,都是为了得到一个可参与运算的「函数实例」,当然一个函数代码被实例化多次,可以有多个函数实例。而闭包就是记录函数实例在运行期的「可访问标识符」的结构。因此一个函数实例的一次执行,就会带来一个新运行期的作用域,就是一个闭包。在运行代码看来,它就是运行期的「作用域链」,因为外部引用指向它被调用时的作用域。再继续分析,其实大家知道函数其实是用 () 括号运算符来调用的,是由一个「声明实例化」的内部阶段来构建的,也就是说,闭包内的初始信息就是函数代码中的那些声明。一个函数实例只有一个闭包,在闭包中的数据没有被引用的时候,函数实例与闭包就同时被回收了。不过:- JavaScript 函数实例与闭包的生存周期是分别管理的;
JavaScript 函数被调用时,总是初始化一个闭包,至于上次的闭包是否被销毁,取决于上次的闭包中是否有被引用的变量或者数据。在语法分析阶段,JavaScript 能从函数的代码文本中得到下面两个列表:
上面的信息可以构成在语法分析阶段的作用域,这个作用域包括的是一组名字列表。
当函数开始执行的时候,JavaScript 会创建一个执行环境,并把标识符列表指向函数实例中的作用域,用来完成从作用域到闭包,概念上的映射,也可以说闭包实际上是运行环境对作用域的一个引用。
闭包是运行期的,所以它是变化的、有状态的、可存储的。
在初始化的时候,JavaScript 会通过语法分析得到的两个列表,把两个列表合并起来,形成一份可访问标识符列表,这个列表由两部分构成「变量环境」和「词法环境」,标识符列表包含这个函数内顶层的变量、常量、函数名、参数名、类名、标签,还有一个特殊的名字 arguments。
function myFunc(){
console.log(i)
var i = 100;
}
myFunc();//undefined
myFunc();//undefined
在调用 myFunc() 的时候,JavaScript 引擎就为 myFunc() 函数实例准备好了一个闭包,包括两个可访问的标识符:
num 变量实际上是通过形式参数来绑定的,和 var num 没有关系,在 var 那行只做了赋值操作。
在函数开始执行时,闭包中「变量」值都将重置,在上面的代码中,调用了两次 myFunc() 函数创建了两个闭包。
输出的结果,一直都是 undefined,变量在闭包的实例化阶段就已经创建好了,在内部访问变量 i 不会报错,会访问到初始值 undefined,因为在执行前被初始化,所以,值一直是 undefined。
function MyFunc(){
var value = 100;
function setValue(v){
value = v;
}
function getValue(){
return value
}
//将内部函数公布到全局
return [setValue, getValue]
}
var [setter, getter] = MyFunc()
// 测试
getter()//输出 100
//当我们调用 setter 时,会直接影响内部数据
setter(300)
getter()// 输出300
因为函数退出,并不是闭包销毁的条件,而由于闭包没有被销毁,所以变量也就不会被重置,正因为这样 JavaScript 借此提供了「在函数中保存数据」的这种函数式语言特性。
换句话说就是:不需要再次调用函数,通过上述闭包作用域的访问权,就可以查看函数闭包内部数据。
闭包的生存周期是和函数实例是否活动是相关的。因此闭包内数据的生存周期,取决于这个函数实例是否存在活动引用,如果不存在了,引擎就会销毁(垃圾回收)它。
闭包在函数执行过程中,处于激活的,可访问的状态,并在函数实例被调用结束后保持上述数据信息的最终数据状态,直到闭包被销毁。
题图授权基于 www.pixabay.com 相关协议
内容来源于《JavaScript 语言精髓与编程实战》
原创文章,作者:小道研究,如若转载,请注明出处:https://www.sudun.com/ask/34583.html