JavaScript 系列之闭包(一)

这是我参与8月更文挑战的第8天,活动详情查看:8月更文挑战markdown

1、闭包的定义

MDN 对闭包的定义为:闭包

闭包是指那些可以访问自由变量的函数app

那什么是自由变量呢?异步

自由变量是指在函数中使用的,但既不是函数参数也不是函数的局部变量的变量。ide

由此,咱们能够看出闭包共有两部分组成:函数

闭包 = 函数 + 函数可以访问的自由变量post

简单来说:ui

闭包就是可以读取其余函数内部变量的函数lua

在函数 A 中还有函数 B,函数 B 调用了函数 A 中的变量,那么函数 B 就称为函数 A 的闭包。url

function foo() {
  let a = 2;
  function bar() {
    console.log(a);
  }
  bar();
}
foo(); // 2
复制代码

2、循环和闭包

for (var i = 0; i < 5; i++) {
  setTimeout(function () {
    console.log("i: ", i);
  }, 1000);
}
console.log(i);

// 输出
// 5;
// i: 5;
// i: 5;
// i: 5;
// i: 5;
// i: 5;
复制代码

image.png

要理解上述代码,首先咱们得了解一下概念:

  • JS 分为同步任务异步任务
  • 同步任务都在主线程上执行,造成一个执行栈
  • 主线程以外,事件触发线程管理着一个任务队列,只要异步任务有了运行结果,就在任务队列之中放置一个事件。
  • 一旦执行栈中的全部同步任务执行完毕(此时JS引擎空闲),系统就会读取任务队列,将可运行的异步任务添加到可执行栈中,开始执行。

上述代码中使用定时器,当 JS 引擎线程执行到该段代码,便把定时器放到定时器线程去计时,此时 JS 引擎线程执行同步栈里面的任务。当定时器计时完成以后,将回调函数推入消息队列。等待栈中的代码执行完毕以后会去读取消息队列中的事件。

因为 JS 的函数做用域,当回调函数被推入消息队列的时候没有带上参数。for 循环结束以后,由于 i 是用 var 定义的,因此 var 是全局变量(这里没有函数,若是有就是函数内部的变量),这个时候的 i 是 5。

image.png

如何解决?

// let 是块级做用域,当前块是循环体
for (let i = 0; i < 5; i++) {
  setTimeout(function() {
    console.log('i: ',i);
  }, 1000);
}
console.log(i);

// 输出
// 5
// i: 0
// i: 1
// i: 2
// i: 3
// i: 4
复制代码
//这是函数级做用域,利用闭包模拟块级做用域
for (var i = 0; i < 5; i++) {
  (function(){
    var j = i;
    setTimeout(function() {
      console.log('i: ',j);
    }, 1000);
  })();
}
console.log(i);

// 输出
// 5
// i: 0
// i: 1
// i: 2
// i: 3
// i: 4
复制代码
// 或者采起传参的方式,使用了闭包
for (var i = 0; i < 5; i++) {
  (function(j){
    setTimeout(function() {
      console.log('i: ',j);
    }, 1000);
  })(i);
}
console.log(i);

// 输出
// i is not defined
// i: 0
// i: 1
// i: 2
// i: 3
// i: 4
复制代码
// setTimeout 的第三个参数
for (var i = 0; i < 5; i++) {
  setTimeout(function timer(j) {
    console.log('i: ', j);
  }, 1000, i);
}
console.log(i);

// 输出
// i is not defined
// i: 0
// i: 1
// i: 2
// i: 3
// i: 4
复制代码
相关文章
相关标签/搜索