JS执行环境与作用域

Posted by Mars at

JS函数词法环境和作用域问题,还有var和let的区别问题。

字节跳动面试

一、 作用域

JS有三种作用域:块级作用域函数作用域全局作用域

if{}、for(){}、while(){}和普通的{}都会创建块级作用域。

for{}和while{}循环,相当于多次执行了块级作用域创建,并在新的块作用域内修改变量。

函数内部是函数作用域,全局环境是全局作用域。

二、词法环境

块级作用域(代码块)、函数乃至全局作用域在创建的时候,都存在一个与之关联的词法环境(Lexical Environment)内部对象。

词法环境对象由三部分组成:1.当前环境的所有局部变量(变量名作为属性,值作为属性值);2.this的值 3.外部环境的引用。

修改词法环境下的变量,相当于修改词法环境对象上对应名称的属性值。

全局词法环境,外部环境的引用为null。其他词法环境通过层层引用,最外层会引用到全局词法环境。

每创建一个新的词法环境,就会自动记录当前环境的局部变量,this指向,以及对外层词法环境的引用。

在某一个词法环境下,访问一个变量的时候,先寻找内部词法环境,然后逐层向外寻找,直到找到全局环境。

函数在声明的时候,会通过内部的[[environment]]属性记录下它创建时候的词法环境,然后在执行的时候才会创建内部的词法环境,并把外部环境的引用设定为[[environment]]指定的词法环境对象。

三、for、while循环内声明函数

for{}和while{}循环,相当于多次创建了块级作用域,并在新的块作用域内修改变量。

因此每一个在for、while循环内声明的函数,都通过[[environment]]记录下当前的外部词法环境,也就是不同的块级作用域,因此当它们在执行的时候,创建的内部词法环境引用的也是不同的外部块词法环境。这也就解释了如下的代码:

for (let i = 0; i < 10; i++){
  setTimeout(() => {
    console.log(i)
  })
}

// 0,1,2,3,4,5,6,7,8,9
// let声明的i,绑定在块级作用域词法环境内,相当于有如下词法环境{i:0}、{i:1}、{i:2}、{i:3}...
// setTimeout内部声明的箭头函数的[[environment]],记录了每次迭代时外部的块级作用域词法环境。
// 当后续这个箭头函数按序执行的时候,创建函数内部的词法环境,外部引用的是创建时[[environment]]记录的外部块级作用域的词法环境,因此log的是每个词法环境下的i,也就是0-9

三、var和let的区别

1. 是否有块级作用域

var只能存在函数作用域和全局作用域,没有块级作用域。在块级作用域内部声明的var,会穿透块级作用域泄漏到全局。

let可以有块级作用域,块级作用域内部用let声明的变量,只能在块内部访问,外部无法访问。

2. 变量提升

var声明的变量,声明会被提升到块级作用域的头部,而赋值还是在本地。

// other js code..

var a = 2;

其实是发生了下面的事情:

var a;  //此时a是undefined.
// other js code..

a = 2;

3. var可以重复声明

var可以重复声明同名的变量,后面的覆盖前面的。而let不可以(报错)。

Keywords: JavaScript
previousPost nextPost
已经有 1000000 个小伙伴看完了这篇推文。