ES6标准入门(阮一峰)-阅读记录与心得

我的最新博客在:Secret_wu's coding note

目标:学习ES6标准,并能灵活使用ES6标准的JavaScript

  (其免费电子书为:http://es6.ruanyifeng.com/#docs/string )

内容:(都是新的内容,需要结合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(),Object.getPrototypeOf(),Object.keys(),Object.values(),Object.entries()。对象的扩展运算符(...),可用于解构赋值(其复制也是浅复制)。Object.getOwnPropertyDescriptors()。Null传导运算符。

9,第十章,Symbol。ES6中引入了一种新的原始数据类型Symbol,表示独一无二的值。Symbol值通过Symbol函数生成,对象的属性名除了用字符串,也可以使用Symbol。Symbol非对象,其类似于字符串的数据类型。常量也可以使用Symbol值。Symbol适合消除魔术字符串,所谓魔术字符串是指在代码中多次出现,与代码形成强耦合的某一个具体的字符串或者数值。风格良好的代码,应该尽量消除魔术字符串。Object.getOwnProperySymbols方法可以获取指定对象的所有Symbol属性名,Reflect.ownKeys方法可以返回所有类型的键名,包括常规键名和Symbol键名。通常,利用Symbol特性为对象定义一些非私有但又希望只用于内部的方法。Symbol.for(),Symbol.keysFor()。模块的SingleTon模式,其指的是调用一个类并且在任何时候都返回同一个实例。内置的Symbol值,除了自定义使用的Symbol值,ES6提供了11个内置的Symbol值,指向语言内部使用的方法。Symbol.hasInstance,Symbol.isConcatSpreadable,Symbol.species,Symbol,match,Symbol.replace,Symbol.search,Symbol.split,Symbol.iterator,Symbol.toPrimitive,Symbol.toStringTag,Symbol.unscopables。

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拦截对for...in 循环不生效。construct()用于拦截new命令。deleteProperty方法用于拦截delete操作(若不可配置则会报错)。defineProperty方法拦截了Object.defineProperty操作。getOwnPropertyDesciptor方法拦截Object.getOwnPropertyDesctiptor(),返回一个属性描述对象(值、可读,枚举,配置)或者undefined。getPrototypeOf方法主要用来拦截获取对象原型。isExtensible方法拦截Object.isExtensible操作(该方法有一个强限制,它的返回值必须与目标对象的isExtensible属性保持一致,否则抛出错误)。ownKeys方法用来拦截对象自身属性的读取操作。preventExtensions方法拦截Object.preventExtensions(),返回一个布尔值(只有目标对象不可扩展时,才能返回true,否则报错)。setPrototypeOf方法用于拦截Object.setPrototypeOf方法。Proxy.revocable()返回一个可取消的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对象的方法是一一对应的。Reflect.get方法查找并返回target对象的name属性。Reflect.set方法设置target对象name属性等于value(会触发Proxy.defineProperty拦截)。Reflect.has方法对应name in obj 中的in运算符。Reflect.deleteProperty方法等同于delete obj[name],用于删除对象的属性。Reflect.construct方法等同于new target(),提供了一种不使用new来调用构造函数的方法。Reflect.getPrototypeOf方法用于读取对象的_proto_属性,对应Object.getPrototypeof(obj)。Reflect.setPrototypeOf方法用于读取对象的_proto_属性,对应Object.setPrototypeof(obj, newProto)。Reflect.apply方法等同于Function.prototype.apply.call(func, thisArg, args),用于绑定this对象后执行给定函数。Reflect.defineProperty方法基本等同于Object.defineProperty,用于为对象定义属性(作者提出,后者会逐渐被废除,请开始使用前者代替后者)。Reflect.getOwnPropertyDescriptor基本等同于Object.getOwnPropertyDescriptor,用于获取指定属性的描述对象(也是要使用前者)。Reflect.isExtensible方法等同于Object.isExtensible,返回一个布尔值,表示当前对象是否可扩展。Reflect.preventExtensions对应Object.preventExtensions方法,用于使一个对象变为不可扩展。Reflect.ownKeys方法用于返回对象的所有属性,基本等同于Object.getOwnPropertyNames与Object.getOwnPropertySymbols之和。实例:使用Proxy实现观察者模式(即使用Proxy编写observable和observe这两个函数),思路是,observable函数返回一个原始对象的Proxy代理,拦截赋值操作,触发充当观察者的各个函数。所谓观察者模式(Observer mode)指的是函数自动观察数据对象的模式,一旦对象有变化,函数就会自动执行。

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(),Promise.resolve(),Promise.reject()。ES6中的Promise的API提供的方法不多,可以自己部署一些有用的方法(例如本书部署了done()和finally())。应用有:加载图片;Generator函数与Promised的结合。实际开发中不知道或者不想区分函数f是同步还是异步,有两个办法:使用async函数和使用new Promise()。也有一个Promise.try()(提案)。

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命令;Generator.prototype.return();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命令用于将程序的执行权移除Generator函数,可以使用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关键字,否则会报错。Object.getPrototypeOf方法可以用来从子类上获取父类。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模型格式,模块的输出都定义在module.exports属性上面。在Node环境中,import加载CommonJS模块,Node会自动将module.exports属性当做模块的默认输出。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取代module.exports;不要同时使用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的一些方法。由于视图的构造函数可以指定起始位置和长度,所以在同一段内存中可以依次存放不同类型的数据,这叫做“复合视图”。DataView视图用于处理网络设备传来的数据,所以大端字节序或小端字节序可以自行设定。DataView视图本身也是构造函数,接受一个ArrayBuffer对象作为参数生成视图。二进制数组的应用:AJAX,Canvas,WebSocket,Fetch API,File API,SharedArrayBuffer。Atomics对象,保证所有共享内存的操作都是“原子性”的,并且可以在所有线程内同步。

原文地址:https://www.cnblogs.com/xinkuiwu/p/11551679.html