EcmaScript中的词法环境和闭包

哈Ha!

我已经很长时间没有写任何东西了,最近几个星期在该项目上做了很多工作,但是现在我有空闲时间,所以我决定向您介绍新文章。

今天,我们将继续分析关键的EcmaScript概念,讨论词法环境和闭包。 理解Lexical环境的概念对于理解闭包非常重要,而闭包是JS世界(基于EcmaScript规范)中众多优秀技术的基础。

因此,让我们开始吧。

词法环境(LexicalEnvironment,LO,LE)


正式的ES6规范将该术语定义为:
词法环境是一种规范类型,用于在基于ECMAScript代码嵌套的词法结构搜索特定变量和函数时解析标识符名称。 词法环境由环境记录以及对外部词法环境的空引用组成。
让我们仔细看看。

我将想象词汇环境是一种存储上下文标识符及其含义的连接的结构。 这是在此上下文范围内声明的变量,函数,类的存储库。

从技术上讲,LO是具有两个属性的对象:

  • 环境记录(这是所有广告的存储位置)
  • 链接到LO生成上下文。

通过链接到当前上下文的父上下文,如有必要,我们可以链接到父上下文的“祖父上下文”,依此类推,链接到全局上下文,对全局上下文的引用将为null。 从这个定义可以得出,词法环境是实体与其来源上下文的连接。 函数中的一种ScopeChain类似于Lexical环境。 我们在本文中详细讨论了ScopeChain。

let x = 10; let y = 20; const foo = z => { let x = 100; return x + y + z; } foo(30);// 150.   foo    {record: {z: 30, x: 100}, parent: __parent}; // __parent      {record: {x: 10, y: 20}, parent: null} 

从技术上讲,解析标识符名称的过程将像ScopeChain中一样发生,即 在LO循环中将对对象进行顺序轮询,直到找到所需的标识符为止。 如果找不到标识符,则引用错误。

在创建上下文的阶段创建并填充词汇环境。 当当前上下文完成执行时,会将其从调用堆栈中删除,但是只要至少有一个链接指向它的Lexical环境就可以继续存在。 这是现代编程语言设计方法的优点之一。 我认为值得一提!

堆栈组织与动态共享内存


在堆栈语言中,局部变量存储在堆栈中,当激活函数时会对其进行补充,而在函数退出时,将从堆栈中删除其局部变量。

对于堆叠式组织,将不可能从函数返回局部函数或将函数调用为自由变量。

自由变量是函数使用的变量,但它既不是形式参数,也不是该函数的局部变量。

 function testFn() { var locaVar = 10; //     innerFn function innerFn(p) { alert(p + localVar); } return innerFn; //  } var test = testFn();//  innerFn   test();//       

使用堆栈组织,既不可能在外部LexicalEnvironment中进行locaVar搜索,也无法返回innerFn函数,因为 innerFn还是testFn的本地声明。 完成testFn后,只需将其所有局部变量从堆栈中删除即可。

因此,提出了另一个概念-动态分配的内存(堆,堆)+垃圾收集器+参考计数的概念。 这个概念的本质很简单:只要对一个对象至少有一个引用,就不会从内存中删除它。 可以在这里找到更多详细信息。

封闭(封闭)


闭包是代码块和在其中生成该块的上下文数据的组合,即 它是实体与通过LO或SopeChain链生成的上下文之间的关系。

让我引用关于这个主题的一篇非常好的文章

 function person() { let name = 'Peter'; return function displayName() { console.log(name); }; } let peter = person(); peter(); // prints 'Peter' 

执行人员功能时,JavaScript将为该功能创建新的执行上下文和词法环境。 该函数完成后,它将返回displayName函数并分配给变量peter。

因此,她的词汇环境将如下所示:

 personLexicalEnvironment = { environmentRecord: { name : 'Peter', displayName: < displayName function reference> } outer: <globalLexicalEnvironment> } 

当person函数完成时,其执行上下文将从堆栈中弹出。 但是其词法环境仍将保留在内存中,因为其内部displayName函数的词法环境引用了它。 因此,其变量仍将在内存中可用。

当执行peter函数(实际上是对displayName函数的引用)时,JavaScript会为此函数创建一个新的执行上下文和词法环境。

因此他的词汇环境将如下所示:

 displayNameLexicalEnvironment = { environmentRecord: { } outer: <personLexicalEnvironment> } 

displayName函数中没有变量;其环境记录将为空。 在执行此函数期间,JavaScript将尝试在该函数的词法环境中查找name变量。

由于displayName函数的词法环境中没有变量,因此它将在仍在内存中的外部词法环境(即person函数的词法环境)中搜索。 JavaScript将找到此变量,并且名称会打印到控制台。
ES中闭包的最重要特征是它使用静态作用域(在许多其他使用闭包的语言中,情况有所不同)。

一个例子:

 var a = 5; function testFn() { alert(a); } (function(funArg) { var a = 20; funArg();//  5 ..  ScopeChain/LexicalEnvironment testFn   ,    = 5 })(testFn) 

另一个重要的关闭属性是以下情况:

 var first; var second; function testFn() { var a = 10; first = function() { return ++a; } second = function() { return --a; } a = 2; first();//3 } testFn(); first();//4 second();//3 

即 我们看到存在于多个函数的闭包中的自由变量被它们的引用所改变。

结论


在本文的框架中,我们简要描述了EcmaScript的两个主要概念:词法环境和闭包。 实际上,这两个主题都更为广泛。 如果社区想要更深入地描述不同类型的词汇环境之间的差异,或者想了解v8如何构建闭包,请在注释中写上。

Source: https://habr.com/ru/post/zh-CN474852/


All Articles