es6中let和const的使用方法详解


Posted in Javascript onFebruary 24, 2020

本文实例讲述了es6中let和const的使用方法。分享给大家供大家参考,具体如下:

ES6 新增了let命令,用来声明变量。它的用法类似于var,但是所声明的变量,只在let命令所在的代码块内有效。

{
 let a = 10;
 var b = 1;
}
a // ReferenceError: a is not defined.
b // 1

上面代码在代码块之中,分别用let和var声明了两个变量。然后在代码块之外调用这两个变量,结果let声明的变量报错,var声明的变量返回了正确的值。这表明,let声明的变量只在它所在的代码块有效。

for循环的计数器,就很合适使用let命令。

for (let i = 0; i < 10; i++) {
 // ...
}
console.log(i);

上面代码中,计数器i只在for循环体内有效,在循环体外引用就会报错。

//下面的代码如果使用var,最后输出的是10。
var a = [];
for (var i = 0; i < 10; i++) {
 a[i] = function () {
  console.log(i);
 };
}
a[6](); // 10

上面代码中,变量i是var命令声明的,在全局范围内都有效,所以全局只有一个变量i。每一次循环,变量i的值都会发生改变,而循环内被赋给数组a的函数内部的console.log(i),里面的i指向的就是全局的i。也就是说,所有数组a的成员里面的i,指向的都是同一个i,导致运行时输出的是最后一轮的i的值,也就是 10。

如果使用let,声明的变量仅在块级作用域内有效,最后输出的是 6。

var a = [];
for (let i = 0; i < 10; i++) {
 a[i] = function () {
  console.log(i);
 };
}
a[6](); // 6

上面代码中,变量i是let声明的,当前的i只在本轮循环有效,所以每一次循环的i其实都是一个新的变量,所以最后输出的是6。你可能会问,如果每一轮循环的变量i都是重新声明的,那它怎么知道上一轮循环的值,从而计算出本轮循环的值?这是因为 JavaScript 引擎内部会记住上一轮循环的值,初始化本轮的变量i时,就在上一轮循环的基础上进行计算。

不存在变量提升

var命令会发生“变量提升”现象,即变量可以在声明之前使用,值为undefined。这种现象多多少少是有些奇怪的,按照一般的逻辑,变量应该在声明语句之后才可以使用。

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

// var 的情况
console.log(foo); // 输出undefined
var foo = 2;
// let 的情况
console.log(bar); // 报错ReferenceError
let bar = 2;

上面代码中,变量foo用var命令声明,会发生变量提升,即脚本开始运行时,变量foo已经存在了,但是没有值,所以会输出undefined。变量bar用let命令声明,不会发生变量提升。这表示在声明它之前,变量bar是不存在的,这时如果用到它,就会抛出一个错误。

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

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

“暂时性死区”也意味着typeof不再是一个百分之百安全的操作。

typeof x; // ReferenceError
let x;

上面代码中,变量x使用let命令声明,所以在声明之前,都属于x的“死区”,只要用到该变量就会报错。因此,typeof运行时就会抛出一个ReferenceError。

作为比较,如果一个变量根本没有被声明,使用typeof反而不会报错。

typeof undeclared_variable // "undefined"

上面代码中,undeclared_variable是一个不存在的变量名,结果返回“undefined”。所以,在没有let之前,typeof运算符是百分之百安全的,永远不会报错。现在这一点不成立了。这样的设计是为了让大家养成良好的编程习惯,变量一定要在声明之后使用,否则就报错。

function bar(x = y, y = 2) {
 return [x, y];
}
bar(); // 报错

上面代码中,调用bar函数之所以报错(某些实现可能不报错),是因为参数x默认值等于另一个参数y,而此时y还没有声明,属于“死区”。如果y的默认值是x,就不会报错,因为此时x已经声明了。

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

不允许重复声明 § ⇧

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

// 报错
function func() {
 let a = 10;
 var a = 1;
}
// 报错
function func() {
 let a = 10;
 let a = 1;
}

因此,不能在函数内部重新声明参数。

function func(arg) {
 let arg; // 报错
}
function func(arg) {
 {
  let arg; // 不报错
 }
}

2.块级作用域

为什么需要块级作用域?

ES5 只有全局作用域和函数作用域,没有块级作用域,这带来很多不合理的场景。

第一种场景,内层变量可能会覆盖外层变量。

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

上面代码的原意是,if代码块的外部使用外层的tmp变量,内部使用内层的tmp变量。但是,函数f执行后,输出结果为undefined,原因在于变量提升,导致内层的tmp变量覆盖了外层的tmp变量。

第二种场景,用来计数的循环变量泄露为全局变量。

var s = 'hello';
for (var i = 0; i < s.length; i++) {
 console.log(s[i]);
}
console.log(i); // 5

上面代码中,变量i只用来控制循环,但是循环结束后,它并没有消失,泄露成了全局变量。

ES6 的块级作用域

let实际上为 JavaScript 新增了块级作用域。

function f1() {
 let n = 5;
 if (true) {
  let n = 10;
 }
 console.log(n); // 5
}

上面的函数有两个代码块,都声明了变量n,运行后输出 5。这表示外层代码块不受内层代码块的影响。如果两次都使用var定义变量n,最后输出的值才是 10。

ES6 允许块级作用域的任意嵌套。

{{{{{let insane = 'Hello World'}}}}};

上面代码使用了一个五层的块级作用域。外层作用域无法读取内层作用域的变量。

{{{{
 {let insane = 'Hello World'}
 console.log(insane); // 报错
}}}};

内层作用域可以定义外层作用域的同名变量。

{{{{
 let insane = 'Hello World';
 {let insane = 'Hello World'}
}}}};

块级作用域与函数声明

函数能不能在块级作用域之中声明?这是一个相当令人混淆的问题。

ES5 规定,函数只能在顶层作用域和函数作用域之中声明,不能在块级作用域声明。

// 情况一
if (true) {
 function f() {}
}
// 情况二
try {
 function f() {}
} catch(e) {
 // ...
}

上面两种函数声明,根据 ES5 的规定都是非法的。

但是,浏览器没有遵守这个规定,为了兼容以前的旧代码,还是支持在块级作用域之中声明函数,因此上面两种情况实际都能运行,不会报错。

3.const 命令

基本用法

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

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

上面代码表明改变常量的值会报错。

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

const foo;
// SyntaxError: Missing initializer in const declaration

上面代码表示,对于const来说,只声明不赋值,就会报错。

const的作用域与let命令相同:只在声明所在的块级作用域内有效。

if (true) {
 const MAX = 5;
}
MAX // Uncaught ReferenceError: MAX is not defined

const命令声明的常量也是不提升,同样存在暂时性死区,只能在声明的位置后面使用。

if (true) {
 console.log(MAX); // ReferenceError
 const MAX = 5;
}

上面代码在常量MAX声明之前就调用,结果报错。

const声明的常量,也与let一样不可重复声明。

var message = "Hello!";
let age = 25;
// 以下两行都会报错
const message = "Goodbye!";
const age = 30;

本质

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

const foo = {};
// 为 foo 添加一个属性,可以成功
foo.prop = 123;
foo.prop // 123
// 将 foo 指向另一个对象,就会报错
foo = {}; // TypeError: "foo" is read-only

上面代码中,常量foo储存的是一个地址,这个地址指向一个对象。不可变的只是这个地址,即不能把foo指向另一个地址,但对象本身是可变的,所以依然可以为其添加新属性。

4.ES6 声明变量的六种方法

ES5 只有两种声明变量的方法:var命令和function命令。ES6 除了添加let和const命令,后面章节还会提到,另外两种声明变量的方法:import命令和class命令。所以,ES6 一共有 6 种声明变量的方法。

顶层对象的属性

顶层对象,在浏览器环境指的是window对象,在 Node 指的是global对象。ES5 之中,顶层对象的属性与全局变量是等价的。

window.a = 1;
a // 1
a = 2;
window.a // 2

上面代码中,顶层对象的属性赋值与全局变量的赋值,是同一件事。

感兴趣的朋友可以使用在线HTML/CSS/JavaScript代码运行工具:http://tools.3water.com/code/HtmlJsRun测试上述代码运行效果。

更多关于JavaScript相关内容可查看本站专题:《JavaScript操作DOM技巧总结》、《JavaScript页面元素操作技巧总结》、《JavaScript事件相关操作与技巧大全》、《JavaScript查找算法技巧总结》、《JavaScript数据结构与算法技巧总结》、《JavaScript遍历算法与技巧总结》及《JavaScript错误与调试技巧总结》

希望本文所述对大家JavaScript程序设计有所帮助。

Javascript 相关文章推荐
全面理解面向对象的 JavaScript(来自ibm)
Nov 10 Javascript
Javascript玩转继承(二)
May 08 Javascript
js中运算符&amp;&amp; 和 || 的使用记录
Aug 21 Javascript
angularjs学习笔记之简单介绍
Sep 26 Javascript
jquery仿ps颜色拾取功能
Mar 08 Javascript
Vue 2.0 服务端渲染入门介绍
Mar 29 Javascript
jQuery实现拖动效果的实例代码
Jun 25 jQuery
vue拖拽组件 vuedraggable API options实现盒子之间相互拖拽排序
Jul 08 Javascript
JS实现电商商品展示放大镜特效
Jan 07 Javascript
js实现div色块碰撞
Jan 16 Javascript
Bootstrap实现前端登录页面带验证码功能完整示例
Mar 26 Javascript
微信小程序实现拨打电话功能的示例代码
Jun 28 Javascript
jquery制作的移动端购物车效果完整示例
Feb 24 #jQuery
jquery实现的放大镜效果示例
Feb 24 #jQuery
JS数组扁平化、去重、排序操作实例详解
Feb 24 #Javascript
JS前端面试必备——基本排序算法原理与实现方法详解【插入/选择/归并/冒泡/快速排序】
Feb 24 #Javascript
Vue快速实现通用表单验证的方法
Feb 24 #Javascript
微信小程序后端实现授权登录
Feb 24 #Javascript
Node使用Nodemailer发送邮件的方法实现
Feb 24 #Javascript
You might like
德劲1107的电路分析与打磨
2021/03/02 无线电
php数组(array)输出的三种形式详解
2013/06/05 PHP
PHP输入输出流学习笔记
2015/05/12 PHP
PHP+Jquery与ajax相结合实现下拉淡出瀑布流效果【无需插件】
2016/05/06 PHP
thinkPHP5.0框架引入Traits功能实例分析
2017/03/18 PHP
详解laravel安装使用Passport(Api认证)
2018/07/27 PHP
jQuery formValidator表单验证插件开源了 含API帮助、源码、示例
2008/08/14 Javascript
风吟的小型JavaScirpt库 (FY.JS).
2010/03/09 Javascript
JQUERY设置IFRAME的SRC值的代码
2010/11/30 Javascript
js用typeof方法判断undefined类型
2014/07/15 Javascript
推荐4个原生javascript常用的函数
2015/01/12 Javascript
javascript实现图片左右滚动效果【可自动滚动,有左右按钮】
2016/09/19 Javascript
JavaScript获取URL中参数querystring的方法详解
2016/10/11 Javascript
js判断手机系统是android还是ios
2017/03/07 Javascript
JS设计模式之数据访问对象模式的实例讲解
2017/09/30 Javascript
小程序点赞收藏功能的实现代码示例
2018/09/07 Javascript
详解webpack模块加载器兼打包工具
2018/09/11 Javascript
js嵌套的数组扁平化:将多维数组变成一维数组以及push()与concat()区别的讲解
2019/01/19 Javascript
Vue实现类似Spring官网图片滑动效果方法
2019/03/01 Javascript
ES6 新增的创建数组的方法(小结)
2019/08/01 Javascript
vue中使用elementUI组件手动上传图片功能
2019/12/13 Javascript
Element实现表格嵌套、多个表格共用一个表头的方法
2020/05/09 Javascript
[02:32]DOTA2完美大师赛场馆静安体育中心观赛全攻略
2017/11/08 DOTA
python中numpy包使用教程之数组和相关操作详解
2017/07/30 Python
解决Pycharm运行时找不到文件的问题
2018/10/29 Python
SkinCeuticals官网:美国药妆品牌
2018/04/19 全球购物
师范生的个人求职信范文
2014/01/04 职场文书
打架检讨书100字
2014/01/08 职场文书
小学防溺水制度
2014/01/29 职场文书
评析教师个人的自我评价
2014/02/19 职场文书
节电标语大全
2014/06/23 职场文书
纪检干部对照检查材料
2014/08/22 职场文书
小学运动会宣传稿
2015/07/23 职场文书
Nginx同一个域名配置多个项目的实现方法
2021/03/31 Servers
Django利用AJAX技术实现博文实时搜索
2021/05/06 Python
MySQL中VARCHAR与CHAR格式数据的区别
2021/05/26 MySQL