前端生活点滴

ES6系列之变量与块级作用域

2018-06-20  本文已影响2人  ld1024

本篇目录:


ECMAScript 6.0(以下简称 ES6)是 JavaScript 语言的下一代标准,已经在 2015 年 6 月正式发布了。它的目标,是使得 JavaScript 语言可以用来编写复杂的大型应用程序,成为企业级开发语言。

ES6的出现已经很久了,由于之前的项目只是基于ES5以及之前的版本内容进行开发的,所以没有系统性的梳理学习ES6的相关知识。感觉自己已经被发展的车轮落下了好远好远(@﹏@)~,所以在接下来这段时间针对性的进行学习整理,希望能够让自己有所提高,也希望能帮助和我一样的人……

变量

常量 const命令

常量的定义

在之前的版本ES5甚至更早的版本(后面统称ES5)中,很少能够听到常量的概念,我们也知道,JS不同于其他的语言,如Java、C++等,有常量的概念。虽然通过其他的方法能够达到实现常量的方法,但是并不直接、方便。

如可通过下述方式进行常量的实现:

// 此处在window对象上面定义了一个圆周率变量PI,使之不能被修改,模拟常量
Object.defineProperty(window, "PI", {
    value: 3.1415926,
    writable: false
})

在ES6中,定义一个常量则非常简单,通过const进行声明即可。

const声明一个只读的常量。一旦声明,常量的值就不能改变。

const PI = 3.1415926;

const声明的变量不得改变值,这意味着,const一旦声明变量,就必须立即初始化,不能留到以后赋值。

const定义常量的本质

const实际上保证的,并不是变量的值不得改动,而是变量指向的那个内存地址不得改动。对于简单类型的数据(数值、字符串、布尔值),值就保存在变量指向的那个内存地址,因此等同于常量。但对于复合类型的数据(主要是对象和数组),变量指向的内存地址,保存的只是一个指针,const只能保证这个指针是固定的,至于它指向的数据结构是不是可变的,就完全不能控制了。

如:

const foo = {};

// 为 foo 添加一个属性,可以成功
foo.prop = 123;
foo.prop // 123

// 将 foo 指向另一个对象,就会报错
foo = {}; // TypeError: "foo" is read-only

let命令

在ES6中新增了let命令,可以用来声明一个变量,作用类似于之前常用的var,但是和var声明的变量其有效的作用域不同,let声明的变量只在let命令所在的代码块内有效。

如:

{
    let a = 1;
    var b = 2;
}
console.log(a); // Uncaught ReferenceError: a is not defined
console.log(b); // 2

通过上面代码的执行结果来看能够看出,let声明的变量只在它所在的代码块中有效,即{}所在的代码块内。

那么let声明的变量在什么情况下会比较有用呢?先让我们来看下下面一段代码:

var arrays = [];
for (var i = 0; i <= 2; i++) {
    arrays[i] = function() {
        return i * 2;
    }
}

console.table([
    arrays[0](),
    arrays[1](),
    arrays[2]()
])

可能我们预期的结果是显示0,2,4,但是从执行代码的结果中能够看出,得到的却是6,6,6,这是为什么呢?
首先我们上面是使用的var声明的一个i变量,此处会有一个变量提升,相当于在for循环外面声明了var i = 0变量。当在for循环中时,arrays[i]中的i即为每次循环的i的值(0,1,2),但是在函数体中并没有将i变量取值为每次循环的值,它只是对变量的一个引用,并不是值的引用。当在执行循环中的函数体时,i变量已经变成了3,所以每次的结构输出都是6。这个时候,ES6中的let命令就能够满足我们的要求了。

var arrays = [];
for (let i = 0; i <= 2; i++) {
    arrays[i] = function() {
        return i * 2;
    }
}

console.table([
    arrays[0](),
    arrays[1](),
    arrays[2]()
])

上面代码中变量i只在for循环体内有效,只在其所在的块作用域内有效,每次for循环时会将i变量的值保存在其对应的块作用域中(for循环的{}),每一轮循环都是重新生成一个新的作用域,在执行for循环中声明的函数时,都会获取其对应作用域内的i的值。这部分涉及到的作用域相关的概念,我们后面会说到。

1、在前面提到过,var命令声明的变量会发生变量提升的现象,即变量可以在声明之前使用,值为undefined。按照一般的逻辑来说,变量应该在声明语句之后才可以使用。所以在ES6中为了纠正这个现象,let命令改变了语法行为,它所声明的变量一定要在声明后使用,否则报错。

2、只要在块级作用域中存在let命令,它所声明的变量就“绑定”(binding)这个区域,不再受外部的影响。

var tmp = 123;

if (true) {
  tmp = 'abc'; // ReferenceError
  let tmp;
}

ES6 明确规定,如果区块中存在let和const命令,这个区块对这些命令声明的变量,从一开始就形成了封闭作用域。凡是在声明之前就使用这些变量,就会报错。

总之,在代码块内,使用let命令声明变量之前,该变量都是不可用的。这在语法上,称为“暂时性死区”(temporal dead zone,简称 TDZ)。

3、typeof不再是一个完全安全的操作。之前我们知道,针对一个没有生命的变量调用typeof方法时会返回undefined,但是在上面出现的“暂时性死区”中,typeof将会被报错。

使用let声明变量时,只要变量在还没有声明完成前使用,就会报错。

4、let不允许在相同作用域内,重复声明同一个变量。

块级作用域

为什么需要块级作用域

1、内层变量可能会覆盖外层变量

var tmp = new Date();

function f() {
  console.log(tmp);
  if (false) {
    var tmp = 'hello world';
  }
}

f(); // undefined

内层的tmp变量由于变量提升覆盖了外层的tmp变量,导致输出不符合预期

2、计数的循环变量泄露为全局变量

我们上面用到的for循环的例子就是这个现象的证明,var声明的变量i在循环结束后泄露成了全局变量。

ES6的块级作用域

在ES6中{}包裹的块即为一个块级作用域,并且块级作用域支持嵌套,如

{{{{{let insane = 'Hello World'}}}}}; // 5层块级作用域

并且外层作用域不能读取内层作用域的变量;
内层作用域可以定义外层作用域的同名变量。

在之前的版本中,当我们想要实现一个变量只在一个代码块中运行,我们往往需要通过使用立即执行函数来实现。如

(function () {
  var tmp = ...;
  ...
}());

但是在ES6中则不再必要了,我们可以通过块级作用域就能够实现

{
  let tmp = ...;
  ...
}

本次主要针对ES6中的变量和块级作用域进行了梳理学习,并且通过与ES5的实现方式进行了对比,从而看出其变化以及快捷与便利。希望能够对您有所帮助,如有写的不对的地方望请指正,从而共同进步。

上一篇 下一篇

猜你喜欢

热点阅读