本节将会介绍一部分简单的ES 6语法,仅供原来使用过老版本JavaScript的开发者参考。如果读者对ES 6并不了解或者之前没有接触过JavaScript,完全可以跳过本节进行后面内容的学习,本节对全书的学习或Vue.js的入门并没有任何影响,仅仅作为ES 6的介绍。
注意: 本书并不是一本专门用于讲解ES 6或JavaScript的书,仅供读者参考和简单了解。
JavaScript定义默认参数的方式如下:
//以前的JavaScript原先定义方式 var link = function (height, color, url) { var height = height || 50; var color = color || 'red'; var url = url || 'http:// baidu.com'; ... }
但在ES 6中,可以直接把默认值放在函数声明里:
// 新的JavaScript定义方式 var link = function(height = 50, color = 'red', url = 'http://baidu.com ') { ... }
在其他语言中,使用模板和插入值是在字符串里输出变量的一种方式。因此在ES 5中,开发者可以这样组合一个字符串:
// ES6之前方式只能使用组合字符串方式 var name = 'Your name is ' + first + ' ' + last + '.'; var url = 'http://localhost:3000/api/messages/' + id;
在ES 6中,可以使用新的语法${NAME},并将其放在反引号里:
// 支持模板文本 var name = `Your name is ${first} ${last}. `; var url = `http://localhost:3000/api/messages/${id}`;
ES 6的多行字符串是一个非常实用的功能。在ES 5中,我们不得不使用以下方法来表示多行字符串:
// 多行字符串 var roadPoem = '江南好,风景旧曾谙。' + '日出江花红胜火,' + '春来江水绿如蓝。' + '能不忆江南?' + '忆江南·江南好';
然而在ES 6中,仅仅用反引号就可以解决了:
// 支持多行文本的字符串 var roadPoem = `江南好,风景旧曾谙。 日出江花红胜火, 春来江水绿如蓝。 能不忆江南? 忆江南·江南好`;
解构可能是一个比较难以掌握的概念。我们先从一个简单的赋值讲起,其中house和mouse是key,同时house和mouse也是一个变量,在ES 5中是这样的:
var data = $('body').data(); // data 拥有两个属性 house 和mouse house = data.house; mouse = data.mouse;
在Node.js中用ES 5是这样的:
var jsonMiddleware = require('body-parser').jsonMiddleware ; var body = req.body; // body 两个属性 username 和password username = body.username; password = body.password;
在ES 6中,可以使用以下语句来代替上面的ES 5代码:
var { house, mouse} = $('body').data(); var {jsonMiddleware} = require('body-parser'); var {username, password} = req.body;
这个同样也适用于数组,是非常赞的用法:
var [col1, col2] = $('.column'), [line1, line2, line3, , line5] = file.split('n');
使用对象文本可以做许多让人意想不到的事情!通过ES 6,我们可以把ES 5中的JSON变得更加接近于一个类。
下面是一个典型的ES 5对象文本,里面有一些方法和属性:
// 文本对象 var serviceBase = {port: 3000, url: 'baidu.com'}, getAccounts = function(){return [1,2,3]}; var accountServiceES 5 = { port: serviceBase.port, url: serviceBase.url, getAccounts: getAccounts, toString: function() { return JSON.stringify(this.valueOf()); }, getUrl: function() {return "http://" + this.url + ':' + this.port}, valueOf_1_2_3: getAccounts() }
如果开发者想让它更有意思,可以用Object.create从ServiceBase继承原型的方法:
var accountServiceES 5ObjectCreate = Object.create(serviceBase) var accountServiceES 5ObjectCreate = { getAccounts: getAccounts, toString: function() { return JSON.stringify(this.valueOf()); }, getUrl: function() {return "http://" + this.url + ':' + this.port}, valueOf_1_2_3: getAccounts() }
其实对于以上两种accountServiceES 5ObjectCreate和accountServiceES 5并不是完全一致的。Object.Create()方法创建一个新对象,其是使用现有的对象来继承创建一个新的对象,而accountSerivce ES 5并且继承现有对象。
为了方便举例,我们只考虑它们的相似处。所以在ES 6的对象文本中,既可以直接分配getAccounts:getAccounts,也可以只需用一个getAccounts。此外,可以通过__proto__(并不是通过proto)设置属性:
var serviceBase = {port: 3000, url: 'baidu.com'}, getAccounts = function(){return [1,2,3]}; var accountService = { __proto__: serviceBase, getAccounts,
另外,可以调用super防范,以及使用动态key值(valueOf_1_2_3):
toString() { return JSON.stringify((super.valueOf())); }, getUrl() {return "http://" + this.url + ':' + this.port}, [ 'valueOf_' + getAccounts().join('_') ]: getAccounts() }; console.log(accountService)
对于旧版的对象来说,ES 6的对象文本是一个很大的进步。
CoffeeScript就是因为有丰富的箭头函数所以让很多开发者所喜爱。在ES 6中,也有丰富的箭头函数。比如,以前我们使用闭包,this总是预期之外地产生改变,而箭头函数的好处在于,现在this可以按照你的预期使用了,身处箭头函数里面,this还是原来的this。
有了箭头函数,我们就不必用that=this或self=this、_this=this、.bind(this)那么麻烦了。例如,下面的代码用ES 5就不是很优雅:
var _this = this; $('.btn').click(function(event){ _this.sendData(); })
在ES 6中则不需要用_this=this:
$('.btn').click((event) =>{ this.sendData(); })
但并不是完全否定之前的方案,ES 6委员会决定,以前的function的传递方式也是一个很好的方案,所以它们仍然保留了以前的功能。
下面是另一个例子,通过call传递文本给logUpperCase()函数,在ES 5中:
var logUpperCase = function() { var _this = this; this.string = this.string.toUpperCase(); return function () { return console.log(_this.string); } } logUpperCase.call({ string: 'ES 6 rocks' })();
而在ES 6中并不需要用_this浪费时间:
var logUpperCase = function() { this.string = this.string.toUpperCase(); return () => console.log(this.string); } logUpperCase.call({ string: 'ES 6 rocks' })();
注意: 在ES 6中,“=>”可以混合和匹配老的函数一起使用。当在一行代码中用了箭头函数后,它就变成了一个表达式,其将暗中返回单个语句的结果。如果结果超过一行,将需要明确使用return。
在箭头函数中,对于单个参数,括号()是可省略的,但当参数超过一个时就需要括号()了。
在ES 5代码中有明确的返回功能:
var ids = ['5632953c4e345e145fdf2df8', '563295464e345e145fdf2df9']; var messages = ids.map(function (value, index, list) { return 'ID of ' + index + ' element is ' + value + ' '; });
在ES 6中有更加严谨的版本,参数需要被包含在括号里并且是隐式地返回:
var ids = ['5632953c4e345e145fdf2df8','563295464e345e145fdf2df9']; var messages = ids.map((value, index, list) => `ID of ${index} element is ${value} `); // 隐式返回
Promise是一个有争议的话题,有人说我们不需要Promise,仅仅使用异步、生成器、回调等就够了,但是许多人尝试在写多个嵌套的回调函数时基本上会在超过三层之后产生“回调地狱”。令人高兴的是,在ES 6中有标准的Promise实现。
下面是一个简单地用setTimeout()函数实现的异步延迟加载函数:
setTimeout(function(){ console.log('Yay!'); }, 1000);
在ES 6中,可以用Promise重写,虽然在此实例中并不能减少大量的代码,甚至多写了数行,但是逻辑却清晰了不少:
var wait1000 = new Promise((resolve, reject)=> { setTimeout(resolve, 1000); }).then(()=> { console.log('Yay!'); });
在ES 6里,let并不是一个“花哨”的特性,是更复杂的。let是一种新的变量声明方式,允许我们把变量作用域控制在块级里面,用大括号定义代码块,在ES 5中,块级作用域起不了任何作用:
function calculateTotalAmount (vip) { //只能使用var方式定义变量 var amount = 0; if (vip) { //在此定义会覆盖 var amount = 1; } { //在此定义会覆盖 var amount = 100; { //在此定义会覆盖 var amount = 1000; } } return amount; } //打印输出内容 console.log(calculateTotalAmount(true));
以上代码结果将返回1000,这真是一个bug。在ES 6中,用let限制块级作用域,而var限制函数作用域。
function calculateTotalAmount (vip) { // 使用var方式定义变量 var amount = 0; if (vip) { // 使用let定义的局部变量 let amount = 1; //第1个let } { let amount = 100; //第2个let { let amount = 1000; //第3个let } } return amount; } console.log(calculateTotalAmount(true));
程序结果将会是0,因为块作用域中有了let,如果(amount=1),那么这个表达式将返回1。本例是一个演示,这里有一堆常量,它们互不影响,因为它们属于不同的块级作用域。
如果读者了解面向对象编程(OOP),将会更喜爱这个特性,以后写一个类和继承将变得在微博上写一个评论那么容易。
在之前的JavaScript版本中,对于类的创建和使用是令人非常头疼的一件事。不同于直接使用class命名一个类的语言(在JavaScript中class关键字被保留,但是没有任何作用),因为没有官方的类功能,加上大量继承模型的出现(pseudo classical、classical、functional等),造成了JavaScript类使用的困难和不规范。
用ES 5写一个类有很多种方法,这里就先不说了,现在就来看看如何用ES 6写一个类吧。ES 6没有用函数,而是使用原型实现类,我们创建一个类baseModel,并且在这个类里定义一个constructor()和一个getName()方法:
class baseModel { constructor(options, data) { // class constructor,Node.js 5.6暂时不支持 options = {}, data = []这样传参 this.name = 'Base'; this.url = 'http://baidu.com/api'; this.data = data; this.options = options; } getName() { //类的方法 console.log(`Class name: ${this.name}`); } }
注意: 这里对options和data使用了默认参数值,方法名也不需要加function关键字,而且冒号“:”也不需要了;另一个大的区别就是不需要分配属性this。现在设置一个属性的值,只需简单地在构造函数中分配即可。
众所周知,在ES 6以前JavaScript并不支持本地的模块,于是人们想出了AMD、RequireJS、CommonJS及其他解决方法。现在ES 6中可以用模块import和export操作了。
在ES 5中,可以在<script>中直接写可以运行的代码(简称IIFE),或一些库,如AMD。然而在ES 6中,可以用export导入类。下面举个例子,在ES 5中,module.js有port变量和getAccounts()方法:
module.exports = { port: 3000, getAccounts: function() { ... } }
在ES 5中,main.js需要依赖require('module')导入module.js:
var service = require('module.js'); console.log(service.port); // 3000
但在ES 6中,将用export and import进行一个模块的引入和抛出。例如,以下是用ES 6写的module.js文件库:
export var port = 3000; export function getAccounts(url) { ... }
如果用ES 6将上述的module.js导入到文件main.js中,就变得非常简单了,只需用import{name}from'my-module'语法,例如:
import {port, getAccounts} from 'module'; console.log(port); // 3000
或者可以在main.js中导入整个模块,并命名为service:
import * as service from 'module'; console.log(service.port); // 3000