ES6全面来袭

作者 Simmin 日期 2016-08-31
ES6全面来袭


ECMAScript 6.0,简称 ES6 ,已经于2015年6月正式发布,是JavaScript语言当前最新一代标准。因为标准的制定者有计划,以后每年发布一次标准,使用年份作为版本号,所以ES6页被称作ECMAScript 2015(简称 ES2015)。
ES6的目标是使得JS语言可以用来编写复杂的大型应用程序,成为企业级开发语言。

一、纵观ECMAScript

  • 1997年,ECMAScript 1.0发布

  • 1998年6月,ECMAScript 2.0发布

  • 1999年12月,ECMAScript 3.0发布,这个版本是一个巨大的成功,在业界得到广泛支持,成为通行标准,奠定了JavaScript语言的基本语法,以后的版本完全继承。直到今天,初学者一开始学习JavaScript,其实就是在学3.0版的语法。

  • 2000年,ECMAScript 4.0开始酝酿。但最后没有通过,它的大部分内容被ES6继承了,所以ES6制定的起点其实是2000年。

  • 2007年10月,ECMAScript 4.0版草案发布,但后来各方对于是否 通过这个标准发生严重分歧 。

  • 2008年7月,中止ECMAScript 4.0的开发,将其中涉及现有功能改善的一小部分,发布为ECMAScript 3.1,后改名为改名为ECMAScript 5。

  • 2009年12月,ECMAScript 5.0版正式发布。

  • 2011年6月,ECMAscript 5.1版发布,并且成为ISO国际标准(ISO/IEC 16262:2011)。

  • 2013年3月,ECMAScript 6草案冻结,不再添加新功能。新的功能设想将被放到ECMAScript 7。

  • 2013年12月,ECMAScript 6草案发布。然后是12个月的讨论期,听取各方反馈。

  • 2015年6月,ECMAScript 6正式通过,成为国际标准。从2000年算起,这时已经过去了15年。

  • 根据计划,2017年6月将发布 ES2017。

二、ES与JS的关系

要讲清楚这个问题,需要回顾历史。1996年11月,JavaScript的创造者Netscape公司,决定将JavaScript提交给国际标准化组织ECMA,希望这种语言能够成为国际标准。次年,ECMA发布262号标准文件(ECMA-262)的第一版,规定了浏览器脚本语言的标准,并将这种语言称为ECMAScript,这个版本就是1.0版。

该标准从一开始就是针对JavaScript语言制定的,但是之所以不叫JavaScript,有两个原因。一是商标,Java是Sun公司的商标,根据授权协议,只有Netscape公司可以合法地使用JavaScript这个名字,且JavaScript本身也已经被Netscape公司注册为商标。二是想体现这门语言的制定者是ECMA,不是Netscape,这样有利于保证这门语言的开放性和中立性。

因此,ECMAScript和JavaScript的关系是,前者是后者的规格,后者是前者的一种实现(另外的ECMAScript方言还有Jscript和ActionScript)。日常场合,这两个词是可以互换的。

三、ES6的现状

ECMAScript 6 目前基本成为业界标准,它的普及速度比 ES5 要快很多,主要原因是现代浏览器对 ES6 的支持相当迅速,尤其是 Chrome 和 Firefox 浏览器,已经支持 ES6 中绝大多数的特性。

四、ES6新特性

4.1 let和const命令

在之前的JS开发中,我们都知道对于变量的定义是使用关键字var,这种定义方式存在一定的问题:

1.没有块级作用域

所谓块级作用域,是指任何一对花括号{和}中的语句集都属于一个块,在这之中定义的所有变量在代码块外都是不可见的,我们称之为“块级作用域”。

var a= 1;
if(true){
var a = 2;
}
console.log(a); //2

如果是在C、C++或Java中,a的值应该为1,而对于js来说,if语句中的变量声明会将变量添加到当前的执行环境中。

2.存在变量提升

所谓变量提升(Hoisting),是指把定义在后面的变量或函数提升到前面定义。但只是提升变量的声明,并不会把赋值也提升上来。

var v='Hello World';
(function(){
alert(v);
var v='Hello Test';
})()

结果是undefined,为什么呢?
先来切实的理解一下变量提升,定义3个变量并赋值

(function(){
var a = 'a';
var b = 'b';
var c = 'c';
})()

实际它是这样的:

(function(){
var a,b,c;
a = 'a';
b = 'b';
c = 'c';
})()

实质就是全部先定义,再赋值。之所以上面的结果是undefined,是因为实际它是

var v = 'Hello World';
(function(){
var v;
alert(v);
v = 'Hello Test';
})();

函数提升在这里就不多说了。

3.没有常量的定义

ES6之前,没有常量的定义,只能像这样写

var SIZE = 10; // 用大写定义变量约定为常量,后面不去修改

但是这样依然会有被修改的风险。

在 ES6 中 新增了 let 命令来定义变量, const 命令来定义常量。

1.使用let定义变量之后,就可以有块级作用域了。

if(true){
let a = 2;
}
console.log(a); // Uncaught ReferenceError: a is not defined(…)

2.使用let定义变量之后,没有变量提升

console.log(a); //undefined
console.log(b); //Uncaught ReferenceError: b is not defined(…)
var a = 'a';
let b = 'b';

3.使用let定义变量之后,不允许在相同作用域内,重复声明同一个变量。

// 报错 Uncaught SyntaxError: Identifier 'a' has already been declared
function c() {
let a = 10;
var a = 1;
}
// 报错 Uncaught SyntaxError: Identifier 'a' has already been declared
function d() {
let a = 10;
let a = 1;
}

4.使用const定义变量,不能被修改

const SIZE = 10;
size = 20; //报错 Uncaught SyntaxError: Identifier 'SIZE' has already been declared

const声明的变量不得改变值,这意味着,const一旦声明变量,就必须立即初始化,不能留到以后赋值。

4.2 数组和对象解构

ES6允许按照一定模式,从数组和对象中提取值,对变量进行赋值,这被称为解构(Destructuring)。

1.数组解构

以前,为变量赋值,只能直接指定值。

var a = 1;
var b = 2;
var c = 3;

ES6允许写成下面这样。

var [a, b, c] = [1, 2, 3];

上面代码表示,可以从数组中提取值,按照对应位置,对变量赋值。

本质上,这种写法属于“模式匹配”,只要等号两边的模式相同,左边的变量就会被赋予对应的值。

2.对象解构

对象的解构与数组有一个重要的不同。数组的元素是按次序排列的,变量的取值由它的位置决定;而对象的属性没有次序,变量必须与属性同名,才能取到正确的值。

var { bar, foo } = { foo: "aaa", bar: "bbb" };
foo // "aaa"
bar // "bbb"

4.3 箭头函数(Arrow Functions)

ES6允许使用“箭头”(=>)定义函数。

var fun = v => v;
// 等同于
var fun = 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;
};

箭头函数不仅仅是让代码变得简洁,函数中 this 总是绑定指向对象自身。
我们想实现的效果是这样:

function Person() {
this.age = 0;
setInterval(function growUp() {
// 在非严格模式下,growUp() 函数的 this 指向 window 对象
this.age++;
}, 1000);
}
var person = new Person();

但是我们需要使用一个变量来保存 this,然后在 growUp 函数中引用

function Person() {
var self = this;
self.age = 0;
setInterval(function growUp() {
self.age++;
}, 1000);
}

而使用箭头函数可以省却这个麻烦:

function Person(){
this.age = 0;
setInterval(() => {
// |this| 指向 person 对象
this.age++;
}, 1000);
}
var person = new Person();

4.4 默认参数

在ES6之前我们不得不通过下面方式来定义默认参数:

var link = function (height, color, url) {
var height = height || 50;
var color = color || 'red';
var url = url || 'http://simmin.github.io';
}

一般情况下是没有问题的,可如果参数值是0,就有问题了。在JS的逻辑表达式中,0表示false,所以它并不能变成参数本身的值。当然,也不是没有解决办法,使用逻辑OR也是可以的。
但在ES6中,我们可以直接把默认值放在函数声明里:

var link = function(height = 50, color = 'red', url = 'http://simmin.github.io') {
}

4.5 模板字符串

传统的JavaScript语言,输出模板通常是这样写的。

$('#result').append(
'There are <b>' + basket.count + '</b> ' +
'items in your basket, ' +
'<em>' + basket.onSale +
'</em> are on sale!'
);

这样写起来很繁琐,在ES6中我们可以用新的语法${NAME},并把它放在反引号中

$('#result').append(`
There are <b>${basket.count}</b> items
in your basket, <em>${basket.onSale}</em>
are on sale!
`);

如果使用模板字符串表示多行字符串,所有的空格和缩进都会被保留在输出之中。

$('#list').html(`
<ul>
<li>first</li>
<li>second</li>
</ul>
`);

大括号内部可以放入任意的JavaScript表达式,可以进行运算,还能调用函数。

var x = 1;
var y = 2;
`${x} + ${y} = ${x + y}`
// "1 + 2 = 3"
`${x} + ${y * 2} = ${x + y * 2}`
// "1 + 4 = 5"
function fn() {
return "Hello World";
}
`foo ${fn()} bar`// foo Hello World bar

4.6 类(class)

在ES6之前是没有类的(class关键字被保留),传统方法是通过构造函数定义并生成新的对象

function Person (name, age) {
this.name = name;
this.age = age;
}
// 利用原型绑定方法,不会每次实例化都创建,提升性能
Person.prototype.say = function () {
console.log('My name is ' + this.name + '. I`m ' + this.age + ' years old');
}
var han = new Person('韩梅梅', 16)

ES6提供了更接近传统语言的写法,引入了Class(类)这个概念,作为对象的模板。通过class关键字,可以定义类。基本上,ES6的class可以看作只是一个语法糖,它的绝大部分功能,ES5都可以做到,新的class写法只是让对象原型的写法更加清晰、更像面向对象编程的语法而已。

1.定义类

class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
say() {
console.log('My name is ' + this.name + '. I`m ' + this.age + ' years old');
}
}

注意,定义“类”的方法的时候,前面不需要加上function这个关键字,直接把函数定义放进去了就可以了。另外,方法之间不需要逗号分隔,加了会报错。

2.继承

class Woman extends Person {
constructor (name, age) {
super(name, age) // 通过 super 调用父类的同名方法
}
}

3.static关键字

class Person {
constructor (name, age) {
this.name = name;
this.age = age;
}
say () {
console.log('My name is ' + this.name + '. I`m ' + this.age + ' years old');
}
static desc () {
console.log('this is a static function');
}
}
Person.desc() //this is a static function
var han = new Person('韩梅梅', 16)
hong.desc() // Uncaught TypeError: han.desc is not a function(…)

静态方法不会被实例继承,而是直接通过类来调用或者被子类继承

class Person {
static Person() {
console.log('this is a static function');
}
}
class Woman extends Person {
}
Woman.Person(); // this is a static function

ES6明确规定,Class内部只有静态方法,没有静态属性。

//正确写法
class Person {
}
Person.age = 20;
Person.age // 20
//错误写法
class Person {
// 写法一
age: 20
// 写法二
static age: 20
}
Person.age // 报语法错误

这里只提到了一些常用或可能会用到的新特性,更多详细资料还是要去翻阅《ECMAScript 6入门》- 阮一峰(http://es6.ruanyifeng.com/)

更多参考资料:
ES6 学习笔记
ECMAScript 6 扫盲
前端开发者不得不知的ES6十大特性