作用域(Scoping)

javascript作用域之所以迷惑,是因为它程序语法本身长的像C家族的语言。我对作用域的理解是只会对某个范围产生作用,而不会对外产生影响的封闭空间。在这样的一些空间里,外部不能访问内部变量,但内部可以访问外部变量。

c语言的变量分为全局变量和局部变量,全局变量的作用范围是任何文件和函数访问(当然,对于非变量定义的其他c文件,需要使用extern关键字进行申明,使用static关键字也可以将作用范围限定在当前文件中),局部变量的作用范围就是从申明到最近的大括号涵盖的块级范围。java则无全局变量,有类变量,成员变量和局部变量,作用范围根据public,protected,private等访问权限有不同的作用范围,这里就不多述。

JS作用域有哪些?

在ES5中,js只有两种形式的作用域:全局作用域和函数作用域。

全局作用域其实是全局对象的作用域,任意地方都可以访问到(如果没有被函数作用域覆盖)。

函数对象作用域跟c的局部变量作用域是不同的,它的作用域是整个函数范围,不论他是在函数的任意位置申明的!这就是所谓的hoisting,也就是变量提升的概念。不过不着急,下面会专门针对hoisting来进行解释。

不过,在ES6中,新增了一个块级作用域(最近的大括号涵盖的范围),但是仅限于let方式申明的变量。
作用域演示:

定义变量时,如果不写var,比如 i=0,则会被定义为全局变量,作用域为全局作用域,否则为局部变量,作用域为函数作用域。上面第一行的var i=0,之所以说它是全局变量,是因为它已经是在全局区申明的了,并不在函数范围内,因此跟 i=0 是一样的。

至于,为什么结果会是这样,继续往下看就知道了。

申明形式

变量声明:

函数申明:

变量提升(Hoisting)

引出一个问题

下面这段代码会输出什么内容?

这道题大多数初学者都说输出的是日期。但真实的结果是undefined。为什么是这样呢?这里就引出了一个概念--hoisting,中文的意思就是变量提升。MDN中对变量hoisting的解释是这样的:

var hoisting

Because variable declarations (and declarations in general) are processed before any code is executed, declaring a variable anywhere in the code is equivalent to declaring it at the top. This also means that a variable can appear to be used before it's declared. This behavior is called "hoisting", as it appears that the variable declaration is moved to the top of the function or global code.

这段话翻译下来就是

因为变量申明是在任意代码执行前处理的,在代码区中任意地方申明变量和在最开始(最上面)的地方申明是一样的。也就是说,看起来一个变量可以在申明之前被使用!这种行为就是所谓的“hoisting”,也就是变量提升,看起来就像变量的申明被自动移动到了函数或全局代码的最顶上。

注意:仅仅是申明提升了,定义并不会被提升。

如此,上面这段代码其实就是下面的形式:

所以,这样就应该理解了,console输出的时候,tmp变量仅仅是申明了但未定义,所以输出应该是undefined。

这里需要说明的是,虽然所有的申明(包括ES5的var、function,和ES6的function *、let、const、class)都会被提升,但是var、function、function *和let、const、class的的提升却并不相同!具体原因可以看这里的说明(大体的意思是虽然let,const,class也被提升了,但是却并不会被初始化,这时候去访问他们则会报ReferenceError异常,他们需要到语句执行的时候才会被初始化,而在被初始化之前的状态叫做temporal dead zone)。我们来看一段代码就知道了:


这里a被提升,但因为定义在后,所以输出undefined

这里a虽然被提升,但却报了引用错误!

之所以或这样

因为这样的原因,推荐的做法是在申明变量的时候,将所用的变量都写在作用域(全局作用域或函数作用域)的最顶上,这样代码看起来就会更清晰,更容易看出来那个变量是来自函数作用域的,哪个又是来自作用域链(本文不对此多做解释,请读者自行百度,有机会再补充说明)。

重复声明

上面的输出其实是:1 2 2。虽然看起来里面x申明了两次,但上面说了,js的var变量只有全局作用域和函数作用域两种,且申明会被提升,因此实际上x只会在最顶上开始的地方申明一次,var x=2的申明会被忽略,仅用于赋值。也就是说上面的代码实际上跟下面是一致的。

函数和变量同时提升的问题

如果是函数和变量类型同时申明定义了,会发生什么事情呢?看下面的代码


A

上面的输出结果其实是: function foo(){} ,也就是函数内容。

而如果是这样的形式呢


B

它的输出却变成:undefined

为什么会这样呢?

原来函数提升分为两种情况:

一种:函数申明。就是上面A,function foo(){}这种形式

另一种:函数表达式。就是上面B,var foo=function(){}这种形式

第二种形式其实就是var变量的声明定义,因此上面的B输出结果为undefined应该就能理解了。

而第一种函数申明的形式,在提升的时候,会被整个提升上去,包括函数定义的部分!因此A跟下面的这种方式是等价的!

原因是因为:1、函数声明被提升到最顶上;2、申明只进行一次,因此后面var foo='i am text'的申明会被忽略。

并且函数申明的优先级优于变量申明,所以以下形式的输出,同样是函数内容:

总结

要彻底理解JS的作用域和Hoisting,只要记住以下三点即可:

1、所有申明都会被提升到作用域的最顶上

2、同一个变量申明只进行一次,并且因此其他申明都会被忽略

3、函数声明的优先级优于变量申明,且函数声明会连带定义一起被提升

注意:

通过with语句,可以临时改变运行期上下文的作用域链,此时的对非var定义的变量进行访问,会首先访问with中对象的属性,然后才会向上顺着作用域链向上检查该属性。

JS中作用域和变量提升(hoisting)的深入理解的更多相关文章

  1. JS 函数作用域及变量提升那些事!

    虽然看了多次js函数作用域及变量提升的理论知识,但小编也是一知半解~ 这几天做了几道js小题,对这部分进行了从新的理解,还是有所收获的~ 主要参考书籍: <你不知道的JavaScript(上卷) ...

  2. JS _函数作用域及变量提升

    虽然看了多次js函数作用域及变量提升的理论知识,但也是一知半解~ 这几天做了几道js小题,对这部分进行了从新的理解,还是有所收获的~ 主要参考书籍: <你不知道的JavaScript(上卷)&g ...

  3. JavaScript中的各种变量提升(Hoisting)

    首先纠正下,文章标题里的 “变量提升” 名词是随大流叫法,“变量提升” 改为 “标识符提升” 更准确.因为变量一般指使用 var 声明的标识符,JS 里使用 function 声明的标识符也存在提升( ...

  4. javascript中的变量作用域以及变量提升

    在javascript中, 理解变量的作用域以及变量提升是非常有必要的.这个看起来是否很简单,但其实并不是你想的那样,还要一些重要的细节你需要理解. 变量作用域 “一个变量的作用域表示这个变量存在的上 ...

  5. javascript中的变量作用域以及变量提升详细介绍

    在javascript中, 理解变量的作用域以及变量提升是非常有必要的.这个看起来是否很简单,但其实并不是你想的那样,还要一些重要的细节你需要理解变量作用域 “一个变量的作用域表示这个变量存在的上下文 ...

  6. JS中for循环变量作用域--解决for循环异步执行的问题

    被这个问题困惑了很久,终于在网上找到了答案,感谢~ 现在分享给大家~ js中如何让一个for循环走完之后,再去执行下面的语句? 这涉及for循环变量作用域的问题,js中作用域只有函数作用域和全局作用域 ...

  7. js中作用域链和作用域

    作用域 在JavaScript中,我们可以将作用域定义为一套规则,这套规则用来管理引擎如何在当前作用域以及嵌套的子作用域中根据标识符名称(变量名或者函数名)进行变量查找. 经过研究<高级程序设计 ...

  8. Javascript作用域和变量提升

    下面的程序是什么结果? var foo = 1; function bar() { if (!foo) { var foo = 10; } alert(foo); } bar(); 结果是10: 那么 ...

  9. JS中的let变量

    介绍JS中的let变量: let允许你声明一个作用域被限制在块级中的变量.语句或者表达式.在Function中局部变量推荐使用let变量,避免变量名冲突. 作用域规则 let 声明的变量只在其声明的块 ...

随机推荐

  1. iframe父页面和子页面相互调用的方法

    随着W3C一声令下,几年前使用非常频繁的frameset + frame已完成使命,光荣退伍.作为frameset的替代方案(姑且这么称吧),iframe的使用也多了起来.较frameset方案,if ...

  2. python中出现 IndentationError:unindent does not match any outer indentation level

    python中出现IndentationError:unindent does not match any outer indentation level 今天在网上copy的一段代码,代码很简单,每 ...

  3. iOS有用的三方库和高效工具记录

    DKNightVersion https://github.com/Draveness/DKNightVersion#podfile 用来为APP添加夜间模式和换肤功能

  4. Android开发中,那些让你觉得相见恨晚的方法、类或接口

    Throwable类中的getStackTrace()方法,根据这个方法可以得到函数的逐层调用地址,其返回值为StackTraceElement[],而在StackTraceElement类中有四个方 ...

  5. 根据xml文件自动生成xsd文件

    根据xml生成xsd文档 1. 找到vs自带的xsd.exe工具所在的文件夹位置: C:\Program Files (x86)\Microsoft SDKs\Windows\v7.0A\Bin 注意 ...

  6. cshtml 获取session值

    在 cshtml 中,我们如何获取 session 的呢? 首先我们先设置 session 值,我们一般都会在 Controller 设置 session. Session["isAdmin ...

  7. 51nod1127(尺取法)

    题目链接:https://www.51nod.com/onlineJudge/questionCode.html#!problemId=1127 题意:中文题诶- 思路:尺取法 维护一个队列,若当前队 ...

  8. 简述RadioGroup和RadioButton的使用

    简述RadioGroup和RadioButton的使用 在项目中有身份选择的选项,需要用到RadioGroup和RadioButton,这里做个记录,和大家交流一下. 话不多说,一起看代码 XML代码 ...

  9. 手工sql注入判断是否存在注入点

    1.加入单引号 ’提交,结果:如果出现错误提示,则该网站可能就存在注入漏洞.2.数字型判断是否有注入;语句:and 1=1 ;and 1=2 (经典).' and '1'=1(字符型)结果:分别返回不 ...

  10. ThinkSNS+ 是如何计算字符显示长度的

    什么是ThinkSNS+ ThinkSNS(简称TS),一款全平台综合性社交系统,目前最新版本为ThinkSNS+.ThinkSNS V4 ThinkSNS[简]. 今天我们来聊一下可能很多人都会头疼 ...