xxleyi / js_note

JS 精选笔记

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

12. 词法作用域

xxleyi opened this issue · comments

JS 中所有的声明,比如变量声明,函数声明,类声明,均使用词法作用域,也就是我们常说的静态作用域,即声明带来的变量,其求值规则是静态的,直接由静态的源代码确定,不受动态的执行过程影响。

更具体的来说,由静态源代码中的什么元素决定呢?

ES6 之前,只有全局作用域和函数作用域。ES6 对此做了升级, 引入块级作用域,就是由花括号包起来的块级代码都自成一个块级作用,当然 var 声明的变量会无视块级作用域。let const 以及 函数声明,类声明会遵守。

note: 除非想要借助块级作用域构造闭包,否则极度不推荐在块级作用域中进行函数声明和类声明。

关于词法作用域,到此基本就介绍完毕了。内容似乎有些单薄,但这又是重中之重,甚至超越具体的编程语言,成为时下几乎所有主流编程语言的通用求值规则(细节处会有差异)。

这个词法作用域看似挺简单的啊,能有什么细节让不同语言的选择不一样呢?

举两个我知道的例子吧。

先看第一个例子:

// js
function foo(a, b=[]) {
  b.push(a)
  console.log(b)
}

foo(1)
foo(2)
# python3
def foo(a, b=[]):
  b.append(a)
  print(b)

foo(1)
foo(2)

问:上面这两段代码,其输出一样吗?

答:肯定不一样,一样的话,我就不会出这个问题了。JS这边是 [1] [2],而 Python3 这边是 [1] [1, 2]

为什么会有这种差异呢?JS 中默认形数是在 ES5 之后引入的,形参自己单独是一个作用域,之后的每一次函数调用,都会去重新执行一下形参部分。而 Python 中的形参呢?采取了一个特别取巧的设计,直接将形参设为函数对象的一个属性,一旦函数对象有了,形参就固定在函数对象之上了,之后的每次函数调用,如果用户没加形参部分,则不会重新执行。

想表达的点是:不光花括号可以产生块级作用域,其他部分也可以,这取决于语言设计。ES5 之后,就把形参部分视为单独的作用域。

同样的,还有一个很微妙的地方,ES5 之后,还会把 for 循环中的 let 初始化部分也视为单独的作用域,同时后面的判断表达式语句和递增表达式语句实际上和循环体处在同一个块级作用域。

具体可见 JavaScript for-loops are… complicated - HTTP203 - YouTube


以上是关于词法作用域,但如果想真正掌握词法作用域,必须学习并掌握另一个概念:词法环境。

请认真考虑抽出足够时间啃下这篇雄文:https://javascript.info/closure