美文网首页
学习笔记:关于JS变量作用域

学习笔记:关于JS变量作用域

作者: 银月silvermoon | 来源:发表于2018-07-14 19:44 被阅读0次

前言:今天看到一道结合了JS执行机制+作用域的题目,关于JS执行机制之前已经写过一篇文章:https://www.jianshu.com/p/871665d10db7,下面开始关于作用域的学习!


首先上代码

预期的结果可能是顺序打印12345,但实际上输出结果为55555

首先来分析整个程序运行的顺序,setTimeout()为异步任务,所以每执行一次for循环,都将setTimout里面的函数放入event table(事件表)中,并在i*1000秒后推入event queue(事件队列),因此当for循环全部执行完后,event queue里有5个console.log(i)等待执行。

接着,由于在setTimeout函数内寻找不到关于i的定义,于是向上去查找i,由于i是用var定义的,具有函数级的作用域,因此可以被访问到,此时for循环已全部执行完,i=5,所以最后的打印结果都为5。

如果希望代码与预期一样打印12345,可以简单的将var 改成 let,因为let的作用域是块级的,当for循环执行结束后,作用域在setTimeout()未执行前都不会被释放,每一次console.log(i)都会引用for循环代码块作用域下的i。


定义变量看起来虽然只是一行代码的事情,但真要研究起来,却会发现里面门道有不少,在ES6之前,定义变量都是使用var,ES6以后增加了let和const,那么为什么要增加这2中定义变量的方法呢?他们之间又有哪些区别呢?下面一一来做探究。

一、var

1、var具有函数级作用域

首先来了解一下JS中的作用域,有以下3种

(1)全局作用域--不使用关键字声明,直接赋值

(2)函数作用域--var

(3)块级作用域--let,ES6新增,块作用域由{}包括,if语句和for循环语句种的{}也属于块作用域

接下来看一段代码以更好的进行理解

function test(){

     for ( var i = 0; i < 3; i++) {

            console.log(i)           //依次打印 0,1,2

    }

   console.log(i);           // 打印 2,因为是函数级作用域,因此在整个函数体内都能访问到

}

console.log(i);        //报错,i 未定义,无法跨函数作用域访问 i

i定义在test函数 for 循环的代码块中,但它在整个函数体内都能访问到,因为var定义的变量具有函数级的作用域,可以实现跨块级作用域的访问,但注意,通过var定义的变量是不能够跨函数作用域访问的,在函数外部直接访问会报错 undefined。

2、var可以在相同作用域反复声明相同标识符的变量

var i = 5;

i    //  输出5

var i = 6;

i    //  输出6

3、var支持变量提升

首先看一个例子

console.log(a);     //undefined

var a = '123';

虽然这里在a声明前调用了a,但是执行代码并未报错,而是返回了一个undefined的值,实际上是因为var支持变量提升(即var在当前作用域下声明的所有变量和函数都会提到函数的顶部声明,并且会使用undefined作为缺省值),a在调用前就已经被声明了,只是没有被初始化,上面的代码实际上可以理解成

var a;

console.log(a);     //undefined

a = '123';

二、let

let是ES6中用来替代var的设计,它与var主要有以下不同

1、let使用块级作用域

function test() {

    for (let i = 0; i < 3; i++){

         console.log(i)          // 依次打印0,1,2

    }

    console.log(i)          // Uncaught ReferenceError: i is not defined,块作用域变量在花括号 {} 外无法访问到

}

console.log(i)       //Uncaught ReferenceError: i is not defined

由于let定义的函数作用域是块级的,即其创建的变量只存活于当前变量所在的花括号{}内部,花括号外部是无法访问到的(但注意,let是支持向上查找的),实现原理是let使用了匿名函数自调,let的出现让js变得更加安全和规范,有效的解决了内存泄漏的问题,如开篇讲的关于for循环的问题通过let可以很方便的实现想要的效果。

2、let不支持在同作用域中声明重复声明相同的变量

let i = 5;

i     // 输出5

let i = 6;    //Uncaught SyntaxError: Identifier 'a' has already been declared

之前在var的介绍中可知,var是可以重复定义相同的变量的,但let不支持,重复定义会抛出变量已经被定义的错误

3、let支持变量提升,但它使用了TDZ(暂时死区)禁止了声明前访问

同样先看一个例子

console.log(a);   //Uncaught ReferenceError: a is not defined

let a = '123';

我们通过前面的例子可以知道,var因为支持变量提升的关系,上述相同的代码在用var定义的情况下会返回一个undefined,既然let也支持变量提升,为什么会报错呢?

这是因为let的死区设计,let与const在ES6标准中有如下的文字说明

The variables are created when their containing Lexical Environment is instantiated but may not be accessed in any way until the variable’s LexicalBinding is evaluated.

即是说在程序的控制流程序在作用域进行实例化时,由let和const声明的变量会在作用域中先被创建出来,但此时创建的变量还未进行语法绑定(即还没有被赋值),所以是不能访问的,访问就会报错。因此在控制流进入作用域创建变量,到变量可以开始访问之间的这一段时间,就被称之为暂时死区(TDZ)。

三、const

const也是在ES6后才出现的,主要用于常量的定义,它与let在许多方面性能都类似,主要区别在于:const定义的变量不可再修改,而且必须初始化


最后再倒回开头给出的程序,要想让打印结果为12345,除了用let来替代var的方法外,还有以下2种解决方案:

1、通过立即执行函数创造作用域,保存i的值

2、另外还可以通过创建一个一个新的函数来捕获每一次for循环的i值

相关文章

  • JavaScript作用域学习笔记

    @(JS技巧)[JavaScript, 作用域] JavaScript作用域学习笔记 概念: 作用域就是变量与函数...

  • 变量作用域

    变量作用域:静态作用域、动态作用域JS变量作用域:JS使用静态作用域JS没有块级作用域(全局作用域、函数作用域等)...

  • 学习笔记:关于JS变量作用域

    前言:今天看到一道结合了JS执行机制+作用域的题目,关于JS执行机制之前已经写过一篇文章:https://www....

  • 2020-05-15 浅谈js中的闭包

    一、情景引入: 关于js的作用域,我们都知道: 1.js的作用域分两种,全局和局部 2.在js作用域环境中访问变量...

  • 我的JS笔记汇总

    学习JS的笔记整理: 变量; 类型检测; 类型转换; 作用域; 执行上下文; 函数; 闭包; 模块; 原型; 类;...

  • 老司机也翻车的闭包

    前置知识 es6之前,js中变量作用域分为两种:全局作用域、局部作用域。学习闭包之前需要先了解作用域及变量提升的概...

  • js中作用域与作用域链

    js中作用域与作用域链 作用域 *作用域基本概念 作用域一般指变量的作用范围,变量分为全局变量和局部变量,对应的作...

  • 作用域与变量提升

    作用域与变量提升 作用域 JS中变量的作用域有全局作用域和局部作用域两种,作用域简单来讲就是变量与函数的可访问范围...

  • 闭包

    导学:变量作用域:全局作用域、局部作用域 js中,函数内部可以直接读取全局变量 *链式作用...

  • JavaScript快速上手:关于闭包的知识分享

    JS中变量的作用域 在理解闭包之前,我们得弄清楚JS中变量的作用域原理,它分为全局作用域和局部作用域,它有一...

网友评论

      本文标题:学习笔记:关于JS变量作用域

      本文链接:https://www.haomeiwen.com/subject/afeypftx.html