再谈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…

相关文章
|
1月前
|
自然语言处理 JavaScript 前端开发
深入理解JavaScript中的闭包:原理与实战
【10月更文挑战第12天】深入理解JavaScript中的闭包:原理与实战
|
20天前
|
JavaScript 前端开发
js 闭包的优点和缺点
【10月更文挑战第27天】JavaScript闭包是一把双刃剑,在合理使用的情况下,它可以带来很多好处,如实现数据封装、记忆功能和模块化等;但如果不注意其缺点,如内存泄漏、变量共享和性能开销等问题,可能会导致代码出现难以调试的错误和性能问题。因此,在使用闭包时,需要谨慎权衡其优缺点,根据具体的应用场景合理地运用闭包。
105 58
|
20天前
|
缓存 JavaScript 前端开发
js 闭包
【10月更文挑战第27天】JavaScript闭包是一种强大的特性,它可以用于实现数据隐藏、记忆和缓存等功能,但在使用时也需要注意内存泄漏和变量共享等问题,以确保代码的质量和性能。
35 7
|
22天前
|
自然语言处理 JavaScript 前端开发
JavaScript闭包:解锁编程潜能,释放你的创造力
【10月更文挑战第25天】本文深入探讨了JavaScript中的闭包,包括其基本概念、创建方法和实践应用。闭包允许函数访问其定义时的作用域链,常用于数据封装、函数柯里化和模块化编程。文章还提供了闭包的最佳实践,帮助读者更好地理解和使用这一强大特性。
14 2
|
1月前
|
设计模式 JavaScript 前端开发
探索JavaScript中的闭包:从基础概念到实际应用
在本文中,我们将深入探讨JavaScript中的一个重要概念——闭包。闭包是一种强大的编程工具,它允许函数记住并访问其所在作用域的变量,即使该函数在其作用域之外被调用。通过详细解析闭包的定义、创建方法以及实际应用场景,本文旨在帮助读者不仅理解闭包的理论概念,还能在实际开发中灵活运用这一技巧。
|
1月前
|
缓存 JavaScript 前端开发
深入了解JavaScript的闭包:概念与应用
【10月更文挑战第8天】深入了解JavaScript的闭包:概念与应用
|
1月前
|
自然语言处理 JavaScript 前端开发
Javascript中的闭包encloure
【10月更文挑战第1天】闭包是 JavaScript 中一种重要的概念,指函数能够访问其定义时的作用域内的变量,即使该函数在其词法作用域之外执行。闭包由函数及其词法环境组成。作用域链和词法作用域是闭包的核心原理。闭包常用于数据隐藏和封装,如模块模式;在异步操作中也广泛应用,如定时器和事件处理。然而,闭包也可能导致内存泄漏和变量共享问题,需谨慎使用。
|
1月前
|
自然语言处理 JavaScript 前端开发
深入理解JavaScript中的闭包:原理、应用与代码演示
【10月更文挑战第12天】深入理解JavaScript中的闭包:原理、应用与代码演示
|
1月前
|
自然语言处理 JavaScript 前端开发
深入理解JavaScript闭包:原理与应用
【10月更文挑战第11天】深入理解JavaScript闭包:原理与应用
19 0
|
2月前
|
JSON JavaScript 前端开发
JavaScript第五天(函数,this,严格模式,高阶函数,闭包,递归,正则,ES6)高级
JavaScript第五天(函数,this,严格模式,高阶函数,闭包,递归,正则,ES6)高级
下一篇
无影云桌面