你不知道的JavaScript——上卷——作用域

编译的原理步骤

1、分词/词法分析

    这个过程会把由字符组成的字符串分解成有意义的代码块(也叫词法单元),如var a=2;会被分解成var、a、=、2、;。空格如果没有意义,就不会被当作词法单元解析出来。

2、解析/词法分析

    将词法单元流(数组)转换成一个由元素逐级嵌套所组成的代表程序语法结构的树(抽象语法树,AST)。

3、生成代码

    这是一个将AST转换成可执行代码的过程,将AST转化为一组机器指令,以用来创建一个叫a的变量(包括内存分配等),并将值存储在a中。

作用域——(LHS、RHS)

    编译器在编译过程中生成了代码,引擎执行它时,会通过查找变量名来判断它是否声明过。查找的方式有LHS(Left-Hand-Side)以及RHS(Right-Hand-Side)类型,当变量出现在赋值操作的左侧时会进行LHS查询,出现在右侧时会进行RHS查询,讲得准确一点,RHS查询与简单地查找某个变量别无二致,而LHS查询则是试图找到变量的容器本身。以下程序,其中既有LHS又有RHS引用:

function foo(a){
    console.log(a);   //2
}
foo(2);//2
}
foo(2);

    最后一行foo(...)函数的调用需要会foo进行RHS引用,意味着“去找到foo的值,并把它给我”。而且(...)意味着foo的值需要被执行,因此它最好是一个函数类型的值!当2被当作参数传递给foo(...)函数时,2会被分配给参数a。为了给a(隐式)分配值,需要进行一次LHS查询。

    异常

    如果RHS查询在所有嵌套的作用域中找不到所需的变量,引擎就会抛出ReferenceError(引用错误)异常,相比之下,当引擎执行LHS查询时,如果在全局作用域都找不到目标变量,全局作用域中就会创建一个具有该名称的变量,并将其返回给引擎(全局变量会导致内存溢出——不会触发JS的垃圾回收机制),前提程序不是运行在“严格模式”下,ES5中引入了“严格模式”,其规定禁止自动或者隐式创建全局变量,因此,在严格模式中LHS查询失败时,不会创建全局变量,相对应的,引擎会抛出ReferenceError。

    如果RHS查询找到变量,但是你尝试对这个变量的值进行不合理的操作,比如试图对一个非函数类型的值进行函数调用,引擎会抛出TypeError异常。

词法作用域

    词法作用域就是定义在词法阶段的作用域,无论函数在哪被调用,无论如何被调用,它的词法作用域都只由函数被声明时所处的位置决定,因此当词法分析器处理代码时会保持作用域不变。

   在多层的嵌套作用域中可以定义同名的标识符,但是内部的标识符会“遮蔽”了外部的标识符(遮蔽效应),因为作用域查找会在找到第一个匹配的标识符后停止。如果是局部的标识符遮蔽了全局的标识符,可以间接地通过对全局对象属性的引用来访问被遮蔽的全局变量(window . 全局标识符),但如果被遮蔽的标识符不是全局变量,那无论如何也无法在被遮蔽后进行访问。

var a = 100;
var test = function(){
   alert(a)            //undefined
   var a = 500;
   alert(a)           //500
}
test()

    第一次之所以弹出 undefined,是因为引擎在解析代码之前都会对声明(函数声明以及变量声明)进行提升,也就是会先查找到整个代码文件中的声明,把变量声明(var)定义为undefined,函数声明定义为整个函数表达式(包括注释),在提升的时候,函数里面把变量a的作用域提升到了函数里。

    箭头函数的this总是指向词法作用域,也就是外层调用者

    欺骗词法作用域(不建议使用)

    欺骗词法就是修改词法的作用域,欺骗词法作用域会导致性能下降

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;会被当成本来就在那里一样来处理,变成局部变量,遮蔽了全局变量(var b=2;)

 

2、with()

    with通常被当作重复引用同一个对象中的多个属性的快捷方式,可以不需要重复引用对象本身。

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);//undefinde
    console.log(a);   //2 (a被泄露到全局作用域上了!)

  o1具有a属性,o2没有,foo(...)函数接收一个参数,该参数是一个对象引用,并对这个对象执行了with(obj){...}。在with快内部,我们写的代码看起来只是对变量 a进行简单的词法引用,实际上就是一个LHS引用,并将2赋值给它。当o2传递进去时,o2没有a属性,o2不会创建这个属性,所以o2.a保持undefined。但是foo(...)的作用域和全局作用域都没有找到标识符a,因此a=2执行时,自动创建了一个全局作用域(LHS在非严格模式下查找的结果)。                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      

 

    小结

作用域是一套规则,用于确定在何处以及如何查找变量。如果查找的目的是对变量进行赋值操作(例如:赋值操作符或者调用函数时传入参数的操作),就使用LHS查询,如果目的是获取变量的值(例如调用函数),就会使用RHS查询。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值