乐趣区

关于javascript:细说JavaScript闭包

JavaScript 闭包难点分析

一、作用域根本介绍

ES6 之前只有全局作用域与函数作用域两种,ES6 呈现之后,新增了块级作用域

1. 全局作用域

在 JavaScript 中,全局变量是挂载在 window 对象下的变量,所以在网页中的任何地位你都能够应用并且拜访到这个全局变量

  • 当咱们定义很多全局变量的时候,会容易引起变量命名的抵触,所以在定义变量的时候应该留神作用域的问题
var globalName = 'global'
function getName() {console.log(globalName) // global
  var name = 'inner'
  console.log(name) // inner
}
getName()
console.log(name) // 报错
console.log(globalName) // global
function setName() {vName = 'setName'}
setName()
console.log(vName) // setName
console.log(windwo.vName) // setName

2. 函数作用域

在 JavaScript 中,函数定义的变量叫作函数变量,这个时候只能在函数外部能力拜访到它,所以它的作用域也就是函数的内存,称为函数作用域

  • 当这个函数被执行完之后,这个局部变量也相应会被销毁。所以你会看到在 getName 函数里面的 name 是拜访不到的
function getName() {
  var name = 'inner'
  console.log(name) // inner
}
getName()
console.log(name) // 报错

3. 块级作用域

ES6 新增了块级作用域,最间接的体现就是新增的 let 关键词,应用 let 关键词定义的变量只能在块级作用域中被拜访,有 ” 暂时性死区 ” 的特定,也就是说这个变量在定义之前是不能被应用的。

  • if 语句及 for 语句前面的 {…} 这外面所包含的,就是块级作用域
console.log(a) // a is not defined
if (true) {
  let a = '123'
  console.log(a) // 123
}
console.log(a) // a is not defined

二、什么是闭包?

红宝书:闭包是指有权拜访另外一个函数作用域中的变量的函数
MDN:一个函数和对其四周状态的援用捆绑在一起(或者说函数被援用突围),这样的组合就是闭包。也就是说,闭包让你能够在一个内层函数中拜访到其外层函数的作用域。

1. 闭包的基本概念

  • 闭包其实就是一个能够拜访其余函数外部变量的函数。即一个定义在函数外部的函数,或者间接说闭包是个内嵌函数也能够。
  • 因为通常状况下,函数外部变量是无奈在内部拜访的(即全局变量和局部变量的区别),因而应用闭包的作用,就具备实现了能在内部拜访某个函数外部变量的性能,让这些外部变量的值始终能够保留在内存中。
function fun1() {
  var a = 1
  return function () {console.log(a)
  }
}
fun1()
var result = fun1()
result() // 1

2. 闭包产生的起因

当拜访一个变量时,代码解释器会首先在以后的作用域查找,如果没找到,就去父级作用域去查找,直到找到该变量或者不存在父级作用域中,这样的链路就是作用域链。

var a = 1
function fun1() {
  var a = 2
  function fun2() {
    var a = 3
    console.log(a) // 3
  }
}
// fun1 函数的作用域指向全局作用域(window)和它本人自身;fun2 函数的作用域指向全局作用域(window)、fun1 和它自身;而作用域是从最底层向上找,直到找到全局作用域 window 为止,如果全局还没有的话就会报错

function fun1() {
  var a = 2
  function fun2() {console.log(a) // 2
  }
  return fun2
}
var result = fun1()
result()
// 那是不是只有返回函数才算是产生了闭包呢?其实也不是,回到闭包的实质,** 咱们只须要让父级作用域的援用存在即可 **

var fun3
function fun1() {
  var a = 2
  fun3 = function () {console.log(a)
  }
}
fun1()
fun3()

  • 闭包产生的实质:以后环境中存在指向父级作用域的援用

3. 闭包的表现形式

  1. 返回一个函数,下面将起因的时候曾经说过,这里就不在赘述了
  2. 在定时器、事件监听、Ajax 申请、Web Workers 或者任何异步中,只有应用了回调函数,实际上就是在应用闭包。
// 2.1 定时器
setTimeout(function handler() {console.log('1')
}, 1000)
// 2.2 事件监听
$('app').click(function () {console.log('Event Listener')
})
  1. 作为函数参数传递的模式,比方上面的例子
// 3. 作为函数参数传递的模式
var a = 1
function foo() {
  var a = 2
  function baz() {console.log(a)
  }
  bar(baz)
}
function bar(fn) {
  // 这就是闭包
  fn()}
foo() // 输入 2,而不是 1
  1. IIFE(立刻执行函数),创立了闭包,保留了全局作用域(window)和以后函数的作用域,因而能够输入全局的变量,如下所示。
// 4.IIFE(立刻执行函数)var a = 2
(function IIFE() {console.log(a) // 输入 2
})()
  • IIFE 这个函数会略微有些非凡,算是一种自执行匿名函数,这个匿名函数领有独立的作用域。这不仅能够防止了外界拜访此 IIFE 中的变量,而且又不会净化全局作用域,咱们常常能在高级的 JavaScript 编程中看见此类函数。

三、如何解决循环输入问题?

for (var i = 1; i <= 5; i++) {setTimeout(function () {console.log(i)
  }, 0)
}
// 顺次输入 5 个 6
  1. setTimeout 为宏工作,因为 JS 中单线程 eventLoop 机制,在主线程同步工作执行完后才去执行宏工作,因而循环完结后 setTimeout 中的回调才顺次执行
  2. 因为 setTimeout 函数也是一种闭包,往上找它的父级作用域就是 window,变量 i 为 window 上的全局变量,开始执行 setTimeout 之前变量 i 曾经是 6 了,因而最初输入的间断都是 6
  3. 参考视频解说:进入学习

1. 利用 IIFE

利用 IIFE,当每次 for 循环时,把此时的变量 i 传递到定时器中,而后执行

for (var i = 1; i <= 5; i++) {(function (j) {setTimeout(function timer() {console.log(j)
    }, 0)
  })(i)
}

2. 应用 ES6 中的 let

let 让 JS 有了块级作用域,代码的作用域以块级为单位进行执行。

for(let i = 1; i <= 5; i++) {setTimeout(function() {console.log()
  },0)
}

3. 定时器传入第三个参数

setTimeout 作为常常应用的定时器,它是存在第三个参数的,日常工作中咱们常常应用的个别是前两个,一个是回调函数,另外一个是工夫,而第三个参数用得比拟少。

for(var i=1;i<=5;i++) {setTimeout(function(j) {console.log(j)
  },0,i)
}
  • 第三个参数的传递,扭转了 setTimeout 的执行逻辑,从而实现咱们想要的后果,这也是一种解决循环输入问题的路径
退出移动版