前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >ES6常用新特性学习1-let和const

ES6常用新特性学习1-let和const

作者头像
love丁酥酥
发布2018-08-27 15:52:15
4290
发布2018-08-27 15:52:15
举报
文章被收录于专栏:coding for lovecoding for love

1. 简介

在ES6以前,变量的声明都是使用var关键字,且会进行变量声明提升。另外,我们曾经讲过,JS中是没有块级作用域的,这一点也带来了很多的不便。ES6 新增了let和var两个关键字,用来声明变量。下面我们就来看看他们的用法。

2. let

我们来看下面一段代码。

代码语言:javascript
复制
function f() {
    var a = 1;
}
{
    var b = 2;
}
// console.log(a); // Uncaught ReferenceError: a is not defined
console.log(b);  // 2

当变量a在函数f内使用var声明时,在全局无法直接引用该变量。但是在全局函数内用一对花括号包裹的区域中生命的变量b,却可以在全局中直接引用。因为对于JS来讲,是没有块作用域的。这一点和JAVA等语言有着很大的不同,也带来了很多不便。举一个简单的例子:

代码语言:javascript
复制
var i = 1;
... // 一堆其他的操作
for (var i = 0;i<3;i++) {
    console.log(i);  // 0 1 2
}
console.log(i);  // 3

在for循环内部生命的变量i其实是无效的,因为在同个作用域(此处是全局作用域)已经声明过变量i。此时var i = 3;中的声明会被忽略,而只保留i =3;(这块内容可以参考我的文章JS入门难点解析3-作用域)。所以,在for循环结束以后,你满心以为会输出最开始在全局声明赋值的var i = 1;时,结果却是被循环改变的结果3。这明显不是我们希望的结果,那么js中能否也使用块级作用域呢,我们生命的变量可否只在块级作用域中生效呢?ES6给我们提供了let。看下面代码:

代码语言:javascript
复制
let i = 1;
... // 一堆其他的操作
for (let i = 0;i<3;i++) {
    console.log(i);  // 0 1 2
}
console.log(i);  // 1

将for循环中的var改成let,其声明的变量i就只在循环内生效了。是不是更加灵活方便了呢?当然,let使用时有些需要注意的地方。

2.1 不存在变量提升

var命令会发生”变量提升“现象,即变量可以在声明之前使用,值为undefined。(可以参考我的文章 JS入门难点解析2-JS的变量提升和函数提升)这种现象多多少少是有些奇怪的,按照一般的逻辑,变量应该在声明语句之后才可以使用。

为了纠正这种现象,let命令改变了语法行为,它所声明的变量一定要在声明后使用,否则报错。

代码语言:javascript
复制
// var 的情况
console.log(foo); // 2
var foo = 2;

// let 的情况
console.log(bar); // Uncaught ReferenceError: bar is not defined
let bar = 2;

2.2 暂时性死区

我们之前讲到过,let没有变量提升,也就是说在let声明一个变量之前对其引用会报错。这个很好理解。但如果此时该变量在块作用域外部也被声明了呢?是否此时的引用是对外部该变量的引用呢?

看下面这段代码:

代码语言:javascript
复制
var tmp = 123;

if (true) {
  tmp = 'abc';  // Uncaught ReferenceError: tmp is not defined
  let tmp;
}

这里,tmp = 'abc';一句会报错。也就是说,let不仅不允许其声明的变量在其声明前被引用,还不允许其引用外部的同名变量,相当地霸道。其实,ES6 明确规定,如果区块中存在let和const命令,这个区块对这些命令声明的变量,从一开始就形成了封闭作用域。凡是在声明之前就使用这些变量,就会报错。

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

代码语言:javascript
复制
if (true) {
  // TDZ开始
  tmp = 'abc'; // ReferenceError
  console.log(tmp); // ReferenceError

  let tmp; // TDZ结束
  console.log(tmp); // undefined

  tmp = 123;
  console.log(tmp); // 123
}

需要注意,TDZ有时会导致代码出错。比如:

曾经安全的typeof可能不在安全:

代码语言:javascript
复制
typeof y; // 未被let锁定,输出undefined
typeof x; // 被let锁定,报ReferenceError
let x;

另外,有些TDZ导致的错误会十分隐晦:

代码语言:javascript
复制
function bar(x = y, y = 2) {
  return [x, y];
}

bar(); // 报错

改成

代码语言:javascript
复制
function bar(x = 2, y = x) {
  return [x, y];
}
bar(); // [2, 2]

就okay了。还有如下情况:

代码语言:javascript
复制
// 不报错
var x = x;

// 报错
let x = x;
// ReferenceError: x is not defined

ES6 规定暂时性死区和let、const语句不出现变量提升,主要是为了减少运行时错误,防止在变量声明前就使用这个变量,从而导致意料之外的行为。这样的错误在 ES5 是很常见的,现在有了这种规定,避免此类错误就很容易了。

总之,暂时性死区的本质就是,只要一进入当前作用域,所要使用的变量就已经存在了,但是不可获取,只有等到声明变量的那一行代码出现,才可以获取和使用该变量。

2.3 不允许重复声明

let不允许在相同作用域内(指的是ES5中规定的作用域,不包含块级作用域)重复声明一个变量,不管是使用var还是let。

代码语言:javascript
复制
var a = 1;
let a = 2;
console.log(a);  // Uncaught SyntaxError: Identifier 'a' has already been declared

以及

代码语言:javascript
复制
let b = 2;
var b = 1;
console.log(b);  // Uncaught SyntaxError: Identifier 'b' has already been declared

还有

代码语言:javascript
复制
let c = 1;
let c = 2;
console.log(c);  // Uncaught SyntaxError: Identifier 'c' has already been declared

另外看下边两组代码。

代码语言:javascript
复制
let a = 1;
if (true) {
    var a = 2;
    console.log(a);  // Uncaught SyntaxError: Identifier 'a' has already been declared
}
代码语言:javascript
复制
let a = 1;
function f(){
    var a = 2;
    console.log(a);  // 2
}
f();

还有一点,需要注意:

考虑到环境导致的行为差异太大,应该避免在块级作用域内声明函数。如果确实需要,也应该写成函数表达式,而不是函数声明语句。

3. const

const的作用很简单。const声明一个只读的常量。一旦声明,常量的值就不能改变。

代码语言:javascript
复制
const PI = 3.1415;
PI // 3.1415

PI = 3;
// TypeError: Assignment to constant variable.

在代码中,我们将长会将一些常量用一些有实际意义的名称去命名。比如上面代码段中的圆周率PI。

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

代码语言:javascript
复制
const foo;
// SyntaxError: Missing initializer in const declaration

const的作用域与let命令相同:只在声明所在的块级作用域内有效,其声明的常量也是不提升,同样存在暂时性死区,只能在声明的位置后面使用。

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

如下:

代码语言:javascript
复制
const a = [];
a.push('Hello'); // 可执行
a.length = 0;    // 可执行
a = ['Dave'];    // 报错

上面代码中,常量a是一个数组,这个数组本身是可写的,但是如果将另一个数组赋值给a,就会报错。

如果真的想将对象冻结,应该使用Object.freeze方法。(可以参考我的文章 JS入门难点解析13-属性描述符,数据属性和访问器属性)

代码语言:javascript
复制
const foo = Object.freeze({});

// 常规模式时,下面一行不起作用;
// 严格模式时,该行会报错
foo.prop = 123;

除了将对象本身冻结,对象的属性也应该冻结。下面是一个将对象彻底冻结的函数。

代码语言:javascript
复制
var constantize = (obj) => {
  Object.freeze(obj);
  Object.keys(obj).forEach( (key, i) => {
    if ( typeof obj[key] === 'object' ) {
      constantize( obj[key] );
    }
  });
};

参考

let 和 const 命令

深入ES6 (二)let和const

ES6这些就够了

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2018.03.10 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1. 简介
  • 2. let
    • 2.1 不存在变量提升
      • 2.2 暂时性死区
        • 2.3 不允许重复声明
        • 3. const
        • 参考
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档