JavaScript的编译

通常来说JavaScript是一门“动态”或者“解释执行”语言,但事实上它是一门编译语言,晦涩的编译原理咱就不说了(我也不懂),直接说一下JavaScript的编译情况。对于JavaScript来说,大部分情况下编译发生在代码执行前几微秒的时间内
最简单的一段JavaScript的代码:

    var a = 2;

编译器对于这行代码会进行两个步骤的处理:

    //变量声明
    var a;
    //赋值操作
    a = 2;

这个过程中会涉及到变量提升的问题。

  1. 编译器遇到var a,会检查当前的作用域中是否有a的声明。如果有,那么就会忽略掉a的声明,如果没有就会在当前的作用域中声明一个新的变量,并命名为a。

  2. 接下来编译器就把a = 2这条语句翻译成机器代码等待运行。引擎运行时会查找当前的作用域中是否有一个名字为a的变量。如果有就使用;如果没有,引擎会到上层的作用域中查找,直到全局作用域中。

区分RHS和LHS

    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则代表了作用域判别成功,但对结果的操作是非法或不合理的。

词法作用域和动态作用域

词法作用域就是定义在词法阶段的作用域。更通俗的说法是词法作用域是你书写代码的顺序决定的。例如如下代码:

    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中有两种实现方式evalwith。但这两种方式都会导致性能的下降,所以还是少用。

eval

JavaScript中的eval函数可以接受一个字符串的参数,并将其中的内容视为好像在书写的时候就存在于程序中这个位置一样。例如如下代码:

    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表面上是一种引用同一个对象不同属性的快捷方式,但更重要的是它会创建一个新的作用域,例如如下代码:

    //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暴露在全局作用域中。


852774731
975 声望7 粉丝

« 上一篇
JSP与Servlet02
下一篇 »
maven的依赖