文章目录
前言
今天用到了最新的js的一些新特性,什么箭头函数啊,我是搞qml的,所以也支持,想着肯定不止这一个新特性,于是乎,搜索了一番,果然发现一些非常好用的新东西,现记录下,以备不时之需
下面的记录均为其他地方的借鉴,最后结尾我会给出具体借鉴参考地址,其中细节之处有我自己的验证和具体释义,因为很多地方我也看不懂,但是我会用更简洁的方式表达出来,绝对不是一味的无脑copy
当然我也会把格式整理的更加工整
正文
这是为忙碌的开发者准备的ES6中最棒的十个特性(无特定顺序):
- 默认参数
- 模版表达式
- 多行字符串
- 拆包表达式
- 改进的对象表达式
- 箭头函数 =&>
- Promise
- 块级作用域的let和const
- 类
- 模块化
注意:这个列表十分主观并且带有偏见,其他未上榜的特性并不是因为没有作用,我这么做只是单纯的希望将这份列表中的项目保持在十个。
首先,一个简单的JavaScript时间线,不了解历史的人也无法创造历史。
1995年:JavaScript以LiveScript之名诞生
1997年:ECMAScript标准确立
1999年:ES3发布,IE5非常生气
2000年-2005年:XMLHttpRequest,熟知为AJAX,在如Outlook Web Access(2002)、Oddpost(2002)、
Gmail(2004)、Google Maps(2005)中得到了广泛的应用
2009年:ES5发布(这是我们目前用的最多的版本),带来了forEach / Object.keys/ Object.create
(特地为Douglas Crockford所造,JSON标准创建者) ,还有JSON标准。
历史课上完了,我们回来讲编程。
1. ES6中的默认参数
还记得我们以前要这样子来定义默认参数:
var link = function (height, color, url) {
var height = height || 50
var color = color || 'red'
var url = url || 'http://azat.co'
...
}
这样做一直都没什么问题,直到参数的值为0,因为0在JavaScript中算是false值,它会直接变成后面硬编码的值而不是0本身。当然了,谁要用0来传值啊(讽刺脸)?所以我们也忽略了这个瑕疵,沿用了这个逻辑,否则的话只能……没有否则!
在ES6中,我们可以把这些默认值直接放在函数签名中。
var link = function(height = 50, color = 'red', url = 'http://azat.co') {
...
}
对了,这个语法和Ruby很像!
下面的2个方法我要提前说明一下
模板字符串(Template String) 是增强版的字符串,用 反引号(``)标识,可以定义多行字符串,所有的空格、缩进和换行都会被保留
下面的所有提到的 反引号 ---- 都是这种 (``)标识
2. ES6中的模版表达式
模版表达式在其他语言中一般是为了在模版字符串中输出变量,所以在ES5中,我们非得把字符串破开变成这样:
var name = 'Your name is ' + first + ' ' + last + '.'
var url = 'http://localhost:3000/api/messages/' + id
幸运的是在ES6中我们有了新语法,在 反引号 包裹的字符串中,使用 ${NAME} 语法来表示模板字符:
var name = `Your name is ${first} ${last}`
var url = `http://localhost:3000/api/messages/${id}`
Example
var x=88;
var y=100;
console.log(`x=${++x},y=${x+y}`);
打印结果:
x=88, y=189
更强大的是:模版字符串还可以调用函数:
function string(){
return "zzw likes es6!";
}
console.log(`你想说什么?
嗯,${string()}`);
打印结果:
你想说什么
嗯,zzw likes es6!
3. ES6中的多行字符串
另一个好吃的语法糖就是多行字符串,以前我们的实现是像这样的:
var roadPoem = 'Then took the other, as just as fair,nt'
+ 'And having perhaps the better claimnt'
+ 'Because it was grassy and wanted wear,nt'
+ 'Though as for that the passing therent'
+ 'Had worn them really about the same,nt'
var fourAgreements = 'You have the right to be you.n
You can only be you when you do your best.'
但是在ES6中,只要充分利用反引号。
var roadPoem = `Then took the other, as just as fair,
And having perhaps the better claim
Because it was grassy and wanted wear,
Though as for that the passing there
Had worn them really about the same,`
var fourAgreements = `You have the right to be you.
You can only be you when you do your best.`
4. ES6中的拆包表达式
拆包可能是一个比较难理解的概念,因为这里面真的是有魔法发生。假如说你有一个简单的赋值表达式,把对象中的house的mouse赋值为house和mouse的变量。
var data = $('body').data(), // 假设data中有 mouse 和 house 的值
house = data.house,
mouse = data.mouse
另一个拆包的实例(Node.js):
var jsonMiddleware = require('body-parser').json
var body = req.body, // body中有用户名和密码值
username = body.username,
password = body.password
但是在ES6中我们可以用以下语句替换:
var { house, mouse} = $('body').data() // 我们会拿到house和mouse的值的
var {jsonMiddleware} = require('body-parser')
var {username, password} = req.body
甚至在数组中也能用,简直疯狂!
var [col1, col2] = $('.column'),
[line1, line2, line3, , line5] = file.split('n')
看上面的原文章的demo释义我是云里雾里,一脸懵逼,所以下面我列出更为通用的demo来说明这个用法:
Example:
使用对象作为返回载体(带有标签的多返回值)
用法:{ arg1, arg2 } = { arg1: value1, arg2: value2}
function getState(){
// …
return {
error: null,
logined: true,
user: { /* … */ },
// …
}
}
const { error, logined, user } = getState();
if(error) { /* … */ }
使用数组作为返回载体
用法:[ arg1, arg2 ] = [ value1, value2]
const [foo, bar] = [1, 2];
console.log(foo, bar); //1 2
如果希望跳过数组中某些元素,可以通过空开一个元素的方式实现:
用法:[ arg1, , arg2 ] = [ value1, value2, value3]
const [foo, , bar] = [1, 2, 3];
console.log(foo, bar); //1 3
如果希望能在获取指定位置的元素以外,也可以不定项地获取后续的元素,那么可以用 … 语句 来实现:
用法:[ arg1, arg2, …rest ] = [ value1, value2, value3, value4]
const [a, b, ...rest] = [1, 2, 3, 4, 5];
console.log(a, b); // 1 2
console.log(rest); // [3, 4, 5]
使用数组作为返回载体与使用对象作为返回载体的区别是:数组需要让被赋予的变量(或常量)名按照数组的顺序获得值。
看了上面我整改的用法肯定就一目了然吧!,继续下面的记录
5. ES6中改进的对象表达式
你能用对象表达式所做的是超乎想象的!类定义的方法从ES5中一个美化版的JSON,进化到ES6中更像类的构造。
这是一个ES5中典型的对象表达式,定义了一些方法和属性。
var serviceBase = {port: 3000, url: 'azat.co'},
getAccounts = function(){return [1,2,3]}
var accountServiceES5 = {
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 成为 accountServiceES5 的 prototype 从而实现继承。
var accountServiceES5ObjectCreate = Object.create(serviceBase)
var accountServiceES5ObjectCreate = {
getAccounts: getAccounts,
toString: function() {
return JSON.stringify(this.valueOf())
},
getUrl: function() {return 'http://' + this.url + ':' + this.port},
valueOf_1_2_3: getAccounts()
}
我知道 accountServiceES5ObjectCreate 和 accountServiceES5 是不完全相同的。因为一个对象 accountServiceES5 会有如下所示的 __proto__ 属性:
但对于这个示例,我们就把这两者考虑为相同的。所以在ES6的对象表达式中,我们把getAccounts: getAccounts简化为getAccounts,,并且我们还可以用__proto__直接设置prototype,这样听起来合理的多。(不过并不是用proto)
var serviceBase = {port: 3000, url: 'azat.co'},
getAccounts = function(){return [1,2,3]}
var accountService = {
__proto__: serviceBase,
getAccounts,
还有,我们可以调用 super 和动态索引(valueOf_1_2_3)
// 续上段代码
toString() {
return JSON.stringify((super.valueOf()))
},
getUrl() {return 'http://' + this.url + ':' + this.port},
[ 'valueOf_' + getAccounts().join('_') ]: getAccounts()
};
console.log(accountService)
这是对老旧的对象表达式一个很大的改进!
因为我不是专业做js开发的,是从事qml,我想说,上面的说的我是一点都看不懂,不知道说的是什么,并不是说写的不好,而是我自身理解能力达不到那个层次,所以,我整理出我自身理解出来的这个特性的用法
首先 对象(object) 是 JavaScript 最重要的数据结构
ES6 允许直接写入变量和函数,作为对象的属性和方法。这样的书写更加简洁。
const foo = ‘bar‘;
const baz = {foo};
baz // {foo: "bar"}
// 等同于
const baz = {foo: foo};
ES6 允许在对象之中,直接写变量。这时,属性名为变量名, 属性值为变量的值。
function f(x, y) {
return {x, y};
}
// 等同于
function f(x, y) {
return {x: x, y: y};
}
f(1, 2) // Object {x: 1, y: 2}
除了属性简写,方法也可以简写。
const o = {
method() {
return "Hello!";
}
};
// 等同于
const o = {
method: function() {
return "Hello!";
}
};
如:
let birth = ‘2000/01/01‘;
const Person = {
name: ‘张三‘,
birth, //等同于birth: birth[重要]
hello() { console.log(‘我的名字是‘, this.name); } // 等同于hello: function ()...
};
这种写法用于函数的返回值,非常方便。
function getPoint() {
const x = 1;
const y = 10;
return {x, y};
}
getPoint() // {x:1, y:10}
我理解的大概就是这个意思吧!!
下面在贴出
属性名表达式(题外知识点)
JS定义对象的属性有两种方式:
- 直接用标识符作为属性名
- 用表达式作为属性名,放在方括号内
方法是name属性
函数的name属性返回函数名,对象方法也是函数,因此也有name属性。
如果对象的方法使用了取值函数(getter)和存值函数(setter),则name属性不在该方法上面,而在该方法属性的描述对象的get和set属性上面,返回值是方法名前加上get和set。
Object.is()
ES5比较两个值是否相等,用运算符()和(=)。但是缺点也很明显,前者会自动转换数据类型,后者的NaN不等于自身,以及+0等于-0。
Es6提出了同值相等算法来实现,Object.is方法就是用来比较两个值是否严格相等,与(===)的行为基本一致。
不同之处:
- -0不等于+0
- NaN等同于自身
Object.assign()
Object.assign方法用于将源对象的所有可枚举属性复制到目标对象。
Object.assign方法第一个参数是目标对象,后面的参数都是源对象。
注意:如果目标对象与源对象有同名属性,或多个源对象有同名属性,则后面的属性会覆盖前面的属性。
如果只有一个参数,Object.assign 方法会直接返回该参数。
如果该参数不是对象,则会先转为对象,然后返回。
由于undefined和null无法转成对象,所以如果将它们作为参数,就会报错。但是如果不是首参数,则不会出错。其他类型的值(数值,字符串,布尔值)不在首参数也不会报错。除去字符串会以数组形式复制到目标对象,其他值都不会产生效果。
Object.assign复制的属性是有限的,只会复制源对象的自身属性,不会复制继承属性,也不复制不可枚举的属性(enumerable: false)。
Object.assign方法实行的是浅复制,而不是深复制。也就是说如果源对象某个属性的值是对象,那么目标对象复制得到的是这个对象的引用。
Object.getOwnPropertyDeors()
ES5的Object.getOwnPropertyDescritptor()用来返回某个对象属性的描述对象。
ES2017引入Object.getOwnPropertyDescritptors()返回指定对象所有自身属性(非继承属性)的描述对象。
__proto__属性、Object.setPrototypeOf()
__proto__属性(前后各两个下画线)用来读取和设置当前属性的prototype对象。
因为它本质上是一个内部属性,而不是一个正式的对外API,因此最好用Object.setPrototypeOf()(写操作),Object.getPrototypeOf()(读操作),Object.create()(生操作)替代。
Object.setPrototypeOf()方法作用与__proto__相同,用来设置一个对象的prototype对象,返回参数对象本身。Object.getPrototypeOf()用于读取一个对象的prototype对象
- 第一个参数不是对象,则会自动转为对象。
- 由于undefined和null无法转为对象,所以如果第一个参数是undefined和null就会报错
Object.keys()、Object.values()、Object.entries()
- Object.keys():对键名的遍历
- Object.values():对键值的遍历
- Object.entries():对键值对的遍历
与数组的很相似。
扩展知识介绍完毕,继续下面的特性
6. ES6中的箭头函数
这或许是我最想要的一个特性,我爱 CoffeeScript 就是因为他胖胖的箭头(=&>相对于-&>),现在ES6中也有了。这些箭头最神奇的地方在于他会让你写正确的代码。比如,this在上下文和函数中的值应当是相同的,它不会变化,通常变化的原因都是因为你创建了闭包。
使用箭头函数可以让我们不再用that = this或者self = this或者_this = this或者.bind(this)这样的代码,比如,这些代码在ES5中就特别丑。
var _this = this
$('.btn').click(function(event){
_this.sendData()
})
这是在ES6中去掉_this = this之后:
$('.btn').click((event) =>{
this.sendData()
})
可惜的是,ES6委员会觉得再加上瘦箭头(-&>)的话就对我们太好了,所以他们留下了一个老旧的function。(瘦箭头在CoffeeScript中的作用就像ES5/6中一样)
var logUpperCase = function() {
var _this = this
this.string = this.string.toUpperCase()
return function () {
return console.log(_this.string)
}
}
logUpperCase.call({ string: 'es6 rocks' })()
在ES6中我们无需_this
var logUpperCase = function() {
this.string = this.string.toUpperCase()
return () => console.log(this.string)
}
logUpperCase.call({ string: 'es6 rocks' })()
注意,在ES6中你可以合理的把箭头函数和旧式 function 函数混用。当箭头函数所在语句只有一行时,它就会变成一个表达式,它会直接返回这个语句的值。但是如果你有多行语句,你就要明确的使用return。
这是ES5中利用messages数组创建一个数组的代码:
var ids = ['5632953c4e345e145fdf2df8','563295464e345e145fdf2df9']
var messages = ids.map(function (value) {
return 'ID is ' + value // 显式返回
});
在ES6中会变成这样:
var ids = ['5632953c4e345e145fdf2df8','563295464e345e145fdf2df9']
var messages = ids.map(value => `ID is ${value}`) // 隐式返回
注意到我用了字符串模版吗,又一个从CoffeeScript中来的功能,我爱它们!
在只有一个参数的函数签名中,括号是可有可无的,但是如果多于一个参数时就要加上。
var ids = ['5632953c4e345e145fdf2df8','563295464e345e145fdf2df9']
var messages = ids.map((value, index, list) => `ID of ${index} element is ${value} `) // 隐式返回
7. ES6中的Promise
Promise是一个有争议的话题。现在有很多Promise实现,语法也大致相同,比如q/ bluebird/ deferred.js/ vow/ avow/ jquery deferred等等。其他人说我们并不需要Promise,异步,回调和generator之类的就很好。庆幸的是,现在在ES6中终于有一个标准的Promise实现。
我们来看一个相当微不足道的延迟异步执行,用setTimeout实现
setTimeout(function(){
console.log('Yay!')
}, 1000)
我们可以用ES6中的Promise重写:
var wait1000 = new Promise(function(resolve, reject) {
setTimeout(resolve, 1000)
}).then(function() {
console.log('Yay!')
})
或者用ES6的箭头函数:
var wait1000 = new Promise((resolve, reject)=> {
setTimeout(resolve, 1000)
}).then(()=> {
console.log('Yay!')
})
到现在为止,我们只是单纯增加了代码的行数,还明显没有带来任何好处,你说的对。但是如果我们有更多复杂的逻辑内嵌在setTimeout()中的回调时好处就来了:
setTimeout(function(){
console.log('Yay!')
setTimeout(function(){
console.log('Wheeyee!')
}, 1000)
}, 1000)
可以用ES6中的Promise重写:
var wait1000 = ()=> new Promise((resolve, reject)=> {setTimeout(resolve, 1000)})
wait1000()
.then(function() {
console.log('Yay!')
return wait1000()
})
.then(function() {
console.log('Wheeyee!')
});
还是无法相信Promise比普通回调要好?我也不信。我想一旦知道了回调这个方法它就会在你脑中萦绕,额外的复杂的Promise也没有必要存在了。
不论怎么说,ES6中的Promise是为会欣赏的人准备的,Promise有一个不错的失败-捕捉回调机制,看看这篇文章吧,里面有更多关于Promise的信息。ES6 Promise介绍
8. 块级作用域的let和const
你可能早就听过对ES6中的let那些奇怪的传说,我记得我第一次到伦敦时为那些TO LET牌子感到非常困惑。但是ES6中的let和出租无关,这不算是语法糖,它很复杂。let是一个更新的var,可以让你把变量作用域限制在当前块里。我们用{}来定义块,但是在ES5中这些花括号起不到任何作用。
function calculateTotalAmount (vip) {
var amount = 0
if (vip) {
var amount = 1
}
{ // 让块来的更疯狂
var amount = 100
{
var amount = 1000
}
}
return amount
}
console.log(calculateTotalAmount(true))
运行结果将会是1000。天啊!这是多大的一个Bug。在ES6中,我们用let来限制变量作用域为函数内。
function calculateTotalAmount (vip) {
var amount = 0 // 或许应该用let, 但你可以混用
if (vip) {
let amount = 1 // 第一个数量为 0
}
{ // 更多的块
let amount = 100 // 第一个数量为 0
{
let amount = 1000 // 第一个数量为 0
}
}
return amount
}
console.log(calculateTotalAmount(true))
运行结果是0,因为在if块中也有let。如果什么都没有的话(amount=1),那么结果将会是1。
说到const,事情就简单多了。他仅仅产生是一个不可变的变量,并且他的作用域也像let一样只有块级。为了演示,这里有定义了一堆常量,并且由于作用域的原因,这些定义都是有效的。
function calculateTotalAmount (vip) {
const amount = 0
if (vip) {
const amount = 1
}
{ // 更多的块
const amount = 100
{
const amount = 1000
}
}
return amount
}
console.log(calculateTotalAmount(true))
依我愚见,let和const让这门语言变得更加复杂,没有这些的时候我们只有一条路可以走,但是现在可以要考虑更多的情景。????
9. ES6中的类
如果你喜欢面向对象编程,那么你会特别喜欢这个特性。他让你编写和继承类时就跟在Facebook上发一个评论这么简单。
在ES5中,因为没有class关键字(但它是毫无作用的保留字),类的创建和使用是让人十分痛苦的事情。更惨的是,很多伪类的实现像pseude-classical, classical, functional让人越来越摸不着头脑,为JavaScript的信仰战争火上浇油。
我不会给你展示在ES5中怎么去编写一个类(是啦是啦从对象可以衍生出来其他的类和对象),因为有太多方法去完成。我们直接看ES6的示例,告诉你ES6的类会用prototype来实现而不是function。现在有一个baseModel类,其中我们可以定义构造函数和getName()方法。
class baseModel {
constructor(options = {}, data = []) { // class constructor
this.name = 'Base'
this.url = 'http://azat.co/api'
this.data = data
this.options = options
}
getName() { // class method
console.log(`Class name: ${this.name}`)
}
}
注意到我给options和data用了默认参数,而且方法名再也不用加上function或者:了。还有一个很大的区别,你不能像构造函数里面一样向this.Name指派值。怎么说呢,和函数有相同缩进的代码里,你不能向name赋值。如果有这个需要的话,在构造函数里面完成。
使用NAME extends PARENT_NAME语法,AccountModel从baseModel继承而来。
class AccountModel extends baseModel {
constructor(options, data) {
调用父类构造函数时,只需带上参数轻松的调用super()方法。
super({private: true}, ['32113123123', '524214691'])
//call the parent method with super
this.name = 'Account Model'
this.url +='/accounts/'
}
想要高级一点的话,你可以像这样弄一个getter方法,这样accountsData就会变成一个属性。
get accountsData() { // 返回计算后的数据
// ... make XHR
return this.data
}
}
现在你要怎么用这个魔咒,很简单,就跟让三岁小孩相信圣诞老人存在一样。
let accounts = new AccountModel(5)
accounts.getName()
console.log('Data is %s', accounts.accountsData)
如果好奇输出结果的话:
Class name: Account Model
Data is 32113123123,524214691
10. ES6中的模块化
你可能知道,ES6之前JavaScript并没有对模块化有过原生的支持,人们想出来AMD,RequireJS,CommenJS等等,现在终于有import和export运算符来实现了。
ES5中你会用script标签和IIFE(立即执行函数),或者是其他的像AMD之类的库,但是ES6中你可以用export来暴露你的类。我是喜欢Node.js的人,所以我用和Node.js语法一样的CommonJS,然后用Browserfy来浏览器化。现在我们有一个port变量和getAccounts方法,在ES5中:
module.exports = {
port: 3000,
getAccounts: function() {
...
}
}
在ES5的main.js中,用require(‘模块’)来导入:
var service = require('module.js')
console.log(service.port) // 3000
但是在ES6中,我们用export和import。比如这是ES6中的module.js文件:
export var port = 3000
export function getAccounts(url) {
...
}
在需要引入的main.js文件中,可以用import {名称} from '模块’语法:
import {port, getAccounts} from 'module'
console.log(port) // 3000
或者就直接在main.js中引入所有的变量:
import * as service from 'module'
console.log(service.port) // 3000
个人来说,我觉得这样的模块化有些搞不懂。确实,这样会更传神一些 。但是Node.js中的模块不会马上就改过来,浏览器和服务器的代码最好是用同样的标准,所以目前我还是会坚持CommonJS/Node.js的方式。
目前来说浏览器对ES6的支持还遥遥无期(本文写作时),所以你需要一些像jspm这样的工具来用ES6的模块。
想要了解更多ES6中的模块化和例子的话,来看这篇文章,不管怎么说,写现代化的JavaScript吧!
还有一些知识点,感觉用不到,我就不贴了,有需求的自己去看吧
ES6的一些总结
ES6中还有很多你可能都用不上(至少现在用不上)的可圈可点的特性,以下无特定顺序:
- Math / Number / String / Array / Object中新的方法
- 二进制和八进制数据类型
- 自动展开多余参数
- For of循环(又见面了CoffeeScript)
- Symbols
- 尾部调用优化
- generator
- 更新的数据结构(如Map和Set)
和有些人吃了一片薯片就一发不可收拾的人一样(再来一片嘛就一片),对于那些停不下来想要知道关于更多ES6相关信息的成绩优秀的同学,我准备了扩展阅读:
- ES6速查手册(PDF)
- Nicholas C. Zakas所著的理解ECMAScript6
- Dr. Axel Rauschmayer所著的探索ECMAScript6 如果有人让你推荐前端技术书,请让他看这个列表 ->《经典前端技术书籍》