前言
上一边《作用域是什么》中,我们将作用域定义为一套规则,这套规则用来定义如何在当前作用域以及嵌套的子作用域中根据标识符名称进行变量查找。
作用域有两种主要的工作模型,一种是词法作用域,一种是动态作用域。
正文
1.词法阶段
前面介绍过,大部分标准语言编译器的第一个工作阶段就是叫做词法化(也叫做单词化),词法化的过程会对源代码中的字符进行检查,如果是有状态的解析过程,还会赋予单词语义。
1.1词法作用域的概念
词法作用域就是定义在词法阶段的作用域,它是由你写代码时将变量和作用域写写在哪里决定的,因此当词法分析器代理时会保持作用域不变(大部分情况)
1.1.2举例
function foo(a) {
var b = a * 2
function bar(c) {
console.log(a, b, c);
}
}
foo(2)
// 因为foo函数内部没有bar调用,所以无输出,外部无法访问内部函数变量
注意:一个内部函数的标识符只会出现其相邻的第一个父级函数所创建的作用域内,不会出现在两个父级函数中,foo函数的父级是全局,不是本身。
扩展:
疑问:参数不算标识符吗?
变量名、函数名和属性名这三个属于标识符。但是参数不算。
作用域气泡由其对应的作用域代码写在哪里决定的,他是逐级包含的。
作用域气泡的结构和互相之间的位置关系给引擎提供了足够的位置信息,引擎用这些信息来查找标识符的位置
作用域查找会在找到的第一匹配的标识符时停止。
1.2欺骗词法
前面说过词法作用域完全是由写代码期间函数所声明的位置来决定,一般来说,词法作用域是不变的,那么,怎样才能在运行时来修改词法作用域呢?
eval&with
JS中的eval(…)函数可以接受一个字符串为参数,就好像在书写时就存在于程序中这个位置的代码一样。
也是就是说,eval就像是骗子一样,将他参数中的代码骗到对应位置,此过程修改了词法作用域环境。
function foo(str, a) {
eval(str);
console.log(a, b);
}
var b = 2
foo('var b=3;', 1)
// 输出1,3 eval可以把对应位置的参数里面的代码替换到相应位置,但是此过程修改了词法作用域,不提倡使用
注意:此过程修改了词法作用域的环境,在实际开发中非常不建议使用,极容易造成内部封装模块代码的污染
小结
- 词法作用域意味着作用域是由书写代码时函数声明的位置决定的
- 编译的词法分析阶段基本能够知道全部标识符在哪里以及如何声明的,从而能够预测在执行过程中如何对它进行查找
- 引擎在编译时会对作用域查找进行优化.。