三分钟看完JavaScript作用域与闭包

304 阅读3分钟

image

  • 写了挺久的也没写多少。
  • 所以说其实闭包是很简单的啊233。

正文

作用域

什么是作用域

作用域按词义理解可以理解为变量的作用范围,也就是在哪里才能够访问到这个变量。

function foo() {
    function bar() {
        var b = 2
    }
    console.log(b)  // b is not defined
}

以上代码中,函数foo就创建了一个作用域,foo内部的函数bar也创建了一个作用域。这时如果在foo内访问bar内的变量b,则会报错:b未被定义。这是因为bar的作用域确定了变量b只在bar的函数体内可以被访问。

function foo() {
    var a = 1
    function bar() {
        console.log(a)  // 1
    }
}

而如果在foo内定义变量a并在bar内调用的话则会正常访问并输出。这是因为foo和bar形成了嵌套的关系,而在作用域中查找变量时会首先从自身作用域开始寻找,如果没有找到则会继续跳转到父级作用域中继续查找,直到得到该变量或查找到全局作用域为止。而这种作用域层层嵌套的结构就被称为作用域链。

函数作用域和块级作用域

在ES6出现之前,JavaScript中没有块级作用域的概念,只有函数才能创建作用域,也就是函数作用域。

函数作用域如上文所示,只要创建了一个函数就会创建一个函数作用域,外界无法访问作用域内部的变量。而块级作用域则由一对花括号创建:

var foo = true

if (foo) {
    var a = 10
}

console.log(a)  // 10

以上代码看似为a创建了一个单独的作用域,然而因为JavaScript中没有块级作用域,导致在花括号外依然能够访问到a。这很令人困惑,而且可能会导致一些意想不到的问题。好在ES6解决了这个问题,使用let关键字来声明变量,就可以将变量绑定到块级作用域中。

var foo = true

if (foo) {
    let a = 10
}

console.log(a)  // a is not defined

词法作用域和动态作用域

简单来说,词法作用域取决于代码书写的位置,而动态作用域取决于代码运行的位置。JavaScript中采用的是词法作用域,记住这一点对理解闭包很有帮助。

闭包

什么是闭包?

var a = 10
function foo() {
    console.log(a)  // 10
}

这就是一个闭包。

当然这样是难以理解的,这里将它稍微修改一下:

function foo() {
    var a = 10
    function bar() {
        console.log(a)
    }
    return bar
}

var a = 20
var baz = foo()
baz()   // 10

首先按照错误的思路来理解这段代码:foo执行时声明一个函数bar并返回,这里将它赋值到变量baz上。foo执行结束后其内部作用域理应被销毁。这时再执行函数baz时需要访问变量a,于是就在当前作用域中找到a为20并输出。

然而实际上foo函数在执行结束后,由于bar函数内部存在对foo内变量a的引用,使得foo的内部作用域不会被销毁而是随着函数bar一同被保留了下来。当bar函数在外部被调用时,访问的也并非是它被调用时所在的作用域,而是它被定义时所在的作用域,也就是词法作用域

回到这部分第一句话,什么时候会产生闭包?

当一个函数拥有其自身作用域以外的变量的引用,这时就产生了闭包。

闭包基于词法作用域产生,只要掌握了作用域,也就理解了闭包。而实际编程中闭包也总是在不经意间被创建,我们只需要看到写完的代码,能知道 “啊,这是闭包” 就够了。

结语

推荐《你不知道的JavaScript(上卷)》