Javascript作用域和闭包

122 阅读2分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第1天,点击查看活动详情

大家好!我是前端爬楼工程师🚹,一个野生程序员。好奇新技术,仰慕牛大佬。如果喜欢我的文章,可以关注➕点赞,为我注入能量,与我一同成长吧~

Javascript能够在浏览器端运行,离不开三样东西,分别是:v8引擎,编译器,作用域。

一切由var a = 1开始:

编译器

首先是编译器工作:

  • 将以上代码拆分var a = 1四个部分,这个步骤叫分词,也叫词法分析;
  • 根据分词解析成AST,其结构类似与JS对象{声明:{变量:{赋值:{值:}}}},这个步骤叫解析或者语法分析;
  • 最后根据AST生成可执行的字节码,由引擎运行起来, 这个步骤叫代码生成;

作用域

编译完毕之后,就是引擎来执行代码了,执行代码的过程需要作用域来辅助。

在此之前,我们了解一下作用域的范围,作用域范围如图所示,是嵌套的。

image.png

  • 全局作用域有foo
  • foo作用域下有abar
  • bar作用域下游b

执行var a,首先引擎会通过作用域使用LHS寻找a这个变量,因为变量是用var修饰的,所以就会在当前作用域创建这个变量a,如果没有修饰则会挂在window下。(严格模式会创建失败并抛出ReferenceError

接着执行a = 1, 这个时候作用域会使用RHS查询a这个变量,它的作法和LHS不一样,它如果找不到直接抛出ReferenceError,并不会创建变量,如果找到了变量,将1赋值给a;如果此时有代码a(),这时会抛出TypeError

闭包

通常呢,函数在执行完毕之后就会被垃圾回收,如下所示:

function getList(){
    // code ...
}
getList()

但是如果是这样呢:

function tools(){
    var transfer = function(){}
    var isNull = function(){}
    return {
        transfer: transfer,
        isNull: isNull
    }
}

var myTools = tools()
myTools.transfer()
myTools.isNull()

我们顺着上面的思维,tools执行完毕,是要被垃圾回收的,然而它的返回值被myTools接收, 相当于tools的作用域被myTools保护起来了,我们称之为闭包,myTools可以在其作用域范围内的任何位置都可以使用其内部作用域的属性。

类似的回调函数也是一种闭包:

 function foo(cb){
     cb()
 }
var a = 0 
function bar(){
  console.log(a)
}
foo(bar)

这里可以总结一下: 当函数可以记住并能访问所在词法作用域,即使函数是在当前词法作用域之外执行,这时就产生了闭包。