ES6标准⼊门(阮⼀峰)-阅读记录与⼼得
我的最新博客在:
⽬标:学习ES6标准,并能灵活使⽤ES6标准的JavaScript
内容:(都是新的内容,需要结合ES5的理解去看,这样看得⽐较有意思)
第⼀阶段:准备阶段
1、第⼀章,⼊门准备。主要讲了ES6简介与ECMAScript历史(这部分快速阅读),重点掌握ES6环境具体部署,建议使⽤Babel转码器。在配置Babel转码器之前,需要下载安
装node环境,可以适当理解包管理的概念。(也可以适当理解webpack的⽤法)
第⼆阶段:基础:新特性
1、第⼆章,let和const命令。let是ES6新增的命令。注意let改进了ES5中很多特性,let要求我们养成先声明再使⽤变量的习惯(为了避免暂时性死区),也提出了ES5没有的块
作⽤域。还有const,声明常量就必须初始化,还有ES6声明变量的6种⽅法(ES5中的var和function,
还有ES6本章的let和const,及后⾯章节的import和class),ES6中的顶层
对象和global对象。
2、第三章,变量的解构赋值。主要讲述ES6中各⾃变量的解构赋值格式与说明(可能有⼀些基于ES6的新⽅法、函数的解构赋值,可以先看,有个思路,后⾯章节遇到对应部分
在回过头看这部分),末节总结的应⽤场景可以细细品味。
3、第四章,字符串的扩展。牢记通常的⽤法,例如padStart()⽤于为数值补全指定位数,或者⽤于提⽰字符串格式。模板字符串,⽤反引号把变量嵌⼊(变量名写在${}中就⾏,
⼤括号内部理解为要执⾏的JS代码),适⽤于函数,对象。还有⼀些模板字符串的扩展例⼦。
4、第五章,正则的扩展。ES6添加了许多正则修饰符,如u,i,y,s等;还有添加了后⾏断⾔(提案);具名组匹配(提案)。
5、第六章,数值的扩展。数值(number)的⼀些⽅法,如进制转换,有限判断,NAN判断,数值类型转换,整型数判断(3和3.0看做⼀样的),EPSILON值(⽤于表⽰⼀个可
以接受的误差范围,例如0.1+0.2与0.3的差值⼩于EPSILON就可以认为是相等),安全整数(整型数的范围的上下限)及其判断。此外,还有Math对象的扩展,新增许多静态⽅
法(即只能调⽤Math对象使⽤),包括.trunc(),.sign(),.imul()(⽤于很⼤数值乘法,使其低位数值精确),.hypot()(返回所有参数的平⽅和的平⽅根)和对数⽅法、指数⽅法
(**,**=)等。(注:对于没有部署某个⽅法的环境,我们应该学会⾃⼰去编写相应功能的代码)
6、第七章,函数的扩展。ES6中,可以直接在函数参数中设置默认值(是默认声明了,所以不能在⽤let和const再次声明),此外,函数可以与(对象的)解构赋值的默认值结
合使⽤。rest参数(剩余参数),严格模式(在ES6中,如果函数参数使⽤了默认值、解析赋值、扩展运算符,则函数内部就不能显式设定严格模式),函数的name属性。箭头
函数,注意⼀些在箭头函数中的事项,例如箭头函数中的this对象就是定义时所在的对象,⽽不是使⽤时所在的对象(根本原因在于箭头函数内部没有this对象,⽽是引⽤外层的
this),ES7提案中的函数绑定(Babel转码器已经⽀持)。尾调⽤优化,尾调⽤是指某个函数的最后⼀步(不⼀定是出现在函数的尾部)是“纯粹地”调⽤另⼀个函数;⽽尾调⽤
优化就是只保留内部函数的调⽤帧,节省内存。尾递归,尾调⽤⾃⾝,相当于普通的递归,由于只存在⼀个调⽤帧,不会发⽣“栈溢出”,例如⽤尾调⽤写阶乘函数,Fibonacci数
列等(好好理解,很有意思)。ES6也明确规定,ES的实现都必须部署“尾调⽤优化”,即在ES中,只要使⽤尾递归,就不会发⽣栈溢出。递归的本质就是⼀种循环操作,也这是
尾调⽤的重要性。
7、第⼋章,数组的扩展。扩展运算符(...)及其应⽤(如将函数参数转为⼀个参数序列,合并数组,与解构赋值结合,⽤于函数的多个返回值(相当于可以返回数组或对象),
字符串转换为数组,任何Iterator接⼝对象,如Map,set结构,Generator函数都可以⽤扩展运算符转为真正的数组)。Array.from()⽅法可以将类似数组的对象和可遍历的对象转为
真正的数组,并且相⽐于扩展运算符,其可以提供map功能。Array.of()⽅法基本可以代替Array(),new Array()将⼀组值转换为数组。此外,还有数组实例的⽅法,例如
copyWithin(),find(),findIndex(),fill(),还有ES6的新⽅法:entries(),keys(),values()。数组实例的includes()⽅法,数组的空位,在ES6中上⾯的⽅法会将空位处理为undefined。
8、第九章,对象的扩展。对象中的属性和⽅法可以简写,但属性名表达式和简洁表⽰法不能同时使⽤。对象中的⽅法也有name属性,Object.is()⽅法与严格相等===类似,
但有所不同。Object.assign()是⼀种浅复制,其常见⽤途有为对象添加属性、⽅法,克隆对象,合并多个对象,为属性指定默认值(但注意是浅复制)。对象有属性的描述对
象,控制着对象属性的⾏为。属性的可枚举性及其遍历⽅法。_proto_属
性,Object.setPrototypeOf(),PrototypeOf(),Object.keys(),Object.values(),ies()。对象的扩展运算符(...),可⽤于解构赋值(其复制也是浅复制)。
9,第⼗章,Symbol。ES6中引⼊了⼀种新的原始数据类型Symbol,表⽰独⼀⽆⼆的值。Symbol值通过Symbol函数⽣成,对象的属性名除了⽤字符串,也可以使⽤Symbol。
Symbol⾮对象,其类似于字符串的数据类型。常量也可以使⽤Symbol值。Symbol适合消除魔术字符串,所谓魔术字符串是指在代码中多次出现,与代码形成强耦合的某⼀个具
体的字符串或者数值。风格良好的代码,应该尽量消除魔术字符串。OwnProperySymbols⽅法可以获取指定对象的所有Symbol属性名,Reflect.ownKeys⽅法可以返
回所有类型的键名,包括常规键名和Symbol键名。通常,利⽤Symbol特性为对象定义⼀些⾮私有但⼜希望只⽤于内部的⽅法。Symbol.for(),Symbol.keysFor()。模块的
SingleTon模式,其指的是调⽤⼀个类并且在任何时候都返回同⼀个实例。内置的Symbol值,除了⾃定义使⽤的Symbol值,ES6提供了11个内置的Symbol值,指向语⾔内部使
⽤的⽅法。
Symbol.hasInstance,Symbol.isConcatSpreadable,Symbol.species,Symbol,match,place,Symbol.search,Symbol.split,Symbol.iterator,Primitive,StringTag,S 10、第⼗⼀章,Set和Map数据结构。ES6提供了新的数据结构--Set(集合),它类似于数组,但是成员的值都是唯⼀的,没有重复。Set本⾝是⼀个构造函数,⽤来⽣成Set数
据结构。set结构的属性和⽅法(操作⽅法和遍历⽅法)。WeakSet结构,其与Set结构类似,也是不重复的值的集合,区别在于:WeakSet的成员只能是对象;WeakSet中的对
象都是弱引⽤,如果其他对象都不在引⽤该对象,那么垃圾回收机制会⾃动回收该对象所占⽤的内存,不考虑该对象是否还存在于WeakSet之中,WeakSet是不可遍历的。
WeakSet的⼀个⽤处是存储DOM节点,⽽不⽤担⼼这些节点从⽂档移除时会引发内存泄露。JavaScript对象本质上是键值对的集合(Hash结构),但是只能⽤字符串作为键,这
给它的使⽤带来了很⼤的限制。为了解决这个问题,ES6提供了Map数据结构,它类似于对象,也是键值对的集合,但是“键”的范围不限于字符串,各种类型的值(包括对象)都
可以当做键。相当于Object结构提供了“字符串--值”的对应,Map结构提供了“值--值”的对应,是⼀种更完善的Hash结构实现。Map的键实际上市和内存地址绑定的,只要内存地
址不⼀样,就视为两个键,这就可以解决同名属性碰撞(clash)的问题。如果Map的键是⼀个简单类型的值(数字、字符串、布尔值),只要两个值严格相等,Map就将其视为
⼀个键,包括0和-0(NAN不严格等于⾃⾝,但是也Map视为同⼀个值)。Map结构的实例的属性和操作⽅法,遍历⽅法。Map与数组,对象,JSON的相互转化。WeakMap结
构,与Map结构类似,也⽤于⽣成键值对的集合,区别在于WeakMap只接受对象作为键名(null除外);WeakMap的键名所指向的对象不计⼊垃圾回收机制。WeakMap同样是
为了解决内存泄漏问题⽽诞⽣的。WeakMap弱引⽤的只是键名⽽不是键值,键值依然是正常引⽤的。此外,WeakMap适合实现注册监听事件的listener对象和部署私有属性。
11、第⼗⼆章,Proxy。Proxy(代理器)⽤于修改某些操作的默认⾏为,等同于在语⾔层⾯做出修改,所以属于⼀种“元编程”(meta programming),即对编程语⾔进⾏编程。
Proxy可以理解成在⽬标对象前架设⼀个“拦截”层,外界对该对象的访问都必须先通过这层拦截,因此提供了⼀种机制可以对外界的访问进⾏过滤和改写。ES6原⽣提供Proxy构
造函数,⽤于⽣成Proxy实例。要使Proxy起作⽤,必须针对Proxy实例进⾏操作,⽽不是针对⽬标对象进⾏操作。拦截⽅法有
get(),set(),has(),deleteProperty(),ownKeys(),个
tOwnPropertyDescriptor(),defineProperty(),preventExtensions(),getPrototype(),isExtensible(),setPrototypeOf(),apply(),construct()。利⽤Proxy,可以将读取属性的操
作(get)转变为执⾏某个函数,从⽽实现属性的链式操作。例如可以⽤get拦截实现⼀个⽣成各种DOM节点的通⽤函数dom。如果⼀个属性不可配置或者不可写,则该属性不能
被代理,通过Proxy对象访问该属性将会报错。⽽set()可以保证⼀些属性的输⼊值范围符合要求的(相当于数据验证的⼀种实现⽅法)。利⽤set()还可以实现数据绑定,即每当对
象发⽣变化时,会⾃动更新DOM。当对对象设置内部属性时,下划线命名属性之外,可以结合get()和set(),能够防⽌这些内部属性被外部读、写。同样地,如果对象某个属性不
可写不可配置,set不能改变该属性,会报错。apply()拦截函数的调⽤、call和apply操作。has()⽤来拦截HasProperty操作(注意不是HasOwnProperty),且对in循环⽣效。has
⽅法当对象不可配置或禁⽌扩展时,拦截会出错。has拦截对in 循环不⽣效。construct()⽤于拦截new命令。deleteProperty⽅法⽤于拦截delete操作(若不可配置则会报
错)。defineProperty⽅法拦截了Object.defineProperty操作。getOwnPropertyDesciptor⽅法拦截OwnPropertyDesctiptor(),返回⼀个属性描述对象(值、可读,枚
举,配置)或者undefined。getPrototypeOf⽅法主要⽤来拦截获取对象原型。isExtensible⽅法拦截Object.isExtensible操作(该⽅法有⼀个强限制,它的返回值必须与⽬标对象
的isExtensible属性保持⼀致,否则抛出错误)。ownKeys⽅法⽤来拦截对象⾃⾝属性的读取操作。preventExtensions⽅法拦截Object.preventExtensions(),返回⼀个布尔值
(只有⽬标对象不可扩展时,才能返回true,否则报错)。setPrototypeOf⽅法⽤于拦截Object.setPrototypeOf⽅法。vocable()返回⼀个可取消的Proxy实例(其⼀个使⽤
场景是,⽬标对象不允许直接⽅法,必须通过代理访问,⼀旦访问结束,就收回代理权,不允许再次访问)。虽然Proxy可以代理针对⽬标对象的访问,但不做任何拦截的情况
下⽆法保证与⽬标对象的⾏为⼀致,主要在于⽬标对象内部的this关键词会指向Proxy代理,此时,可以⽤this绑定原始对象可以解决这个问题。Proxy适⽤于编写Web服务的客户
端,也可以实现数据库的ORM层。
12、第⼗三章,Reflect。Reflect对象与Proxy对象⼀样,也是ES6为了操作对象⽽提供的新的API。Reflect对象设计的⽬的是将Object对象的⼀些明显属于语⾔内部的⽅法(如
Object.defineProperty)放到Reflect对象上;修改某些Object⽅法的返回结果(如把返回抛出错误变为返回false);让Object操作都变成函数⾏为;Reflect对象的⽅法与Proxy对
象⽅法⼀⼀对应(例如可以确保原⽣⾏为正常执⾏,然后再部署额外的功能)。Reflect对象⼀共有13个静态⽅法,⼤部分与Object对象的同名⽅法的作业是相同的(在报错⽅法
有所不同),且与Proxy对象的⽅法是⼀⼀对应的。⽅法查并返回target对象的name属性。Reflect.set⽅法设置target对象name属性等于value(会触发
Proxy.defineProperty拦截)。Reflect.has⽅法对应name in obj 中的in运算符。Reflect.deleteProperty⽅法等同于delete obj[name],⽤于删除对象的属性。struct⽅法
等同于new target(),提供了⼀种不使⽤new来调⽤构造函数的⽅法。PrototypeOf⽅法⽤于读取对象的_proto_属性,对应Prototypeof(obj)。
Reflect.setPrototypeOf⽅法⽤于读取对象的_proto_属性,对应Object.setPrototypeof(obj, newProto)。Reflect.apply⽅法等同于Function.prototype.apply.call(func, thisArg,
args),⽤于绑定this对象后执⾏给定函数。Reflect.defineProperty⽅法基本等同于Object.defineProperty,⽤于为对象定义属性(作者提出,后者会逐渐被废除,请开始使⽤前者
代替后者)。OwnPropertyDescriptor基本等同于OwnPropertyDescriptor,⽤于获取指定属性的描述对象(也是要使⽤前者)。Reflect.isExtensible⽅法等
同于Object.isExtensible,返回⼀个布尔值,表⽰当前对象是否可扩展。Reflect.preventExtensions对应Object.preventExtensions⽅法,⽤于使⼀个对象变为不可扩展。
Reflect.ownKeys⽅法⽤于返回对象的所有属性,基本等同于OwnPropertyNames与OwnPropertySymbols之和。实例:使⽤Proxy实现观察者模式(即使⽤
Proxy编写observable和observe这两个函数),思路是,observable函数返回⼀个原始对象的Proxy代理,拦截赋值操作,触发充当观察者的各个函数。所谓观察者模式
(Observer mode)指的是函数⾃动观察数据对象的模式,⼀旦对象有变化,函数就会⾃动执⾏。
const的作用13、第⼗四章,Promise对象。Promise是异步编程的⼀种解决⽅案,⽐传统的解决⽅案(回调函数和事件)更合理更强⼤。ES6将其写⼊了语⾔标准,统⼀了⽤法并原⽣提供了Promise对象。Promise,简单来说就是⼀个容器,⾥⾯保存着某个未来才会结束的事件(通常是⼀个异步操作)的结果。Promise对象可以获取异步操作的消息。Promise提供统⼀的API,各种异步操作都可以⽤同样的⽅法进⾏处理。Promise对象有两个特点:对象的状态不受外界影响;⼀旦状态改变就不会再变。Promise对象代表⼀个异步操作,有3种状态:Pending、Fulfilled和Rejected。Promise对象的状态改变只有两种可能:从Pending变为Fulfilled和从Pending变为Rejected。只有这两种情况发⽣,状态就凝固了,不会再变,称为Resolved(已定型)。有了Promise对象,就可以将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数。Promise⼀些缺点:⽆法取消,⼀旦新建它就会⽴即执⾏,⽆法中途取消;如果不设置回调函数,Promise内部抛出的错误不会反应到外部;当处于Pending状态时,⽆法得知⽬前进展到哪⼀个阶段(刚刚开始还是即将完成)。如果某些事件不断地反复发⽣,使⽤Stream模式⽐部署Promise更好。ES6规定,Promise对象是⼀个构造函数,⽤来⽣成Promise实例。Promise基本⽤
法,Promise.prototype.then(),Promise.prototype.catch(),Promise.all(),Promise.race(),solve(),ject()。ES6中的Promise的API提供的⽅法不多,可以⾃⼰部署⼀些有⽤的⽅法(例如本书部署了done()和finally())。应⽤有:加载图⽚;Generator函数与Promised的结合。实际开发中不知道或者不想区分函数f是同步还是异步,有两个办法:使⽤async函数和使⽤new Promise()。也有⼀个()(提案)。
14、第⼗五章,Iterator和for···of循环。ES6中,Array,Object,Map和Set可以表⽰“集合”的数据结构,它们需要⼀种统⼀的接⼝机制来处理所有不同的数据结构。遍历器Iterator 就是这样⼀种机制。它是⼀种接⼝(可遍历的),为各种不同的数据结构提供统⼀的访问机制。任何数据结构只要部署了Iterator接⼝,就可以完成遍历操作(即依次处理该数据结构的所有成员)。Iterator的3个作⽤:为各种数据结构提供⼀个统⼀、便捷的访问接⼝;使得数据结构的成员能够按某种次序排列;ES6新的遍历命令for···of循环(遍历器对象本质是⼀个指针对象,通过调⽤只针对新的next⽅法实现遍历)。原⽣具备Iterator接⼝的数据结构(即原⽣部署了Symbol.iterator属性)有:
Array,Map,Set,String,TypedArray,函数的arguments对象,NodeList对象。外此之外,其他数据结构(主要是对象)的Iterator接⼝都需要⾃⼰在Symbol.iterator属性上⾯部署,这样才会被for```of循环遍历。调⽤Iterator接⼝的场合:解构赋值;扩展运算符(相当于,只要某个数据结构部署了Iterator接⼝,就看对它使⽤扩展运算符,将其转化为数组);yield*;其他场合(由于数组的遍历会调
⽤遍历器接⼝,所以任何接受数组作为参数的场合其实都调⽤了遍历器接⼝)。Iterator接⼝与Generator函数。遍历器对象除了必须具有的next⽅法,还可以部署两个可选的return⽅法和throw⽅法。(for··in循环可以遍历键名)
15、第⼗六章,Generator函数的语法。Generator(“⽣成器”)函数是ES6提供的⼀种异步编程解决⽅案,语法上,可以把它理解成⼀个状态机,封装了多个内部状态。执⾏Generator函数会返回⼀个遍历器对象(即为遍历器对象⽣成函数)。返回的遍历器对象可以⼀次遍历Generator函数内部的每⼀个状态。形式上,Generator函数是⼀个普通函数,有两个特征:function命令与函数名之间有⼀个星号*(只要星号是在function和函数名之间的任何位置都⾏,⼀般是⽤function* name());内部使⽤yield(“产出”)语句定义不同的内部状态。调⽤Generator函数后,函数并不执⾏,返回的也不是函数运⾏结果,⽽是⼀个指向内部状态的指针对象,即遍历器对象,必须调⽤遍历器对象的next⽅法,使得指针移向下⼀个状态。换⽽⾔之,Generator函数是分段执⾏的,yield语句是暂停执⾏的标记,⽽next⽅法可以恢复执⾏。Generator函数其实就是提供了⼀种可以暂停执⾏的函数,yield语句就是暂停标志(相当于JS提供了⼿动的“惰性求值”的语法功能)。Generator函数可以通过多个yield⽣成多个值(返回值),但⼀个函数体⾥⾯,⾄多只能通过return返回⼀个值。如果Generator函数中没有使⽤yield语句,这时变为⼀个单纯的暂缓执⾏函数(只有调⽤⼀次next⽅法才能执⾏Generator函数)。yield只能⽤在Generator函数⾥⾯(其for循环⾥⾯也⾏);如果⽤在另⼀个表达式中,要放在圆括号⾥⾯(若是
作为函数参数或赋值表达式右边,可以不加括号)。可以把Generator赋值给对象的Symbol.iterator属性,使其对象具有Iterator接⼝。Generator函数执⾏后,返回⼀个遍历器对象,其本⾝具有Symbol.iterator属性,执⾏后返回⾃⾝。yield语句本⾝没有返回值(或者理解为总是返回undefined),next⽅法可以带有⼀个参数,该参数当做上⼀条yield语句的返回值(相当于可以在Generator函数运⾏的不同阶段从外部向内部注⼊不同的值,从⽽调整函数⾏为)。for```of循环可以⾃动遍历Generator函数⽣成的Iterator对象,且此时不再需要调⽤next⽅法。利⽤for```of循环,可以写出遍历任意对象的⽅法,由于原⽣的JS对象没有遍历接⼝,⽆法使⽤for```of,通过Generator函数为它加上这个接⼝后就可以⽤了(另⼀种⽅法:将Generator函数加到对象的Symbol.iterator属性上)。Generator.prototype.throw()与全局的throw命令;urn();yield*(⽤来在⼀个Generator函数⾥⾯执⾏另⼀个Generator函数,表明返回的是⼀个遍历器对象或者是有Iterator接⼝的数据结构都⾏);作为对象属性的Generator函数;Generator函数this。Generator与状态机;Generator与协程(协作的线程),协程既可以单线程实现(⼀种特殊的⼦例程),也可以多线程实现(⼀种特殊的线程)。传统的“⼦例程”采⽤堆栈式“后进先出”的执⾏⽅式,只有当调⽤的⼦函数完全执⾏完毕,才会结束执⾏⽗函数。协程是多个线程(单线程情况即多个函数)可以并⾏执⾏,但只有⼀个线程(或函数)出于正在运⾏的状态,其他线程(或函数)都处于暂停态,线程(或函数)之间可以交换执⾏权。从实现上看,在内存中⼦例程只使⽤⼀个栈,⽽协程是同时存在多个栈,但只有⼀个栈是在运⾏态,即协程是以多占⽤内存为代价实现多任务的并⾏运⾏。JS是单线程语⾔,只能保持⼀
个调⽤栈,引⼊协程以后,每个任务可以保持⾃⼰的调⽤栈。Generator函数被称为“半协程”,只有Generator函数的调⽤者才能将程序的执⾏权还给Generator 函数,如果将Generator函数当做协程,完全可以将多个需要需互相协作的任务写成Generator函数,它们之间使⽤yield语句交换控制权。Generator的应⽤:异步操作的同步化表达;控制流管理;部署Iterator接⼝;作为数据结构。
16、第⼗七章,Generator函数的异步应⽤。异步编程对JS语⾔来说⾮常重要,由于JS语⾔的执⾏环境是“单线程”的,如果没有异步编程,根本⽆法使⽤,不然会造成卡死。ES6以前,异步编程的⽅法⼤概有4种:回调函数,事件监听,发布/订阅,Promise对象。Generator函数将JS异步编程带⼊了⼀个全新的阶段。所谓“异步”,简单来说就是⼀个任务不是连续完成的,可以理解成任务被⼈为分成两段,先执⾏第⼀段,然后转⽽执⾏其他任务,等做好了准备后再回过头执⾏第⼆段。相应地,连续执⾏的叫做同步。所谓回调函数(callback),就是把任务的第⼆段单独写在⼀个函数⾥⾯,等到重新执⾏这个任务时便直接调⽤这个函数。回调函数本⾝并没有问题,问题出在多个回调函数的嵌套上,代码不是纵向发展的,⽽是横向发展,这样的多个异步操作形成了强耦合,只要有⼀个操作需要修改,其上层和下层的回调函数就得修改(callback hell)。Promise对象就是为了解决这个问题⽽被提出的,这是⼀种新的写法,允许将回调函数的嵌套改写成链式调⽤。Promise的最⼤问题是代码冗余,原来的任务被Promise包装后,⽆论什么操作,⼀眼看去都是许多then的堆积,原来的语义变得很不清楚。传统的编程语⾔中早有异步编程的解决⽅案,其中⼀种叫做协程(coroutine),即多个线程相互协作,完
成异步任务。在Generator函数中的yield命令(相当于交出函数的执⾏权)就是异步两个阶段的分界线,⽤Generator函数写异步编程的最⼤优点:代码的写法⾮常像同步操作(如果去除yield命令,⼏乎⼀模⼀样)。Generator函数就是⼀个封装的异步任务(异步任务的容器)。异步操作需要暂停的地⽅都⽤yield语句注明,next⽅法的作⽤是分阶段执⾏Generator函数,每次调⽤next⽅法都会返回⼀个对象,表⽰当前阶段的信息(value属性和done属性)。Generator函数还有两个特性使其可以作为异步编程的完成解决⽅案:函数体内的数据交换(value,next(参数))和错误处理机制(try···catch···)。错误代码与处理错误的代码实现了时间和空间上的分离,这对于异步编程⽆疑是⼗分重要的。Generator函数将异步操作表⽰得很简洁,但是流程管理却不⽅便(即何时执⾏第⼀阶段、何时执⾏第⼆阶段)。Thunk函数是⾃动执⾏Generator函数的⼀种⽅法。参数的求值策略(函数的参数到底应该在何时求值),⼀种意见是“传值调⽤”(call by value),C语⾔就是采⽤这种策略;另⼀种意见是“传名调⽤”(call by name),Haskell语⾔采⽤这种策略。两者各有利弊。编译器的“传名调⽤”的实现往往是将参数放到⼀个临时函数之中,再将这个临时函数传⼊函数体。这个临时函数就称为Thunk函数,可以⽤来替代某个表达式。JavaScript语⾔是传值调⽤,他的Thunk函数含义有所不同。在JS中,Thunk函数替换的不是表达式,⽽是多参数函数,将其替换成⼀个只接受回调函数作为参数的单参数函数。任何函数只要参数有回调函数,就能写成Thunk函数的形式。⽣产环境中的转换器建议使⽤Thunkify模块(源码中设计了,回调函数只运⾏⼀次,这样⽅便与Generator函数结合使⽤)。在ES6中,Thunk函数可以⽤于Generator函数的⾃动流程管理。yield命令⽤于将程序的执⾏权移除Generato
r函数,可以使⽤Thunk函数,在其回调函数⾥⾯将执⾏权交还给Generator函数。Thunk函数的⾃动流程管理。⾃动执⾏的关键是,必须有⼀种机制⾃动控制Generator函数的流程,接收和交还程序的执⾏权。回调函数可以做到这⼀点(将异步操作包装成Thunk函数),Promise对象也可以做到(将异步操作包装成Promise对象)。co模块,使⽤co模块⽆序编写Generator函数的执⾏器,co函数返回⼀个Promise对象,因此可以⽤then⽅法(交回执⾏权)添加回调函数。co模块其实就是将两种⾃动执⾏器包装成⼀个模块,使⽤co的前提是Generator函数的yield命令只能是Thunk函数或Promise对象。co⽀持并发的异步操作,即允许某些操作同时进⾏,等到它们全部完成才进⾏下⼀步。实例:处理Stream。
17、第⼗⼋章,async函数。ES2017标准引⼊了async函数,使得异步操作变得更加⽅便,async就是Generator函数的语法糖(计算机语⾔中添加的某种语法,这种语法对语⾔的功能并没有影响,但是更⽅便程序员使⽤。通常来说使⽤语法糖能够增加程序的可读性,从⽽减少程序代码出错的机会)。async函数对Generator函数的改进体现在以下4点:内置执⾏器;更好的语义;更⼴的适⽤性;返回值是Promise对象。⽤法;语法:难点在于错误处理机制。async函数的实现原理就是将Generator函数和⾃动执⾏器包装在⼀个函数⾥。实例:按顺序完成异步操作。异步遍历器(提案):为异步操作提供原⽣的遍历器接⼝,即value和done两个属性都是异步产⽣的(之前是同步产⽣的)。异步遍历器与同步遍历器的最终⾏为是⼀致的,只是会先返回Promise对象,作为中介。for```await```of。异步遍历
器的设计⽬的之⼀,就是使Generator函数处理同步操作和异步操作时能够使⽤同⼀套接⼝。JS4种函数形式:普通函数,async函数,Generator函数和异步Generator函数。
18、第⼗九章,Class的基本语法。ES6引⼊了Class(类)这个概念作为对象的模板。通过class关键字可以定义类。基本上,ES6中的class可以看做只是⼀个语法糖,它的绝⼤部分功能,ES5都可以做到,class的写法只是让对象原型的写法更加清晰。注意类中的⽅法不需要加逗号分隔,加了可能会出错。ES6中的类完全可以看作构造函数的另⼀种写法(类的数据类型就是函数,类本⾝就指向构造函数)。类的所有⽅法都定义在类的prototype属性上。在类的实例上调⽤⽅法,其实就是调⽤原型上的⽅法。类的内部定义的所有⽅法都是不可枚举的(这点和ES5的⾏为不⼀致)。类和模块的内部默认使⽤严格模式,所以不需要使⽤use strict指定运⾏模式。考虑到未来所有的代码其实都是运⾏在模块之中,所以ES6实际上已经把整个语⾔都升级到了严格模式下。constructor⽅法是类的默认⽅法,通过new命令⽣成对象实例时⾃动调⽤该⽅法。⼀个类必须有constructor⽅法,如果没有显式定义,⼀个空的constructor⽅法会默认添加。constructor⽅法默认返回实例对象(即this),不过完全指定返回另⼀个对象。类必须使⽤new来调⽤,否则会报错。这是它跟普通构造函数的⼀个主要区别,后者不⽤new也可以执⾏。与ES5⼀样,实例的属性除⾮显式定义在其本⾝(即this对象)上,否则都是定义在原型(即class)上。与ES5⼀样,类的所有实例共享⼀个原型对象(所以最好不要⽤实例的_proto_属性为“类”添加⽅法,因为其会影响class的原始定义)。与函数⼀样,class也可以使⽤表达式的形式
定义(注意此时的类名字是被赋值的变量/常量的名)。采⽤Class表达式,可以写出⽴即执⾏的Class。类不存在变量提升(hoist),这⼀点与ES5完全不同(因为ES6不会把变量声明提升到代码头部,与继承规则有关,必须保证⼦类在⽗类之后定义)。私有⽅法是常见需求,但ES6不提供,只能通过变通的⽅法来模拟实现(命名下划线法;将私有⽅法移除模块法;⽤Symbol值的唯⼀性将私有⽅法的名字命名为⼀个Symbol值)。ES6也不⽀持私有属性,⽬前有⼀个提案为class加了私有属性(⽅法是在属性名之前,使⽤#来表⽰)。this的指向(要在构造⽅法中绑定this;使⽤箭头函数;使⽤Proxy)。name属性。与ES5⼀样,在“类”的内部可以使⽤get和set关键词对某个属性设置存值函数(setter)和取值函数(getter),拦截该属性的存取⾏为。如果在某个⽅法之前加上星号,就表⽰该⽅法是⼀个Generator函数。类相当于实例的原型,所有在类中定义的⽅法都会被实例继承。如果在⼀个⽅法前加上static关键字,就表⽰该⽅法不会被实例继承,⽽是直接通过类调⽤,称为“静态⽅法”。⽗类的静态⽅法可以被⼦类继承。Class的静态属性和实例属性。new.target属性。ES6为new命令引⼊了new.target属性,(在构造函数中)返回new命令所作⽤的构造函数。如果构造函数不是通过new命令调⽤的,那么new.target会返回undefined,因此这个属性可⽤于确定构造函数是怎么调⽤的。
19、第⼆⼗章,Class的继承。Class可以通过extends关键字实现继承,这⽐ES5通过修改原型链实现继承更加清晰和⽅便。⼦类必须在constructor⽅法汇中调⽤super⽅法,否则新建实例时会报错(因为⼦类没有⾃⼰的this对象,⽽是继承⽗类的this对象,然后对其进⾏加⼯,如果不调⽤super⽅法,⼦类
就得不到this对象)。在⼦类的构造函数中,只有调⽤super之后才能使⽤this关键字,否则会报错。PrototypeOf⽅法可以⽤来从⼦类上获取⽗类。super关键字可以当做函数使⽤(代表⽗类的构造函数),也可以当做对象使⽤(在普通⽅法中指向⽗类的原型对象;在静态⽅法中指向⽗类)。在⼤多数浏览器的ES5实现中,每⼀个对象都有_proto_属性,指向对应的构造函数的prototype属性。Class作为构造函数的语法糖,同时有prototype属性和_proto_属性,因此同时存在两条继承链。⼦类的_proto_属性表⽰构造函数的继承,总是指向⽗类。⼦类prototype属性的_proto_属性表⽰⽅法的继承,总是指向⽗类的prototype属性。extends关键字后⾯可以跟多种类型的值。⼦类的原型的原型是⽗类的原型。原⽣构造函数的继承。原⽣构造函数是指语⾔内置的构造函数,通常⽤来⽣成数据结构。ES的原⽣构造函数⼤致有:
Boolean(),Number(),String(),Array(),Date(),Function(),RegExp(),Error(),Object()。原⽣构造函数的this⽆法绑定,导致拿不到内部属性(ES5中,原⽣构造函数是⽆法继承的)。ES6允许继承原⽣构造函数定义⼦类,因为ES6先新建⽗类的实例对象this,然后再⽤⼦类的构造函数修饰this,使得⽗类的所有⾏为都可以继承。Mixin模式,其是指将多个类的接⼝“混⼊”(mix in)另⼀个类。
20、第⼆⼗⼀章,修饰器。修饰器(Decorator)是⼀个函数,⽤来修饰类的⾏为。ES2017引⼊了这项功能,⽬前Babel转码器已经⽀持。修饰器本质就是编译时执⾏的函数。修饰器不仅可以修饰类,还可以修饰类的属性。修饰器有注释的作⽤,此外还能⽤来进⾏类型检查(这将是JS代码静态分析的重
要⼯具)。如果同⼀个⽅法有多个修饰器,那么该⽅法会先从外到内进⼊修饰器,然后由内向外执⾏。修饰器不能⽤于函数,因为存在函数提升。core-decorators.js是⼀个第三⽅模块,提供了⼏个常见的修饰器,通过它可以
更好地理解修饰器,主要有@autobind,@readonly,@override,@deprecate,@suppressWarnings。使⽤修饰器实现⾃动发布事件。在修饰器的基础上可以实现Mixin模式,所谓Mixin模式,就是对象继承的⼀种替代⽅案,意为在⼀个对象中混⼊另外⼀个对象的⽅法。Trait也是⼀种修饰器,效果与Mixin类似,但是提供了更多功能,⽐如防⽌同名⽅法的冲突、排除混⼊某些⽅法、为混⼊的⽅法起别名等。⽬前,Babel转码器已经⽀持Decorator。
21、第⼆⼗⼆章,Module的语法。在ES6之前,社区指定了⼀些模块加载⽅案,最主要的有CommonJS和AMD两种。前者⽤于服务器,后者⽤于浏览器。ES6在语⾔规格的层⾯上实现了模块功能,完全可以取代现有的CommonJS和AMD规范,称为浏览器和服务器通⽤的模块解决⽅案。ES6模块的设计思想是尽量静态化(编译时加载),使得编译时就能确定模块的依赖关系,以及输⼊和输出的变量(CommonJS和AMD都只能在运⾏时确定这些东西)。ES6模块不是对象,⽽是通过export命令显式指定输出的代码,在通过import命令输⼊。ES6模块是编译时加载,使得静态分析成为可能,能进⼀步拓展JS的语法,⽐如引⼊宏macro和类型检验type system。ES6的模块⾃动采⽤严格模式,不管有没有在模块头部加上“use strict”。模块功能主要由两个命令构成:export和import。export命令⽤
于规定模块的对外接⼝,import命令⽤于输⼊其他模块提供的功能。⼀个模块就是⼀个独⽴⽂件,该⽂件的所有变量,外部⽆法获取,如果希望外部能获取模块内部的某个变量,就必须使⽤export关键字输出该变量。注意:export命令规定的是对外的接⼝,必须与模块内部的变量、函数、类建⽴⼀⼀对应关系。export语句输出的接⼝与其对应的值是动态绑定关系,即通过该接⼝可以取到模型内部实时的值(这点与CommonJS不同)。export必须处在模块顶层,不能是块级作⽤域内。import后⾯的from指定模块⽂件的位置,可以是相对路径或绝对路径。由于import是静态执⾏,所以不能使⽤表达式和变量,只有在运⾏时才能得到结构的语法结构。除了指定加载某个输出值,还可以使⽤整体加载(即星号*)来指定⼀个对象,所有输出值都加载在这个对象上。为了⽅便⽤户,使其不⽤阅读⽂档就能加载模块,可以使⽤export default命令为模块指定默认输出,⽽且⼀个模块只能有⼀个默认输出(注意此时的import命令后⾯不使⽤⼤括号)。如果想在⼀条import语句中同时输⼊默认⽅法和其他接⼝,可以写成:import _,{a1, a2 as a3} from XX。如果在⼀个模块之中先输⼊后输出同⼀个模块,import语句可以与export语句写在⼀起。模块之间也可以继承。跨模块常量。import是静态加载,导致⽆法取代Node的require动态加载功能(同步加载),因⽽有个提案建议引⼊import()函数,完成动态加载(返回promise对象,异步加载)。import()适⽤场合:按需加载,条件加载,动态的模块路径。
22、第⼆⼗三章,Module的加载实现。浏览器加载,在HTML⽹页中,浏览器通过<script>标签加载JS脚本。浏览器允许脚本异步加载,defer和async属性。浏览器加载ES6模块时也使⽤<script>标签并
且加⼊type=“module”属性(此时,都是异步加载)。ES6模块和CommonJS模块差异:CommonJS模块输出的是⼀个值的复制,ES6模块输出的是值的引⽤;CommonJS模块是运⾏时加载,ES6模块是编译时输出接⼝。ES6模块是动态引⽤,并且不会缓存值,模块⾥⾯的变量绑定其所在的模块。Node加载。⽬前的解决⽅案是,将两者分开,ES6模块和CommonJS采⽤各⾃的加载⽅案。在静态分析阶段,⼀个模块脚本只要有⼀⾏import或者export语句,Node就会认为该脚本是ES6模块,否则就为CommonJS模块。如果不输出任何借⼝,但是希望被Node认为是ES6模块,可以在脚本中加上export {};语句(表⽰不输出任何接⼝的ES6标准写法)。ES6模块中顶层的this指向undefined,CommonJS模块的顶层this指向当前模块。Node采⽤CommonJS模型格式,模块的输出都定义在ports属性上⾯。在Node环境中,import加载CommonJS模块,Node会⾃动将ports属性当做模块的默认输出。CommonJS模块的输出缓存机制在ES6加载⽅式下依然有效。采⽤require命令加载ES6模块
时,ES6模块的所有输出接⼝都会成为输⼊对象的属性。循环加载(circular dependency)指的是,a脚本的执⾏依赖b脚本,⽽b脚本的执⾏⼜依赖a脚本。对于JS语⾔来说,常见的两种模块加载CommonJS和ES6在处理循环加载是⽅法不⼀样的,结果也不⼀样。CommonJS的⼀个模块就是⼀个脚本⽂件,require命令第⼀次加载该脚本时就会执⾏整个脚本,然后在内存中⽣成⼀个对象。CommonJS模块的重要特性是加载时执⾏,即脚本代码在require的时候就会全部执⾏。⼀旦出现某个
模块被“循环加载”,就只输出已经执⾏的部分,还未执⾏的部分不会输出。ES6模块是动态引⽤,如果使⽤import从⼀个模块中加载变量,变量不会缓存,⽽是成为⼀个指向被加载模块的引⽤,需要开发者保证在真正取值的时候能够取到值。ES6模块转码除了可以使⽤Babel还可以⽤ES6 module transpiler将ES6模块转成CommonJS或者AMD模块;SystemJS。
第三阶段:扩展部分
1、第⼆⼗四章,编程风格。块级作⽤域:⽤let取代var;在let和const之间,建议优先使⽤const,尤其是在全局环境中,不应该设置变量,只应设置常量(优点:让阅读代码的⼈意识不应该修改这个值;防⽌⽆意间修改该值导致错误),所有的函数都应该设置为常量。长远来看,JS可能会有多线程的实现,这时let表⽰的变量只应出现在单线程运⾏的代码中,不能是多线程共享的,这样有利于保证线程安全。字符串:静态字符串⼀律使⽤单引号或反引号(⽤于动态字符串),不适⽤双引号。解构赋值:使⽤数组成员对变量赋值时,优先使⽤解构赋值;函数的参数如果是对象的成员,优先使⽤解构赋值;如果函数返回多个值,优先使⽤对象的解构赋值,⽽不是数组的,有利于以后添加返回值,以及更改返回值的顺序。对象:单⾏定义的对象,最后⼀个成员不以逗号结尾,多⾏定义的对象,最后⼀个成员以逗号结尾;对象尽量静态化,⼀旦定义,就不得随意添加新的属性。如果⾮要添加属性,要⽤Object.assign();对象的属性和⽅法尽量采⽤简洁表达式。数组:使⽤扩展运算符···复制数组;使⽤Array.from⽅法将类似数组的对象转为数组。函数:⽴即执⾏函数可以写成箭头函数的形式;
那些需要使⽤函数表达式的场合尽量使⽤箭头函数代替,表达简洁,⽽且绑定了this;箭头函数取代Function.prototype.bind,不应该⽤self/_this/that绑定this;简单的、单⾏的、不会复⽤的函数⽤箭头函数写,复杂多⾏的函数还是⽤传统⽅式写;所有配置项都应该集中在⼀个对象,放在最后⼀个参数,布尔值不可以直接作为参数;不要在函数体内使⽤arguments变量,使⽤rest运算符···代替;使⽤默认值语法设置函数参数的默认值。Map结构:只有模拟实体对象时才使⽤Object,如果只是需要key:value的数据结构,则使⽤Map,因为Map有内建的遍历机制。Class:总是⽤Class取代需要Prototype的操作;使⽤extends实现继承。模块:使⽤import取代require;使⽤export取代ports;不要同时使⽤export default和普通的export;不要在模块输⼊中使⽤通配符,因为这样可以确保模块中有⼀个默认输出;如果模块默认输出⼀个函数,函数名⾸字母⼩写,如果是对象,对象名⾸字母⼤写。ESLint是⼀个语法规则和代码风格的检查⼯具,可⽤于保证写出语法正确、风格统⼀的代码。
2、第⼆⼗五章,ECMAScript规格。规格⽂件是计算语⾔的官⽅标准,详细描述了语法规则和实现⽅法。⼀般情况下没有必要阅读规格,除⾮要写编译器。因为规格写得⾮常抽象和精炼,⼜缺乏实例,不容易理解。规格是解决问题的“最后⼀招”。这对javaScript语⾔很有必要,因为它的使⽤场景很复杂,语法规则不统⼀,各种运⾏环境的⾏为不⼀致,导致奇怪的语法问题层出不穷,查看规格不失为⼀种最可靠、最权威的终极⽅法。ES6规格⽂件⼀共有26章。对于⼀般⽤户⽽⾔,除了第4章,其他章
节都涉及某⼀⽅⾯的细节,不⽤通读,只要在⽤到时查阅相关章节即可。例如相等运算符,数组的空位,数组的map⽅法等。
3、第⼆⼗六章,ArrayBuffer。ArrayBuffer对象、TypedArray视图和DataView视图是JavaScript操作⼆进制数据的⼀个接⼝。这些对象早就存在,属于独⽴规格,ES6将其纳⼊ES规格并增加新的⽅法,它们都以数组的语法处理⼆进制数据,所以统称为⼆进制数组(并不是真正的数组,⽽是类似数组的对象)。⼆进制数组很像C语⾔的数组,允许开发者以数组下标的形式直接操作内存,⼤⼤增强了JS处理⼆进制数据的能⼒,使开发者有可能通过JS与操作系统的原⽣接⼝进⾏⼆进制通信。ArrayBuffer对象代表原始的⼆进制数据,TypedArray视图⽤于读写简单类型的⼆进制数据,DataView视图⽤于读写复杂类型的⼆进制数据。ArrayBuffer对象代表储存⼆进制数据的⼀段内存,它不能直接读写,只能通过视图读写,视图的作⽤是以指定格式解读⼆进制数据。ArrayBuffer也是⼀个构造函数,可分配⼀段可以存放数据的连续内存区域。TypedArray是⼀组构造函
数,DataView是⼀个构造函数。字节序指的是数值在内存中的表⽰⽅式。由于x86体系的计算机都采⽤⼩段字节序(little endian),相对重要的字节排在后⾯的内存地址,相对不重要的字节排在前⾯的内存地址。ArrayBuffer与字符串的互相转换。不同的视图类型所能容纳的数值范围是确定的,超出这个范围就会出现溢出(正向溢出,负向溢出)。ArrayBuffer的⼀些⽅法。由于视图的构造函数可以指定起始位置和长度,所以在同⼀段内存中可以依次存放不同类型的数据,这叫做“复合视图”。DataVie
w视图⽤于处理⽹络设备传来的数据,所以⼤端字节序或⼩端字节序可以⾃⾏设定。DataView视图本⾝也是构造函数,接受⼀个ArrayBuffer对象作为参数⽣成视图。⼆进制数组的应⽤:
AJAX,Canvas,WebSocket,Fetch API,File API,SharedArrayBuffer。Atomics对象,保证所有共享内存的操作都是“原⼦性”的,并且可以在所有线程内同步。
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论