从基本术语开始,4步完成对JavaScript闭包的理解
作者:admin 时间:2022-6-7 14:36:4 浏览:JavaScript闭包在实际开发中使用并不多,但它却是面试官必问的问题,因此,作为一个前端开发人员,闭包是躲不开的一个技术。在本文中,我将从基本术语开始:作用域和词法作用域,然后,在掌握了基础知识之后,最终理解闭包。
在开始之前,我建议你不要跳过范围和词法范围这两部分,这些概念对闭包至关重要,如果你把它们搞好,闭包的概念就变得不言而喻了。
一、适用范围
当你定义一个变量时,你希望它存在于某些边界内。例如,result变量作为内部细节存在于calculate()函数中是有意义的。在calculate()之外,该result变量是无用的。
变量的可访问性由范围管理,你可以自由访问在其范围内定义的变量。但在该范围之外,该变量是不可访问的。
在 JavaScript 中,作用域由函数或代码块创建。
让我们看看范围如何影响count变量的可用性。此变量属于由foo()创建的范围:
function foo() {
  // 函数范围
  let count = 0;
  console.log(count); // logs 0
}
foo();
console.log(count); // ReferenceError: count is not definedcount在foo()范围内可以自由访问。
但是,超出foo()范围,count是无法访问的。如果你尝试从外部访问count,JavaScript 会抛出ReferenceError: count is not defined。
如果你在函数或代码块内定义了变量,则只能在该函数或代码块内使用此变量。上面的示例演示了这种行为。
  
现在,让我们看一个通用的公式:
范围是一种空间策略,用于规则变量的可访问性。
一个直接的属性出现了——作用域隔离了变量。这很好,因为不同的作用域可以有同名的变量。
你可以在不同的范围内重用公共变量名称(count、index、current、value等)而不会发生冲突。
foo()和bar()函数范围有它们自己但同名的变量count: 
function foo() {
  // "foo" 函数范围
  let count = 0;
  console.log(count); // logs 0
}
function bar() {
  // "bar" 函数范围
  let count = 1;
  console.log(count); // logs 1
}
foo();
bar();来自foo()和bar()函数范围的变量count不会发生冲突。
2、范围嵌套
让我们更多地使用范围,并将一个范围嵌套到另一个范围中。例如,函数innerFunc()嵌套在外部函数outerFunc()中。
两个函数作用域如何相互交互?我可以从innerFunc()范围内访问outerFunc()范围的变量outerVar吗?
让我们在示例中尝试一下:
function outerFunc() {
  // 外部范围
  let outerVar = 'I am outside!';
  function innerFunc() {
    // 内部范围
    console.log(outerVar); // => logs "I am outside!"
  }
  innerFunc();
}
outerFunc();结果是,outerVar变量可以在innerFunc()范围内访问。外部范围的变量可以在内部范围内访问。
现在你知道了两点:
- 范围可以嵌套
- 外部范围的变量可以在内部范围内访问
3、词法范围
JavaScript 是如何理解innerFunc()内部的变量outerVar对应outerFunc()的变量outerVar的?
JavaScript 实现了一种名为词法作用域(或静态作用域)的作用域机制。词法作用域意味着变量的可访问性由变量在嵌套作用域内的位置决定。
更简单,词法作用域意味着在内部作用域内可以访问外部作用域的变量。
它被称为词法(或静态),因为引擎(在词法分析时)仅通过查看 JavaScript 源代码而不执行它来确定范围的嵌套。
词法作用域的思想是:
词法范围由静态确定的外部范围组成。
例如:
const myGlobal = 0;
function func() {
const myVar = 1;
console.log(myGlobal); // logs "0"
function innerOfFunc() {
const myInnerVar = 2;
console.log(myVar, myGlobal); // logs "1 0"
function innerOfInnerOfFunc() {
console.log(myInnerVar, myVar, myGlobal); // logs "2 1 0"
}
innerOfInnerOfFunc();
}
innerOfFunc();
}
func();
innerOfInnerOfFunc()的词法范围由innerOfFunc()、func()和全局范围(最外层范围)组成的。在innerOfInnerOfFunc()里面,你可以访问词法范围变量myInnerVar、myVar 和 myGlobal。
innerOfFunc()的词法范围由func()和全局范围组成。你可以在innerOfFunc()里访问词法范围变量myVar和myGlobal。
最后,func()的词法作用域仅由全局作用域组成。你可以在func()里访问词法范围变量myGlobal。
4、闭包
通过前面的介绍,我们知道了词法范围允许静态访问外部范围的变量。距离闭包仅一步之遥!
让我们再看一下outerFunc()和innerFunc()例子:
function outerFunc() {
  let outerVar = 'I am outside!';
  function innerFunc() {
    console.log(outerVar); // => logs "I am outside!"
  }
  innerFunc();
}
outerFunc();在innerFunc()作用域内,从词法作用域访问outerVar变量,这个我们已经知道了。
请注意,innerFunc()调用发生在其词法范围(outerFunc()的范围)内。
让我们做一个改变:innerFunc()在其词法范围之外被调用:在一个函数exec()中。innerFunc()还能访问outerVar吗?
让我们对代码片段进行调整:
function outerFunc() {
  let outerVar = 'I am outside!';
  function innerFunc() {
    console.log(outerVar); // => logs "I am outside!"
  }
  return innerFunc;
}
function exec() {
  const myInnerFunc = outerFunc();
  myInnerFunc();
}
exec();现在innerFunc()在其词法范围之外执行,但在exec()函数范围内。重要的是:
innerFunc()仍然可以从其词法范围里访问outerVar,甚至在其词法范围之外执行。
换句话说,innerFunc() 从它的词法范围中关闭了(也就是捕获、记住)outerVar变量。
换句话说,innerFunc()是一个闭包,因为它从其词法范围内关闭了outerVar变量。 
 
 
现在,你应该了解什么是闭包了:
闭包是一个访问其词法范围的函数,甚至在其词法范围之外执行。
更简单地说,闭包是一个函数,它从定义它的地方记住变量,而不管它后来在哪里执行。
识别闭包的经验法则:如果在函数内部看到一个外来变量(未在该函数内部定义),则该函数很可能是一个闭包,因为该外来变量已被捕获。
在前面的代码片段中,outerVar是innerFunc()闭包内的一个外来变量,它从outerFunc()的作用域捕获。
总结
本文通过4个方面,从基本术语开始,由浅入深逐步介绍了什么是闭包。通过本文的学习,你应该知道了闭包的基本概念和其固有特点。
相关文章
 
 


