作用域
什么是作用域?
作用域是代码在运行时,某些特定部分中的变量,函数,和对象的可访问性。作用域负责收集并维护由所有声明的标识符(变量)组成的一系列查询,并实施一套非常严格的规则,确定当前执行的代码对这些标识符的访问权限。换句话说,作用域决定了变量和函数的可访问范围,即作用域控制着变量和函数的可见性和生命周期。
JavaScript中的作用域
- 全局作用域
- 局部作用域
如果一个变量在函数外或者大括号{}
外声明的,那么就定义了一个全局作用域;在ES6之前局部作用域只包含了函数作用域,ES6为我们提供了块级作用域,也属于局部作用域。
全局作用域
拥有全局作用域的对象可以在代码的任何地方访问到。
以下情形拥有全局作用域:
1 | var a = 5; //①最外层变量 |
局部作用域
局部作用域一般只能在固定代码片段中可以访问到。最常见的为函数作用域
函数作用域
定义在函数中的变量就在函数作用域中。并且函数在每次调用时都有一个不同的作用域。这意味着同名变量可以用在不同的函数中。因为这些变量绑定在不同的函数中,拥有不同作用域,彼此之间不能访问。
函数作用域:
1 | function func(){ |
块级作用域测试:
1 | for(var i = 0; i < 3; i++){ |
关于变量提升
在Javascript中,函数及变量的声明都将被提升到函数的最顶部,也就是说我们可以先使用后声明,但函数表达式和变量表达式只是将函数或者变量的声明提升到函数顶部,函数表达式和变量的初始化将不被提升
变量提升的例子:
1 | var tmp = new Date(); |
存在重复声明的例子:
1 | var a = 3; |
变量和函数同时提升:
1 | //情况一 |
情况二就相当于重复声明的例子,容易理解。对于情况一,其等价形式:
1 | var a = function(){}; |
- 函数声明被提升到最顶上;
- 申明只进行一次,因此后面
var a = 1
的申明会被忽略。 - 函数申明的优先级优于变量申明,且函数声明会连带定义一起被提升(这里与变量不同)
块级作用域
ES6新增了
let
和const
命令,可以用来创建块级作用域变量,使用let
命令声明的变量只在let
命令所在代码块内有效。
使用let
声明变量,会将变量的作用域限制在当前代码块中。特点:
- 变量不会提升到代码块顶部且不允许从外部访问块级作用域内部变量
- 不允许反复声明
1 | console.log(a); //error |
作用域链
关于编译原理
传统编译语言流程:
- 分词/词法解析:这个过程会由字符组成的字符串分解成有意义的代码块,这些代码块被称为词法单元(token)。例如:
var a = 2;
通常被分解为:var、a、=、2、; 。 - 解析/语法分析:这个过程将词法单元流(数组)转换成一个由元素逐级嵌套所组成的代表了程序语法结构的树。这个数被抽象为“抽象语法树”(AST)。
- 代码生成。将AST转换为可执行的过程被称为代码生成。
JavaScript编译过程不同之处:
JavaScript
大部分情况下编译发生在代码执行前的几微秒(甚至更短!)的时间内JavaScript
引擎用尽了各种办法(比如JIT
,可以延 迟编译甚至实施重编译)来保证性能最佳
JavaScript是如何执行的
- 核心重点:变量和函数在内的所有声明都会在任何代码被执行前首先被处理。
- 函数运行的瞬间,创建一个AO (Active Object 活动对象)运行载体。
作用域链是什么?
JavaScript上每一个函数执行时,会先在自己创建的AO上找对应属性值。若找不到则往父函数的AO上找,再找不到则再上一层的AO,直到找到window(全局作用域)。而这一条形成的“AO链” 就是JavaScript中的作用域链。
参考资料
- 《你不知道的JavaScript》上卷
- 深入理解JavaScript
- javascript作用域