给 JavaScript 初心者的 ES实战.docx

上传人:b****8 文档编号:28324991 上传时间:2023-07-10 格式:DOCX 页数:30 大小:28.55KB
下载 相关 举报
给 JavaScript 初心者的 ES实战.docx_第1页
第1页 / 共30页
给 JavaScript 初心者的 ES实战.docx_第2页
第2页 / 共30页
给 JavaScript 初心者的 ES实战.docx_第3页
第3页 / 共30页
给 JavaScript 初心者的 ES实战.docx_第4页
第4页 / 共30页
给 JavaScript 初心者的 ES实战.docx_第5页
第5页 / 共30页
点击查看更多>>
下载资源
资源描述

给 JavaScript 初心者的 ES实战.docx

《给 JavaScript 初心者的 ES实战.docx》由会员分享,可在线阅读,更多相关《给 JavaScript 初心者的 ES实战.docx(30页珍藏版)》请在冰豆网上搜索。

给 JavaScript 初心者的 ES实战.docx

给JavaScript初心者的ES实战

给JavaScript初心者的ES2015实战

作者:

小问

感谢以下厂商联合为作者提供的4000元写作赞助:

 

成为赞助方/开始写作

 

给JavaScript初心者的ES2015实战

前言

历时将近6年的时间来制定的新ECMAScript标准ECMAScript6(亦称ECMAScriptHarmony,简称ES6)终于在2015年6月正式发布。

自从上一个标准版本ES5在2009年发布以后,ES6就一直以新语法、新特性的优越性吸引著众多JavaScript开发者,驱使他们积极尝鲜。

虽然至今各大浏览器厂商所开发的JavaScript引擎都还没有完成对ES2015中所有特性的完美支持,但这并不能阻挡工程师们对ES6的热情,于是乎如babel、Traceur等编译器便出现了。

它们能将尚未得到支持的ES2015特性转换为ES5标准的代码,使其得到浏览器的支持。

其中,babel因其模块化转换器(Transformer)的设计特点赢得了绝大部份JavaScript开发者的青睐,本文也将以babel为基础工具,向大家展示ES2015的神奇魅力。

笔者目前所负责的项目中,已经在前端和后端全方位的使用了ES2015标准进行JavaScript开发,已有将近两年的ES2015开发经验。

如今ES2015以成为ECMA国际委员会的首要语言标准,使用ES2015标准所进行的工程开发已打好了坚实的基础,而ES7(ES2016)的定制也走上了正轨,所以在这个如此恰当的时机,我觉得应该写一篇通俗易懂的ES2015教程来引导广大JavaScript爱好者和工程师向新时代前进。

若您能从本文中有所收获,便是对我最大的鼓励。

我希望你在阅读本文前,已经掌握了JavaScript的基本知识,并具有一定的WebApp开发基础和Node.js基本使用经验。

目录

一言蔽之ES2015

ES2015能为JavaScript的开发带来什么

语法糖

工程优势

ES2015新语法详解

let、const和块级作用域

块级作用域

箭头函数(ArrowFunction)

使用方法

箭头函数与上下文绑定

注意事项

模板字符串

支持变量注入

支持换行

对象字面量扩展语法

方法属性省略function

支持注入__proto__

同名方法属性省略语法

可以动态计算的属性名称

表达式解构

函数参数表达、传参

默认参数值

后续参数

注意事项

解构传参

注意事项

新的数据结构

Set和WeakSet

Map和WeakMap

类(Classes)

语法

定义

继承

静态方法

遗憾与期望

生成器(Generator)

来龙

基本概念

GeneratorFunction

Generator

基本使用方法

Promise

概念

基本用法

弊端

原生的模块化

历史小回顾

基本用法

全局引入

局部引入

接口暴露

降级兼容

Symbol

黑科技

Proxy(代理)

ES2015的前端开发实战

构建界面

结构定义

架构设计

模块化

数据支持

界面渲染

构建应用

入口文件

数据层:

文章

路由:

首页

准备页面渲染

加载数据

设计组件

路由:

文章页面

路由:

发布新文章

路由绑定

合并代码

ES2015的Node.js开发实战

架构设计

构建应用

入口文件

数据抽象层

Posts控制器

API:

获取所有文章

API:

获取指定文章

API:

发布新文章

Comments控制器

API:

获取指定文章的评论

API:

发表新评论

配置路由

配置任务文件

部署到DaoCloud

Dockerfile

创建DaoCloud上的MongoDB服务

代码构建

一窥ES7

async/await

Decorators

简单实例

黑科技

后记

本文的实战部份将以开发一个动态博客系统为背景,向大家展示如何使用ES2015进行项目开发。

成品代码将在GitHub上展示。

一言蔽之ES2015

说到ES2015,有了解过的同学一定会马上想到各种新语法,如箭头函数(=>)、class、模板字符串等。

是的,ECMA委员会吸取了许多来自全球众多JavaScript开发者的意见和来自其他优秀编程语言的经验,致力于制定出一个更适合现代JavaScript开发的标准,以达到“和谐”(Harmony)。

一言蔽之:

ES2015标准提供了许多新的语法和编程特性以提高JavaScript的开发效率和体验

从ES6的别名被定为Harmony开始,就注定了这个新的语言标准将以一种更优雅的姿态展现出来,以适应日趋复杂的应用开发需求。

ES2015能为JavaScript的开发带来什么

语法糖

如果您有其他语言(如Ruby、Scala)或是某些JavaScript的衍生语言(如CoffeeScript、TypeScript)的开发经验,就一定会了解一些很有意思的语法糖,如Ruby中的Range->1..10,Scala和CoffeeScript中的箭头函数(a,b)=>a+b。

ECMA委员会借鉴了许多其他编程语言的标准,给ECMAScript家族带来了许多可用性非常高的语法糖,下文将会一一讲解。

这些语法糖能让JavaScript开发者更舒心地开发JavaScript应用,提高我们的工作效率~~,多一些时间出去浪~~。

工程优势

ES2015除了提供了许多语法糖以外,还由官方解决了多年来困扰众多JavaScript开发者的问题:

JavaScript的模块化构建。

从许多年前开始,各大公司、团队、大牛都相继给出了他们对于这个问题的不同解决方案,以至于定下了如CommonJS、AMD、CMD或是UMD等JavaScript模块化标准,RequireJS、SeaJS、FIS、Browserify、webpack等模块加载库都以各自不同的优势占领著一方土地。

然而正正是因为这春秋战国般的现状,广大的前端搬砖工们表示很纳闷。

这?

究竟哪种好?

哪种适合我?

求大神带我飞!

对此,ECMA委员会终于是坐不住了,站了起来表示不服,并制订了ES2015的原生模块加载器标准。

importfsfrom'fs'

importreadlinefrom'readline'

importpathfrom'path'

letModule={

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

letrl=readline.createInterface({

input:

fs.createReadStream(path.resolve(__dirname,'./big_file.txt'))

})

rl.on('line',line=>{

//...dosomethingwiththecurrentline

callback(line)

})

rl.on('close',complete)

returnrl

}

}

functionnoop(){returnfalse}

exportdefaultModule

~~老实说,这套模块化语法不禁让我们又得要对那个很silly的问题进行重新思考了:

JavaScript和Java有什么关系?

~~

可惜的是,目前暂时还没有任何浏览器厂商或是JavaScript引擎支持这种模块化语法。

所以我们需要用babel进行转换为CommonJS、AMD或是UMD等模块化标准的语法。

ES2015新语法详解

经过以上的介(xun)绍(tao),相信你对ES2015也有了一定的了解和期待。

接下来我将带大家慢慢看看ECMA委员会含辛茹苦制定的新语言特性吧。

let、const和块级作用域

在ES2015的新语法中,影响速度最为直接,范围最大的,恐怕得数let和const了,它们是继var之后,新的变量定义方法。

与let相比,const更容易被理解:

const也就是constant的缩写,跟C/C++等经典语言一样,用于定义常量,即不可变量。

但由于在ES6之前的ECMAScript标准中,并没有原生的实现,所以在降级编译中,会马上进行引用检查,然后使用var代替。

//foo.js

constfoo='bar'

foo='newvalue'

$babelfoo.js

...

SyntaxError:

test.js:

Line3:

"foo"isread-only

1|constfoo='bar'

2|

>3|foo='newvalue'

...

块级作用域

在ES6诞生之前,我们在给JavaScript新手解答困惑时,经常会提到一个观点:

JavaScript没有块级作用域

在ES6诞生之前的时代中,JavaScript确实是没有块级作用域的。

这个问题之所以为人所熟知,是因为它引发了诸如历遍监听事件需要使用闭包解决等问题。

四varbuttons=document.querySelectorAll('button')

varoutput=document.querySelector('#output')

for(vari=0;i

buttons[i].addEventListener('click',function(){

output.innerText=buttons[i].innerText

})

}

前端新手非常容易写出类似的代码,因为从直观的角度看这段代码并没有语义上的错误,但是当我们点击任意一个按钮时,就会报出这样的错误信息:

UncaughtTypeError:

Cannotreadproperty'innerText'ofundefined

出现这个错误的原因是因为buttons[i]不存在,即为undefined。

为什么会出现按钮不存在结果呢?

通过排查,我们可以发现,每次我们点击按钮时,事件监听回调函数中得到的变量i都会等于buttons.length,也就是这里的4。

而buttons[4]恰恰不存在,所以导致了错误的发生。

再而导致i得到的值都是buttons.length的原因就是因为JavaScript中没有块级作用域,而使对i的变量引用(Reference)一直保持在上一层作用域(循环语句所在层)上,而当循环结束时i则正好是buttons.length。

而在ES6中,我们只需做出一个小小的改动,便可以解决该问题(假设所使用的浏览器已经支持所需要的特性):

//...

for(/*var*/leti=0;i

//...

}

//...

通过把for语句中对计数器i的定义语句从var换成let,即可。

因为let语句会使该变量处于一个块级作用域中,从而让事件监听回调函数中的变量引用得到保持。

我们不妨看看改进后的代码经过babel的编译会变成什么样子:

//...

var_loop=function(i){

buttons[i].addEventListener('click',function(){

output.innerText=buttons[i].innerText

})

}

for(vari=0;i

_loop(i)

}

//...

实现方法一目了然,通过传值的方法防止了i的值错误。

箭头函数(ArrowFunction)

继let和const之后,箭头函数就是使用率最高的新特性了。

当然了,如果你了解过Scala或者曾经如日中天的JavaScript衍生语言CoffeeScript,就会知道箭头函数并非ES6独创。

箭头函数,顾名思义便是使用箭头(=>)进行定义的函数,属于匿名函数(Lambda)一类。

当然了,也可以作为定义式函数使用,但我们并不推荐这样做,随后会详细解释。

使用

箭头函数有好几种使用语法:

1.foo=>foo+'world'//meansreturn`foo+'world'`

2.(foo,bar)=>foo+bar

3.

foo=>{

returnfoo+'world'

}

4.

(foo,bar)=>{

returnfoo+bar

}

以上都是被支持的箭头函数表达方式,其最大的好处便是简洁明了,省略了function关键字,而使用=>代替。

箭头函数语言简洁的特点使其特别适合用於单行回调函数的定义:

letnames=['Will','Jack','Peter','Steve','John','Hugo','Mike']

letnewSet=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']

如果你有Scala+Spark的开发经验,就一定会觉得这非常亲切,因为这跟其中的RDD操作几乎如出一辙:

将原本的由名字组成的数组转换为一个格式为{id,name}的对象,id则为每个名字在原数组中的位置

剔除其中id为奇数的元素,只保留id为偶数的元素

将剩下的元素转换为一个包含当前元素中原名字的单元数组,以方便下一步的处理

通过不断合并相邻的两个数组,最后能得到的一个数组,便是我们需要得到的目标值

箭头函数与上下文绑定

事实上,箭头函数在ES2015标准中,并不只是作为一种新的语法出现。

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

letobj={

hello:

'world',

foo(){

letbar=()=>{

returnthis.hello

}

returnbar

}

}

window.hello='ES6'

window.bar=obj.foo()

window.bar()//=>'world'

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

//...

foo(){

letbar=(function(){

returnthis.hello

}).bind(this)

returnbar

}

//...

为什么要为箭头函数给予这样的特性呢?

我们可以假设出这样的一个应用场景,我们需要创建一个实例,用于对一些数据进行查询和筛选。

letDataCenter={

baseUrl:

'

search(query){

fetch(`${this.baseUrl}/search?

query=${query}`)

.then(res=>res.json())

.then(rows=>{

//TODO

})

}

}

此时,从服务器获得数据是一个JSON编码的数组,其中包含的元素是若干元素的ID,我们需要另外请求服务器的其他API以获得元素本身(当然了,实际上的API设计大部份不会这么使用这么蛋疼的设计)。

我们就需要在回调函数中再次使用this.baseUrl这个属性,如果要同时兼顾代码的可阅读性和美观性,ES2015允许我们这样做。

letDataCenter={

baseUrl:

'

search(query){

returnfetch(`${this.baseUrl}/search?

query=${query}`)

.then(res=>res.json())

.then(rows=>{

returnfetch(`${this.baseUrl}/fetch?

ids=${rows.join(',')}`)

//此处的this是DataCenter,而不是fetch中的某个实例

})

.then(res=>res.json())

}

}

DataCenter.search('iwillwen')

.then(rows=>console.log(rows))

因为在单行匿名函数中,如果this指向的是该函数的上下文,就会不符合直观的语义表达。

注意事项

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

leta={

init(){

this.bar=()=>this.dam

},

dam:

'hei',

foo(){

returnthis.dam

}

}

letb={

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`

letobj={

msg:

'pong',

ping:

()=>{

returnthis.msg//Warning!

}

}

obj.ping()//=>undefined

letmsg='bang!

'

obj.ping()//=>bang!

为什么上面这段代码会如此让人费解呢?

我们来看看它的等价代码吧。

letobj={

//...

ping:

(function(){

returnthis.msg//Warning!

}).bind(this)

}

//同样等价于

letobj={/*...*/}

obj.ping=(function(){

returnthis.msg

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

模板字符串

模板字符串模板出现简直对Node.js应用的开发和Node.js自身的发展起到了相当大的推动作用!

我的意思并不是说这个原生的模板字符串能代替现有的模板引擎,而是说它的出现可以让非常多的字符串使用变得尤为轻松。

模板字符串要求使用`代替原本的单/双引号来包裹字符串内容。

它有两大特点:

支持变量注入

支持换行

支持变量注入

模板字符串之所以称之为“模板”,就是因为它允许我们在字符串中引用外部变量,而不需要像以往需要不断地相加、相加、相加……

letname='WillWenGunn'

lettitle='Founder'

letcompany='LikMoonCreation'

letgreet=`Hi,I'm${name},Iamthe${title}at${company}`

console.log(greet)//=>Hi,I'mWillWenGunn,IamtheFounderatLikMoonCreation

支持换行

在Node.js中,如果我们没有支持换行的模板字符串,若需要拼接一条SQL,则很有可能是这样的:

varsql=

"SELECT*FROMUsers"+

"WHEREFirstName='Mike'"+

"LIMIT5;"

或者是这样的:

varsql=[

"SELECT*FROMUsers",

"WHEREFirstName='Mike'",

"LIMIT5;"

].join('')

无论是上面的哪一种,都会让我们感到很不爽。

但若使用模板字符串,仿佛打开了新世界的大门~

letsql=`

SELECT*FROMUsers

WHEREFirstName='Mike'

LIMIT5;

`

Sweet!

在Node.js应用的实际开发中,除了SQL的编写,还有如Lua等嵌入语言的出现(如Redis中的SCRIPT命令),或是手工的XML拼接。

模板字符串的出现使这些需求的解决变得不再纠结了~

对象字面量扩展语法

看到这个标题的时候,相信有很多同学会感到奇怪,对象字面量还有什么可以扩展的?

确实,对象字面量的语法在ES2015之前早已挺完善的了。

不过,对于聪明的工程师们来说,细微的改变,也能带来不少的价值。

方法属性省略function

这个新特性可以算是比较有用但并不是很显眼的一个。

letobj={

//before

foo:

function(){

return'foo'

},

//after

bar(){

return'bar'

}

}

支持__proto__注入

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

classFoo{

constructor(){

this.pingMsg='pong'

}

ping(){

console.log(this.pingMsg)

}

}

leto={

__proto__:

newFoo()

}

o.ping()//=>pong

什么?

有什么卵用?

有一个比较特殊的场景会需要用到:

我想扩展或者覆盖一个类的方法,并生成一个实例,但觉得另外定义一个类就感觉浪费了。

那我可以这样做:

leto={

__proto__:

newFoo(),

constructor(){

this.pingMsg='alive'

}

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 农林牧渔 > 林学

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1