Javascript深入之作用域与闭包

相信绝大多数同学都听过闭包这个概念,但闭包具体是什么估计很少有人能够说的很详细。说实话闭包在我们平时开发中应该是很常见的,并且在前端面试中闭包也是常见的重要考点,在学习闭包之前我们先来看看作用域与作用域链,因为这是闭包的关键。

作用域

简单来说,作用域是指程序中定义变量的区域,它决定了当前执行代码对变量的访问权限

在ES5中,一般只有两种作用域类型:

  • 全局作用域:全局作用域作为程序的最外层作用域,一直存在
  • 函数作用域:函数作用域只有在函数被定义时才会被创建,包含在父级函数作用域或全局作用域中

说完概念,我们来看下面这段代码:

1
2
3
4
5
6
7
8
9
var a = 100
function test(){
var b = a * 2
var a = 200
var c = a/2
console.log(b)
console.log(c)
}
test() // 这里会打印出什么?

解析:

1.首先这段代码形成了全局作用域与函数作用域

2.全局作用域有一个变量a赋值为100

3.在test函数作用域中定义了局部变量b,a,c

4.这里又存在变量提升,在函数作用域内先进行变量提升var b; var a; var c;

5.再对b进行赋值,这时候a还没有被赋值,所以a的值为undefined,再将a*2,所以b为NaN

6.再给a赋值为200,c赋值为a/2等于100

所以最终会打印出 NaN,100

在ES6中,新增了一种块级作用域

简单来说,花括号{...}内的区域就是块级作用域,但Javascript并不是原生支持块级作用域的,需要借助ES6提出的letconst来创建块级作用域

1
2
3
4
5
6
7
8
9
10
11
// ES5
if(true) {
var name = '南玖'
}
console.log(name) // 南玖

// ES6
if(true) {
let age = 18
}
console.log(age) // 这里会报错

作用域链

当可执行代码内部访问变量时,会先查找当前作用域下有无该变量,有则立即返回,没有的话则会去父级作用域中查找…一直找到全局作用域。我们把这种作用域的嵌套机制称为作用域链

词法作用域

词法作用域是作用域的一种工作模型,词法作用域是JavaScript中使用的一种作用域类型,词法作用域也可以被叫做静态作用域

所谓的词法作用域就是在你写代码时将变量和作用域写在哪里来决定的,也就是词法作用域是静态的作用域,在你写代码时就决定了。函数作用域取决于它申明的位置,与实际调用的位置无关

MDN对闭包的定义:

一个函数和对其周围(词法环境)的引用捆绑在一起(或者说函数被引用包围),这样一个组合就是闭包(closure

也就是说,闭包让你可以在一个内层函数中访问到其外层函数的作用域。在JavaScript中,每当创建一个函数,闭包就会在函数创建的同时被创建出来。

我们可以得出:

闭包 = 函数 + 外层作用域

我们先来看段代码:

1
2
3
4
5
6
var name = '前端南玖'

function say() {
console.log(name)
}
say()

解析:say函数可以访问到外层作用域的变量a,那么这样不就是形成了一个闭包吗?

在《Javascript权威指南》书中有这样一句话:严格来讲,所以JavaScript函数都是闭包

但这只是理论上的闭包,与我们平时使用的不太一样。上面这个例子只是一个简单的闭包。

ECMAScript对闭包的定义:

  • 从理论上来讲:所有函数都是闭包。因为它们在创建的时候就已经上层上下文的数据保存起来了。
  • 从实践上来讲:闭包应该满足两个条件:1.在代码中引用了外层作用域的变量;2.即使创建它的上下文已经销毁,它仍然存在;

我们再看一段《JavaScript权威指南》上的代码:

1
2
3
4
5
6
7
8
9
10
11
let scope = 'global scope'
function checkscope(){
let scope = 'local scope'
function f(){
return scope
}
return f
}

let s = checkscope()
s() // 这里返回什么?

很多同学可能觉得是global scope,但真的是这样吗,我们一起来看下它的执行过程:

1.首先执行全局代码,创建全局执行上下文,定义全局变量scope并赋值

2.申明checkscope函数,并创建该函数的执行上下文,创建局部变量scope并赋值

3.申明f函数,创建该函数的执行上下文

4.执行checkscope函数,该函数又返回了一个f函数赋值给了变量s

5.执行s函数,相当于执行了f函数。这里返回的scope是local scope。至于为什么是local scope,我们上面讲到了词法作用的基本规则:JavaScript函数是使用定义它们的作用域来执行的。在定义f函数的作用域中,变量scope的值为local scope

闭包的应用

闭包的应用,绝大多是都是在维护内部变量的场景下使用

闭包的缺陷

  • 由于闭包的存在可能会造成变量常驻内存,使用不当会造成内存泄漏
  • 内存泄漏可能会导致应用程序卡顿或崩溃

高频闭包面试题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
var arr = []
for(var i=0;i<3;i++){
arr[i] = function(){
console.log(i)
}
}
arr[0]() // 3
arr[1]() // 3
arr[2]() // 3
// 这里在执行的时候i已经变成了3

// 使用闭包解决
var arr = []
for(var i=0;i<3;i++){
arr[i] = (function(i){
return function(){
console.log(i)
}
})(i)

}
arr[0]() // 0
arr[1]() // 1
arr[2]() // 2
songyao wechat
扫一扫,欢迎关注前端南玖~
坚持技术分享,您的支持将鼓励我继续创作~
0%