闭包

闭包 属于 JavaScript

1 个练习

关于 闭包

_闭包_是 JavaScript 里的一种编程模式,它让外层词法作用域中的变量可以在嵌套的代码块内使用。JavaScript 透明地支持闭包,人们常常在并不知道它是什么的情况下就用上了它。

// Top-level declarations are global-scope
const dozen = 12;

// Functions create a new function-scope and block-scope.
// Referencing the outer variable here is a closure.
function nDozen(n) {
  return dozen * n;
}

_闭包_这个名字在历史上源自λ 演算,由 scheme 推广开来(来源),用来指代函数开放的和封闭的变量绑定。

在 JavaScript 中使用闭包的理由

  1. 数据隐私 / 数据封装

    • 与其他语言不同,2020 年的 JavaScript 还没办法指定 私有 变量。所以可以用闭包来有效地模拟 私有 变量(当时有一个引入私有变量表示法的提案,等你读到这段内容时,它说不定已经成为标准了)。
    // Consider this function:
    
    const saveNumber = (number) => {
      // The saved value is unaccessible by the outer lexical scope.
      const value = number;
    
      // We can provide access to the primitive value with a function, but the original will never change
      return () => value;
    };
    
    // Attempting to set the variable outside of its lexical scope results in an error
    value = 42;
    
  2. 部分应用

    • 函数可以返回函数。当被返回的函数用到了创建它的那个函数的实参时,这就是用闭包进行部分应用的一个例子。有时这也叫做把函数 柯里化。
    // The arguments are applied one-by-one, using closures to arrive at the final result
    function partialBuildUri(scheme) {
      return function (domain) {
        return function (path) {
          return `${scheme}://${domain}/${path}`;
        };
      };
    }
    
    // A function could apply them all at once.
    function buildUri(scheme, domain, path) {
      return partialBuildUri(scheme)(domain)(path);
    }
    
    // Or apply a few to create a flexible system of functions for reuse.
    function buildHttpsExercismUri(path) {
      return partialBuildUri('https')('exercism.org');
    }
    

变量的词法作用域

在任何编程语言中,闭包都和变量作用域密切相关。在 JavaScript 中,变量的词法作用域有 3 种:

  1. 全局作用域:在函数和代码块之外声明的变量。
  2. 函数作用域:用var关键字在函数内部声明的变量。
  3. 块级作用域:用let和const关键字在代码块({...})内部声明的变量。

在实践中,闭包常常出现在函数(或代码块)使用了外层作用域里的变量时。在 JavaScript 中,这一点是透明支持的,所以人们经常在不特意提及的情况下就用到了闭包。

函数作用域

var关键字定义的是函数作用域变量。也就是说,用var定义的变量,在它所在的整个函数内任何地方都能访问,在任何嵌套的词法作用域(无论是函数还是代码块)中也能访问。

{
  var five = 5;
}

// This will succeed because 'five' exists in the same scope
var tenTimes = five * 10;

块级作用域

let和const关键字定义的是块级作用域变量。let定义的是可变变量,它的值可以被多次赋值,也可以只声明而不初始化。const定义的变量在定义时必须初始化,之后它的值就不能再改变。如果const变量被赋为原始值,就不能再修改;如果被赋为引用值,比如一个 数组 或 对象,那么引用不能改变,但数组的内容可以修改。

看看这个代码块:

// 'two' is defined at the top-level, therefore available to the global-scope
const two = 2;

{
  // This succeeds, as 'two' is available from the outer lexical scope
  const oneLess = two - 1;
}

// This throws an error because oneLess is not defined outside of the previous inner lexical scope
console.log(oneLess);

虽然我们可以用大括号({...})随意创建代码块,但最常见的情况是它们包在 if语句、_函数_和_类_的周围。

const PI = 3.14;

// This function uses a closure for the value of PI (π)
function areaOfCircle(radius) {
  return PI * radius * radius;
}
通过 GitHub 编辑 该链接会在新窗口或标签页中打开

学习 闭包