ES6 学习笔记之一 块做用域与let和const

在学习ES6的块做用域和 let、const 以前,咱们先来看看ES5之前的 var 关键字。chrome

var 关键字用于定义一个变量,一般咱们会将其与变量的赋值合并为一条语句,就像下面这样(例1):编程

var age = 30;

但实际状况是有些微妙的。闭包

在JavaScript中,变量的定义与否,虽然不像强类型语言那样重要,但也仍是有所不一样的。app

变量未定义,是一种未捕获类型的错误,输出的结果是变量未定义,同时终止后续脚本的执行,示例以下(例2):编程语言

console.log(age);

输出:函数

age is not defined.

已经定义但未赋值的变量,输出的结果是undefined(这个undefined不是未定义,是指变量的值为undefined,这是JavaSript中的一个特殊的值)。示例以下(例3):学习

var age;
console.log(age);

输出:spa

undefined

var 定义变量语句,在JavaScript中会被编译器提高到整个做用域内有效,不管它写在脚本的哪一个位置。示例以下(例4):firefox

console.log(age);
var age;

输出code

undefined

这个待遇只有 var 定义语句都有,赋值语句是不会被提高的。当咱们在代码中写下一条定义的同时赋值的 var 语句时,编译器会将其拆分为定义语句和赋值语句,并将定义语句提高,而赋值语句仍然只对其后的代码有效。

请看下面的例子(例5):

console.log(age);
var age = 30;
console.log(age);

它与以下例子的输出是一致的(例6):

var age;
console.log(age);
age = 30;
console.log(age);

输出结果:

undefined
30

咱们还知道,JavaScript中变量能够不定义就使用,好比(例7):

age = 30;
console.log(age);

输出:

30

这种直接赋值的语句,与定义的同时赋值,仍是有所不一样。参见下面的例子(例8)

console.log(age);
age = 30;
console.log(age);

输出结果:

ReferenceError: age is not defined

之因此一例5有这么大的区别,在于这里只有变量赋值,没有 var 定义部分。编译器不会自动补充一个 var 定义并提高(或者说编译器补充了一个隐含的 var 定义,可是这个隐含的 var 定义不会被提高),所以第一个console.log语句会直接报出变量未定义的错误,以后的变量赋值和输出语句不被执行。

为避免误用未定义的变量,在使用变量前,要养成在使用变量前用 var 进行定义的好习惯:)。除非在函数做用域内要使用父做用域的变量,这也是闭包存在的根源。

若是可能,可使用严格模式,即在脚本做用域的第一行,写下 "use strict"(注意要带着又引号哦)。这样作的一个好处是,WebStorm 这样的 IDE 会在为未定义的变量赋值时,直接给出警告。

块做用域

块做用域是指用一对花括号括住的语句块使用域。在ES5之前,是没有块做用域的,它只有函数做用域,所以 var 在非函数做用域的括号内,与在括号外,没有区别。为了保持向下兼容,在ES6中,var 在块做用域中仍然保持其原有性状。以下例(例9):

console.log(age);
{
    var age = 30;
}
console.log(age);

未定义就赋值的变量,也与原来的表现一致(例10)

{
    age = 30;
}
console.log(age);

要使块做用域别有不同的意义,就须要 let 和 const,这两个关键字了。

 let 关键字与 var 的做用相似,都是定义变量,也均可以在定义变量的同时,为变量赋值。不一样之处有如下两点:

第一,let 能够定义(仅在)块做用域内(有效)的变量。以下例所示(例11):

{
    let age = 30;
}
console.log(age);

输出结果为:

ReferenceError: age is not defined

对照例9,能够看出区别所在。

第二,let 定义变量语句,不会被提高到整个做用域有效。见下例(例12):

console.log(age);
let age = 30;

输出结果为:

ReferenceError: age is not defined

对照例4,能够看出区别所在。

还有一个细节,let 和 var 都有在子做用域内定义与父做用域同名变量时,屏蔽父做用域同名变量的做用。

见以下两例:

(例13)

var age = 20;
(function () {
    var age = 30;
    console.log(age);
})();
console.log(age);

(例14)

var age = 20;
{
    let age = 30;
    console.log(age);
}
console.log(age);

输出结果均为:

30
20

区别在于,若是在子做用域中,定义同名变量以前就使用该变量,对于 var 而言,因为其会被提高,所以获得的是undefined。对于let,不会被提高,却也不会使用父做用域的变量,而是报出引用错误。示例以下:

(例15)

var age = 20;
(function () {
    console.log(age);
    var age = 30;
})();

输出:

undefined

(例16)

var age = 20;
{
    console.log(age);
    let age = 30;
}

输出:

ReferenceError: age is not defined

 还要说明一点,在ES6中,全部具备语句块含义花括号括住的部分都构成块做用域,包括单纯的语句块、if语句块、while语句块、for语句块、break语句块和函数语句块。

在 for 的循环变量定义,会在每次迭代中执行一遍,就至关于每次迭代定义一个变量,迭代结束销毁,循环多少次,生成多少个不一样的循环变量。 这个特色的实际意义,能够用下面的两个示例说明。

(例17)

var i;
var fn = [];
for (i = 0; i < 3; i++) {
    fn.push(function () {
        console.log(i);
    });
}
fn[0]();
fn[1]();
fn[2]();

可能会预期其输出结果为:

0
1
2

实际输出结果为:

3
3
3

(例18)

var fn = [];
for (let i = 0; i < 3; i++) {
    fn.push(function () {
        console.log(i);
    });
}
fn[0]();
fn[1]();
fn[2]();

输出结果为:

0
1
2

在没有块做用域和let关键字以前,要实现一样效果,须要利用闭包(例19)

var i;
var fn  = [];
for (i = 0; i < 3; i++) {
    fn.push((function (i) {
        return function () {
            console.log(i);
        }
    })(i));
}
fn[0]();
fn[1]();
fn[2]();

附带说一句:for 语句块的循环变量赋值部分的let定义,做用域很特殊,是循环体做用域的父做用域。

例20:

for (let i = 0; i < 3; i++) {
    let i = "s";
    console.log(i);
}

输出结果为:

s
s
s

输出三个 s ,说明循环变量 i 保持了从 0 到 3 的自增,而循环体内的 i 屏蔽了循环变量 i ,因此会输出 s。

因为 let 相较于 var 优点明显,又没有明显的缺点,在ES6兼容环境下,能够彻底抛弃 var,而用 let 代替。

《你不知道的JavaScript》中,提倡C语言风格的 let 使用方法,即在做用域的起始处使用 let 定义做用域内的全部变量,这样能够避免变量未定义就使用的状况发生。

我的以为仍是按现代编程语言就近声明的惯例更合适,能够避免无心识的误用,并且在阅读时也不须要在代码间跳跃以寻找变量定义和赋值。不过若是能保持代码块的简短,又能坚持同一变量在同一做用域内不做两用的原则,其实放在哪儿都可有可无。

对于 const,其实没有太多须要说明的,常量定义而已。

PS:

还有一个细节:变量在使用时,可能处于如下四种状态:未声明、已声明未初始化、已初始化、已赋值。这里的初始化,指的是默认赋值undefined。

var 和 let 语句,在声明变量的同时,若是没有明确给变量赋值,则被初始化为undefined,以下两例能够证实:

(例21)

var age;
console.log(age);

(例22)

let age;
console.log(age)

两例均输出:

undefined

前面提到 var 语句会被提高,指的就是初始化这个功能,let 的初始化功能不会被提高。

可是 var 和 let 的变量声明功能,则是在整个做用域内生效的。即无论 var 和 let 处于做用域内什么位置,所声明的变量在整个做用域内都是已声明状态。没有使用 var 和 let 声明直接赋值的变量,则没有此待遇,在其赋值前,变量未声明。

下面举例说明:

未声明(例23)

console.log(age);
age = 30;
console.log(age);

输出结果为:

ReferenceError: age is not defined

已声明未初始化(例24):

console.log(age);
let age = 30;
console.log(age);

firefox 输出:

ReferenceError: can't access lexical declaration `a' before initialization

chrome 的输出比较含混:

ReferenceError: age is not defined

已初始化(例25)

console.log(age);
var age = 30;
console.log(age);

输出:

undefined
30

对于已声明未初始化和未声明,能够用 typeof 运算符来作区分,未声明的变量 typeof 的结果为 undefined,而对于已声明未初始化的变量,typeof 则会抛出 ReferenceError。

未声明(例26)

console.log(typeof age);
age = 30;
console.log(typeof age);

输出结果为:

undefined
number

已声明未初始化(例27)

console.log(typeof age);
let age = 30;
console.log(typeof age);

chrome 输出:

ReferenceError: age is not defined

firefox 输出:

ReferenceError: can't access lexical declaration `a' before initialization

对于未声明和已声明且已初始化为 undefined 的变量,typeof 就力不从心了,由于 typeof undefined 的结果也是 undefined,和未声明变量的 typeof 结果相同。见下例(例28)

console.log(typeof age);
age = 30;
console.log(typeof name);
var name;

输出:

undefined
undefined

还想区别,就用以下方式(例29):

console.log(typeof age && undefined === age);

对于已声明且已初始化为 undefined 的变量,输出结果为 true。

不然,chrome 抛出 ReferenceError: age is not undefined,firefox 抛出 ReferenceError: can't access lexical declaration `a' before initialization。

相关文章
相关标签/搜索