众所周知,在ES6之前,JavaScript是没有块级作用域的,如下图所示:

  学过其他语言的同学肯定有点诧异,为什么会这样呢?因为js还是不同于其他语言的,在ES5中,只有全局作用域和函数作用域,并没有块作用域,当然我们可以实现块作用域的功能。看下面代码:

  在这段段代码中,我们使用立即执行函数(IIFE)创建了一个局部函数来模仿块级作用域。在ES5时代,JavaScript的作用域只有用全局作用域和局部作用域的说法。到了ES6时代,块级作用域的登场。

  一、关于ES5时代

    1.变量提升

      说到js的变量提升,就不得不说一下js的词法分析。总所周知js代码自上而下执行,但是在js代码执行前,会先进行词法分析。所以js运行要分为词法分析程序执行两个阶段。

      js词法分析主要分为3个步骤:

        1.分析形参:如果函数有形参,则给当前活动对象增加形参属性,默认为undefined。

        2.分析变量声明:如果有类似var a  之类的声明,若没有该属性则增加属性,若已存在则不做操作。默认为undefined。变量的赋值在执行阶段才进行,即执行到该变量的时候才有 a = 11。

        3.分析函数声明:类似 function a(){},若当前活动对象没有该属性则新增否则重写该属性为方法a。

      如图所示,在这段代码中,按照一般的逻辑,第一个console.log会报错为“a is not defined”。

      但是事实上,根据js词法分析的第二步,var a这个声明会被提前到代码的顶部。但是a=1这个赋值却不会,所以这段函数正确的步骤为:

      这就是所谓的变量提升。

    2.函数提升

      在js中,我们常见的常见函数的创建方式有三种——函数构造式(不推荐使用,此处不做分析),函数声明式和函数表达式。下面第一行的代码为函数声明式,第二个为函数表达式。

      

 function fn1(){}
var fn2=function(){};

      在以上两种创建方式中,函数表达式的常见方式与普通变量var a=1的创建方式相同,因此它也会受变量提升的影响。而另一种,函数声明式会存在函数提升的情况,并且函数提升比变量提升优先级高!因此分别在创建前打印上述fn1和fn2得到以下结果:

      根据变量提升和函数提升的分析得:

      因此,fn1是作为函数声明被提升到最前面,而fn2先被作为变量创建并提到顶部,然后在相应位置被赋值的。

    3.作用域链

      我们在上面说到js在ES5时代没有块级作用域,只有局部作用域和全局作用域。作用域链用于保证对执行环境有权访问的所有变量和函数的有序访问。看下图,按照一般的思路,b输出为3。这里就用到了作用域链的知识。

      当函数在执行的过程中,先从自己内部找变量。如果找不到,再从创建当前函数所在的作用域去找, 以此往上。因此我们分析调整函数得到下图。在这个函数中给fn赋值为fn1(),即fn1的返回值fn2。在运行fn时,即运行fn2。此时fn2的内部是没用b的,因此我们要去fn2的创建环境中找b=2。所以此处输出为2.

  二、ES6时代

    1.let和const的来临

      首先letconst的作用和var是相同的,但是都是不存在提升,声明的都是块级标识符。大括号内部即形成块级作用域,此时let声明的a在块级作用域外是访问调用不到的

 {
let a=1;
}
console.log(a)//报错

      并且let和const都禁止重复声明的:

 var a = 30;
var message = 2;
// 这两条都会抛出语法错误
let a = 40;
const message = 1;

      每个const声明的常量必须进行初始化const定义的常量不能修改,但是用const声明的对象可以修改值,即

 const a; // 语法错误:常量未初始化
const b = {
name: 'a'
};
b.name = 'b'; // 可以修改 // SyntaxError: "person" is read-only
b = {
name: 'c
}

      letconst声明不会var一样提升到作用域顶部,如果在声明之前访问这些变量,会形成所谓的临时死区(Temporal Dead Zone)即使是相对安全的typeof操作符也会触发引用错误。用let来举例(const也一样):

 console.log(typeof value);
let value = 1;

      在上面的代码中,let无变量提升的作用,即在let value=1之前的代码出现临时死区(Temporal Dead Zone),即报错value is not defined而不是undefined。

 console.log(typeof a);
if(1){
let a=1;
}

      而在上述的代码中,let在块级作用域中,因此在全局作用域中不存在所谓的死区,因此此处打印出undefined。

    2.全局块作用域绑定

      在全局作用域中,var 声明的变量会成为全局对象(浏览器环境中的window)的属性。这意味着var很可能会无意中覆盖一个已经存在的全局变量。


 var Test=1;
window.Test === Test; // true

      如果在全局作用域中使用let或const,会在全局作用域下创建一个新的绑定,但该绑定不会添加为全局对象的属性。换句话说,用letconst不能覆盖全局变量,而只能遮蔽它。


 const foo = 1;
window.foo = 2;
console.log(foo); //
console.log(window.foo); //

  在实际开发中,let实际上与我们所用的的var的用法是一样的,直接替换符合逻辑。对于需要些保护的变量,我们要使用const。默认使用const,只有确实需要改变变量的值时使用let

js作用域的相关知识的更多相关文章

  1. js预解析相关知识总结以及一些好玩的面试题

    js预解析的题像在做智力题一样有意思~ 预解析 预解析:在解释这行代码之前发生的事情——变量的声明提前了,函数的声明提前 console.log(num) ——未定义Num,结果是报错 var num ...

  2. 深入理解ES6之——JS类的相关知识

    基本的类声明 类声明以class关键字开始,其后是类的名称:剩余部分的语法看起来像对象字面量中的方法简写,并且在方法之间不需要使用逗号. class Person { //等价于prototype的构 ...

  3. JS的数组相关知识

    创建数组方法一: var a1=new Array(5); console.log(a1.length); console.log(a1); //[] ,数组是空的 var a2=new Array( ...

  4. js鼠标事件相关知识

    1.mousedown->mouseup依次触发后相当于click事件 2.除了mouseenter和mouseleave外,其它的鼠标事件都是冒泡的 3.mouseover和mouseout事 ...

  5. JS作用域相关知识(#精)

    在学习<你不知道的JS>一书中,特将作用域相关知识在此分享一下: #说到作用域,就不得不提到LHS查询和RHS查询: 1)如果查询目的是对变量进行赋值,则使用LHS查询 2)如果查询目的是 ...

  6. JS 作用域与变量提升---JS 学习笔记(三)

    你知道下面的JavaScript代码执行时会输出什么吗? var foo = 1; function bar() { if (!foo) { var foo = 10; } console.log(f ...

  7. 【Python五篇慢慢弹(5)】类的继承案例解析,python相关知识延伸

    类的继承案例解析,python相关知识延伸 作者:白宁超 2016年10月10日22:36:57 摘要:继<快速上手学python>一文之后,笔者又将python官方文档认真学习下.官方给 ...

  8. js作用域问题

    <script type="text/javascript"> alert(i);//Uncaught ReferenceError: i is not defined ...

  9. AJAX跨域调用相关知识-CORS和JSONP(引)

    AJAX跨域调用相关知识-CORS和JSONP 1.什么是跨域 跨域问题产生的原因,是由于浏览器的安全机制,JS只能访问与所在页面同一个域(相同协议.域名.端口)的内容. 但是我们项目开发过程中,经常 ...

随机推荐

  1. Spring MVC 学习总结(一)——MVC概要与环境配置 转载自【张果】博客

    Spring MVC 学习总结(一)--MVC概要与环境配置   目录 一.MVC概要 二.Spring MVC介绍 三.第一个Spring MVC 项目:Hello World 3.1.通过Mave ...

  2. 学习JVM-GC收集器

    1. 前言 在上一篇文章中,介绍了JVM中垃圾回收的原理和算法.介绍了通过引用计数和对象可达性分析的算法来筛选出已经没有使用的对象,然后介绍了垃圾收集器中使用的三种收集算法:标记-清除.标记-整理.标 ...

  3. thinkphp使用PHPMailer发送邮件

    第一步:准备PHPMailer 使用PHPMailer发送邮件,首先下载个PHPMailer 将下载的PHPMailer放到ThinkPHP文件夹里面的ThinkPHPExtendVendor 第二步 ...

  4. ASP.NET网络爬虫小研究 HtmlAgilityPack基础,爬取数据保存在数据库中再显示再自己的网页中

    1.什么是网络爬虫 关于爬虫百度百科这样定义的:网络爬虫(又被称为网页蜘蛛,网络机器人,在FOAF社区中间,更经常的称为网页追逐者),是一种按照一定的规则,自动地抓取万维网信息的程序或者脚本.另外一些 ...

  5. 邓_PHP面试2

    又开始搞php了,好多php知识忘记了,学习php的方法是看面试题 下面是我搜集的一份php的面试题目 1.用PHP打印出前一天的时间格式是2006-5-10 22:21:21(2分) echo da ...

  6. QT5 Thread线程

    QT5 Thread线程继承QThread方式 一.首先分析一下 QTimer Class与 Sleep()函数之间的秘密 QTimer *t = new QTimer(*parent); //创建Q ...

  7. [拾 得]pipe和xargs的恩怨情仇

    Photo by Joshua Sortino on Unsplash   坚持知识分享,该文章由Alopex编著, 转载请注明源地址: http://www.cnblogs.com/alopex/  ...

  8. 【jsp】MyEclipse10.7.1最新版+破解下载

    MyEclipse企业级工作平台[1](MyEclipse Enterprise Workbench ,简称MyEclipse)是对EclipseIDE的扩展,利用它我们可以在数据库和JavaEE的开 ...

  9. python 与redis

    一.redis安装 源码安装:   1.wget http://download.redis.io/redis-stable.tar.gz   2.yum install gcc   3.tar zx ...

  10. MySQL如何选择合适的引擎以及引擎的转换。

    我们怎么选择合适的引擎?这里简单归纳一句话:"除非需要用到某些InnoDB不具备的特性,并且没有其他办法可以替代,否则都应该优先选择InnoDB引擎." 除非万不得已,否则不建议混 ...