概要
这篇笔记记录以下ECMAScript的新概念
- ECMAScript 和 Javascript的关系
- ECMAScript 和 Node.js的关系
- ECMAScript2015为什么如此重要?
- ECMAScript2015为什么又叫做ES6?
- ES6已成为一种泛指
- 如何给ES2015+新特性分类
- 为什么ES2015开始,规定暂时性死区和let、const语句不存在变量提升?
- 为什么需要块级作用域?
ECMAScript 和 Javascript的关系
ECMAScript是脚本语言的标准化规范,Javascript是一门ECMAScript的扩展语言,ECMAScript只是定义了语言层面的基础语法,通常前端开发者认为浏览器中的Javascript不仅仅只是一门单纯的ECMAScript规范的实现语言,它也包含了DOM API 以及 BOM API,所以,Javascript == ECMAScript + DOM + BOM
ECMAScript 和 Node.js的关系
Node.js也同样实现了ECMAScript规范,但是作为一门后端语言,它同样提供了读写文件的内置模块如fs、网络通信的API如Net,因此 Node.js == ECMAScript + Node.js在ECMAScript规范之外提供的API(如fs、net、etc)
ECMAScript2015为什么如此重要?
ECMAScript2015的上一个ECMAScript版本是在2009年,这6年的时间,前端发展尤其迅速,ECMAScript2015也与上一个版本的语法特性有了翻天覆地的差异。这种剧烈的差异变化和优异的语法功能升级使得熟练掌握ECMAScript2015变得尤其重要。
ECMAScript2015为什么又叫做ES6?
ECMAScript2015之前,ECMAScript每一个大版本的版本号都是按数字依序升级,ECMAScript2015之前刚好是ES5.1的时代,到了这一版本也就是ES6,但也因为6年内,前端发展迅速,从2015开始,ECMAScript的版本发布变为了一年一次,并且版本号按年算,依次也就是ES2015、ES2016、ES2017、ES2018、ES2019、ES2020...
ES6已成为一种泛指
自从ES2015开始,ECMAScipt保持着每年一个大版本的迭代。这也导致很多开发者无法准确的区分哪些新特性是来自哪一个大版本,因此开发者会使用ES6泛指自ES2015及其之后的所有ECMAScript新特性,例如很多人常说,ES6的async 和 await,但我们知道,async 和 await实际是在ES2017中定义的。
如何给ES2015+新特性分类
ES2015及其之后的版本,相对于ES5.1,语法变化还是较大的,那么为了更好的记住这些新特性,可以从以下分类中找出最佳记忆路线
- 解决原有语法的问题和不足(let 和 const的块级作用域)
- 对原有语法进行增强(解构、展开、参数默认值、模板字符串)
- 全新的对象、全新的方法、全新的功能(Promise、Proxy、Object.assign、Reflect、Generator函数)
- 全新的数据结构和数据类型(Set、Map、Symbol、ArrayBuffer)
为什么ES2015开始,规定暂时性死区和let、const语句不存在变量提升?
暂时性死区:一个代码块内,在使用let、const声明变量之前,该变量都是不可用的。这在语法上被称为暂时性死区(temporal dead zone,简称 TDZ)
if (true) {
// TDZ开始
tmp = 'abc'; // ReferenceError
console.log(tmp); // ReferenceError
let tmp; // TDZ结束
console.log(tmp); // undefined
tmp = 123;
console.log(tmp); // 123
}
主要原因:ES2015规定暂时性死区和let、const语句不出现变量提升,主要是为了减少运行时错误,防止在变量声明之前就使用这个变量,从而导致意料之外的行为,这样的错误在ES5中国是很常见的,现在有了此类规定,避免此类错误就很容易了。
为什么需要块级作用域
var存在的问题
- 内层变量覆盖外层同名变量
var tmp = new Data();
function f(){
console.log(tmp);
if (false){
var tmp = "hello word";
}
}
f(); // undefined
按照预期,上面代码肯定是希望打印出当前日期的,但是内部的代码块会存在变量提升行为,内部代码块的变量申明在执行之前便被提升了,使得全局的tmp变量也变为了undefined,打印的结果也变为了undefined
- 计数器泄露为全局变量
var s = 'hello';
for (var i = 0; i < s.length; i++){
console.log(s[i]);
}
console.log(i); // 5
上面的代码中,变量i只是用来控制循环,但是循环结束后,它并没有消失,泄露成了全局变量
块级作用域带来的变化
- 变量只在当前代码块中生效
if (true) {
let foo = "foo";
var bar = "bar";
}
console.log(bar); // "bar"
console.log(foo); // Uncaught ReferenceError: foo is not defined
- 完全取代匿名IIFE(匿名立即执行函数表达式)
// 一个典型的闭包使用场景 - 现象
var arr = [];
for (var i = 0; i < 3; i++) {
arr[i] = function(){
console.log(i);
}
}
arr[0](); // 3
arr[1](); // 3
arr[2](); // 3
出现上面问题的主要原因就是,i变量是仅仅存在在全局作用域中,当arr中函数执行时,循环早已结束,计数器的值已变为3
解决上述问题的方法可以使用函数作用域去存储每次循环时计数器的值
// 一个典型的闭包使用场景 - 解决方法之闭包
var arr = [];
for (var i = 0; i < 3; i++) {
arr[i] = (function(i){
return function(){
console.log(i);
}
})(i)
}
arr[0](); // 0
arr[1](); // 1
arr[2](); // 2
那么如果使用let所提供的块级作用域,就完全没有必要使用闭包了
let arr = [];
for (let i = 0; i < 3; i++) {
arr[i] = function(){
console.log(i);
}
}
arr[0](); // 0
arr[1](); // 1
arr[2](); // 2