一般来讲JavaScript是一门“动态”或者“解释执行”语言,但事实上它是一门编译语言
,晦涩的编译原理咱就不说了(我也不懂),直接说一下JavaScript的编译状况。对于JavaScript来讲,大部分状况下编译发生在代码执行前几微秒的时间内
。
最简单的一段JavaScript的代码:segmentfault
var a = 2;
编译器对于这行代码会进行两个步骤的处理:函数
//变量声明 var a; //赋值操做 a = 2;
这个过程当中会涉及到变量提高的问题。性能
编译器遇到var a,会检查当前的做用域中是否有a的声明。若是有,那么就会忽略掉a的声明,若是没有就会在当前的做用域中声明一个新的变量,并命名为a。翻译
接下来编译器就把a = 2这条语句翻译成机器代码等待运行。引擎运行时会查找当前的做用域中是否有一个名字为a的变量。若是有就使用;若是没有,引擎会到上层的做用域中查找,直到全局做用域中。code
var a = 2; //这里是一个LHS引用 console.log(a); //这里是一个RHS引用
乍一看LHS就是‘=’的左边,RHS就是‘=’的右边。但我对LHS的理解是我把值放到哪里
,RHS是我去哪里找我要的值
。为何要区分RHS和LHS,由于在变量尚未声明的时候,这两种查询的行为是不同的。对象
function foo(a){ console.log(a+b); b = a; } foo(2);
运行时,第一次对b
进行的是RHS的查询,引擎在全部做用域中都找不到,最后会抛出一个ReferenceError
的错误。而对于b=a
而言,b
进行的是LHS查询,若是在全局做用域中都找不到,那么就会在全局做用域中建立一个变量b。(前提是代码运行在非严格模式
)。
接下来,若是RHS查询到了一个变量,但你尝试对这个变量的值进行不合理的操做。好比,对一个非函数类型的值进行函数调用,那么引擎会抛出TypeError
。ReferenceError
与做用域判别失败相关,TypeError
则表明了做用域判别成功,但对结果的操做是非法或不合理的。ip
词法做用域
就是定义在词法阶段的做用域。更通俗的说法是词法做用域是你书写代码的顺序决定的。例如以下代码:作用域
function foo(a){ var b = a*2; function bar(c){ console.log(a,b,c); } bar(b*3); } foo(2);
在全局做用域中只有一个变量foo;在foo的做用域中有变量a,b和函数bar;在bar的做用域中有变量c。这种层层嵌套的关系是在书写时就已经决定了。动态做用域
就是在程序运行的时候才能肯定的做用域。JavaScript中有两种实现方式eval
和with
。但这两种方式都会致使性能的降低,因此仍是少用。字符串
JavaScript中的eval函数能够接受一个字符串的参数,并将其中的内容视为好像在书写的时候就存在于程序中这个位置同样。例如以下代码:get
function foo(str,a){ eval(str); console.log(a,b); } var b = 2; foo('var b = 3;',1);//1,3
eval函数的参数是“var b = 3;”,这段代码就会被当作原本就在那里同样。也就是会遮蔽全局做用域中的b变量。
with表面上是一种引用同一个对象不一样属性的快捷方式
,但更重要的是它会建立一个新的做用域
,例如以下代码:
//with体现了访问对象的快捷 var obj = { a:1, b:2, c:3 }; //不使用with obj.a = 2; obj.b = 3; obj.c = 4; //使用with with(obj){ a = 2; b = 3; c = 4; }
建立新的做用域能够看以下代码:
function foo(obj){ with(obj){ a = 2; } } var o1 = { a:3 }; var o2 = { b:3 }; foo(o1); console.log(o1.a);//2 foo(o2); console.log(o2.a);//undefined console.log(a);//2,a暴露在全局做用域中
a为何会暴露在全局做用域中?
当执行foo(o1)时,由于使用了with,因此会建立一个全新的做用域,命名为o1。o1对象里的属性会变成o1做用域里变量。当执行a=2时,能够在o1的做用域中找到a,因此就修改了a的值。当执行foo(o2)时,o2的做用域中没有a变量,因此会执行LHS的查询,最终会在全局做用局中声明一个变量a,因此就致使了a暴露在全局做用域中。