前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >再谈JS闭包

再谈JS闭包

作者头像
前端柒八九
发布2022-08-25 14:17:25
2.8K0
发布2022-08-25 14:17:25
举报
文章被收录于专栏:柒八九技术收纳盒

简明扼要

  1. 作用域(scope)控制着变量的可见性和生命周期
  2. 在JS中,生成作用域 1. 函数 2. 块级作用域
  3. 不同的作用域能够拥有同名的变量
  4. 外部作用域的变量可以在内部作用域中访问
  5. JS通过「词法作用域」(静态作用域)来实现变量查询机制
  6. 「闭包(closure)是一个函数」:其有权访问其词法作用域内部的变量即使该函数在词法作用域外部被调用
  7. 常规的闭包生成方式 1. Event handler(事件处理器) 2. callback(回调函数) 3. 函数式编程-柯里化

一图胜千言

文章概要

  1. 作用域
  2. 作用域嵌套
  3. 词法作用域(lexicsl scope)
  4. 闭包
  5. 闭包示例

在进行闭包讲解之前,我们需要对一些前置知识点,做一些简单的介绍:何为作用域 和 词法作用域。只有在了解了这些概念,我们才会对闭包的认识有的放矢。

1. 作用域

当你定义一个变量,你想要该变量在某些范围内是「可访问的」。例如:在fnnc()内部定义一个变量 result,该变量只能在函数内部可见;而在函数外部是不可访问的。

「作用域(scope)管理变量的是否被有权访问」

❝作用域就是变量与函数的可访问范围,即作用域控制着变量的「可见性」「生命周期」

你可以在作用域「内」访问在该作用域内定义的变量,而在作用域「外」,无法访问该变量。

❝在JS中,作用域由 1. 函数 2. 块级作用域生成 ❞

代码语言:javascript
复制
function foo() {
  // 函数作用域
  let count = 0;
  console.log(count); // 有权访问count变量
}
foo();
// 函数作用域外访问作用域内的变量
console.log(count); // ReferenceError: count is not defined

foo()函数作用域内,有权访问count变量;而在foo()函数作用域外,count变量是不能够被访问的。

如果你在函数内部或者块级作用域内定义了一个变量,你只能在函数内部或块级作用域内部访问该变量。

一图胜千言

我们得出一个结论:「作用域是一个空间策略:控制的变量的可访问性」

针对不同的作用域,我们还可以得出如下结论:

❝不同的作用域能够拥有同名的变量 ❞

代码语言:javascript
复制
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. 作用域嵌套

由上文的作用域介绍所知,在JS中每个函数或者块级作用域都会产生与其对应的作用域对象。而我们在平时开发中,经常会看到多个函数嵌套的现象。例如:函数innerFunc()内嵌在函数outerFunc()中。

代码语言:javascript
复制
function outerFunc() {
  // 外部作用域
  let outerVar = 'I am outside!';
  function innerFunc() {
    // 内部作用域
    console.log(outerVar); // => 输出 "I am outside!"
  }
  innerFunc();
}
outerFunc();

通过代码可知,outerVar能够在内部作用域innerFunc()中可见。

❝外部作用域的变量可以在内部作用域中访问 ❞

一图胜千言

从上面的示例中我们可以得出两个结论

  1. 作用域可以嵌套
  2. 外部作用域的变量可以在内部作用域中访问

3. 词法作用域(lexicsl scope)

❝JS通过「词法作用域」(静态作用域)来实现作用域查询机制。 ❞

词法作用域意味着变量的可访问性由变量在嵌套作用域中的位置决定。之所以叫词法(静态)作用域,是因为JS引擎(V8)在JS源代码「编译阶段」就将作用域之间的关系通过他们的位置关系确定下来了,而非执行阶段。

代码语言:javascript
复制
const myGlobal = 0;
function func() {
  const myVar = 1;
  console.log(myGlobal); // 输出 "0"
  function innerOfFunc() {
    const myInnerVar = 2;
    console.log(myVar, myGlobal); // 输出 "1 0"
    function innerOfInnerOfFunc() {
      console.log(myInnerVar, myVar, myGlobal); // 输出 "2 1 0"
    }
    innerOfInnerOfFunc();
  }
  innerOfFunc();
}
func();

innerOfInnerOfFunc()的词法作用域包含innerOfFunc(),func()和全局作用域(最外层的作用域)。所以,在innerOfInnerOfFunc()中你有权访问myInnerVar, myVarmyGlobal

4. 闭包

词法作用域允许「静态地」访问外部作用域的变量,这个定律仅差一步就能实现闭包。

代码语言:javascript
复制
function outerFunc() {
  let outerVar = 'I am outside!';
  function innerFunc() {
    console.log(outerVar); // => 输出 "I am outside!"
  }
  innerFunc();
}
outerFunc();

innerFunc()作用域中,有权访问外部作用域的变量(outerVar)。

innerFunc()函数调用发生在词法作用域内(outerFunc())。

将代码进行改动,将innerFunc()的调用移动到词法作用域外部:在ecec()中执行。

代码语言:javascript
复制
function outerFunc() {
  let outerVar = 'I am outside!';
  function innerFunc() {
    console.log(outerVar); // => 输出 "I am outside!"
  }
  return innerFunc;
}

function exec() {
  const myInnerFunc = outerFunc();
  myInnerFunc();
}
exec();

innerFunc()在它的词法作用域外部被执行,也就是在exec()作用域内被执行。

innerFunc()仍然有权访问存在其词法作用域内部的变量(outerVar),甚至能够在其词法作用域外部被调用。

换句话说,innerFunc()「记住了」(closes over)来自它词法作用域内的变量(outerVar)。

innerFunc()是一个闭包:它记住了来自它词法作用域内的变量(outerVar)。

一图胜千言

我们可以得出如下结论

「闭包(closure)是一个函数」:其有权访问其词法作用域内部的变量即使该函数在词法作用域外部被调用 ❞

更简单的讲:闭包是一个函数,它会从定义它的地方记住变量,而不管它稍后在哪里执行。

有一个识别闭包的经验:如果函数内部存在外部变量,那么该函数就是一个闭包,因为外部变量已经被「记住了」

5. 闭包示例

5.1 Event handler

代码语言:javascript
复制
let countClicked = 0;
myButton.addEventListener('click', function handleClick() {
  countClicked++;
  myText.innerText = `You clicked ${countClicked} times`;
});

执行如上代码,myText的文本显示的是按钮被点击的次数。

当按钮被点击,handleClick()是在DOM节点的范围内被执行。「函数执行和函数定义的地方大相径庭」

但是,由于handleClick()是一个闭包,所以,它能够记住(捕获)对应词法作用域中的变量countClicked,并且在点击按钮的时候,更新该变量的值。

5.2 Callbacks

在回调函数中,也存在变量捕获的情况。例如:setTimeout的回调函数

代码语言:javascript
复制
const message = 'Hello, World!';
setTimeout(function callback() {
  console.log(message); //输出 "Hello, World!"
}, 1000);

callback是一个闭包,它捕获了message外部变量。

例如:递归函数forEach()

代码语言:javascript
复制
let countEven = 0;
const items = [1, 5, 100, 10];
items.forEach(function iterator(number) {
  if (number % 2 === 0) {
    countEven++;
  }
});
countEven; // => 2

5.3 函数式编程(柯里化)

柯里化技术,主要体现在函数里面返回函数。就是将多变量函数拆解为单变量(或部分变量)的多个函数并依次调用。

代码语言:javascript
复制
function multiply(a) {
  return function executeMultiply(b) {
    return a * b;
  }
}
const double = multiply(2);
double(3); // => 6
double(5); // => 10
const triple = multiply(3);
triple(4); // => 12

利用闭包,可以形成一个不销毁的私有作用域,把预先处理的内容都存在这个不销毁的作用域里面,并且返回一个函数,以后要执行的就是这个函数。

文章来源:https://dmitripavlutin.com/simple-explanation-of-javascript-closures/

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2022-02-21,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 前端柒八九 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 简明扼要
  • 一图胜千言
  • 文章概要
  • 1. 作用域
    • 一图胜千言
    • 2. 作用域嵌套
      • 一图胜千言
      • 3. 词法作用域(lexicsl scope)
      • 4. 闭包
        • 一图胜千言
        • 5. 闭包示例
          • 5.1 Event handler
            • 5.2 Callbacks
              • 5.3 函数式编程(柯里化)
              领券
              问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档