JS作用域和重复声明var背后的原理

JS的容错率很高,一些其他语言常见的小错误JS都能大度得包容,比如给一个方法传入超出预计的参数、在声明变量之前使用该变量(变量的声明提升解决了这个问题)等等,这里我们就要解剖一下JS变量重复声明以及当我们忽略var使用 a=2来声明变量时a为全局变量的问题:

 //第一段代码  
var a = 2;  
var a = 3;  
alert(a);//3  
//第二段代码  
a = 2;  
alert(a);//2  

这两段代码在JS的眼中是完全可行的,JS会默默忽略掉第二个var声明来将程序继续执行下去,而且后面声明的值会覆盖掉前面声明的值,而第二段代码JS会将忽略var的声明默认声明为全局变量。这些大家都应该很清楚,但是JS遇到重复声明时背后到底是怎样运行的呢?那就关系到了JS的幕后黑手:引擎以及他的左膀右臂:编译器以及作用域。

在JS代码运行过程中:

引擎负责整个代码的编译以及运行,编译器则负责词法分析、语法分析、代码生成等工作而作用域则如我们熟知的一样,负责维护所有的标识符(变量)。

当我们执行上面的代码时,我们可以简单的理解为新变量分配一块儿内存,命名为a,并赋值为2,但在运行的时候编译器与引擎还会进行两项额外的操作:判断变量是否已经声明:

  1. 首先编译器对代码进行分析拆解,从左至右遇见var a,则编译器会询问作用域是否已经存在叫a的变量了,如果不存在,则招呼作用域声明一个新的变量a,若已经存在,则忽略var 继续向下编译,这时a = 2被编译成可执行的代码供引擎使用。

  2. 引擎遇见a=2时同样会询问在当前的作用域下是否有变量a,若存在,则将a赋值为2(由于第一步编译器忽略了重复声明的var,且作用域中已经有a,所以重复声明会发生值得覆盖而并不会报错)。若不存在,则顺着作用域链向上查找,若最终找到了变量a则将其赋值2,若没有找到,则招呼作用域声明一个变量a并赋值为2(这就是为什么第二段代码可以正确执行且a变量为全局变量的原因,当然,在严格模式下JS会直接抛出异常:a is not defined)。

虽然JS很勤劳,可以帮我们解决一些小问题,但是作为程序员的我们最好按照代码规范来进行书写,于人于己都大有裨益,何乐而不为呢。

注:关于a = 2 a会被声明为全局变量其中涉及到LHS查询方式

在书写代码的时候我们无时无刻不在与作用域较劲,而引擎是如何在沿着作用域链把我们想要的东西查找出来的呢?这里就涉及到了L与R的区别。

通过字面意思就很容易理解L代表left R代表right,而LHS与RHS查询我们可以先简单的区分为:查询在 = 号左边的变量时,引擎使用LHS,查询在 = 右边的变量时,引擎使用RHS。LHS查询出来的是变量的地址,方便进行形如a = 2的赋值操作,因为引擎根本不需要关心a里面存的是什么鬼,按照程序猿的要求把2塞给a就可以了,而RHS查询出来的是变量存储的值,以便形如 a = b的赋值操作,引擎同样不需要关心 b 放在内存的哪个“格子”,只需要知道格子里面放的什么就可以了。

当然, 根据 = 左右来区分LHS RHS是不全面的,因为我们很容易漏掉一些隐藏的LHS与RHS:

 var c =3;  
 function a(b){  
 console.log(b+c);  
 }  
 a(2);  

在上面一段代码中,我们可以很明显的得出 c ...使用了LHS,console.log()中的b、c使用了RHS,但是在调用函数a、console.log的时候同样使用了RHS,参数b的赋值也同样使用了LHS,所以我们最好通过取值、取地址这两个行为来判断引擎使用的查询方式。

对于上面的代码,引擎与作用域是这样交流的:

引擎:全局作用域,我想找一下c,你见过他么?
全局作用域:嗨,别提了,编译器那小子刚刚声明了它,拿去吧!
引擎:太棒了!我现在要把3丢给他
引擎:等一下,还有一个事情想麻烦你一下,我想对a函数进行引用,你知道她在哪里么?
全局作用域:就是那个跟c一起被丢进来的家伙把?喏,在这里呢,给你。
引擎:哈哈,太感谢了,这样我只需要把2.....。
a函数作用域:萨瓦迪卡,引擎,今天天气不错啊,一起出去玩吧!
引擎:别提了,我手头上忙的要死,对了,你碰到过一个叫b的么?
a函数作用域:哦,他是a函数的一个形参,我这刚好有,拿去用
引擎:够哥们,这样我只需要把2放到b里面,之后.....哎,小a,console你有么
a函数作用域:有有有,这是个内置对象,给你
引擎:哈哈,你一直这么靠谱,我找找,哎呦,真有log这个函数,我得赶紧引用他
引擎:你看我这脑子,你能在帮我找一下b么,我得确认一下b的内容
a函数作用域:放心,看!b没有变过,放心
引擎:那最好了,就差最后一步了,做完喝酒去,你那里有没有c,交出来我请你一包辣条!
a函数作用域:真的么!我找找,嗯.....不行,我这里没有,你得去问问我大哥 全局作用域
引擎:全局作用域,不好意思,又来找你了,不知道你有没有c,我拿辣条跟你换
全局作用域:看你累的满头大汗的,辣条你自己留着吧,c给你,做完快歇歇吧
引擎:么么哒,你最好了,晚上我请你吃饭!

看完上面的对话,不知道你对LHS RHS是否有了足够的了解,还有一点需要注意的就是,当查找到全局作用域时,若还没有查找到要找的变量信息,若为LHS查询,会默认声明一个与请求的变量同名的全局变量,而RHS则会抛出错误,当然,在严格模式下,LHS也同样会报错,这是需要注意的地方。

你可能感兴趣的:(JS作用域和重复声明var背后的原理)