作用域是什么
编译原理
JavaScript 常常被称为‘弱类型’或者‘动态’语言,实际上它也是一门编译语言。
与传统编译语言不同,它不是提前编译的。
传统编译语言流程:
- 分词/词法分析
将由字符组成的字符串分解成有意义的代码块(词法单元) - 解析/语法分析
将词法单元流转换成“抽象语法树”(AST) - 代码生成
将AST转换成可执行代码
但是对于 JavaScript 来说编译一般发生在代码执行前的极短时间内,JS 引擎也想尽了一切办法对性能进行优化。
作用域
作用域是根据名称查找变量的一套规则,决定了当前执行的代码可以访问哪些标识符(变量)。
如果访问标识符的目的是为其赋值,会进行 LHS 查询(a = 1
);如果目的是获取变量的值,会进行 RHS 查询(b = a
)。
作用域嵌套
当一个块或函数嵌套在另一个块或函数中时,就发生了作用域嵌套。
在当前作用域中找不到的某个变量,会在其外部作用域中查找,直到全局作用域为止,在浏览器中全局作用域为 window 对象。
词法作用域
词法阶段
简单说,词法作用域是定义在词法阶段的作用域。
换句话说,词法作用域是由代码写在哪里决定的,所以函数的词法作用域决定于函数声明的位置,不被运行时的所处的作用域位置改变。
作用域查找会在找到第一个匹配的标识符后停止,外层嵌套作用域嵌套的同名标识符会被忽略。
欺骗词法
有两种机制可以在运行时修改词法作用域,eval()
和with
操作符,但是会产生难以理解的代码、降低性能、某些时候带来安全隐患等问题,所以早已经 禁止使用。
函数作用域和块作用域
函数作用域
函数作用域是指函数内包括参数的全部变量都可以在整个函数内部使用,当然也包含内部的嵌套作用域内。
隐藏内部实现
函数作用域内的变量和函数被 隐藏 了起来。可以用这个特性生成函数的私有变量和私有函数。
另一个好处是可以避免同名标识符的冲突,毕竟标识符查找到第一个匹配就会停止。
块作用域
感谢 ES6 带来的 let
和 const
,实现块级作用域更加的方便。
提升
JavaScript 代码在很多情况下是从上到下一行一行执行的,但是在某些特殊情况下不是。
var a =1; //实际上引擎是这样来执行的 var a; a = 1;
先声明后赋值才是背后的流程。在每个作用域内,申明提升都会各自存在。注意 let
和 const
并没有申明提升。
函数优先
还是那句话,函数是一等公民。函数的声明同样会提升,并且会提升在变量之前。
作用域闭包
闭包是基于词法作用域书写代码的自然结果,不需刻意使用。
当函数可以记住并访问所在词法作用域,并可以在当前词法作用域之外执行,就产生了闭包。
function foo() {
var a = 2;
function bar() {
console.log( a );
}
return bar;
}
var baz = foo();
baz(); // 2