前端开发说说ES6核心基础中的let和const命令
Posted 李猫er
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了前端开发说说ES6核心基础中的let和const命令相关的知识,希望对你有一定的参考价值。
目录
ECMAScript 6简介
ECMAScript 6.0(简称ES6)是javascript语言的下一代标准,已经在2015年6月正式发布了。它的目标,是使得JavaScript语言可以用来编写复杂的大型应用程序,成为企业级开发语言。几年过去了,目前各大浏览器的最新版本对ES6的支持度已经越来越高了,ES6的大部分特性都实现了。
而我们都知道Node.js是JavaScript语言的服务器运行环境,Node.js对ES6的支持度比浏览器更高。通过Node,我们可以体验更多ES6的特性。使用版本管理工具nvm
,来安装Node,优点是可以自由切换版本。
Babel 转换器
Babel是一个广泛使用的ES6转码器,可以将ES6代码转为ES5代码,也就是说你可以用ES6的方式去编程,又可以在你现有的环境执行又不用担心现有环境是否支持。实例:
// 转换前
input.map(number => number + 1);
// 转换后
input.map(function (number) {
return number + 1;
});
原理:上面的原始ES6代码用了箭头函数
,这个特性还没有目前得到广泛支持,Babel 转换器将其转为普通函数,就能在现有的JavaScript环境执行了。
配置文件 .babelrc
Babel的配置文件是.babelrc
,存放在项目的根目录下。使用Babel的第一步,就是配置这个文件。
该文件用来设置转码规则和插件,基本格式如下:
{
"presets": [],
"plugins": []
}
presets
字段用来设定转码规则,官方提供以下的规则集,可以根据需要安装:
# ES2015转码规则
$ npm install --save-dev babel-preset-es2015
# react转码规则
$ npm install --save-dev babel-preset-react
# ES7不同阶段语法提案的转码规则(共有4个阶段),选装一个
$ npm install --save-dev babel-preset-stage-0
$ npm install --save-dev babel-preset-stage-1
$ npm install --save-dev babel-preset-stage-2
$ npm install --save-dev babel-preset-stage-3
然后将安装的规则加入到配置文件中:.babelrc
:
{
"presets": [
"es2015",
"react",
"stage-1"
],
"plugins": []
}
ES6 let 和 Const 命令
let 命令
ES6 新增了let
命令,用来声明变量。它的用法跟es5的var
类似,但是用它所声明的变量让只在let
命令所在的代码有效,也就是块级作用域,这也弥补了es5中var的缺陷。超出的这个代码块的话会报错。
例如下面:
上面代码在代码块之中,分别用let
和var
声明了两个变量。然后在代码块之外调用这两个变量,结果let声明的变量报错,var声明的变量返回了正确的值。这表明,let声明的变量只在它所在的代码块有效。
循环作用域
用 let
for (let i = 0; i < 10; i++) {}
console.log(i); //报错: ReferenceError: i is not defined
计数器i只在for循环体内有效,在循环体外引用就会报错。
用 var
for(var i = 0;i<10;i++){
console.log(i); // 0,1,2,3,4,5,6,7,8,9
}
console.log(i); // 0,1,2,3,4,5,6,7,8,9,10
上面代码中,变量i是var声明的,在全局范围内都有效。所以每一次循环,新的i值都会覆盖旧值,导致最后输出的是最后一轮的i的值。
不存在变量提升
在JavaScript 中,var 关键字定义的变量可以在使用后声明,也就是变量可以先使用再声明,这就是所谓的“变量提升
”。而let
不像var
那样会发生“变量提升”现象,不可以在使用后声明。所以,变量一定要在声明后使用,否则报错。
console.log(info); // 输出undefined
var info = "var 关键字定义的变量可以在使用后声明";
console.log(info2); //报错 Uncaught ReferenceError: Cannot access 'info2' before initialization
let info2 = "let 关键字定义的变量不可以在使用后声明";
不允许重复声明
let不允许在相同作用域内,重复声明同一个变量:
- 在相同的作用域或块级作用域中,不能使用 let 关键字来重置 var 关键字声明的变量;
// 报错
function(){
var x = 10;
let x= 20;
}
- 在相同的作用域或块级作用域中,不能使用 let 关键字来重置 let 关键字声明的变量;
// 报错
function(){
let x = 10;
let x = 20;
}
- 在相同的作用域或块级作用域中,不能使用 var 关键字来重置 let 关键字声明的变量。
// 报错
function(){
let x = 10;
var x = 20;
}
let 关键字在不同作用域,或不同块级作用域中是可以重新声明赋值的。
// 不报错
let x = 10;
function(){
let x = 20
}
function func(arg){
{
let arg
}
}
因此,不能在函数内部重新声明参数。
块级作用域
关于块级作用域,为什么需要块级作用域?在 ES6 之前,是没有块级作用域的概念的。ES5只有全局作用域和函数作用域,没有块级作用域,这带来很多场景应用缺陷。
- 其中,第一种场景:内层变量可能会覆盖外层变量。
var date = new Date();
function d(){
console.log(date);
if (false){
var date = "2020-11-21 00:15:34";
}
}
d(); // 输出undefined
上面代码中,函数d()执行后,输出结果为undefined,原因在于变量提升,导致内层的date变量覆盖了外层的date变量。
- 第二种场景:用来计数的循环变量泄露为全局变量。
var str = "hello world!";
for (var i=0;i<str.length;i++){
console.log(str[i]);
}
console.log(i); // 12
上面代码中,变量i只用来控制循环,但是循环结束后,它并没有消失,泄露成了全局变量。
ES6 的块级作用域
ES6新增的let
为JavaScript新增了块级作用域。
let
声明的变量只在 let
命令所在的代码块 {}
内有效,在 {}
之外不能访问。
let i = 1;
function f(){
let i = 2;
if (true){
for(let i=1;i<10;i++){
console.log(i);
}
}
}
console.log(i); // 输出 1
上面的函数有三个代码块,都声明了变量i,运行后输出1。由于块级作用域的作用,外层代码块不受内层代码块的影响。
ES6 允许块级作用域的任意嵌套:
{{{{let username = "李子"}}}};
上面代码使用了一个四层的块级作用域。并且外层作用域无法读取内层作用域的变量。
{{{
{let username = "李子"}
console.log(username); // 报错:Uncaught ReferenceError: username is not defined
}}};
内层作用域可以定义外层作用域的同名变量。
{{{
let username = "李子";
{let username = "李子"}
}}};
块级作用域的出现,实际上使得获得广泛应用的立即执行函数表达式(IIFE)
不再必要了,写法更加简洁。
// IIFE 写法
(function () {
var info = ...;
...
}());
// 块级作用域写法
{
let info = ...;
...
}
块级作用域与函数声明
- 在ES5中,js函数只能在顶层作用域和函数作用域之中声明,不能在块级作用域声明。尤其是在“
严格模式
”下如果在块级作用域中声明是会报错的。
// ES5严格模式
'use strict';
// 报错
if (true) {
function f() {}
}
// 报错
try {
function f() {}
} catch(e) {
}
- 而ES6新增了
块级作用域
,明确允许在块级作用域中声明函数:
// ES6严格模式
'use strict';
if (true) {
function f() {}
}
// 不报错
- ES6 规定,块级作用域之中,函数声明语句的行为类似于let,在块级作用域之外不可引用。
function f(){
console.log("我在块级作用域外部");
}
(function(){
while(true){
function f(){
console.log("我在块级作用域内部");
}
}
f();
}()); // 我在块级作用域内部
上面代码在 ES5 中运行,会得到“我在块级作用域内部”,因为在if内声明的函数f会被提升到函数头部。
而如果用ES6特新来运行的话,结果就完全不一样了,结果依旧是:“我在块级作用域外部”。因为块级作用域内声明的函数类似于let,对作用域之外没有影响,实际运行的代码如下:
function f(){
console.log("我在块级作用域外部");
}
(function(){
f();
}());
- ES6的块级作用域允许声明函数的规则,只在使用大括号的情况下成立,如果没有使用大括号,就会报错。
1. 没有使用{ }:
// 报错
'use strict';
if (true)
function f() {}
2. 使用{}:
//不报错 注意是在es6 浏览器中,如果在es5浏览器中,严格模式下依然会报错
'use strict';
if (true) {
function f() {}
}
块级作用域与函数声明总结:
- 允许在块级作用域内声明函数。
- 函数声明类似于
var
,即会提升到全局作用域或函数作用域的头部。 - 同时,函数声明还会提升到所在的块级作用域的头部。
以上只对支持ES6的浏览器实现有效,其他环境还是将块级作用域的函数声明当作let处理就可以了。
由于环境的差异,如果在块级作用域中声明函数可能会有很多错误,所以,我们尽可能的避免在块级作用域中声明函数,如果必要在块级作用域中声明函数,可以使用函数表达式
的方式代替函数声明语句
:
// 函数声明语句
{
let name = "李子";
function info(){
return name;
}
}
// 函数表达式
{
let name = "李子";
let info = function(){
return name;
};
}
do 表达式
本质上,块级作用域是一个将多个操作封装在一起的语句,没有返回值。
{
let num = f();
num = num + 1;
}
上面代码中,块级作用域将两个语句封装在一起。但是,在块级作用域以外,没有办法得到num的值,因为块级作用域不返回值,除非num是全局变量。
如果可以将块级作用域变为表达式,也就是说可以返回值,那么问题就解决了。那怎么实现?办法就是在块级作用域之前加上do,使它变为do表达式。
let x = do {
let num = f();
num = num + 1;
};
const 命令
const声明一个只读的常量。一旦声明,常量的值就不能改变。
const一旦声明变量,就必须立即初始化,不能留到以后赋值。
// 一旦声明,常量的值就不能改变:
const name = "李子";
name = "李猫er" //报错:TypeError: Assignment to constant variable.
// const一旦声明变量,就必须立即初始化,不能留到以后赋值:
const name; // 报错:Missing initializer in const declaration
对于const来说,一旦声明,常量的值就不能改变;而且只声明不赋值,就会报错。
const
的作用域与let
命令相同:只在声明所在的块级作用域内有效。
if (true) {
const name = "李子";
}
console.log(name); // Uncaught ReferenceError: name is not defined
- const命令声明的常量也是不提升,同样存在暂时性死区,只能在声明的位置后面使用。
在常量name声明之前就调用,结果会报错,如下:
if (true) {
console.log(name); // ReferenceError: Cannot access 'name' before initialization
const name = "李子";
}
- const声明的常量,也与let一样不可重复声明。
var name = "李子";
let age = 20;
const name = "李子"; // eferenceError: Cannot access 'name' before initialization
const age = 20;
- 对于复合类型的变量,变量名不指向数据,而是指向数据所在的地址。const命令只是保证变量名指向的地址不变,并不保证该地址的数据不变,所以将一个对象声明为常量必须非常小心。
const info = {};
info.prop = 678;
info = {} // TypeError: Assignment to constant variable.
上面代码中,常量info储存的是一个地址,这个地址指向一个对象。不可变的只是这个地址,即不能把info指向另一个地址,但对象本身是可变的,所以依然可以为其添加新属性:
const info = [];
info.push('李子');
info.push(20);
info = ['李猫er'] // TypeError: Assignment to constant variable.
上面代码中,常量info是一个数组,这个数组本身是可写的,但是如果将另一个数组赋值给info,就会报错。
ES5只有两种声明变量的方法:var
命令和function
命令。ES6除了添加let和const命令,另外还有两种声明变量的方法:import命令和class命令。所以,ES6一共有6种声明变量的方法。
顶层对象的属性
顶层对象,在浏览器环境指的是window对象,在Node指的是global对象。ES5之中,顶层对象的属性与全局变量是等价的。
window.a = 1;
a // 1
a = 2;
window.a // 2
顶层对象的属性与全局变量挂钩,被认为是JavaScript语言最大的设计败笔之一。这样的设计带来了几个很大的问题,首先是没法在编译时就报出变量未声明的错误,只有运行时才能知道(因为全局变量可能是顶层对象的属性创造的,而属性的创造是动态的);其次,程序员很容易不知不觉地就创建了全局变量(比如打字出错);最后,顶层对象的属性是到处可以读写的,这非常不利于模块化编程。另一方面,window对象有实体含义,指的是浏览器的窗口对象,顶层对象是一个有实体含义的对象,也是不合适的。
ES6为了改变这一点,一方面规定,为了保持兼容性,var命令和function命令声明的全局变量,依旧是顶层对象的属性;另一方面规定,let命令、const命令、class命令声明的全局变量,不属于顶层对象的属性。也就是说,从ES6开始,全局变量将逐步与顶层对象的属性脱钩。
global 对象
ES5的顶层对象,本身也是一个问题,因为它在各种实现里面是不统一的。
-
浏览器里面,顶层对象是window,但 Node 和 Web Worker 没有window。
-
浏览器和 Web Worker 里面,self也指向顶层对象,但是Node没有self。
-
Node 里面,顶层对象是global,但其他环境都不支持。
同一段代码为了能够在各种环境,都能取到顶层对象,现在一般是使用this变量,但是有局限性。 -
全局环境中,this会返回顶层对象。但是,Node模块和ES6模块中,this返回的是当前模块。
-
函数里面的this,如果函数不是作为对象的方法运行,而是单纯作为函数运行,this会指向顶层对象。但是,严格模式下,这时this会返回undefined。
-
不管是严格模式,还是普通模式,new Function(‘return this’)(),总是会返回全局对象。但是,如果浏览器用了CSP(Content Security Policy,内容安全政策),那么eval、new Function这些方法都可能无法使用。
综上所述,很难找到一种方法,可以在所有情况下,都取到顶层对象。下面是两种勉强可以使用的方法。
// 方法一
(typeof window !== 'undefined'
? window
: (typeof process === 'object' &&
typeof require === 'function' &&
typeof global === 'object')
? global
: this);
// 方法二
var getGlobal = function () {
if (typeof self !== 'undefined') { return self; }
if (typeof window !== 'undefined') { return window; }
if (typeof global !== 'undefined') { return global; }
throw new Error('unable to locate global object');
};
现在有一个提案,在语言标准的层面,引入global
作为顶层对象。也就是说,在所有环境下,global
都是存在的,都可以从它拿到顶层对象。
垫片库system.global
模拟了这个提案,可以在所有环境拿到global
。
// CommonJS的写法
require('system.global/shim')();
// ES6模块的写法
import shim from 'system.global/shim'; shim();
上面代码可以保证各种环境里面,global对象都是存在的。
// CommonJS的写法
var global = require('system.global')();
// ES6模块的写法
import getGlobal from 'system.global';
const global = getGlobal();
上面代码将顶层对象放入变量global。
以上是关于前端开发说说ES6核心基础中的let和const命令的主要内容,如果未能解决你的问题,请参考以下文章