es2015也称为ES6,是JavaScript语言的下一代标准,下面将分享如何一步步解开它的面纱,哟没有一种幸福感O(∩_∩)O哈哈~
目录
1. 简介
2. 为什么要了解es6
3. ES-Checker
4. Babel
5. 开发工具
5.1. Sublime Text
5.2. WebStorm
6. 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. 原生的模块化
7. es7透析
7.1. async/await
7.2. Decorators
8. 参与资料
9. Q&A
9.1. 什么时候不能用箭头函数
ECMAScript6(以下简称ES6)是JavaScript语言的下一代标准。因为当前版本的ES6是在2015年6月批准通过发布的,所以又称ECMAScript 2015, 是自从2009年ES5标准化后的第一次重大更新。。虽然目前并不是所有浏览器都能兼容ES6全部特性,但越来越多的程序员在实际项目当中已经开始使用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)致力于全栈开发也全部兼容
ES-Checker用来检查各种运行环境对ES6 的支持情况
$ npm install -g es-checker
$ es-checker
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
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运行,快速熟悉快捷键。
绝对是前端开发大型应用的利器之一,后续介绍
在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 没有块级作用域。这个问题之所以为人所熟知,是因为它引发了诸如历遍监听事件需要使用闭包解决等问题。
<button>一button>
<button>二button>
<button>三button>
<button>四button>
<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)
}
// ...
继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 */)
模板字符串模板出现简直对 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)
对象字面量的语法在 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 }]
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
默认参数值
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
}
在介绍新的数据结构之前,我们先复习一下在 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
类,作为自 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 标准的发展。
对于一个纯前端的 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) 了。
光是 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 会作为一种接口定义方法,而不是逻辑处理工具。
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
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 的兼容性很差,哪怕是降级兼容也难以实现。
历史小回顾
在 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/
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函数的执行,与普通函数一模一样,只要一行。
2. 更好的语义
async和await,比起星号和yield,语义更清楚了
3. 更广的适用性
co模块约定,yield命令后面只能是 Thunk 函数或 Promise 对象,而async函数的await命令后面,可以是Promise 对象和原始类型的值(数值、字符串和布尔值,但这时等同于同步操作)
4. 返回值是Promise
修饰器(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)
}
最重要的是,同一个修饰对象是可以同时使用多个修饰器的,所以说我们还可以用修饰器实现很多很多有意思的功能。
nodejs学习笔记》入门级教程
nodejs学习笔记》常用组件(webMVC+orm)
nodejs学习笔记》演示项目示例
ref