JavaScript 中的函数(二)闭包是怎么形成的
本文最后更新于 339 天前,如有失效请评论区留言。

前言

函数在 JavaScript 是非常重要的知识,在上一节介绍过了 JavaScript 中一些常见的函数,本节继续聊聊相关的函数应用,其中最重要的就是函数闭包

函数作用域

在函数内定义的变量不能在函数之外的任何地方访问,因为变量仅仅在该函数的作用域内定义。相对应的,一个函数可以访问定义在其范围内的任何变量和函数。

比如说,在函数 multiply 内部可以访问其外部的全局变量 num1 和 num2

const num1 = 5;
const num2 = 2;

// 此函数定义在全局作用域中
function multiply() {
  const sum = num1 * num2;
  return sum;
}
console.log(multiply());
console.log(sum);

// 输出
> 10
> ReferenceError: sum is not defined

但是,在函数外部无法访问函数内容的变量 sum,这就是函数的作用域

嵌套函数

既然变量可以在函数的内部进行定义,而函数作为一等公民(first-class) 也可以像变量一样定义在函数的内容,即一个函数里面嵌套另外一个函数,这种函数就是嵌套函数

下面这个函数实现了计算一个数的平方,

function square(x) {
  return function cal() {
    return x*x
  }
}
console.log(square(4)());

// 输出
> 16

square(4) 返回的是 square 函数内部的函数 cal 的一个对象,再通过()来执行 cal 函数

转换下如下

function square(x) {
  var cal = function(){
    return x*x
  };

  return cal;
}

var testSquare = square(4);
console.log(testSquare.name)
console.log(testSquare);
console.log(testSquare());

// 输出
> cal
> function(){ return x*x }
> 16

testSquare 的 name 等于 cal, 所以被嵌套的函数就是一个函数表达式,
testSquare 也就是 cal 函数的函数表达式的一个对象,想想第一节中函数表达式怎么执行的,是不是一样的

闭包

还是上面的例子,

function square(x) {
  var cal = function(){
    return x*x
  };

  return cal;
}

var testSquare = square(4);
console.log(testSquare);
console.log(testSquare());
console.log(x);

// 输出
> function(){ return x*x }
> 16
> ReferenceError: x is not defined

发现一个问题没有,x 是 square 函数的局部变量,在函数外部直接打印提示 x is not defined,而 testSquare 等于 function(){ return x*x } ,那为什么外部执行 testSquare() 还能使用使用这个变量 x 呢?

这里就要引出一个函数闭包的概念了

闭包(closure)是一个函数以及其捆绑的周边环境状态(lexical environment词法环境)的引用的组合

简单来说就是虽然函数不在定义的词法作用域内被调用,但是仍然可以访问词法作用域中定义的变量。即有权访问另一个函数作用域中的变量

这个解释好像还是不太明白,我们知道正常情况下,函数的局部变量在其执行完毕后就会被销毁,以上面这个例子来说,变量 x 在执行完 square(4) 后还保留在内存当中,内部函数cal可以引用外部函数的参数和局部变量,返回的内部函数testSquare中保存了其外部函数square 的参数和变量,因此 testSquare 就是一个闭包

闭包有三步:

  1. 外层函数嵌套内层函数
  2. 内层函数使用外层函数的局部变量
  3. 把内层函数作为外层函数的返回值

经过这样的三步就可以形成一个闭包! 闭包就可以在全局函数里面操作另一个作用域的局部变量!闭包既能重复使用局部变量,又不污染全局

闭包的作用

我们上一节中提到了使用立即执行函数(IIFE)来解决变量提升的问题,其实这就是闭包的一种应用

封装块级作用域

for (var i = 0; i < 3; i++) {
  const button = document.createElement("button");
  button.innerText = `Button ${i}`;
  // IIFE 写法
  (function (ii){
    button.onclick = function() {
      console.log(ii)
    }
  })(i)
  document.body.appendChild(button);
}

是不是很熟悉了,IIFE 函数中,每次循环的的参数 i 依然可以被内部函数 button.onclick 保留到自己的作用域中

私有化变量

这里直接使用 MDN 上的一个例子

var Counter = (function () {
  var privateCounter = 0;
  function changeBy(val) {
    privateCounter += val;
  }
  return {
    increment: function () {
      changeBy(1);
    },
    decrement: function () {
      changeBy(-1);
    },
    value: function () {
      return privateCounter;
    },
  };
})();

console.log(Counter.value()); /* logs 0 */
Counter.increment();
Counter.increment();
console.log(Counter.value()); /* logs 2 */
Counter.decrement();
console.log(Counter.value()); /* logs 1 */

上述示例创建了一个词法环境,为三个函数所共享:Counter.incrementCounter.decrement 和 Counter.value

该共享环境创建于一个立即执行的匿名函数体内。这个环境中包含两个私有项:名为 privateCounter 的变量和名为 changeBy 的函数。这两项都无法在这个匿名函数外部直接访问。必须通过匿名函数返回的三个公共函数访问。

闭包的缺点

闭包引用了外部函数的作用域,所以滥用闭包会有内存问题
让函数的变量都保存在内存中,内存消耗变大。使用不当会造成内存泄漏

版权声明:除特殊说明,博客文章均为Gavin原创,依据CC BY-SA 4.0许可证进行授权,转载请附上出处链接及本声明。
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇