es2015学习笔记>经典入门教程

墨蓝 2022-06-08 02:26 344阅读 0赞

es2015也称为ES6,是JavaScript语言的下一代标准,下面将分享如何一步步解开它的面纱,哟没有一种幸福感O(∩_∩)O哈哈~

目录

  1. 简介

  2. 为什么要了解es6

  3. ES-Checker

  4. Babel

  5. 开发工具

5.1. Sublime Text

5.2. WebStorm

  1. es6新语法

6.1. Let/const和块级作用域

6.2. 箭头函数

6.3. 模板字符串

6.4. 对象字面量拓展语法

6.5. 变量解构赋值

6.6. 函数参数表达/传参

6.7. Set/WeakSet/Map/WeakMap

6.8. Class

6.9. Generator

6.10. Promise

6.11. Symbol

6.12. Proxy

6.13. 原生的模块化

  1. es7透析

7.1. async/await

7.2. Decorators

  1. 参与资料

  2. Q&A

9.1. 什么时候不能用箭头函数


1. 简介

ECMAScript6(以下简称ES6)是JavaScript语言的下一代标准。因为当前版本的ES6是在2015年6月批准通过发布的,所以又称ECMAScript 2015, 是自从2009年ES5标准化后的第一次重大更新。。虽然目前并不是所有浏览器都能兼容ES6全部特性,但越来越多的程序员在实际项目当中已经开始使用ES6了。

2. 为什么要了解es6

随着越来越多的程序员在实际项目当中已经开始使用ES6了。所以就算你现在不打算使用ES6,但为了看懂别人的你也该懂点ES6的语法了…如:

var sum=0**;

[*2.3,2,2].map(function(elem*){

sum+=elem**;

});

console.log(‘sum=’+sum**);

注:Angular.js、react.js、vue.js都是以es6为标准的,koa框架(node的mvc)致力于全栈开发也全部兼容

3. ES-Checker

ES-Checker用来检查各种运行环境对ES6 的支持情况

$ npm install -g es-checker

$ es-checker

4. Babel

Babel是一个广泛使用的 ES6 转码器,可以将 ES6 代码转为 ES5 代码,从而在现有环境执行。这意味着,你可以用 ES6 的方式编写程序,又不用担心现有环境是否支持。

Babel的配置文件是.babelrc,存放在项目的根目录下。使用 Babel 的第一步,就是配置这个文件。

在线转换:http://babeljs.io/

工具:

Ø bable-loader

安装bable-loader

$npm install bable-loader –g

安装转换规则

$npm install bable-preset-es2015 –save-dev

Ø babel-cli工具,用于命令行转码;

$ npm install —*g babel-cli*

# 转码结果输出到标准输出

$ babel example.js

# 转码结果写入一个文件

# —out-file 或 -o 参数指定输出文件

$ babel example.js —*out-file compiled.js*

# 或者

$ babel example.js -o compiled.js

# 整个目录转码

# —out-dir 或 -d 参数指定输出目录

$ babel src —*out-dir lib*

# 或者

$ babel src -d lib

# -s 参数生成source map文件

$ babel src -d lib -s

Ø babel-node:babel-cli工具自带一个命令,提供一个支持ES6的REPL环境。它支持Node的REPL环境的所有功能,而且可以直接运行ES6代码;

Ø babel-register:该模块改写require命令,为它加上一个钩子。此后,每当使用require加载.js、.jsx、.es和.es6后缀名的文件,就会先用Babel进行转码;

Ø babel-core:如果某些代码需要调用 Babel 的 API 进行转码,就要使用该模块;

Ø babel-polyfill:Babel 默认只转换新的 JavaScript 句法(syntax),而不转换新的 API

5. 开发工具

5.1. Sublime Text

1) 下载压缩包地址:https://github.com/tanepiper/SublimeText-Nodejs

2) 解压zip文件,并重命名文件夹“Nodejs”

3) 打开sublime,操作”preference”—>”Browse packages”, 把Nodejs复制到该目录

4) 打开Nodejs文件夹,找到文件“Nodejs.sublime-build”,要更改encoding为GB2312或者utf8否则终端显示乱码

5) 要用sublime打开文件“Nodejs.sublime-settings”并修改

“node_command”:”D:\Program\nodejs\node.exe”,

“npm_command”:”D:\Program\nodejs\npm.cmd”,

6) 重启sublime

新建一个test.js文件,按 Ctrl + B 或者F7运行,快速熟悉快捷键。

5.2. WebStorm

绝对是前端开发大型应用的利器之一,后续介绍

6. es6新语法

6.1. Let/const和块级作用域

在ES2015的新语法中,影响速度最为直接范围最大的,恐怕就是 let 和 const 了,它们是继var 之后,新的变量定义方法。与 let 相比,const 更容易被理解:const 也就是 constant 的缩写,跟 C/C++ 等经典语言一样,用于定义常量,即不可变量。但由于在ES6之前的 ECMAScript 标准中,并没有原生的实现,所以在降级编译中,会马上进行引用检查,然后使用 var 代替。

// foo.js

const foo =*‘bar’*

foo =*‘newvalue’*

$ babel foo.js

SyntaxError**: test.js**: Line 3:“foo” is read-only

  • 1**|const foo =**‘bar’*

  • 2*|

>*3*| foo =*‘newvalue’*

块级作用域

在ES6诞生之前,给JavaScript 新手解答困惑时,经常会提到一个观点:JavaScript 没有块级作用域。这个问题之所以为人所熟知,是因为它引发了诸如历遍监听事件需要使用闭包解决等问题。

<*div id=“output”></div*>

<*script*>

    • var buttons *=document.querySelectorAll(‘button’)*
    • var output *=document.querySelector(‘#output’)*
    • *for(var i =0; i < buttons**.length; i**++){*
  • buttons[i].addEventListener(‘click’**,function(){*

  • output.innerText **= buttons**[i].**innerText*

    • })
    • }

</*script*>

运行时就会报出这样的错误信息:

Uncaught TypeError: Cannot readproperty ‘innerText’ of undefined

出现这个错误的原因是因为buttons[i]不存在,即为undefined。对i的变量引用(Reference)一直保持在上一层作用域(循环语句所在层)上,而当循环结束i 则正好是 buttons.length。而在ES6中,我们只需做出一个小小的改动,便可以解决该问题(假设所使用的浏览器已经支持所需要的特性):

// …

*for(*/\ var */ let i =0; i < buttons**.length; i**++){*

  • // …*

}

// …

改进后的代码经过 babel 的编译后的代码:

// …

var _loop *=function(**i**){*

  • buttons[i].addEventListener(‘click’**,function(){*

  • output.innerText **= buttons**[i].**innerText*

    • })

}

*for(var i =0; i < buttons**.length; i**++){*

  • _loop(i*)

}

// …

6.2. 箭头函数

继let和const之后,箭头函数就是使用率最高的新特性。顾名思义便是使用箭头(=>)进行定义的函数,属于匿名函数(Lambda)一类。当然了,也可以作为定义式函数使用,但我们并不推荐这样做,随后会详细解释。

使用(用法)

foo => foo +*‘ world’//means return`foo + ‘ world’`*

(*foo*, bar**)=>** foo + bar

foo *=>{*

    • return foo +*‘ world’*

}

(*foo*, bar**)=>{

    • return foo + bar

}

其最大的好处便是简洁明了,省略了function关键字,而使用=>代替。

let names *=[‘Will’,‘Jack’,‘Peter’,‘Steve’,‘John’,‘Hugo’,‘Mike’]*

let newSet = names

    • .*map((name*, index**)=>{
    • *return{*
  • id*: index**,

  • name*: name

    • }
    • })
    • .*filter(man **=> man**.*id *%2==0)*
    • .*map(man **=>[**man.name*])
    • .*reduce((a*, b**)=>** a.concat(b**))

console.log(newSet)//=> [ ‘Will’,’Peter’, ‘John’, ‘Mike’ ]

箭头函数与上下文绑定

参考CoffeeScript 中的定义一般,是用于对函数内部的上下文 (this)绑定为定义函数所在的作用域的上下文。

let obj *={*

  • hello:‘world’*,

  • foo**(){*

    • let bar *=()=>{*
    • *returnthis.**hello*
    • }
    • return bar
    • }

}

window.*hello **=**‘ES6’*

window.*bar **= obj**.foo()*

window.*bar()//=> ‘world’*

上面代码中的 obj.foo 等价于:

// …

foo**(){**

    • let bar *=(function(){*
    • *returnthis.**hello*
    • }).*bind*(this)
  • *

    • return bar

}

// …

注意事项

另外,要注意的是箭头函数对上下文的绑定是强制性的,无法通过 apply 或 call 方法改变其上下文。

let a *={*

  • init**(){*

    • this.*bar **=()=>this.**dam*
    • },
  • dam:‘hei’*,

  • foo**(){*

    • *returnthis.**dam*
    • }

}

let b *={*

  • dam:‘ha’*

}

a.init**()

console.log(a.foo())//=> hei

console.log(a.foo.bind(b).call(a))//=> ha

console.log(a.bar.call(b))//=> hei

另外,因为箭头函数会绑定上下文的特性,故不能随意在顶层作用域使用箭头函数,以防出错:

// 假设当前运行环境为浏览器,故顶层作上下文为 `window`

let obj *={*

  • msg:‘pong’*,

  • ping**:()=>{*

    • *returnthis.**msg // Warning!*
    • }

}

obj.ping()//=> undefined

let msg =*‘bang!’*

obj.ping()//=> bang!

// 等价于:

let obj *={*

  • // …*

  • ping**:(function(){*

    • *returnthis.**msg // Warning!*
    • }).*bind*(this)

}

// 同样等价于

let obj *={ /* … */**}*

obj.ping *=(function(){*

    • *returnthis.**msg*

}).*bind(this/* this -> window */*)

6.3. 模板字符串

模板字符串模板出现简直对 Node.js 应用的开发和发展起到了相当大的推动作用。模板字符串要求使用 ` 代替原本的单/双引号来包裹字符串内容。它有两大特点:

l 支持变量注入

let name =*‘Will Wen Gunn’*

let title =*‘Founder’*

let company =*‘LikMoon Creation’*

let greet =*`Hi, I’m ${name}, I am the${title} at ${company}`*

console.log(greet)//=> Hi, I’m Will Wen Gunn, I am theFounder at LikMoon Creation

l 支持换行

let sql =`

SELECT * FROM Users

WHERE FirstName=’Mike’

LIMIT 5;

`

console.log(sql)

6.4. 对象字面量拓展语法

对象字面量的语法在 ES2015 之前早已挺完善的了。不过,对于聪明的工程师们来说,细微的改变,也能带来不少的价值。

方法属性省略function

let obj *={*

  • // before*

  • foo**:function(){*

    • return*‘foo’*
    • },
  • // after*

  • bar**(){*

    • return*‘bar’*
    • }

}

支持 __proto__ 注入

在 ES2015 中,我们可以给一个对象硬生生的赋予其 __proto__,这样它就可以成为这个值所属类的一个实例了。

class Foo {

    • *constructor(){*
    • this.*pingMsg **=**‘pong’*
    • }
  • ping**(){*

  • console.log(this.pingMsg*)

    • }

}

let o *={*

  • __proto__**:new Foo**()*

}

o.ping()//=> pong

有什么卵用?有一个比较特殊的场景会需要用到:我想扩展或者覆盖一个类的方法,并生成一个实例,但觉得另外定义一个类就感觉浪费了。那我可以这样做:

let o ={

  • __proto__:new Foo(),*

  • constructor(){*

  • this.pingMsg =’alive’*

  • },*

  • msg:’bang’,*

  • yell(){*

  • console.log(this.msg)*

  • }*

}

o.yell()//=> bang

o.ping()//=> alive

同名方法属性省略语法

也是看上去有点鸡肋的新特性,不过在做 JavaScript 模块化工程的时候则有了用武之地。

// module.js

*exportdefault{*

  • someMethod*

}

function someMethod**(){**

  • // …*

}

// app.js

import Module from*‘./module’*

Module.someMethod**()

可以动态计算的属性名称

在上面的两个[…] 中,我演示了动态计算的对象属性名称的使用,分别为对应的对象定义了当前计数器n和n的2次方

let arr *=[1,2,3]*

let outArr = arr.map(n *=>{*

    • *return{*
    • [ n ]: n**,
    • [*`${n}^2`]:Math.pow(n,2*)
    • }

})

console.dir(outArr)//=> [{ ‘1’: 1, ‘1^2’: 1 },{ ‘2’: 2, ‘2^2’: 4 },{‘3’: 3, ‘3^2’: 9 }]

6.5. 变量解构赋值

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

数组

let a =*1*;

let b =*2*;

let c =*3*;

//现在可以这样

*let[a, b**, c**]=[1,2,3];*

*let[foo,[[**bar*], baz**]]=[**1**,[[2],3]];**

foo // 1

bar // 2

baz // 3

  • *

*let[,, third**]=[“foo”,“bar”,“baz”];*

third // “baz”

  • *

*let[x,,* y**]=[**1,2,3**];

x // 1

y // 3

  • *

*let[head,…**tail**]=[**1,2,3,4*];

head // 1

tail // [2, 3, 4]

  • *

*let[x, y**,…**z**]=[**‘a’*];

x // “a”

y // undefined

z // []

如果解构不成功,变量的值就等于undefined。

// Matching with object

function search(query**){**

  • // …*

  • // let users = [ … ]*

  • // let posts = [ … ]*

  • // …*

    • *return{*
  • users*: users**,

  • posts*: posts

    • }

}

*let{ users**, posts }=* search(‘iwillwen’**)

// Matching with array

*let[ x**, y ]=[1,2]*

// missing one

[ x**,,**y *]=[**1,2,3*]

function g**({ name**: x**}){**

  • console.log(x*)

}

g**({ name:5**})

// Fail-soft destructuring

*var[a]=[]*

a ===*undefined//=> true*

// Fail-soft destructuring with defaults

*var[*a *=1]=[]*

a ===*1//=> true*

默认值

*let[*foo *=true]=[];*

foo // true

对象

*let{ foo**, bar }={ foo**:“aaa”, bar**:“bbb”};*

foo // “aaa”

bar // “bbb”

*let{ baz }={ foo**:“aaa”, bar**:“bbb”};*

baz // undefined

//数组的元素是按次序排列的,变量的取值由它的位置决定;而对象的属性没有次序,变量必须与属性同名

字符串

*const[a, b**, c**, d**, e**]=**‘hello’*;

a // “h”

b // “e”

c // “l”

d // “l”

e // “o”

数值和布尔值

*let{ toString**: s**}=**123*;

s ===*Number.prototype.toString// true*

*let{ toString**: s**}=true;*

s === Boolean.prototype.toString// true

函数参数的解构

function add([x**, y**]){

    • return x + y**;

}

add([1,2]);// 3

6.6. 函数参数表达/传参

默认参数值

function noop**(){ returnfalse}**

function readLineInFile(filename**, callback = noop**, complete = noop**){**

}

后续参数

我们知道函数的 call 和 apply 在使用上的最大差异便是一个在首参数后传入各个参数,一个是在首参数后传入一个包含所有参数的数组。如果我们在实现某些函数或方法时,也希望实现像 call 一样的使用方法,在 ES2015 之前,我们可能需要这样做:

function fetchSomethings**(){**

    • var args *=[].slice.apply(arguments)*
  • // …*

}

function fetchSomethings(…args**){**

  • // …*

}

要注意的是,…args 后不可再添加

虽然从语言角度看,arguments 和 …args 是可以同时使用,但有一个特殊情况则不可:arguments 在箭头函数中,会跟随上下文绑定到上层,所以在不确定上下文绑定结果的情况下,尽可能不要再箭头函数中再使用 arguments,而使用 …args。虽然 ECMA 委员会和各类编译器都无强制性要求用 …args 代替 arguments,但从实践经验看来,…args 确实可以在绝大部份场景下可以代替 arguments 使用,除非你有很特殊的场景需要使用到 arguments.callee 和 arguments.caller。所以我推荐都使用 …args 而非 arguments。

PS:在严格模式(Strict Mode)中,arguments.callee 和 arguments.caller 是被禁止使用的。

注意事项

默认参数值和后续参数需要遵循顺序原则,否则会出错。

function(…args, last =1){

// This will go wrong

}

6.7. Set/WeakSet/Map/WeakMap

在介绍新的数据结构之前,我们先复习一下在 ES2015 之前,JavaScript 中有哪些基本的数据结构。其中又分为值类型和引用类型,Array 其实是 Object 的一种子类。

l String 字符串

l Number 数字(包含整型和浮点型)

l Boolean 布尔值

l Object 对象

l Array 数组

Set和WeakSet

我们再来复习下高中数学吧,集不能包含相同的元素,我们有很多需要用到集的场景,如搜索、索引建立等。ECMA 委员会为 ECMAScript 增添了集(Set)和“弱”集(WeakSet)。它们都具有元素唯一性,若添加了已存在的元素,会被自动忽略。

let s *=new Set**()*

s.add(‘hello’).add(‘world’).add(‘hello’**)

console.log(s.size)//=> 2

console.log(s.has(‘hello’))//=> true

WeakSet 在 JavaScript 底层作出调整(在非降级兼容的情况下),检查元素的变量引用情况。如果元素的引用已被全部解除,则该元素就会被删除,以节省内存空间。这意味著无法直接加入数字或者字符串。另外 WeakSet 对元素有严格要求,必须是 Object,当然了,你也可以用 new String(‘…’) 等形式处理元素。

let weaks *=new WeakSet**()*

weaks.add(“hello”)//=> Error

weaks.add(3.1415)//=> Error

let foo *=newString(“bar”)*

let pi *=newNumber(3.1415)*

weaks.add(foo**)

weaks.add(pi**)

weaks.has(foo)//=> true

foo *=null*

weaks.has(foo)//=> false

Map和WeakMap

从数据结构的角度来说,映射Map跟原本的Object非常相似,都是Key/Value的键值对结构。但是 Object 有一个让人非常不爽的限制:key 必须是字符串或数字,而Map则解决了这一问题。

let map *=new Map**()*

let object *={ id**:1}*

map.set(object,‘hello’**)

map.set(‘hello’,‘world’**)

map.has(object)//=> true

map.get(object)//=> hello

而 WeakMap 和 WeakSet 很类似,只不过 WeakMap 的键和值都会检查变量引用,只要其一的引用全被解除,该键值对就会被删除。

let weakm *=new WeakMap**()*

let keyObject *={ id**:1}*

let valObject *={ score**:100}*

weakm.set(keyObject**, valObject**)

weakm.get(keyObject)//=> { score:100 }

keyObject *=null*

weakm.has(keyObject)//=> false

6.8. Class

类,作为自 JavaScript 诞生以来最大的痛点之一,终于在 ES2015 中得到了官方的妥协,“实现”了 ECMAScript 中的标准类机制。为什么是带有双引号的呢?因为我们不难发现这样一个现象:

$ node

> class Foo {}

[*Function*: Foo**]

回想一下在 ES2015 以前的时代中,我们是怎么在 JavaScript 中实现类的?

function Foo(){}

var foo = new Foo()

ES6 中的类只是一种语法糖,用于定义原型(Prototype)的。

定义

ES2015 所带来的类语法确实与很多C语言家族的语法相似,与JavaScript中的对象字面量不一样的是,类的属性后不能加逗号,而对象字面量则必须要加逗号。

class Person {

    • constructor(*name*, gender**, age**){**
    • this.*name **=** name*
    • this.*gender **=** gender*
    • this.*age **=** age*
    • }
  • isAdult**(){*

    • *returnthis.*age *>=**18*
    • }

}

let me *=new Person**(‘iwillwen’,‘man’,19)*

console.log(me.isAdult())//=> true

继承

ES2015的类继承总算是为JavaScript类继承之争抛下了一根定海神针

class Animal {

  • yell**(){*

  • console.log(‘yell’*)

    • }

}

class Person extends Animal {

    • constructor(*name*, gender**, age**){**
    • super()*// must call `super` before using `this` if this class has a superclass*
    • this.*name **=** name*
    • this.*gender **=** gender*
    • this.*age **=** age*
    • }
  • isAdult**(){*

    • *returnthis.*age *>=**18*
    • }

}

class Man extends Person {

    • constructor(*name*, age**){**
    • super(*name,‘man’*, age**)
    • }

}

let me *=new Man**(‘iwillwen’,19)*

console.log(me.isAdult())//=> true

me.yell**()

静态方法

class Man {

  • // …*

    • static isMan(obj**){**
    • return obj instanceof Man
    • }

}

let me *=new Man**()*

console.log(Man.isMan(me))//=> true

遗憾的是,ES2015 的类并不能直接地定义静态成员变量,但若必须实现此类需求,可以用static 加上 get 语句和 set 语句实现。

class SyncObject {

  • // …*

    • static get baseUrl**(){**
    • return*‘http://example.com/api/sync'*
    • }

}

遗憾与期望

就目前来说,ES2015 的类机制依然很鸡肋:

不支持私有属性(private)

不支持前置属性定义,但可用 get 语句和 set 语句实现

不支持多重继承

没有类似于协议(Protocl)或接口(Interface)等的概念

中肯地说,ES2015的类机制依然有待加强。但总的来说,是值得尝试和讨论的,我们可以像从前一样,不断尝试新的方法,促进 ECMAScript 标准的发展。

6.9. Generator

对于一个纯前端的 JavaScript 工程师来说,可能 Generator 并没有什么卵用,但若你曾使用过 Node.js 或者你的前端工程中有大量的异步操作(ES6提供的一种异步编程解决方案),Generator 简直是你的“贤者之石”。

来龙

Generator 的设计初衷是为了提供一种能够简便地生成一系列对象的方法,如计算斐波那契数列(Fibonacci Sequence)

function* fibo**(){**

    • let a =*1*
    • let b =*1*
  • yielda*

  • yieldb*

    • *while(true){*
    • let next = a + b
  • a **=** b*

  • b **=** next*

  • yield next*

    • }

}

let generator = fibo**()

*for(var i =0; i <10; i**++)*

  • console.log(generator.next().value)//=> 1 1 2 3 58 13 21 34 55*

Generator函数

Generator 函数是一个普通函数,但是有两个特征。一是,function关键字与函数名之间有一个星号;二是,函数体内部使用yield表达式,定义不同的内部状态(yield在英语里的意思就是“产出”)。

Generator 函数有多种理解角度。从语法上,首先可以把它理解成,Generator 函数是一个状态机,封装了多个内部状态。执行 Generator 函数会返回一个遍历器对象,返回的遍历器对象,可以依次遍历 Generator 函数内部的每一个状态。

yield表达式

其中支持一种新的语法 yield。它的作用与 return 有点相似,但并非退出函数,而是切出生成器运行时。

function* helloWorldGenerator**(){**

  • yield’hello’*;

  • yield’world’*;

    • return*‘ending’*;

}

var hw = helloWorldGenerator**();

hw.next**()

// { value: ‘hello’, done: false }

hw.next**()

// { value: ‘world’, done: false }

hw.next**()

// { value: ‘ending’, done: true }

hw.next**()

// { value: undefined, done: true }

即该函数有三个状态:hello,world 和 return 语句(结束执行),必须调用遍历器对象的next方法,使得指针移向下一个状态。也就是说,每次调用next方法,内部指针就从函数头部或上一次停下来的地方开始执行,直到遇到下一个yield表达式(或return语句)为止。换言之,Generator函数是分段执行的,yield表达式是暂停执行的标记,而next方法可以恢复执行。

事实上,Generator 的用法还是很多种,其中最为著名的一种便是使用 Generator 的特性模拟出 ES7 中的 async/await 特性。而其中最为著名的就是 co 和 koa(基于 co 的 Web Framework) 了。

6.10. Promise

光是 Promise 作为一个老生常谈的话题,作为异步编程的一种解决方案,目前就有多种标准,而目前最为流行的是Promises/A+,而ES2015 中的 Promise 便是基于此。Promise是一种用于解决回调函数无限嵌套的工具(当然,这只是其中一种)。它的作用便是“免去”异步操作的回调函数,保证能通过后续监听而得到返回值,或对错误处理。它能使异步操作变得井然有序,也更好控制。我们以在浏览器中访问一个 API,解析返回的 JSON 数据。

fetch(‘http://example.com/api/users/top'****)**

    • .*then(res **=> res**.json())*
    • .*then(data **=>{*
  • vm.data.topUsers **=** data*

  • *})

  • // Handle the error crash in the chaining processes*

  • .catch(err **=> console**.error(err))*

Promise 在设计上具有原子性,即只有两种状态:未开始和结束(无论成功与否都算是结束),这让我们在调用支持 Promise 的异步方法时,逻辑将变得非常简单,这在大规模的软件工程开发中具有良好的健壮性,带有 resolve 和 reject 参数对应的成功和失败。

function fetchData**(){**

    • *returnnew Promise**((resolve, reject**)=>{*
  • // …*

    • })

}

弊端

虽说 Promise确实很优雅,但是这是在所有需要用到的异步方法都支持 Promise 且遵循标准。而且链式 Promise 强制性要求逻辑必须是线性单向的,一旦出现如并行、回溯等情况,Promise 便显得十分累赘。所以在目前的最佳实践中,Promise 会作为一种接口定义方法,而不是逻辑处理工具。

6.11. Symbol

ES5 的对象属性名都是字符串,这容易造成属性名的冲突。比如,你使用了一个他人提供的对象,但又想为这个对象添加新的方法(mixin 模式),新方法的名字就有可能与现有方法产生冲突。如果有一种机制,保证每个属性的名字都是独一无二的就好了,这样就从根本上防止属性名的冲突。这就是ES6 引入Symbol的原因。

ES6 引入了一种新的原始数据类型Symbol,表示独一无二的值,即便我们看不到它的区别在哪里。这就意味著,我们可以用它来保证一些数据的安全性。

console.log(Symbol(‘key’**)==** Symbol(‘key’))//=> false

如果将一个 Symbol 隐藏于一个封闭的作用域内,并作为一个对象中某属性的键,则外层作用域中便无法取得该属性的值,有效保障了某些私有库的代码安全性。Symbol 作为属性名,该属性不会出现在for…in、for…of循环中,也不会被Object.keys()、Object.getOwnPropertyNames()、JSON.stringify()返回。但是,它也不是私有属性,有一个Object.getOwnPropertySymbols方法,可以获取指定对象的所有 Symbol属性名。

let privateDataStore *={*

  • set(val**){*

    • let key = Symbol(Math.random().toString(32).substr(2**))
    • this[*key**]=** val*
    • return key
    • },
  • get(key**){*

    • *returnthis[key]*
    • }

}

let key = privateDateStore(‘hello world’**)

privateDataStore[key]//=> undefined

privateDataStore.get(key)//=> hello world

黑科技

Symbol 除了带给我们数据安全性以外,还带来了一些很神奇的黑科技,Symbol.iterator,除 Symbol 以外,ES2015 还为 JavaScript 带来了 for…of 语句,这个跟原本的 for…in 又有什么区别?

对象的Symbol.iterator属性,指向该对象的默认遍历器方法。

var myIterable *={};*

myIterable[Symbol.iterator**]=function\(){*

  • yield1*;

  • yield2*;

  • yield3*;

};

[…*myIterable]// [1, 2, 3]*

对象进行for…of循环时,会调用Symbol.iterator方法,返回该对象的默认遍历器

class Collection {

    • \[**Symbol.iterator**](){*
    • let i =*0*;
    • while(this[*i**]!==undefined){*
  • yield **this[i];*

    • ++*i*;
    • }
    • }

}

let myCollection *=new Collection**();*

myCollection[0**]=1;**

myCollection[1**]=2;**

for(let value of myCollection**){**

  • console.log(value*);

}

// 1

// 2

6.12. Proxy

Proxy 可以在不入侵目标对象的情况下,对逻辑行为进行拦截和处理。

假设我要对 api 这一对象进行拦截并记录下代码行为,我就可以这样做:

var obj *=new Proxy**({},{*

  • get**:function(**target*, key**, receiver**){**

  • console.log(`getting ${key}!`*);

    • return Reflect.get(target**, key**, receiver**);
    • },
  • set**:function(**target*, key**, value**, receiver**){**

  • console.log(`setting ${key}!`*);

    • return Reflect.set(target**, key**, value**, receiver**);
    • }

});

obj.count =*1*

// setting count!

++*obj.count*

// getting count!

// setting count!

// 2

可惜的是,目前 Proxy 的兼容性很差,哪怕是降级兼容也难以实现。

6.13. 原生的模块化

历史小回顾

在 JavaScript 的发展历史上,曾出现过多种模块加载库,如 RequireJS、SeaJS、FIS 等,而由它们衍生出来的 JavaScript 模块化标准有 CommonJS、AMD、CMD 和 UMD 等。其中最为典型的是 Node.js 所遵循的 CommonJS 和 RequireJS 的 AMD。

ES2015 中的模块化机制设计也是相当成熟的。基本上所有的 CommonJS 或是 AMD 代码都可以很快地转换为 ES2015 标准的加载器代码。

import name from*“module-name”*

*import* as name from**“module-name”*

*import{ member }from**“module-name”*

*import{ member as alias }from**“module-name”*

*import{ member1 , member2 }from**“module-name”*

*import{ member1 , member2 as alias2**,[…]}from**“module-name”*

import defaultMember**,{** member *[,[…]]}from**“module-name”*

import defaultMember**,** as alias **from**“module-name”*

import defaultMember from*“module-name”*

import*“module-name”*

全局引入

*import* as name from**‘module-name’*

var name = require(‘module-name’)//CommonJS 类似

局部引入

与 CommonJS 等标准不同ES2015 的模块引入机制支持引入模块的部份暴露接口

*import{ A**, B**, C }from**‘module-name’*

暴露单独接口

// module.js

*exportfunction method**(){* /\ … */*}

// main.js

import M from*‘./module’*

M.method**()

基本的 export 语句可以调用多次,单独使用可暴露一个对象到该模块外

暴露复合模块

若需要实现像 CommonJS 中的 module.exports = {} 以覆盖整个模块的暴露对象,则需要在 export 语句后加上 default。

// module.js

*exportdefault{*

  • method1*,

  • method2*

}

// main.js

import M from*‘./module’*

M.method1**()

降级兼容

在实际应用中,我们暂时还需要使用 babel 等工具对代码进行降级兼容。庆幸的是,babel 支持 CommonJS、AMD、UMD 等模块化标准的降级兼容,我们可以根据项目的实际情况选择降级目标。

*$ babel-m common -d dist/common/ src**/*

*$ babel-m amd -d dist/amd/ src**/*

*$ babel-m umd -d dist/umd/ src**/*

7. es7透析

7.1. async/await

Generator Function与普通的 Function在执行方式上有著本质的区别,在某种意义上是无法共同使用的。对于 ES7 的 Async Function 来说,这一点并不存在!它可以以普通函数的执行方式使用,并且有著 Generator Function 的异步优越性,它甚至可以作为事件响应函数使用。

var fs = require(‘fs’**);

var readFile *=function(**fileName**){*

    • *returnnew Promise**(function(**resolve*, reject**){**
  • fs.readFile(fileName**,function(error, data**){*

    • *if(error) reject**(error);*
  • resolve(data*);

    • });
    • });

};

var gen *=function*(){*

    • var f1 = yield readFile(‘/etc/fstab’**);
    • var f2 = yield readFile(‘/etc/shells’**);
  • console.log(f1.toString*());

  • console.log(f2.toString*());

};

// async函数就是将 Generator 函数的星号(\)替换成async,将yield替换成await*

var asyncReadFile = async *function(){*

    • var f1 = await readFile(‘/etc/fstab’**);
    • var f2 = await readFile(‘/etc/shells’**);
  • console.log(f1.toString*());

  • console.log(f2.toString*());

};

async函数对 Generator 函数的改进,体现在以下四点:

  1. 内置执行器

Generator函数的执行必须靠执行器,所以才有了co模块,而async函数自带执行器。也就是说,async函数的执行,与普通函数一模一样,只要一行。

  1. 更好的语义

async和await,比起星号和yield,语义更清楚了

  1. 更广的适用性

co模块约定,yield命令后面只能是 Thunk 函数或 Promise 对象,而async函数的await命令后面,可以是Promise 对象和原始类型的值(数值、字符串和布尔值,但这时等同于同步操作)

  1. 返回值是Promise

7.2. Decorators

修饰器(Decorator)是一个函数,用来修改类的行为。这是 ES 的一个提案,目前 Babel 转码器已经支持。

定义如下:

l 是一个表达式

l Decorator 会调用一个对应的函数

l 调用的函数中可以包含target(装饰的目标对象)、name(装饰目标的名称)和 descriptor(描述器)三个参数

l 调用的函数可以返回一个新的描述器以应用到装饰目标对象上

PS:如果你不记得 descriptor 是什么的话,请回顾一下 Object.defineProperty() 方法。

我们在实现一个类的时候,有的属性并不想被 for..in 或 Object.keys() 等方法检索到,那么在 ES5 时代,我们会用到 Object.defineProperty() 方法来实现:

var obj *={*

  • foo:1*

}

Object.defineProperty(obj,‘bar’**,{**

  • enumerable**:false,*

  • value:2*

})

console.log(obj.bar)//=> 2

var keys *=[]*

*for(var key in obj**)*

  • keys.push(key*)

console.log(keys)//=> [ ‘foo’ ]

console.log(Object.keys(obj))//=> [ ‘foo’ ]

/\**

\**ES7中可以这样*

\*/*

class Obj {

    • *constructor(){*
    • this.*foo **=**1*
    • }
  • @nonenumerable*

  • getbar**(){ return2}*

}

function nonenumerable(target**, name**, descriptor**){**

  • descriptor.enumerable **=false*

    • return descriptor

}

var obj *=new Obj**()*

console.log(obj.foo)//=> 1

console.log(obj.bar)//=> 2

console.log(Object.keys(obj))//=> [ ‘foo’ ]

黑科技

假如我们要实现一个类似于 Koa的CI的框架,且利用Decorator 特性实现 URL 路由,我们可以这样做。

// 框架内部

// 控制器

class Controller {

  • // …*

}

var handlers *=new WeakMap**()*

var urls *={}*

// 定义控制器

@route(‘/‘**)

class HelloController extends Controller {

    • *constructor(){*
    • super()
  • *

    • this.*msg **=**‘World’*
    • }
  • asyncGET(ctx**){*

  • ctx.body **=**`Hello ${this.msg}`*

    • }

}

// Router Decorator

function route(url**){**

    • return target *=>{*
  • target.url **=** url*

    • let urlObject *=newString(url)*
  • urls[url**]=** urlObject*

  • *

  • handlers.set(urlObject*, target**)

    • }

}

// 路由执行部份

function router(url**){**

    • *if(urls[url]){*
    • var handlerClass = handlers.get(urls[url**])
    • *returnnew handlerClass**()*
    • }

}

var handler = router(‘/‘**)

*if(handler){*

    • let context *={}*
  • handler.GET(context*)

  • console.log(context.body*)

}

最重要的是,同一个修饰对象是可以同时使用多个修饰器的,所以说我们还可以用修饰器实现很多很多有意思的功能。77942795CommonJS_AMD_CMD

8. 参与资料

  • 阮一峰《ECMAScript6 入门》
  • nodejs学习笔记》入门级教程
  • nodejs学习笔记》常用组件(webMVC+orm)
  • nodejs学习笔记》演示项目示例

9. Q&A

9.1. 什么时候不能用箭头函数

ref

发表评论

表情:
评论列表 (有 0 条评论,344人围观)

还没有评论,来说两句吧...

相关阅读