热门标签 | 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/


推荐阅读
  • 本文介绍了闭包的定义和运转机制,重点解释了闭包如何能够接触外部函数的作用域中的变量。通过词法作用域的查找规则,闭包可以访问外部函数的作用域。同时还提到了闭包的作用和影响。 ... [详细]
  • 本文介绍了在wepy中运用小顺序页面受权的计划,包含了用户点击作废后的从新受权计划。 ... [详细]
  • Webpack5内置处理图片资源的配置方法
    本文介绍了在Webpack5中处理图片资源的配置方法。在Webpack4中,我们需要使用file-loader和url-loader来处理图片资源,但是在Webpack5中,这两个Loader的功能已经被内置到Webpack中,我们只需要简单配置即可实现图片资源的处理。本文还介绍了一些常用的配置方法,如匹配不同类型的图片文件、设置输出路径等。通过本文的学习,读者可以快速掌握Webpack5处理图片资源的方法。 ... [详细]
  • 本文讨论了在Windows 8上安装gvim中插件时出现的错误加载问题。作者将EasyMotion插件放在了正确的位置,但加载时却出现了错误。作者提供了下载链接和之前放置插件的位置,并列出了出现的错误信息。 ... [详细]
  • 如何使用Java获取服务器硬件信息和磁盘负载率
    本文介绍了使用Java编程语言获取服务器硬件信息和磁盘负载率的方法。首先在远程服务器上搭建一个支持服务端语言的HTTP服务,并获取服务器的磁盘信息,并将结果输出。然后在本地使用JS编写一个AJAX脚本,远程请求服务端的程序,得到结果并展示给用户。其中还介绍了如何提取硬盘序列号的方法。 ... [详细]
  • 本文介绍了C#中数据集DataSet对象的使用及相关方法详解,包括DataSet对象的概述、与数据关系对象的互联、Rows集合和Columns集合的组成,以及DataSet对象常用的方法之一——Merge方法的使用。通过本文的阅读,读者可以了解到DataSet对象在C#中的重要性和使用方法。 ... [详细]
  • 本文记录了在vue cli 3.x中移除console的一些采坑经验,通过使用uglifyjs-webpack-plugin插件,在vue.config.js中进行相关配置,包括设置minimizer、UglifyJsPlugin和compress等参数,最终成功移除了console。同时,还包括了一些可能出现的报错情况和解决方法。 ... [详细]
  • Voicewo在线语音识别转换jQuery插件的特点和示例
    本文介绍了一款名为Voicewo的在线语音识别转换jQuery插件,该插件具有快速、架构、风格、扩展和兼容等特点,适合在互联网应用中使用。同时还提供了一个快速示例供开发人员参考。 ... [详细]
  • 成功安装Sabayon Linux在thinkpad X60上的经验分享
    本文分享了作者在国庆期间在thinkpad X60上成功安装Sabayon Linux的经验。通过修改CHOST和执行emerge命令,作者顺利完成了安装过程。Sabayon Linux是一个基于Gentoo Linux的发行版,可以将电脑快速转变为一个功能强大的系统。除了作为一个live DVD使用外,Sabayon Linux还可以被安装在硬盘上,方便用户使用。 ... [详细]
  • 闭包一直是Java社区中争论不断的话题,很多语言都支持闭包这个语言特性,闭包定义了一个依赖于外部环境的自由变量的函数,这个函数能够访问外部环境的变量。本文以JavaScript的一个闭包为例,介绍了闭包的定义和特性。 ... [详细]
  • 本文介绍了使用cacti监控mssql 2005运行资源情况的操作步骤,包括安装必要的工具和驱动,测试mssql的连接,配置监控脚本等。通过php连接mssql来获取SQL 2005性能计算器的值,实现对mssql的监控。详细的操作步骤和代码请参考附件。 ... [详细]
  • 本文介绍了获取关联数组键的列表的方法,即使用Object.keys()函数。同时还提到了该方法在不同浏览器的支持情况,并附上了一个代码片段供读者参考。 ... [详细]
  • 模板引擎StringTemplate的使用方法和特点
    本文介绍了模板引擎StringTemplate的使用方法和特点,包括强制Model和View的分离、Lazy-Evaluation、Recursive enable等。同时,还介绍了StringTemplate语法中的属性和普通字符的使用方法,并提供了向模板填充属性的示例代码。 ... [详细]
  • 本文介绍了使用哈夫曼树实现文件压缩和解压的方法。首先对数据结构课程设计中的代码进行了分析,包括使用时间调用、常量定义和统计文件中各个字符时相关的结构体。然后讨论了哈夫曼树的实现原理和算法。最后介绍了文件压缩和解压的具体步骤,包括字符统计、构建哈夫曼树、生成编码表、编码和解码过程。通过实例演示了文件压缩和解压的效果。本文的内容对于理解哈夫曼树的实现原理和应用具有一定的参考价值。 ... [详细]
  • 本文介绍了一个React Native新手在尝试将数据发布到服务器时遇到的问题,以及他的React Native代码和服务器端代码。他使用fetch方法将数据发送到服务器,但无法在服务器端读取/获取发布的数据。 ... [详细]
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社区 版权所有