ECMAScript 6.0(简称ES6)是Javascript语言的下一代标准,已经在2015年6月正式发布了。它的目标,是使得Javascript语言可以用来编写复杂的大型应用程序,成为企业级开发语言。几年过去了,目前各大浏览器的最新版本对ES6的支持度已经越来越高了,ES6的大部分特性都实现了。
而我们都知道Node.js是Javascript语言的服务器运行环境,Node.js对ES6的支持度比浏览器更高。通过Node,我们可以体验更多ES6的特性。使用版本管理工具nvm
,来安装Node,优点是可以自由切换版本。
Babel是一个广泛使用的ES6转码器,可以将ES6代码转为ES5代码,也就是说你可以用ES6的方式去编程,又可以在你现有的环境执行又不用担心现有环境是否支持。实例:
// 转换前
input.map(number => number + 1);// 转换后
input.map(function (number) {return number + 1;
});
原理:上面的原始ES6代码用了箭头函数
,这个特性还没有目前得到广泛支持,Babel 转换器将其转为普通函数,就能在现有的Javascript环境执行了。
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 &#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;
// 报错
function(){var x &#61; 10;let x&#61; 20;
}
// 报错
function(){let x &#61; 10;let x &#61; 20;
}
// 报错
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;这带来很多场景应用缺陷。
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变量。
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新增的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严格模式
&#39;use strict&#39;;// 报错
if (true) {function f() {}
}// 报错
try {function f() {}
} catch(e) {
}
块级作用域
&#xff0c;明确允许在块级作用域中声明函数&#xff1a;// ES6严格模式
&#39;use strict&#39;;
if (true) {function f() {}
}
// 不报错
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();}());
// 报错
&#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;如果在块级作用域中声明函数可能会有很多错误&#xff0c;所以&#xff0c;我们尽可能的避免在块级作用域中声明函数&#xff0c;如果必要在块级作用域中声明函数&#xff0c;可以使用函数表达式
的方式代替函数声明语句
&#xff1a;
// 函数声明语句{let name &#61; "李子";function info(){return name;}}// 函数表达式{let name &#61; "李子";let info &#61; function(){return name;};}
本质上&#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
if (true) {console.log(name); // ReferenceError: Cannot access &#39;name&#39; before initializationconst name &#61; "李子";
}
var name &#61; "李子";
let age &#61; 20;const name &#61; "李子"; // eferenceError: Cannot access &#39;name&#39; before initialization
const age &#61; 20;
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;全局变量将逐步与顶层对象的属性脱钩。
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/