热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

【前端开发】说说ES6核心基础中的let和const命令

目录ECMAScript6简介Babel转换器配置文件.babelrcES6let和Const命令let命令循环作用域不存在变量提升不允许重复声明块级作用域ES6的块级作用域块级作

目录

  • ECMAScript 6简介
  • Babel 转换器
  • 配置文件 .babelrc
  • ES6 let 和 Const 命令
  • let 命令
    • 循环作用域
    • 不存在变量提升
    • 不允许重复声明
    • 块级作用域
    • ES6 的块级作用域
    • 块级作用域与函数声明
    • do 表达式
  • const 命令
  • 顶层对象的属性
    • global 对象


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的缺陷。超出的这个代码块的话会报错。
例如下面:
在这里插入图片描述
在这里插入图片描述

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

循环作用域

用 let

for (let i &#61; 0; i < 10; i&#43;&#43;) {}console.log(i); //报错&#xff1a; ReferenceError: i is not defined

计数器i只在for循环体内有效&#xff0c;在循环体外引用就会报错。

用 var

for(var i &#61; 0;i<10;i&#43;&#43;){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

上面代码中&#xff0c;变量i是var声明的&#xff0c;在全局范围内都有效。所以每一次循环&#xff0c;新的i值都会覆盖旧值&#xff0c;导致最后输出的是最后一轮的i的值。

不存在变量提升

在Javascript 中&#xff0c;var 关键字定义的变量可以在使用后声明&#xff0c;也就是变量可以先使用再声明&#xff0c;这就是所谓的“变量提升”。而let不像var那样会发生“变量提升”现象&#xff0c;不可以在使用后声明。所以&#xff0c;变量一定要在声明后使用&#xff0c;否则报错。

console.log(info); // 输出undefined
var info &#61; "var 关键字定义的变量可以在使用后声明";console.log(info2); //报错 Uncaught ReferenceError: Cannot access &#39;info2&#39; before initialization
let info2 &#61; "let 关键字定义的变量不可以在使用后声明";

在这里插入图片描述

不允许重复声明

let不允许在相同作用域内&#xff0c;重复声明同一个变量&#xff1a;

  • 在相同的作用域或块级作用域中&#xff0c;不能使用 let 关键字来重置 var 关键字声明的变量&#xff1b;

// 报错
function(){var x &#61; 10;let x&#61; 20;
}

  • 在相同的作用域或块级作用域中&#xff0c;不能使用 let 关键字来重置 let 关键字声明的变量&#xff1b;

// 报错
function(){let x &#61; 10;let x &#61; 20;
}

  • 在相同的作用域或块级作用域中&#xff0c;不能使用 var 关键字来重置 let 关键字声明的变量。

// 报错
function(){let x &#61; 10;var x &#61; 20;
}

let 关键字在不同作用域&#xff0c;或不同块级作用域中是可以重新声明赋值的。

// 不报错
let x &#61; 10; function(){let x &#61; 20
}function func(arg){{let arg}
}

因此&#xff0c;不能在函数内部重新声明参数。

块级作用域

关于块级作用域&#xff0c;为什么需要块级作用域&#xff1f;在 ES6 之前&#xff0c;是没有块级作用域的概念的。ES5只有全局作用域和函数作用域&#xff0c;没有块级作用域&#xff0c;这带来很多场景应用缺陷。

  • 其中&#xff0c;第一种场景&#xff1a;内层变量可能会覆盖外层变量。

var date &#61; new Date();function d(){console.log(date);if (false){var date &#61; "2020-11-21 00:15:34";}
}d(); // 输出undefined

上面代码中&#xff0c;函数d()执行后&#xff0c;输出结果为undefined&#xff0c;原因在于变量提升&#xff0c;导致内层的date变量覆盖了外层的date变量。

  • 第二种场景&#xff1a;用来计数的循环变量泄露为全局变量。

var str &#61; "hello world!";for (var i&#61;0;i<str.length;i&#43;&#43;){console.log(str[i]);
}console.log(i); // 12

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

ES6 的块级作用域

ES6新增的let为Javascript新增了块级作用域。
let声明的变量只在 let命令所在的代码块 {}内有效&#xff0c;在 {} 之外不能访问。

let i &#61; 1;function f(){let i &#61; 2;if (true){for(let i&#61;1;i<10;i&#43;&#43;){console.log(i);}}
}console.log(i); // 输出 1

上面的函数有三个代码块&#xff0c;都声明了变量i&#xff0c;运行后输出1。由于块级作用域的作用&#xff0c;外层代码块不受内层代码块的影响。

ES6 允许块级作用域的任意嵌套&#xff1a;

{{{{let username &#61; "李子"}}}};

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

{{{
{let username &#61; "李子"}console.log(username); // 报错&#xff1a;Uncaught ReferenceError: username is not defined
}}};

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

{{{
let username &#61; "李子";
{let username &#61; "李子"}
}}};

块级作用域的出现&#xff0c;实际上使得获得广泛应用的立即执行函数表达式&#xff08;IIFE&#xff09;不再必要了,写法更加简洁。

// IIFE 写法
(function () {var info &#61; ...;...
}());// 块级作用域写法
{let info &#61; ...;...
}

块级作用域与函数声明


  • 在ES5中&#xff0c;js函数只能在顶层作用域和函数作用域之中声明&#xff0c;不能在块级作用域声明。尤其是在“严格模式”下如果在块级作用域中声明是会报错的。

// ES5严格模式
&#39;use strict&#39;;// 报错
if (true) {function f() {}
}// 报错
try {function f() {}
} catch(e) {
}

  • 而ES6新增了块级作用域&#xff0c;明确允许在块级作用域中声明函数&#xff1a;

// ES6严格模式
&#39;use strict&#39;;
if (true) {function f() {}
}
// 不报错

  • ES6 规定&#xff0c;块级作用域之中&#xff0c;函数声明语句的行为类似于let&#xff0c;在块级作用域之外不可引用。

function f(){console.log("我在块级作用域外部");}(function(){while(true){function f(){console.log("我在块级作用域内部");}}f();}()); // 我在块级作用域内部

上面代码在 ES5 中运行&#xff0c;会得到“我在块级作用域内部”&#xff0c;因为在if内声明的函数f会被提升到函数头部。

而如果用ES6特新来运行的话&#xff0c;结果就完全不一样了&#xff0c;结果依旧是&#xff1a;“我在块级作用域外部”。因为块级作用域内声明的函数类似于let&#xff0c;对作用域之外没有影响&#xff0c;实际运行的代码如下&#xff1a;

function f(){console.log("我在块级作用域外部");}(function(){f();}());

  • ES6的块级作用域允许声明函数的规则&#xff0c;只在使用大括号的情况下成立&#xff0c;如果没有使用大括号&#xff0c;就会报错。
    1. 没有使用{ }&#xff1a;

// 报错
&#39;use strict&#39;;
if (true)function f() {}

在这里插入图片描述
2. 使用{}&#xff1a;

//不报错 注意是在es6 浏览器中&#xff0c;如果在es5浏览器中&#xff0c;严格模式下依然会报错
&#39;use strict&#39;;
if (true) {function f() {}
}

块级作用域与函数声明总结&#xff1a;

  • 允许在块级作用域内声明函数。
  • 函数声明类似于var&#xff0c;即会提升到全局作用域或函数作用域的头部。
  • 同时&#xff0c;函数声明还会提升到所在的块级作用域的头部。
    以上只对支持ES6的浏览器实现有效&#xff0c;其他环境还是将块级作用域的函数声明当作let处理就可以了。

由于环境的差异&#xff0c;如果在块级作用域中声明函数可能会有很多错误&#xff0c;所以&#xff0c;我们尽可能的避免在块级作用域中声明函数&#xff0c;如果必要在块级作用域中声明函数&#xff0c;可以使用函数表达式的方式代替函数声明语句&#xff1a;

// 函数声明语句{let name &#61; "李子";function info(){return name;}}// 函数表达式{let name &#61; "李子";let info &#61; function(){return name;};}

do 表达式

本质上&#xff0c;块级作用域是一个将多个操作封装在一起的语句&#xff0c;没有返回值。

{let num &#61; f();num &#61; num &#43; 1;
}

上面代码中&#xff0c;块级作用域将两个语句封装在一起。但是&#xff0c;在块级作用域以外&#xff0c;没有办法得到num的值&#xff0c;因为块级作用域不返回值&#xff0c;除非num是全局变量。

如果可以将块级作用域变为表达式&#xff0c;也就是说可以返回值&#xff0c;那么问题就解决了。那怎么实现&#xff1f;办法就是在块级作用域之前加上do&#xff0c;使它变为do表达式。

let x &#61; do {let num &#61; f();num &#61; num &#43; 1;
};

const 命令

const声明一个只读的常量。一旦声明&#xff0c;常量的值就不能改变。
const一旦声明变量&#xff0c;就必须立即初始化&#xff0c;不能留到以后赋值。

// 一旦声明&#xff0c;常量的值就不能改变:
const name &#61; "李子";
name &#61; "李猫er" //报错&#xff1a;TypeError: Assignment to constant variable.// const一旦声明变量&#xff0c;就必须立即初始化&#xff0c;不能留到以后赋值:
const name; // 报错&#xff1a;Missing initializer in const declaration

对于const来说&#xff0c;一旦声明&#xff0c;常量的值就不能改变&#xff1b;而且只声明不赋值&#xff0c;就会报错。

  • const的作用域与let命令相同&#xff1a;只在声明所在的块级作用域内有效。

if (true) {const name &#61; "李子";
}console.log(name); // Uncaught ReferenceError: name is not defined

  • const命令声明的常量也是不提升&#xff0c;同样存在暂时性死区&#xff0c;只能在声明的位置后面使用。
    在常量name声明之前就调用&#xff0c;结果会报错&#xff0c;如下&#xff1a;

if (true) {console.log(name); // ReferenceError: Cannot access &#39;name&#39; before initializationconst name &#61; "李子";
}

  • const声明的常量&#xff0c;也与let一样不可重复声明。

var name &#61; "李子";
let age &#61; 20;const name &#61; "李子"; // eferenceError: Cannot access &#39;name&#39; before initialization
const age &#61; 20;

  • 对于复合类型的变量&#xff0c;变量名不指向数据&#xff0c;而是指向数据所在的地址。const命令只是保证变量名指向的地址不变&#xff0c;并不保证该地址的数据不变&#xff0c;所以将一个对象声明为常量必须非常小心。

const info &#61; {};
info.prop &#61; 678;info &#61; {} // TypeError: Assignment to constant variable.

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

const info &#61; [];
info.push(&#39;李子&#39;);
info.push(20);info &#61; [&#39;李猫er&#39;] // TypeError: Assignment to constant variable.

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

ES5只有两种声明变量的方法&#xff1a;var命令和function命令。ES6除了添加let和const命令&#xff0c;另外还有两种声明变量的方法&#xff1a;import命令和class命令。所以&#xff0c;ES6一共有6种声明变量的方法。

顶层对象的属性

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

window.a &#61; 1;
a // 1a &#61; 2;
window.a // 2

顶层对象的属性与全局变量挂钩&#xff0c;被认为是Javascript语言最大的设计败笔之一。这样的设计带来了几个很大的问题&#xff0c;首先是没法在编译时就报出变量未声明的错误&#xff0c;只有运行时才能知道&#xff08;因为全局变量可能是顶层对象的属性创造的&#xff0c;而属性的创造是动态的&#xff09;&#xff1b;其次&#xff0c;程序员很容易不知不觉地就创建了全局变量&#xff08;比如打字出错&#xff09;&#xff1b;最后&#xff0c;顶层对象的属性是到处可以读写的&#xff0c;这非常不利于模块化编程。另一方面&#xff0c;window对象有实体含义&#xff0c;指的是浏览器的窗口对象&#xff0c;顶层对象是一个有实体含义的对象&#xff0c;也是不合适的。

ES6为了改变这一点&#xff0c;一方面规定&#xff0c;为了保持兼容性&#xff0c;var命令和function命令声明的全局变量&#xff0c;依旧是顶层对象的属性&#xff1b;另一方面规定&#xff0c;let命令、const命令、class命令声明的全局变量&#xff0c;不属于顶层对象的属性。也就是说&#xff0c;从ES6开始&#xff0c;全局变量将逐步与顶层对象的属性脱钩。

global 对象

ES5的顶层对象&#xff0c;本身也是一个问题&#xff0c;因为它在各种实现里面是不统一的。

  • 浏览器里面&#xff0c;顶层对象是window&#xff0c;但 Node 和 Web Worker 没有window。

  • 浏览器和 Web Worker 里面&#xff0c;self也指向顶层对象&#xff0c;但是Node没有self。

  • Node 里面&#xff0c;顶层对象是global&#xff0c;但其他环境都不支持。
    同一段代码为了能够在各种环境&#xff0c;都能取到顶层对象&#xff0c;现在一般是使用this变量&#xff0c;但是有局限性。

  • 全局环境中&#xff0c;this会返回顶层对象。但是&#xff0c;Node模块和ES6模块中&#xff0c;this返回的是当前模块。

  • 函数里面的this&#xff0c;如果函数不是作为对象的方法运行&#xff0c;而是单纯作为函数运行&#xff0c;this会指向顶层对象。但是&#xff0c;严格模式下&#xff0c;这时this会返回undefined。

  • 不管是严格模式&#xff0c;还是普通模式&#xff0c;new Function(‘return this’)()&#xff0c;总是会返回全局对象。但是&#xff0c;如果浏览器用了CSP&#xff08;Content Security Policy&#xff0c;内容安全政策&#xff09;&#xff0c;那么eval、new Function这些方法都可能无法使用。

综上所述&#xff0c;很难找到一种方法&#xff0c;可以在所有情况下&#xff0c;都取到顶层对象。下面是两种勉强可以使用的方法。

// 方法一
(typeof window !&#61;&#61; &#39;undefined&#39;? window: (typeof process &#61;&#61;&#61; &#39;object&#39; &&typeof require &#61;&#61;&#61; &#39;function&#39; &&typeof global &#61;&#61;&#61; &#39;object&#39;)? global: this);// 方法二
var getGlobal &#61; function () {if (typeof self !&#61;&#61; &#39;undefined&#39;) { return self; }if (typeof window !&#61;&#61; &#39;undefined&#39;) { return window; }if (typeof global !&#61;&#61; &#39;undefined&#39;) { return global; }throw new Error(&#39;unable to locate global object&#39;);
};

现在有一个提案&#xff0c;在语言标准的层面&#xff0c;引入global作为顶层对象。也就是说&#xff0c;在所有环境下&#xff0c;global都是存在的&#xff0c;都可以从它拿到顶层对象。

垫片库system.global模拟了这个提案&#xff0c;可以在所有环境拿到global

// CommonJS的写法
require(&#39;system.global/shim&#39;)();// ES6模块的写法
import shim from &#39;system.global/shim&#39;; shim();

上面代码可以保证各种环境里面&#xff0c;global对象都是存在的。

// CommonJS的写法
var global &#61; require(&#39;system.global&#39;)();// ES6模块的写法
import getGlobal from &#39;system.global&#39;;
const global &#61; getGlobal();

上面代码将顶层对象放入变量global。

学习文档:http://caibaojian.com/es6/


推荐阅读
author-avatar
跟-着感觉走
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有