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

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/itsoftchenfei/article/details/77942273

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. 什么时候不能用箭头函数


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 没有块级作用域。这个问题之所以为人所熟知,是因为它引发了诸如历遍监听事件需要使用闭包解决等问题。

<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)

}

// ...

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函数的执行,与普通函数一模一样,只要一行。

2.      更好的语义

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

3.      更广的适用性

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

4.      返回值是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)

}

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

8.   参与资料

9.   Q&A

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

ref

展开阅读全文

没有更多推荐了,返回首页