JavaScript 闭包(closure)

1 概念的提出

如果推断如下的代码的输出结果:

function createCounter() {
  let counter = 0
  const myFunction = function() {
    counter = counter + 1
    return counter
  }
  return myFunction
}
const increment = createCounter()
const c1 = increment()
const c2 = increment()
const c3 = increment()
console.log('example increment', c1, c2, c3)

调用 createCounter() 返回一个函数,因此 increment 是一个函数,调用 increment() 时,按照一般的理解,由于函数体内没有声明 counter 变量,counter = counter + 1 就相当于 counter = undefined + 1 相当于 counter = 1,按照以上推断,输出也许是:

// 错误 !!
1  
1
1

但实际上正确的输出是:

1
2
3

输出之所以是后者,是因为 JavaScript 的闭包机制。
(一个可视化JavaScript 执行过程的网站: JavaScript coding tutor - Learn JavaScript by visualizing code execution )

2 什么是闭包

2.1 词法环境 (Lexical Environment)

在 JavaScript 中,每个正在运行的函数、代码块 {...} 和整个脚本都有一个内部(隐藏的)关联对象,称为词法环境。

词法环境对象由两部分组成:

  1. Environment Record – an object that stores all local variables as its properties (and some other information like the value of this).
    环境记录——一个将所有局部变量 (以及一些其他信息,如 this 的值) 存储为其属性的对象。

  2. A reference to the outer lexical environment, the one associated with the outer code.
    一个指向外部词法环境的引用,它与外部代码相关联。

2.2 内部和外部词法环境(Inner and outer Lexical Environment)

函数调用期间具有有两个词法环境:内部此法环境和外部词法环境。
外部词法环境具有变量以及函数自身,内部词法环境具有一个指向外部词法环境的引用。
当代码想要访问一个变量时——首先搜索内部词法环境,然后是外部词法环境,然后是更外部的词法环境,依此类推,直到全局词法环境。
在这里插入图片描述
当函数开始运行时,会自动创建一个新的词法环境来存储调用的局部变量和参数。

所有函数都会记住它们被创建时的所在的词法环境,所有函数都有一个名为 [[Environment]] 的隐藏属性,持有对创建该函数的词法环境的引用。 [[Environment]] 引用在函数创建时被一次性设置,之后永不会再变。

2.3 闭包

闭包是一个函数,此函数记住了它所有的外部变量,并且能访问这些变量。每一个 JavaScript 函数都是一个闭包。

Closure is the concept that allows a returned inner function to access the lexical scope in which it was created in.
闭包是允许返回的内部函数访问创建它的词法范围的概念。

A closure is the ability of a function to remember the environment it was defined in so when it’s called in another scope, it can access the variables that were in the environment.
闭包是函数能够记住定义它的环境的能力,因此当在另一个范围内调用它时,它可以访问之前的环境中的变量。

比喻: 闭包就好比背着小背包的函数,小背包内装的是创建该函数时作用域内的所有变量。每一个 JavaScript 函数都有一个这样的小背包,每一个JavaScript 函数都是一个闭包。
原文:
The key to remember is that when a function gets declared, it contains a function definition and a closure. The closure is a collection of all the variables in scope at the time of creation of the function.
When a function gets created and passed around or returned from another function, it carries a backpack with it. And in the backpack are all the variables that were in scope when the function was declared.

以下面的例子为例:

function makeCounter() {
  let count = 0;

  return function() {
    return count++;
  };
}

let counter = makeCounter();

所有的函数被创建时都会获得一个隐藏属性 [[Environment]] 指向它们被创建时所在的词法环境。
在调用函数 makeCounter() 时,创建了一个小的嵌套函数。
函数不管是使用函数声明还是函数表达式创建的,都将获得 [[Environment]] 属性,该属性是一个指向创建它们的词法环境的引用。 所以新的小嵌套函数也得到了它。
对于新嵌套函数,[[Environment]] 的值是 makeCounter() 的当前词法环境(它诞生的地方)

本文一开始的例子:

function createCounter() {
  let counter = 0
  const myFunction = function() {
    counter = counter + 1
    return counter
  }
  return myFunction
}
const increment = createCounter()
const c1 = increment()   // increment 能够访问 counter,其值为 0, 然后 counter 被更新成 1
const c2 = increment()   // increment 能够访问 counter,其值为 1, 然后 counter 被更新成 2
const c3 = increment()   // increment 能够访问 counter,其值为 2, 然后 counter 被更新成 3
console.log('example increment', c1, c2, c3)  // example increment 1 2 3

3 闭包 use cases

  1. 信息隐藏

  2. 绑定 event handler 到元素

4 闭包的若干例子

4.1 对象数组排序

let users = [
  { name: "John", age: 20, surname: "Johnson" },
  { name: "Pete", age: 18, surname: "Peterson" },
  { name: "Ann", age: 19, surname: "Hathaway" }
];
function byField(field) {
  return function(a, b) {
    return a[field] > b[field] ? 1 : -1
  }
}
users.sort(byField('name'));
console.log(users)

users.sort(byField('age'));
console.log(users)

4.2 函数作为 filter 参数

function inBetween(a, b) {
  return function(x) {
    return x >= a && x <= b;
  };
}

let arr = [1, 2, 3, 4, 5, 6, 7];
alert( arr.filter(inBetween(3, 6)) ); // 3,4,5,6
function inArray(arr) {
  return function(x) {
    return arr.includes(x);
  };
}

let arr = [1, 2, 3, 4, 5, 6, 7];
alert( arr.filter(inArray([1, 2, 10])) ); // 1,2

4.3 一个 Counter 函数

function Counter() {
  let count = 0;

  this.up = function() {
    return ++count;
  };

  this.down = function() {
    return --count;
  };
}

let counter = new Counter();

alert( counter.up() ); // 1
alert( counter.up() ); // 2
alert( counter.down() ); // 1

4.4 一组函数

function makeArmy() {
  let shooters = [];

  // 每次循环都将创建一个新的词法环境
  for (let i = 0; i < 10; i++) {
    let shooter = function() { // create a shooter function,
      console.log(i); // that should show its number
    };
    shooters.push(shooter); // and add it to the array
  }

  // ...and return the array of shooters
  return shooters;
}

let army = makeArmy();

// all shooters show 10 instead of their numbers 0, 1, 2, 3...
army[0](); // 10 from the shooter number 0
army[1](); // 10 from the shooter number 1
army[2](); // 10 ...and so on.

4.5 油鹳上的例子

// Simple Closure
function f1(a) {
    let b = 2;
    setTimeout(function() {
        console.log(a, b)
    }, 1000)


// Closure with a problem
// 将 var 改成 let 就 ok,因为 var 作用域是整个 function
// 错误输出是: 3, 3, 3, 正确输出是 0, 1, 2
function f2() {
    for (var i = 0; i < 3; i++) {
        setTimeout(function() {
            console.log(i)
        }
    }
}

第2篇和第3篇最详细:

  1. What are the use cases for closures/callback functions in JavaScript?
  2. Variable scope, closure
  3. I never understood JavaScript closures
  4. JS Interview - Practical Closures - Question 7
  5. Master the JavaScript Interview: What is a Closure?
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值