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

  1. //第一段代码
  2. var a = 2;
  3. var a = 3;
  4. alert(a);//3
  5. //第二段代码
  6. <span style="font-size:18px;"></span><pre name="code" class="javascript">a = 2;
  7. 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查询方式,如需了解请移步至本人另一篇文章:JS引擎之LHS RHS

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

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

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

  1. var c =3;
  2. function a(b){
  3. console.log(b+c);
  4. }
  5. 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也同样会报错,这是需要注意的地方。

【repost】 JS变量重复声明以及忽略var 声明的问题及其背后的原理的更多相关文章

  1. JS变量重复声明以及忽略var 声明的问题及其背后的原理

    腾讯的一个笔试题,先看一下 var a = 100; function fn() { alert(a); //undefined var a = 200; alert(a); //200 } fn() ...

  2. es6中的let声明变量与es5中的var声明变量的区别,局部变量与全局变量

    自己通过看typescript官方文档里的let声明,与阮一峰老师翻译的的es6学习文档,总结以下三点 1.var声明可以多次重复声明同一个变量,let不行 2.let变量只在块级作用域里面有效果,v ...

  3. 关于Let和var声明变量的区别

    Let是ES6中添加进来的一个关键字,用于声明变量,其法与var声明变量相同,不同点在于其作用域(块级). 举例可以看出其具体差别 for(var i=0;i<5;i++){ console.l ...

  4. C# var声明变量解析

    C# var声明变量解析: 在C#3.0中提供了一种新的声明变量的方式,这就是var. 通过这个关键字,在声明变量时就无需指定类型了,变量类型是在初始化时由编译器确定的.代码如下: var ss = ...

  5. JS变量对象详解

    JS变量对象详解 开年之后工作热情一直不是很高,这几天一直处于消极怠工状态.早上不想起床,起床了不想上班.明明放假之前工作热情还一直很高,一直心心念念的想把小程序项目怼出来,结果休假回来之后画风完全不 ...

  6. 前端高质量知识(三)-JS变量对象详解

    在JavaScript中,我们肯定不可避免的需要声明变量和函数,可是JS解析器是如何找到这些变量的呢?我们还得对执行上下文有一个进一步的了解. 在上一篇文章中,我们已经知道,当调用一个函数时(激活), ...

  7. js中var的有或无--重复声明和以后的声明

    js中var的有或无--重复声明和以后的声明 使用var语句多次声明一个变量不仅是合法的,而且也不会造成任何错误. 如果重复使用的一个声明有一个初始值,那么它担当的不过是一个赋值语句的角色. 如果重复 ...

  8. js 变量声明 (var使用与不使用的区别)

    js 变量声明 (var使用与不使用的区别) 一.总结 一句话总结:不使用var声明变量的时候,变量是全局对象(window对象)属性,在全局中使用var声明变量是全局变量 var 全局变量 局部变量 ...

  9. js中当for循环中有事件要使用循环变量时,变量用var声明和let声明的区别

    var 声明一个全局变量,声明的变量会变量提升: let 声明一个局部变量: 当页面加载完后,for循环也结束了,如果用var声明的变量此时也随着for循环的结束而自增到满足结束循环的条件, 此时调用 ...

随机推荐

  1. mysql链接服务器,update报错

    select * from Openquery(MySQL, 'SELECT * FROM official.sys_hospital') 执行更新语句: ; 报错,错误信息: 链接服务器" ...

  2. IDEA 对比eclipse环境调节

    小子刚刚接触Intellij IDEA,以前用的都是eclipse.鉴于ieda的火热,开始学习之旅.本文会随时更新,记载idea中的一些调节方法,尽量在环境的配置上跟eclipse接近些. 在此感谢 ...

  3. java中接口和抽象类的异同点

    抽象类和接口的区别:A:成员区别 抽象类: 成员变量:可以变量,也可以常量 构造方法:有 成员方法:可以抽象,也可以非抽象 接口: 成员变量:只可以常量,默认修饰符:public static fin ...

  4. mysql学习笔记--数据库视图

    一.视图 1. 概念 a. 视图是一张虚拟表,它表示一张表的部分或多张表的综合的结构 b. 视图仅仅是表结构,没有数据.视图的结构和数据建立在表的基础上 2. 创建视图 a. 语法: create [ ...

  5. poj 2778 AC自动机+矩阵快速幂

    题目链接:https://vjudge.net/problem/POJ-2778 题意:输入n和m表示n个病毒,和一个长为m的字符串,里面只可以有'A','C','G','T' 这四个字符,现在问这个 ...

  6. Android 自定义的圆角矩形ImageView 工具类

    上图看效果 自定义圆角矩形ImageView工具类 package com.wechaotou.utils; import android.content.Context; import androi ...

  7. synchronized 同步函数的竞争关系验证

    synchronized是Java中的关键字,是一种同步锁.它修饰的对象有以下几种: 1. 修饰一个代码块,被修饰的代码块称为同步语句块,其作用的范围是大括号{}括起来的代码,作用的对象是调用这个代码 ...

  8. 使用nifi采集数据要配置的环境

    第一步 安装 Anaconda3-2019.03-Windows-x86_64.exe 下载地址:https://repo.anaconda.com/archive/Anaconda3-2019.03 ...

  9. break 和continue在循环中起到的作用

    break语句的作用是终止当前循环,跳出循环体.主意,break只能跳出一层循环. continue语句的作用是终止本轮循环并开始下一轮循环,(这里要主意的是在开始下一轮循环之前,会先测试循环条件). ...

  10. CSRedisCore 在net core中的使用

    背景:与net core配套的StackExchange.Redis客户端总是间歇性的发生timeout异常. 由complexer单例对象创建的IDatabase对象,在产生Timeout异常后会导 ...