ECMAScript 6.0(简称ES6)是 JavaScript 一个制定于2015年的新标准,它于2015年6月正式发布。
它的目的是想让JavaScript成为一个企业级的开发语言,可以去开发大型的复杂应用。
我的ES6学习心得将分为以下三个部分来阐述:
1.ECMAScript 6.0简介
2.ECMAScript 6.0新特性
3.ECMAScript 6.0性能总结与个人体会
4.参考文献
1.ECMAScript 6.0简介
想要弄清楚ECMAScript 6.0,那么我们要先弄清楚它是什么,它怎么来的。
(1)ECMAScript
ECMA是“European Computer Manufactures Association”的缩写,中文称欧洲计算机制造联合会。这个组织的目标是评估,开发和认可电信和计算机标准。
1996年的时候,Javascript的创造公司将Javascript提交给ECMA,希望这种语言能够成为国际标准。
次年,ECMA 发布262号标准文件(ECMA-262)的第一版,规定了浏览器脚本语言Javascript的标准,并将这种语言称为 ECMAScript,这个版本就是1.0版。
所以我们这里总结一下ECMAscript与Javascript之间的关系:
ECMAscript是Javascript的标准/规格
Javascript是ECMAscript的实现(另外的 ECMAScript 语言还有 Jscript 和 ActionScript)
但是我们一般日常说的ECMAScript就是Javascript的意思,这两个词是可以互换的,不过还是要清楚它们的区别。
(2)ECMAScript 6.0(ES6) / ECMAScript 2015(ES2015)
2011年,ECMAScript 5.1版发布后,就开始制定6.0版了。因此,ES6 这个词的原意,就是指 ECMAScript 5.1版的下一个版本。
因为ECMA标准委员会会在每年6月份更新并正式发布一次新的ECMAScript标准,作为当年的正式版本。
然后接下来的时间就是在这个版本的基础上面做改动,一直到下年的6月份。这样一来其实就不需要用版本号来区分了了,用年份标记即可。
ES6 的第一个版本,就这样在2015年6月发布了,正式名称就是《ECMAScript 2015标准》(简称 ES2015)。
2016年6月,小幅修订的《ECMAScript 2016标准》(简称 ES2016)如期发布,这个版本可以看作是 ES6.1 版,因为两者的差异非常小(只新增了数组实例的includes
方法和指数运算符),基本上是同一个标准。根据计划,2017年6月发布 ES2017 标准。
因此,ECMAScript6.0既是一个版本名称,也是一个类别。版本名称是指5.1版以后的 ECMAScript 的下一代标准,类别是指它涵盖了ES2015、ES2016、ES2017等等。
而ES2015 则是正式名称,特指该年发布的正式版本的语言标准。
2.ECMAScript 6.0新特性
之前刚开始接触Javascript的时候ES6还没推出,用的还是ES5的标准,那么新推出的ES6有哪些新特性呢?
下文我将总结一些在ES6的学习中接触到的一些比较常用的语法特性,具体有以下几项:
(1)let, const
(2)class, extends, super
(3)arrow function
(4)template string
(5)destructuring
(6)default, reat
--------------------华丽丽的分界线---------------------
(1)let, const
let, const的作用与var类似,都是用于声明变量的,但是它们减少了var在作用域以及赋值方面的缺陷,下面我们来看一下。
①var在作用域方面的缺陷
var name = 'zach' while (true) { var name = 'obama' console.log(name) //obama break } console.log(name) //obama
使用var
两次输出都是obama,这是因为ES5只有全局作用域和函数作用域,没有块级作用域,这带来很多不合理的场景。第一种场景就是你现在看到的内层变量覆盖外层变量。而let
则实际上为JavaScript新增了块级作用域。用它所声明的变量,只在let
命令所在的代码块内有效。
let name = 'zach' while (true) { let name = 'obama' console.log(name) //obama break } console.log(name) //zach
另外一个var
带来的不合理场景就是用来计数的循环变量泄露为全局变量,看下面的例子:
var a = []; for (var i = 0; i < 10; i++) { a[i] = function () { console.log(i); }; } a[6](); // 10
上面代码中,变量i是var声明的,在全局范围内都有效,所以全局只有一个变量i。每一次循环,变量i的值都会发生改变,而循环内被赋给数组a的function在运行时,会通过闭包读到这同一个变量i,导致最后输出的是最后一轮的i的值,也就是10。而使用let则不会出现这个问题。
var a = []; for (let i = 0; i < 10; i++) { a[i] = function () { console.log(i); }; } a[6](); // 6
上面代码中,变量i是let声明的,当前的i只在本轮循环有效,所以每一次循环的i其实都是一个新的变量,所以最后输出的是6。你可能会问,如果每一轮循环的变量i都是重新声明的,那它怎么知道上一轮循环的值,从而计算出本轮循环的值?这是因为 JavaScript 引擎内部会记住上一轮循环的值,初始化本轮的变量i时,就在上一轮循环的基础上进行计算。
另外,for循环还有一个特别之处,就是循环语句部分是一个父作用域,而循环体内部是一个单独的子作用域。
for (let i = 0; i < 3; i++) { let i = 'abc'; console.log(i); } // abc // abc // abc
上面代码输出了3次abc,这表明函数内部的变量i和外部的变量i是分离的。
②var在赋值方面的缺陷
从上面的例子我们可以看出,var的赋值是可以很轻易地被改变的
var name = 'zach' while (true) { var name = 'obama' console.log(name) //obama break } console.log(name) //obama
这就带来了一个问题,一些程序中比较重要的变量很容易就会被他人,特别是你毫不知情的队友改变了,这就给我们大型程序的开发带来了麻烦。
当然上文仅仅是我设想的一个场景,我们的程序中更多的时候是需要一些约定俗成,值不变的常量,比如说pi。
在ES6中,我们添加了const特性来声明常量。一旦声明,常量的值就不能改变。
const PI = Math.PI PI = 23 //Module build failed: SyntaxError: /es6/app.js: "PI" is read-only
当我们尝试去改变用const声明的常量时,浏览器就会报错。
const有一个很好的应用场景,就是当我们引用第三方库的时声明的变量,用const来声明可以避免未来不小心重命名而导致出现bug:
const monent = require('moment')
(2)class, extends, super
ES6还有一大特点就是它跟我们所熟知的入门级语言C、C++更像了,因为它引入了class(类)这个概念。
在ES5的年代,js是没有特定的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);
但是上面这种写法跟传统的面向对象语言(比如C++和Java)差异很大,很容易让刚接触的程序员感到困惑。
ES6提供了更接近传统的面向对象语言的写法,引入了Class(类)这个概念。
在ES6中,通过class
关键字,我们可以定义类。
但其实ES6的class
仅仅是个名字/写法而已,因为它的绝大部分功能,ES5都可以做到。
不过新的class
写法让对象原型的写法更加清晰、更像面向对象编程的语法,这样就可以更接近企业级的开发语言。
上面的代码用ES6的“类”改写,就是下面这样。
//定义类 class Point { constructor(x, y) { this.x = x; this.y = y; } toString() { return '(' + this.x + ', ' + this.y + ')'; } }
上面代码首先用class
定义了一个“类”,可以看到里面有一个constructor
方法,这就是构造方法,而this
关键字则代表实例对象。简单地说,constructor
内定义的方法和属性是实例对象自己的,而constructor
外定义的方法和属性则是所有实例对象可以共享的。
Point
类除了构造方法,还定义了一个toString
方法。注意,定义“类”的方法的时候,前面不需要加上function
这个关键字,直接把函数定义放进去了就可以了。另外,方法之间不需要逗号分隔,加了会报错。
ES6的类,完全可以看作构造函数的另一种写法。
class Point { // ... } typeof Point // "function" Point === Point.prototype.constructor // true
Class之间可以通过extends
关键字实现继承,这比ES5的通过修改原型链实现继承,要清晰和方便很多。下面定义了一个Cat类,该类通过extends
关键字,继承了Animal类的所有属性和方法。
class Animal { constructor(){ this.type = 'animal' } says(say){ console.log(this.type + ' says ' + say) } } let animal = new Animal() animal.says('hello') //animal says hello class Cat extends Animal { constructor(){ super() this.type = 'cat' } } let cat = new Cat() cat.says('hello') //cat says hello
super
关键字,它指代父类的实例(即父类的this对象)。子类必须在constructor
方法中调用super
方法,否则新建实例时会报错。这是因为子类没有自己的this
对象,而是继承父类的this
对象,然后对其进行加工。如果不调用super
方法,子类就得不到this
对象。
ES6的继承机制,实质是先创造父类的实例对象this(所以必须先调用super方法),然后再用子类的构造函数修改this。
(3)arrow function
ES6带来的还有一个新特性,就是简洁。
arrow function应该是ES6最最常用的一个新特性了,用它来写function比原来的写法要简洁清晰很多:
function(i){ return i + 1; } //ES5 (i) => i + 1 //ES6
如果function有多行内容,则需要用{}将其括起来:
function(x, y) { x++; y--; return x + y; }//ES5 (x, y) => {x++; y--; return x+y}//ES6
另外ES5在使用this的时候也需要小心混淆:
class Animal { constructor(){ this.type = 'animal' } says(say){ setTimeout(function(){ console.log(this.type + ' says ' + say) }, 1000) } } var animal = new Animal() animal.says('hi') //undefined says hi
运行上面的代码会报错,这是因为setTimeout
中的this
指向的是全局对象。
一般用ES5的解决方法有传递this或者bind(this)这些, 但现在我们有了箭头函数,就不需要这么麻烦了:
class Animal { constructor(){ this.type = 'animal' } says(say){ setTimeout( () => { console.log(this.type + ' says ' + say) }, 1000) } } var animal = new Animal() animal.says('hi') //animal says hi
当我们使用箭头函数时,函数体内的this对象,就是定义时所在的对象,而不是使用时所在的对象。
并不是因为箭头函数内部有绑定this的机制,实际原因是箭头函数根本没有自己的this,它的this是继承外面的,因此内部的this就是外层代码块的this。
但是在这里我们要说的是,arrow function也并不是一劳永逸的,特别是在你并不知道this指向哪的时候,你需要慎用arrow function。
因为arrow function是 Lexical scoping(词法作用域),this指向定义Arrow Function时外围, 而不是运行时的对象。
词法作用域: 变量的作用域是在定义时决定而不是执行时决定,也就是说词法作用域取决于源码,通过静态分析就能确定,因此词法作用域也叫做静态作用域。
下面我们将根据文章:Do ES6 Arrow Functions Really Solve “this” In JavaScript? 来分析。
如下代码:
// run this in node v4 to see the "expected" behavior this.test = "attached to the module"; var foo = { test: "attached to an object" }; // a method to create methods foo.method = function(name, cb){ this[name] = cb; }; // use an arrow function and get // lexical analysis of "this" foo.method("bar", () => { // not what you expected, maybe? console.log(this.test); }); foo.bar();
输出为:“attached to the module”而不是“attached to an object”
因为
就像我们所说的,arrow function是 Lexical scoping,因为在foo.method("bar", () => {的外围并没有包含着它的代码,所以,foo.bar()中的this.test值将永远是“attached to the module”。
(4)template string
这个新特性可以帮助我们插入大段的html内容到文档中去。传统的写法一般比较麻烦,所以之前我们通常会引用一些模板工具库,比如mustache等等。代码如下:
$("#result").append( "There are <b>" + basket.count + "</b> " + "items in your basket, " + "<em>" + basket.onSale + "</em> are on sale!" );
我们要用一堆的'+'号来连接文本与变量,而使用ES6的新特性模板字符串``后,我们可以直接这么来写:
$("#result").append(` There are <b>${basket.count}</b> items in your basket, <em>${basket.onSale}</em> are on sale! `);
在ES6中,我们用`来标识开始和结尾,用${}来引用变量,而且所有的空格和缩进都会被保留在输出之中。
(5)destructuring
ES6允许按照一定模式,从数组和对象中提取值,对变量进行赋值,这被称为解构(Destructuring)。
当使用ES5的时候,我们一般是这么写的:
let cat = 'ken' let dog = 'lili' let zoo = {cat: cat, dog: dog} console.log(zoo) //Object {cat: "ken", dog: "lili"}
用ES6完全可以像下面这么写:
let cat = 'ken' let dog = 'lili' let zoo = {cat, dog} console.log(zoo) //Object {cat: "ken", dog: "lili"}
反过来可以这么写:
let dog = {type: 'animal', many: 2} let { type, many} = dog console.log(type, many) //animal 2
(6)default, reat
default很简单,意思就是默认值。大家可以看下面的例子,调用animal()
方法时忘了传参数,传统的做法就是加上这一句type = type || 'cat'
来指定默认值。
function animal(type){ type = type || 'cat' console.log(type) } animal()
如果用ES6我们可以直接这么写:
function animal(type = 'cat'){ console.log(type) } animal()
最后一个rest语法也很简单,直接看例子:
function animals(...types){ console.log(types) } animals('cat', 'dog', 'fish') //["cat", "dog", "fish"]
而如果不用ES6的话,我们则得使用ES5的arguments
。
3.ECMAScript 6.0性能总结与个人体会
总体来看,ES6正在朝着更简洁、更友好、可读性更强、更模块化的方向发展,这些特性都能支持其成为一个企业级的开发语言,可以去开发大型的复杂应用。
本文章从ES6的来历,讲到ES6的一些新特性。但以上仅仅列出了ES6中一些比较常用的新特性,而且分析的并不够深入,所以我们还是需要再去深一步学习和摸索的。“实践是检验真理的唯一方法”,以后在实战项目中遇到的问题我会再来写博客分析的。
4.参考文献
http://www.jianshu.com/p/ebfeb687eb70
http://es6.ruanyifeng.com/#docs/function
http://cnodejs.org/topic/57266e375a26c4a841ecbf40
https://derickbailey.com/2015/09/28/do-es6-arrow-functions-really-solve-this-in-javascript/
http://www.cnblogs.com/jiangyi666/p/5998914.html
http://blog.csdn.net/pengju_guo/article/details/7292080