ES6 语言基础 - archering/basic GitHub Wiki
//ES6的系统学习可以参考 这个文章 http://es6.ruanyifeng.com/#docs/symbol
ECMAScript2015 vs ES6
(1)标准委员会决定,标准在每年的 6 月份正式发布一次,作为当年的正式版本 (2)ES6 的第一个版本在 2015 年 6 月发布了,正式名称就是《ECMAScript 2015 标准》(简称 ES2015)。 (3)2016 年 6 月,小幅修订的《ECMAScript 2016 标准》(简称 ES2016)如期发布,这个版本可以看作是 ES6.1 版,因为两者的差异非常小(只新增了数组实例的includes方法和指数运算符),基本上是同一个标准。 (4)2017 年 6 月发布 ES2017 标准。
ES6 既是一个历史名词,也是一个泛指,含义是 5.1 版以后的 JavaScript 的下一代标准,涵盖了 ES2015、ES2016、ES2017 等等,而 ES2015 则是正式名称,特指该年发布的正式版本的语言标准。
由于目前并不是所有浏览器都支持ES6,所以需要借助工具将ES6编写的程序转成ES5的代码。Babel是目前广泛使用的转码器,他是一个第三方开发的jvascript库,运行在node下。
在unix like的操作系统上,常常能看到.bashrc 或者shrc .bowerrc等 rc文件,这个rc的全称是 “run command”。 脚本程序在启动阶段通常会首先读取rc文件。
Babel 的配置文件是.babelrc,存放在项目的根目录下。使用 Babel 的第一步,就是配置这个文件。
该文件用来设置转码规则和插件,基本格式如下。
{
"presets": [],
"plugins": []
}
presets字段设定转码规则,官方提供以下的规则集,你可以根据需要安装。
最新转码规则
$ npm install --save-dev babel-preset-latest
react 转码规则
$ npm install --save-dev babel-preset-react
然后,将这些规则加入.babelrc。
{
"presets": [
"latest",
"react"
],
"plugins": []
}
注意,以下所有 Babel 工具和模块的使用,都必须先写好.babelrc。 Babel 6.0 开始,不再直接提供浏览器版本,而是要用构建工具构建出来.
ES6新增let,const 和块级作用域
const 常量是块级作用域;此声明创建一个常量,其作用域可以是全局或本地声明的块。 与var变量不同,全局常量不会变为窗口对象window(最顶层对象global)的属性。声明后必须立即初始化,初始化后不能修改。
let 声明一个作用域被限制在块级中的变量、语句或者表达式。与var关键字不同的是,它声明的变量只能是全局或者整个函数块的; 和var 不同,像if-else ,function, switch 这样的块都可以阻断let声明变量的延续,let并不会像var一样在全局对象上创造一个属性。不会声明前置。
function varTest() {
var x = 1;
if (true) {
var x = 2; // 同样的变量!
console.log(x); // 2
}
console.log(x); // 2
}
function letTest() {
let x = 1;
if (true) {
let x = 2; // 不同的变量
console.log(x); // 2
}
console.log(x); // 1
var 函数作用域,会声明前置,会给全局对象window创建属性。
块状作用域的可视化理解 :块语句(或其他语言的复合语句)用于组合零个或多个语句。该块由一对__大括号__界定,可以是labelled:
let x = 1;
{
let x = 2;
}
console.log(x); // 输出 1
下面的代码如果使用var,最后输出的是10。
var a = [];
for (var i = 0; i < 10; i++) {
a[i] = function () {
console.log(i);
};
}
a[6](); // 10
如果使用let,声明的变量仅在块级作用域内有效,最后输出的是 6。
var a = [];
for (let i = 0; i < 10; i++) {
a[i] = function () {
console.log(i);
};
}
a[6](); // 6
for循环还有一个特别之处,就是设置循环变量的那部分是一个父作用域,而循环体内部是一个单独的子作用域。
for (let i = 0; i < 3; i++) {
let i = 'abc';
console.log(i);
}
// abc
// abc
// abc
上面代码正确运行,输出了 3 次abc。这表明函数内部的变量i与循环变量i不在同一个作用域,有各自单独的作用域。 箭头函数 Arrow Functions
只要块级作用域内存在let命令,它所声明的变量就“绑定”(binding)这个区域,不再受外部的影响。
var tmp = 123;
if (true) {
tmp = 'abc'; // ReferenceError
let tmp;
}
上面代码中,存在全局变量tmp,但是块级作用域内let又声明了一个局部变量tmp,导致后者绑定这个块级作用域,所以在let声明变量前,对tmp赋值会报错。
ES6 明确规定,如果区块中存在let和const命令,这个区块对这些命令声明的变量,从一开始就形成了封闭作用域。凡是在声明之前就使用这些变量,就会报错。
总之,在代码块内,使用let命令声明变量之前,该变量都是不可用的。这在语法上,称为“暂时性死区”(temporal dead zone,简称 TDZ)。
“暂时性死区”也意味着typeof不再是一个百分之百安全的操作 //会报错ReferenceError
typeof x; // ReferenceError
let x;
如果一个变量根本没有被声明,使用typeof反而不会报错
typeof undeclared_variable // "undefined"
// 不报错
var x = x;
// 报错
let x = x;
// ReferenceError: x is not defined
上面代码报错,也是因为暂时性死区。使用let声明变量时,只要变量在还没有声明完成前使用,就会报错
ES6 数组赋值 Destructuring
var arr = [];
a[0] = 1;
a[1] = 2;
a[2] = 3;
现在允许
let [a,b,c] = [1,2,3];
如果解构不成功,变量的值就等于undefined。
let [foo] = [];
let [bar, foo] = [1];
以上两种情况都属于解构不成功,foo的值都会等于undefined。
解构不仅可以用于数组,还可以用于对象。
let { foo, bar } = { foo: "aaa", bar: "bbb" };
foo // "aaa"
bar // "bbb"
对象的解构与数组有一个重要的不同。数组的元素是按次序排列的,变量的取值由它的位置决定;而对象的属性没有次序,变量必须与属性同名,才能取到正确的值。
ES6 的函数变化非常大
(1) ES6 允许为函数的参数设置默认值 ,定义了默认值的参数,应该是函数的尾参数
function log(x, y = 'World') {
console.log(x, y);
}
//参数变量x是默认声明的,在函数体中,不能用let或const再次声明,否则会报错。
使用参数默认值时,函数不能有同名参数。
// 不报错
function foo(x, x, y) {
// ...
}
// 报错
function foo(x, x, y = 1) {
// ...
}
指定了默认值以后,函数的length属性,将返回没有指定默认值的参数个数
(2) ES6 引入 rest 参数(形式为...变量名), 不知道有几个具体的参数, 和Actionscript 一样;rest 参数之后不能再有其他参数,函数的length属性,不包括 rest 参数
function add(...values) {
let sum = 0;
for (var val of values) {
sum += val;
}
return sum;
}
add(2, 5, 3) // 10
从 ES5 开始,函数内部可以设定为严格模式。
function doSomething(a, b) { 'use strict'; // code } ES2016 做了一点修改,规定只要函数参数使用了默认值、解构赋值、或者扩展运算符,那么函数内部就不能显式设定为严格模式,否则会报错
(3) **箭头函数 ** ;ES6 允许使用“箭头”(=>)定义函数
var f = v => v;
//f 函数名
// v 参数
// => 回调函数 function(){}
//箭头后的 v 返回值 相当于 return v;
上面的箭头函数等同于:
var f = function(v) {
return v;
};
下面逐个说箭头函数的 “参数部分” 和 “代码块部分”
如果箭头函数不需要参数或需要多个参数,就使用一个圆括号代表参数部分
var f = () => 5;
// 等同于
var f = function () { return 5 };
var sum = (num1, num2) => num1 + num2;
// 等同于
var sum = function(num1, num2) {
return num1 + num2;
};
如果箭头函数的代码块部分多于一条语句,就要使用大括号将它们括起来,并且使用return语句返回。
var sum = (num1, num2) => { return num1 + num2; }
由于大括号被解释为代码块,所以如果箭头函数直接返回一个对象{ id: id, name: "Temp" },必须在对象外面加上括号,否则会报错。
// 报错
let getTempItem = id => { id: id, name: "Temp" };
// 不报错
let getTempItem = id => ({ id: id, name: "Temp" });
箭头函数导致this总是指向函数定义生效时所在的对象
如果箭头函数只有一行语句,且不需要返回值,可以采用下面的写法,就不用写大括号了。
let fn = () => void doesNotReturn();
正常的箭头函数怎么写
let hello = (a,b) => {
a = a*3;
b = a*b;
console.log(a + b);
return a+b + 1;
}
//执行
hello(1,2);// return 10
ES6 class
ES6 提供了更接近传统语言的写法,引入了 Class(类)这个概念,作为对象的_模板_。 ES6 的class可以看作只是一个语法糖(syntax sugar) 即换了一种形式的语法包装,实际还是function那一套。 例子说明,如果用class 实现编写同一段代码
function Point(x, y) {
this.x = x;
this.y = y;
}
Point.prototype.toString = function () {
return '(' + this.x + ', ' + this.y + ')';
};
var p = new Point(1, 2);
//定义类
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
//定义“class”的方法的时候,前面不需要加上function这个关键字
toString() {
return '(' + this.x + ', ' + this.y + ')';
}
}
//typeof Point // "function" // 可以看到这个 class 顶一个point 就是function
ES6 的 module 系统和 import export
一个模块就是一个独立的文件。该文件内部的所有变量,外部无法获取 ES6 模块的设计思想是尽量的静态化,使得编译时就能确定模块的依赖关系,以及输入和输出的变量。CommonJS 和 AMD 模块,都只能在运行时确定这些东西. CommonJS 和 AMD 加载都是在运行时,加载整个对象(模块即对象),然后在取对象的具体成员进行操作, ES6模块不是对象,而且ES6模块的是编译时加载,即在编译期决定需要模块的那部分内容,按需加载,在运行期这些模块已经加载好了。 ES6 的模块自动采用严格模式,不管你有没有在模块头部加上"use strict";。
export
export命令用于规定模块的对外接口 e.g
profile.js文件,保存了用户信息, ES6 将其视为一个模块,里面用export命令对外部输出了三个变量。
// profile.js
var firstName = 'Michael';
var lastName = 'Jackson';
var year = 1958;
export {firstName, lastName, year};
export命令除了输出变量,还可以输出函数或类(class)。 只要是这个module的成员都可以输出
export function multiply(x, y) {
return x * y;
};
通常情况下,export输出的变量就是本来的名字,但是可以使用as关键字重命名。
function v1() { ... }
function v2() { ... }
export {
v1 as streamV1,
v2 as streamV2,
v2 as streamLatestVersion
};
需要特别注意的是,export命令规定的是对外的接口,必须与模块内部的变量建立一一对应关系。
// 报错
export 1;
// 报错
var m = 1;
export m;
上面两种写法都会报错,因为没有提供对外的接口。第一种写法直接输出 1,第二种写法通过变量m,还是直接输出 1。> > > 1只是一个值,不是接口。正确的写法是下面这样。
// 写法一
export var m = 1;
// 写法二
var m = 1;
export {m};
// 写法三
var n = 1;
export {n as m};
import 命令
使用export命令定义了模块的对外接口以后,其他 JS 文件就可以通过import命令加载这个模块。