作用域

词法作用域,动态作用域,全局作用域,局部作用域,函数作用域,块级作用域,有些地方还能看到隐式作用域和显示作用域。

关于作用域的名词有这么多,一开始看到的时候是不是有点懵?
其实,对它们进行一下分类,就不会这么懵了。

作用域

作用域是一套规则,用于确定在何处以及如何查找变量(标识符)。 ---- 你不知道的 JavaScript

当前的执行上下文。  ---- mdn

词法作用域与动态作用域

词法作用域 和 动态作用域,是作用域的两种主要工作模型。

现在编程语言大多数采用词法作用域(静态作用域);少数的几种语言,比如 Bash 脚本、Perl 中的一些模式,是雇佣的是动态作用域。

区别

词法作用域(静态作用域),是在编译阶段确定作用域的,代码书写完后,基本就能确定作用域。作用域链是根据嵌套的关系去查找的。(eval 和 with 会改变作用域,而且会影响性能。)

动态作用域,是在调用时,也就是运行时确定作用域的。作用域链是基于调用栈的。(this 的确定和动态作用域很像)

全局作用域与局部作用域

全局作用域和局部作用域,是相对于作用域链来说的。

作用域链的最顶层,就是全局作用域。在顶层作用域内部嵌套的作用域都可以看做是局部作用域。

函数作用域与块作用域

函数作用域与块作用域,是函数中的作用域单元。

在任意代码片段外部添加包装函数,可以将内部的变量和函数定义“隐藏起来”,外部作用域无法访问到包装函数内部的任何内容,这个包装函数内部的访问规则就是函数作用域。

块作用域,ES6之前,没有块作用域。块作用域的用处是使变量的声明和使用距离尽可能近。

catch 块,会形成块作用域,它声明的变量只能catch块中可以访问。除了 IIFE 之外,catch 块也可以作为块作用域的替代方案。
ES6 之后,出现了 let 和 const。

对于已经存在的 if(){}, for(){} 等类似的 {} 代码块,在里面使用 let 或 const 声明变量时,会隐式的形成块作用域(隐式作用域),如果使用 {} 直接创建一个块作用域,里面定义 let,const,形成显示的块作用域。

如果想在块作用域中声明函数,建议使用函数表达式。因为函数声明的行为类似于 var 声明,会提升,不建议使用,详见。

关于作用域的常见名词,上面已经做了介绍。还有关于作用域的作用域链,以及衍生出的闭包和 this。

作用域链

简单来说,就是作用域的层层嵌套,里面变量的访问规则是子作用域可以访问父作用域,父作用域不能访问子作用域。
其实这很好理解,父母和子女之间的爱也是向下的,父母会给子女提供无限的爱,想要什么都可以,但是子女的东西,他们会自己留着,或者准备留给自己的后代。

这其中,子女也都不是自私的。他们也会把自己拥有的东西贡献出去,但是不是直接就暴露出去的。他们会做自我保护,他们会暴露出去行为,外界知道他们会做什么,但是不知道他们到底拥有什么。这个现象就是闭包。(这个例子不太严谨,好像他们比较胆小怕事所以派出了他们的子女。但是意思差不多。)

闭包

闭包,其实并不神秘,就是函数返回了一个函数,返回的函数还引用了它原来定义时所在作用域的变量,导致原来的作用域不能被释放,需要一直存在于内存中。这个就挺像出门历练的子女,在家的父母不放心,所以会时时牵挂,只要有子女外出,父母牵挂的心就无法平静(和 JS 中闭包依赖的父级函数不会被垃圾回收机制回收一样)。而且还为子女提供远程 buff 支持,真是。。。

this

this 的规则,比较像动态作用域,是运行时决定的。但是 ES6 中,箭头函数,是个例外。

关于 this 的稍后添加。

你可能感兴趣的:(作用域)