javascript闭包的作用(javascript闭包概念)

文章有些晦涩,所以先上一波名词解释。
作用域:变量、函数和对象在代码中被访问的范围;
可访问标识符:指在当前作用域内可以被访问到的标识符;
引用:在 JavaScript 中对象、数组和函数,它们的值实际上是存储在内存中的对象,值赋值给变量,变量中存储的是该对象的引用;
顶层声明:在全局作用域中声明的变量、函数或类,也就是不在任何函数或代码块中声明的语句。
函数实例:函数实例指的是一个函数对象的实例,也就是通过一个构造函数创建的函数对象。函数实例可以像普通函数一样被调用,并且它们可以有自己的属性和方法。
函数和闭包的概念
在 JavaScript 中,函数只是一段静态代码、脚本文本,它在一段代码中,属于是「编译期、静态」的概念;
闭包则是函数代码在运行过程中一个动态环境,是一个「运行期、动态」的概念。
在 JavaScript 中还存在「对象闭包」,而我们所说的闭包指的是「函数闭包」,而我们在面试中遇到的闭包面试题,其实主要谈的是闭包的持有。
函数实例
在 JavaScript 中,执行结构无非就是三种:脚本(Script)、模块(Module)、函数(Function),在任何文件中,要么是全局语句(Script),要么是模块中语句(Module),要么是语句之外的函数声明(Function)。
因为 JavaScript 引擎通过「闭包」来为每个函数维护其「运行期」的信息,所以当函数再次执行或者通过某种方法进入函数体内时,就可以通过闭包来得到这些信息,得到当前函数运行期的数据
在我们写代码的过程中,函数只是一段代码文本,在真正的运行环境中需要先将它们变成可处理的数据对象,这个处理过程就是函数类的实例,从文本到数据对象的这个行为:
  • 对于「函数的声明」来说叫「实例化」;
  • 对于「函数表达式」来说叫「创建函数的实例」;
无论怎样称呼,都是为了得到一个可参与运算的「函数实例」,当然一个函数代码被实例化多次,可以有多个函数实例。
看到闭包
闭包就是记录函数实例在运行期的「可访问标识符」的结构。因此一个函数实例的一次执行,就会带来一个新运行期的作用域,就是一个闭包。在运行代码看来,它就是运行期的「作用域链」,因为外部引用指向它被调用时的作用域
再继续分析,其实大家知道函数其实是用 () 括号运算符来调用的,是由一个「声明实例化」的内部阶段来构建的,也就是说,闭包内的初始信息就是函数代码中的那些声明。
一个函数实例只有一个闭包,在闭包中的数据没有被引用的时候,函数实例与闭包就同时被回收了。不过:
  • JavaScript 函数实例可以拥有多个闭包;
  • JavaScript 函数实例与闭包的生存周期是分别管理的;
JavaScript 函数被调用时,总是初始化一个闭包,至于上次的闭包是否被销毁,取决于上次的闭包中是否有被引用的变量或者数据。
在语法分析阶段,JavaScript 能从函数的代码文本中得到下面两个列表:
  • varDecls:所有顶层的变量声明

  • lexicallyDecls:所有顶层的词法声明,包括具名函数、let/const 声明、标签化语句中的标签、export导出的名字。

上面的信息可以构成在语法分析阶段的作用域,这个作用域包括的是一组名字列表。

当函数开始执行的时候,JavaScript 会创建一个执行环境,并把标识符列表指向函数实例中的作用域,用来完成从作用域到闭包,概念上的映射,也可以说闭包实际上是运行环境对作用域的一个引用。
闭包是运行期的,所以它是变化的、有状态的、可存储的。
闭包的变化
在初始化的时候,JavaScript 会通过语法分析得到的两个列表,把两个列表合并起来,形成一份可访问标识符列表,这个列表由两部分构成「变量环境」和「词法环境」,标识符列表包含这个函数内顶层的变量、常量、函数名、参数名、类名、标签,还有一个特殊的名字 arguments。
下面的代码涉及到了我们前面的知识点:
function myFunc(){  console.log(i)  var i = 100;}myFunc();//undefinedmyFunc();//undefined
在调用 myFunc() 的时候,JavaScript 引擎就为 myFunc() 函数实例准备好了一个闭包,包括两个可访问的标识符:
  • num:存放 num 信息,它是一个已绑定的、值为10的、可变的变量声明

  • arguments: 绑定到特定的 arguments 对象,是一个可变的变量声明; 
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

(0)
小道研究's avatar小道研究
上一篇 2024年4月14日 上午6:24
下一篇 2024年4月14日 上午6:26

相关推荐

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注