再谈JS闭包

简介: 作用域作用域嵌套词法作用域(lexicsl scope)闭包闭包示例


环境对人的塑造更持久,更有决定性 --《习惯的力量》

简明扼要

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

一图胜千言

文章概要

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

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

1. 作用域

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

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

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

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

在JS中,作用域由\

  1. 函数\
  2. 块级作用域生成

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

foo()函数作用域内,有权访问count变量;而在foo()函数作用域外,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. 作用域嵌套

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

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源代码**「编译阶段」**就将作用域之间的关系通过他们的位置关系确定下来了,而非执行阶段。

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. 闭包

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

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

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

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

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

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

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

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

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

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

5.2 Callbacks

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

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

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

例如:递归函数forEach()

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

5.3 函数式编程(柯里化)

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

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
复制代码

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


文章来源:dmitripavlutin.com/simple-expl…

相关文章
|
21天前
|
自然语言处理 JavaScript 前端开发
JavaScript中闭包:概念、用途与潜在问题
【4月更文挑战第22天】JavaScript中的闭包是函数及其相关词法环境的组合,允许访问外部作用域,常用于数据封装、回调函数和装饰器。然而,不恰当使用可能导致内存泄漏和性能下降。为避免问题,需及时解除引用,减少不必要的闭包,以及优化闭包使用。理解并慎用闭包是关键。
|
21天前
|
JavaScript
闭包(js的问题)
闭包(js的问题)
13 0
|
21天前
|
JavaScript 前端开发
解释JavaScript闭包的工作原理,并举例说明其在游戏开发中的应用。
JavaScript闭包允许内部函数访问并保持对外部函数变量的引用,即使外部函数执行结束。当函数返回内部函数时,形成闭包,继承父函数作用域链。在游戏开发中,闭包用于创建具有独立状态和行为的角色实例。例如,`createCharacter`函数创建角色并返回包含属性和方法的对象,内部函数如`getHealth`、`setHealth`和`attack`通过闭包访问并操作角色的变量。这种方式确保了每个角色的状态在不同实例间独立,是实现游戏逻辑的强大工具。
17 2
|
21天前
|
存储 缓存 JavaScript
|
20天前
|
JavaScript 前端开发
JavaScript 闭包:让你更深入了解函数和作用域
JavaScript 闭包:让你更深入了解函数和作用域
|
20天前
|
自然语言处理 JavaScript 前端开发
JavaScript闭包基础
JavaScript闭包基础
|
21天前
|
缓存 自然语言处理 JavaScript
JavaScript内存泄漏导致应用性能下降,常见于闭包使用不当
【5月更文挑战第14天】JavaScript内存泄漏导致应用性能下降,常见于闭包使用不当。闭包能记住并访问词法作用域,若函数返回后,其引用的对象未被释放,就会引发泄漏。例如,`createLeakyFunction`创建的闭包保留了对大型对象`someLargeObject`的引用,即使函数执行完毕,对象也无法被垃圾回收。避免泄漏的方法包括及时解除引用、清除事件监听器、使用WeakMap和WeakSet以及定期清理缓存。使用性能分析工具可检测和修复内存泄漏问题。
22 3
|
21天前
|
JavaScript 前端开发
JavaScript闭包允许内部函数访问并保留外部函数的变量,即使外部函数执行结束
【5月更文挑战第13天】JavaScript闭包允许内部函数访问并保留外部函数的变量,即使外部函数执行结束。在游戏开发中,闭包常用于创建独立状态的角色实例。例如,`createCharacter`函数生成角色,内部函数(如`getHealth`、`setHealth`)形成闭包,保存角色的属性(如生命值)。这样,每个角色实例都有自己的变量副本,不互相影响,从而实现角色系统的独立性。
23 0
|
21天前
|
自然语言处理 JavaScript 前端开发
深入理解JavaScript中的闭包机制
闭包是JavaScript中一个重要且常被误解的概念。本文将深入探讨闭包的本质、工作原理以及在实际开发中的应用。通过详细解析闭包的定义、作用域链、内存管理等方面,读者将对闭包有更清晰的理解,并能够运用闭包解决实际开发中的问题。
|
21天前
|
前端开发 JavaScript
闭包在JavaScript中有许多应用场景
【5月更文挑战第7天】闭包在JavaScript中发挥关键作用,如封装私有变量和函数提升安全性,维护变量生命周期,实现高阶函数,模拟块级作用域,支持回调函数以处理异步操作,以及促进模块化编程,增强代码组织和管理。闭包是理解和掌握JavaScript高级特性的重要一环。
30 7