你不知道的JS(上)1

172 阅读3分钟

作用域是什么

  • 引擎 负责javaScript程序的编译及执行过程。
  • 编译器 语法分析及代码生成(类似编译原理这门课程的内容)
  • 作用域 收集并维护由所有声明的变量组成的一系列查询,确定当前执行的代码对这些变量的访问权限。 LHS 变量出现在赋值操作的左侧。a=2,为=2这个赋值找到目标。
    在非严格模式下,如果在顶层中也无法找到目标变量(LHS查询失败时),全局作用域会创建一个具有该名称的变量并返回给引擎。
    严格模式下,禁止自动会隐式创建全局变量,LHS查询失败时,引擎抛出ReferenceError。
    RHS 变量出现在赋值操作的右侧,理解为得到这个值。console.log(a)
    RHS 查询在所有嵌套的作用域里遍寻不到所需的变量,引擎抛出ReferenceError。
function foo(a){
    console.log(a);
 }
 foo(2)

隐含了a=2这个LHS操作(调用foo(2)的时候)
javascript编译步骤
var a=2;

  1. 看到var a,编译器询问是否有同名变量在同一作用域的集合中。若有,忽略声明。若无,在当前作用域集合中声明一个变量,命名为a。
  2. 进行a=2这个赋值操作。引擎询问作用域集合中是否存在一个叫做a的变量。若有,引擎使用这个变量,若无,引擎继续查找这个变量。
    作用域嵌套:一个块或函数嵌套在另一块或函数中。先在当前作用域中找某个变量,找不到会在外层嵌套的作用域中继续查找,找到||抵达全局作用域(最外层作用域)时停止。
function foo(a){
    console.log(a+b);
 }
 var b=2;
 foo(2);

对b的RHS引用无法在函数foo内部完成,可以在上一级作用域(全局作用域)完成。

词法作用域

词法作用域是定义在词法阶段的作用域。

欺骗词法

在运行时修改词法作用域

1.eval

function foo(str,a){
    eval(str);
    console.log(a,b);
}
var b=2;
foo("var b=3;",1);//1,3

var b=3声明了一个新的变量b,对已经存在的foo(..)词法作用域进行了修改。可以理解为在foo内部创建了一个b,覆盖了外部的b。
严格模式下,其他声明无法修改所在的作用域,Reference Error,a is not defined。

2.with

var obj={
    a:1,
    b:2,
    c:3
};
obj.a=2;
obj.b=2;
obj.c=3;
with(obj){
    a=3;
    b=4;
    c=5;
}
function foo(obj){
    with(obj){
        a=2
    }
}
var o2={
    b=3
}
foo(o2)
console.log(a)//2(a被泄露到全局作用域上)

with可以减少简单重复的工作,但可能把变量泄露到全局作用域上,例如上面,将o2传给with,o2没有标识符a,自动创建了一个全局变量(非严格模式下)。
eval函数修改其所处的词法作用域。 with为传递给它的对象创建了一个新的词法作用域。

eval和with的副作用

引擎无法在编译时对作用域查找进行优化,因为无法知道在词法分析阶段明确直达eval(...)会接收什么代码,这些代码对词法作用域的影响,也无法知道with接收到的对象是什么。