es6入门总结
let和const命令
let命令
- 循环体的let变量只对花括号作用域可见,花括号外不可见
- 循环体的语句部分是一个父作用域,而循环体内部是一个单独的子作用域
- let声明的变量不存在变量提升,未声明的使用会报错
- 只要块级作用域内存在let声明,它所声明的变量就绑定了这个区域,不再受外部的影响
- let不允许在相同的作用域重复声明同一个变量,子父级作用域可以同名变量声明
const命令
- const常量的值一旦声明就不得改变
- const一旦声明变量,就必须立即初始化,不能留到以后赋值
- const的作用域与let命令相同,只在声明所在的块级作用域内有效
- const命令同样存在暂时性死区,只能在声明的位置后面使用
- const声明的常量,也与let一样不可重复声明
- 对于复合类型的变量,变量名不指向数据,而是指向数据所在的地址,所以const命令只是保证变量名指向的地址不变,并不保证该地址的数据不变
——let和const命令的声明不再自动纳入global对象(window)
块级作用域与函数声明
- ES6的浏览器下,块级作用域内声明的函数会被提升至全局作用域或函数作用域顶部,作为
var fn = undefined
- 其他的游览器下,还是将块级作用域的函数声明当作let处理
- 应该避免在块级作用域内声明函数,如果确实需要也应该使用函数表达式而不是函数声明
- ES6的块级作用域允许声明函数,但只在使用大括号的情况下成立,如果没有使用大括号就会报错
变量的解构赋值
数组的解构赋值
- 只要某种数据结构具有Iterator接口,都可以采用数组形式的解构赋值
- 解构赋值允许指定默认值,但是如果一个数组成员不严格等于undefined,默认值不会生效
- 如果默认值是一个表达式,那么这个表达式是惰性求值的,即只有在用到的时候才会求值
- 默认值可以引用解构赋值的其他变量,但该变量必须已经声明
- 可以使用嵌套进行解构赋值
let [foo, [[bar], baz]] = [1, [[2], 3]];
对象的解构赋值
- 变量必须与属性同名,才能取到正确的值
let { foo, bar } = { foo: "aaa", bar: "bbb" };
- 如果变量名与属性名不一致,必须写成
let { foo: baz } = { foo: 'aaa', bar: 'bbb' };
- 声明后再进行的赋值必须加圆括号
let foo;——({foo} = {foo: 1});
- 对象的解构也可以指定默认值,同数组一样成员对应必须严格等于undefined,默认值才会生效
字符串的解构赋值
- 字符串进行解构赋值时,会被转换成一个类似数组的对象
const ### a, b, c, d, e = 'hello'
- 类似数组的对象都有一个length属性,因此还可以对这个属性解构赋值
let { length : len } = 'hello'
数值和布尔值的解构赋值
- 解构赋值的规则是,只要等号右边的值不是对象或数组,就先将其转为对象
let {toString: s} = 123;
s === Number.prototype.toString // true
- 由于undefined和null无法转为对象,所以对它们进行解构赋值,都会报错
let { prop: x } = undefined; // TypeError
函数参数的解构赋值
- 函数参数的解构赋值遵循基本解构类型的特点
圆括号问题
- 不能使用圆括号的情况:
- a.变量声明语句中,不能带有圆括号
- b.函数参数中,模式不能带有圆括号
- c.赋值语句中,不能将整个模式,或嵌套模式中的一层,放在圆括号之中
- 能使用圆括号的情况:
- a.赋值语句的非模式部分,可以使用圆括号
解构赋值的用途
- 交换变量的值
- 从函数返回多个值
- 函数参数的定义
- 提取JSON数据
- 函数参数的默认值
- 遍历Map结构
- 输入模块的指定方法
字符串的扩展
- codePointAt()——处理4个字节储存的字符,返回一个字符的码点(默认十进制,十六进制可以使用toString方法转换)
- String.fromCodePoint()——可以识别大于0xFFFF的字符,弥补了String.fromCharCode方法的不足
- ES6为字符串添加了遍历器接口for...of,除了遍历字符串,这个遍历器最大的优点是可以识别大于0xFFFF的码点
- at()提案——识别Unicode编号大于0xFFFF的字符,返回正确的字符
- normalize()——将字符的不同表示方法统一为同样的形式,这称为Unicode正规化
- 索引字符是否存在——接受二个参数,第一个表示索引字符,第二个表示起始位置
- includes()——返回布尔值,表示是否找到了参数字符串
- startsWith()——返回布尔值,表示参数字符串是否在源字符串的头部
- endsWith()——返回布尔值,表示参数字符串是否在源字符串的尾部
- repeat()——将原字符串重复n次,返回一个新字符串
- a.参数如果是小数,会被取整
- b.参数是负数或Infinity,会报错
- c.参数是0到-1之间的小数,则等同于0(这是因为会先进行取整运算)
- d.参数是字符串,则会先转换成数字
- 字符串补全长度——接受两个参数(第一个表示字符串补全的最小长度,第二个表示要参与补全的字符串)
- padStart()——用于头部补全
- padEnd()——用于尾部补全
- a.如果原字符串的长度,等于或大于指定的最小长度,则返回原字符串
- b.如果用来补全的字符串与原字符串,两者的长度之和超过了指定的最小长度,则会截去超出位数的补全字符串
- c.如果省略第二个参数,默认使用空格补全长度
- 用途:
- a.为数值补全指定位数
'1'.padStart(10, '0')
- b.日期字符串格式化
'12'.padStart(10, 'YYYY-MM-DD')
- a.为数值补全指定位数
- 模板字符串
- a.模板字符串是增强版的字符串,用反引号()标识
- b.可以当作普通字符串使用,也可以用来定义多行字符串,或者在字符串中嵌入变量
- c.空格,缩进和换行的状态都会被保持,除非使用.trim()去除
- d.模板字符串中嵌入变量,需要将变量名写在${}之中,{}相当于js执行域,可以放置变量,表达式和函数(可调用)等
- e.如果大括号中的值不是字符串,将按照一般的规则转为字符串;如果内部是一个字符串,将原样输出
- 标签模版
- a.模版字符串前跟函数名,该函数将会被调用来处理该模版字符串,这被称为"标签模版"功能
- b.函数处理模版字符串的时候,会将没有变量${}的部分拼合成数组参数,变量${}部分的结果作为后续参数
- c.函数处理模版字符串时,参数形式被转换为(数组,参数1,参数2..),其中的数组项有一个raw属性,与参数数组的项几乎相同,唯一的区别是字符串里面的斜杠都被转义了
- String.raw模版字符串
- a.用于处理模版字符串,返回一个斜杠都被转义的字符串(如果原字符串斜杠已经转义,String.raw不会做任何处理)
- b.也可以作为正常的函数使用,第一个参数应该是一个具有raw属性的对象,且raw属性的值应该是一个数组
- 模版字符串的限制
- a.默认会将字符串转义,因此导致了无法嵌入其他语言
- b.解决提案——遇到不合法的字符串转义,就返回undefined,而不是报错,并且从raw属性上面可以得到原始字符串
正则的扩展
数值的扩展
二进制和八进制表示法
- 二进制——0b(或0B)
- 八进制——0o(或0O)
新增api
- Number.isInteger()——是否为整数
- Number.EPSILON——极小常量,可以接受的误差范围
- Number.MAX_SAFE_INTEGER
- Number.MIN_SAFE_INTEGER——安全整数
- Number.isSafeInteger()——整数是否落在安全范围(超出精度的结果会被自动设为界点值,所以验证运算结果是否落在安全整数的范围内,不要只验证运算结果,而要同时验证参与运算的每个值)
function trusty (left, right, result) {
if (Number.isSafeInteger(left) &&
Number.isSafeInteger(right) &&
Number.isSafeInteger(result)) {
return result;
}
throw new RangeError('Operation cannot be trusted!');
}
Math对象的拓展
- Math.trunc()——去除一个数的小数部分
- Math.sign()——判断一个数是正数,负数,还是零
- Math.cbrt()——计算一个数的立方根
- Math.clz32()——一个数的32位无符号整数形式有多少个前导0
- Math.imul()——返回两个数以32位带符号整数形式相乘的结果(解决相乘数超过js精度的问题)
- Math.fround()——返回一个数的单精度浮点数形式(主要用于那些无法用64个二进制位精确表示的小数)
- Math.hypot()——返回所有参数的平方和的平方根
指数运算符
- 数值
**
数值=结果——2**3=8
函数的扩展
函数参数的默认值
- 参数变量是默认声明的,不能用let或const再次声明(可用var,两者独立)
- 使用参数默认值时,不能有同名参数
- 参数默认值是惰性求值的——如果参数默认值是变量,那么参数实际值只有运行时才能确定
- 参数默认值可为解构赋值形式
function({a}={}){}
函数的length属性
- 函数的length属性,不包括rest参数和参数默认值
函数默认值作用域
- 一旦设置了参数默认值,函数进行声明初始化时,参数会形成一个单独的作用域
- 参数默认值形成的作用域与函数体内部的作用域不属于同一个作用域,后者优先级大于前者
rest参数
- 获取函数的多余参数,在参数中展现为数组
- rest只能作为最后一个,它之后不能再有其他参数
扩展运算符
- 将一个数组转为用逗号分隔的参数序列——可用于伪数组(arguments,nodelist)
- 如果将扩展运算符用于数组的解构赋值,只能放在参数的最后一位,否则会报错
- 任何实现了Iterator接口的对象,都可以用扩展运算符转为真正的数组——### ...伪数组
- 扩展运算符在处理字符串(Iterator接口)时,除能将其转换为数组还能识别32位的Unicode字符
严格模式
- 函数参数使用了默认值、解构赋值、或者扩展运算符,那么函数内部就不能显式设定为严格模式,否则会报错
- 解决办法——在函数体外设置严格模式
name属性
- 函数表达式下,es5的name取值为空字符串,es6为变量名
- Function构造函数返回的函数实例,name属性的值为anonymous
- bind返回的函数,name属性值会加上bound前缀+函数名
- 用一个Symbol值的变量去定义方法,name属性返回Symbol值的描述
箭头函数
- 函数体内的this对象,就是定义生效时所在的对象,而不是使用时所在的对象
- 不可以当作构造函数的new命令,因为箭头函数内部没有this,只能获取到外层this
- 不可以使用arguments对象,该对象在函数体内不存在,可用rest参数代替
- 不可以使用yield命令,因此箭头函数不能用作Generator函数
- super、new.target在箭头函数中同样不存在,因为不存在内部this,所以用call()、apply()、bind()去改变函数this指向的操作无效
绑定this
- call、apply、bind的替代品——对象::函数,返回原对象
- 该运算符会自动将左边的对象,作为上下文环境(即this对象),绑定到右边的函数上面
尾调用优化
- 当尾调用函数内部不依赖于外层变量作用域的时候,函数执行时调用帧就只有一项,这将大大节省内存
- ES6的尾调用优化只在严格模式下开启,正常模式是无效的
尾递归优化
- 对于尾递归来说,由于只存在一个调用帧,所以永远不会发生“栈溢出”错误
- 尾递归的单次调用帧降低了算法复杂度,减轻了运算压力
- 尾递归应该是单项的,对于多项尾递归同时进行同样会增加调用帧,造成“栈溢出”
- 解决尾递归调用栈太多的办法是采用"循环"换掉"递归",在循环中每一步返回另一个函数并执行
函数参数的尾逗号
- 函数参数定义和调用时的参数书写都可以在参数尾部添加多于的逗号
数组的扩展
静态方法
- Array.from——用于将类似数组的对象和可遍历的对象(带iterable句柄)转换为真正的数组
- a.接受3个参数——Array.from(对象,过滤函数,上下文)
- b.任何有length属性的对象,都可以通过Array.from方法转为数组,扩展运算符无法转做到
- c.能正确处理各种Unicode字符,可以避免JavaScript将大于\uFFFF的Unicode字符,算作两个字符的bug
- Array.of——用于将一组值,转换为数组
- a.弥补用数组构造函数生成数组的差异性
- b.Array(3) ### '','',''与Array(3, 11, 8) ### 3, 11, 8
实例方法
- copyWithin()
在当前数组内部,将指定位置的成员复制到其他位置(覆盖该位置),返回当前数组(开始替换的位置,开始复制的位置,结束复制的位置) - fill()
填充和替换(值,起始位置,结束为止) - includes(值,位置)
是否包含给定值(不像indexOf方法一样采用全等于进行比较) - find(条件函数)
查找符合条件的值并返回 - findIndex(条件函数)
查找符合条件的值并返回位置 - entries()
键值对遍历器(iterator) - keys()
键名遍历器(iterator) - values()
键值遍历器(iterator)
数组的空位
- Array(2)——['','']
- es6的数组遍历方法都会跳过空位,map也会跳过,但是会保留空位
- join()和toString()会将空位视为undefined,而undefined和null会被处理成空字符串
- es6新增方法会默认将空位转换为undefined,for...of循环则会遍历空位
对象的拓展
属性的简洁表示法
- 允许在对象中直接写入变量,变量名作为键,变量值作为值
- 允许对象定义中的方法简写(省却:和function)
属性名表达式
- 允许在对象字面量定义中,[表达式]作为键的写法
- 如果属性名表达式的键是一个对象变量,那它会自动转换为"[object Object]"名
新增api
- Object.is(值1,值2)
是否严格相等 - Object.assign(target,source1,source2..)
对象浅拷贝- a.如果target传递undefind和null会报错
- b.除引用类型外,source只接受字符串,其余忽略
- c.属性名为Symbol值的属性也会被Object.assign拷贝
- d.对于嵌套形式的对象形式,source会覆盖整个键名对应的对象
- f.处理数组时会把数组视为对象,通过对应数组下标进行属性拷贝和覆盖
- e.无法正确拷贝get属性和set属性(undefined)
- Object.setPrototypeOf(obj,prototype)
为对象设置原型 - Object.getPrototypeOf(obj)
返回对象的原型 - Object.getOwnPropertySymbols()
返回Symbol属性键数组 - Object.keys()
返回键数组(可枚举) - Object.values()
返回值数组(可枚举) - Object.entries()
返回键值对数组 - Object.getOwnPropertyDescriptors()
返回指定对象所有自身属性的描述对象
对象的扩展运算符
- 在解构赋值下,"...obj"等于剩余对象集
- 在解构赋值下,"...obj"必须处于最后一位
- 在对象使用中,"...obj"将对象拆散成单个键值对放入{}(可用于浅拷贝和对象合并)
- 在对象使用中,如果出现同名键值对,后面会覆盖前面的(适用于对象的扩展运算符)
- 在对象使用中,如果扩展运算符代表的对象键值对中有get取值函数,这个函数会执行
Null传导运算符·提案
- 通过符号"?."简化逻辑与,简化对象判断
const firstName = (message&&message.body&&message.body.user&&message.body.user.firstName) || 'default'
const firstName = message?.body?.user?.firstName || 'default'
Symbol
使用注意
- 一种新的原始数据类型,表示独一无二的值
- 可以接受一个字符串参数,用于Symbol值的描述区分
Symbol("xjh")
- 不能用new操作符,也不能参与运算,类似于字符串
- 对象操作必须用中括号表示,用点运算符赋值无效——用Symbol值直接定义或赋值会转换为字符串键
- 如果Symbol()的参数是一个对象,就会调用该对象的toString方法将其转为字符串,然后才生成一个Symbol值
- Symbol值可以强制类型转换为字符串,布尔值,数组和对象,但是不能转换为数
相关api
- Symbol.for("name")
- a.如果存在登记为name的symbol值就取到,否则就创建
- b.如果存在登记为name的symbol值,重复的调用只会得到同一个symbol值
- c.Symbol.for创建的symbol值是全局的,iframe生成的可以在主页获取到
- Symbol.keyFor("name")
- a.返回已登记的Symbol类型值的key
- b.Symbol("name")创建的不属于登记返回,无法返回key
内置Symbol
- Symbol.hasInstance
- a.等同于instancsof,判断是否为该对象的实例时,会调用这个方法
- b.foo instanceof Foo在语言内部,实际调用的是Foo[Symbol.hasInstance(foo)]
- Symbol.isConcatSpreadable
- a.等于一个布尔值,表示该对象使用Array.prototype.concat()时,是否可以展开
- b.数组的默认行为是可以展开的,Symbol.isConcatSpreadable属性等于true或undefined,都有这个效果
- c.类似数组的对象也可以展开,但它的Symbol.isConcatSpreadable属性默认为false,必须手动打开
- Symbol.species
- a.指向当前对象的构造函数,创造实例时,默认会调用这个方法
- b.定义Symbol.species属性要采用get读取器,默认读取this
- Symbol.match
- a.返回一个执行正则的match函数
- b.当执行str.match(obj)时,如果obj中存在该属性,则在会调用它
- Symbol.replace
- a.返回一个执行替换的replace函数
- b.当执行str.replace(obj,"World")时,如果obj中存在该属性,则在会调用它
- Symbol.search
- a.返回一个执行查找的search函数
- b.当执行str.search(obj)时,如果obj中存在该属性,则在会调用它
- Symbol.split
- a.返回一个执行查找的search函数
- b.当执行str.split(obj)时,如果obj中存在该属性,则在会调用它
- Symbol.iterator
- a.指向当前对象默认的遍历器方法
- b.对象进行for...of循环时,会调用Symbol.iterator方法
- Symbol.toPrimitive
- a.返回将对象转为原始类型值的方法
- b.Symbol.toPrimitive被调用时,会接受一个字符串参数,表示当前运算的模式
- Number:该场合需要转成数值
- tring:该场合需要转成字符串
- Default:该场合可以转成数值,也可以转成字符串
- Symbol.toStringTag
- a.返回一个类型字符串表示的函数
- b.当执行Object.prototype.toString时,如果obj中存在该属性,则在会调用它
- Symbol.unscopables
- a.指向一个对象,指定了使用with关键字时,哪些属性会被with环境排除
- b.被它指定的属性和方法将在with作用域中被忽略
Set和Map数据结构
Set
- 一种类似于数组的新数据结构,成员的值都是唯一的,不存在重复
- Set的构造函数接受数组(或具有iterable接口的其他数据结构)作为参数
- Set的值是跟内存地址绑定的,只要内存地址不一样,就视为两个值
- Set的实例默认可遍历,它的默认遍历器生成函数就是values方法
- Set的遍历顺序就是插入顺序,keys方法和values方法的行为完全一致
- 实例属性和方法
- size 返回成员个数
- add() 添加某个值(返回Set实例)
- delete() 删除某个值(返回布尔值)
- has() 返回布尔值
- clear() 清除所有成员(没有返回值)
- keys() 返回键名遍历器
- values() 返回键值遍历器
- entries() 返回键值对遍历器
- forEach() 带回调函数的遍历方法
WeakSet
- WeakSet结构与Set类似,也是不重复的值的集合,没有size和length属性
- 构造函数的参数也只能接受数组或类似数组,但其成员必须为对象
- WeakSet中的对象都是弱引用,其指向的对象不计入垃圾回收机制
- WeakSet用于存储DOM节点时,如果节点从文档移除,会自动进行垃圾回收
- 实例属性和方法
- add() 添加对象(返回实例)
- delete() 删除某个值(返回布尔值)
- has() 返回布尔值
Map
- 一种类似于对象的新数据结构,但是键的范围不限于字符串,各种类型的值都可以当作键
- 构造函数接受数组(或具有iterable接口的其他数据结构)作为参数,数组项为代表键值项的数组(### "a",1)
- Map的键上是跟内存地址绑定的,只要内存地址不一样,就视为两个键
- 实例属性和方法
- size 返回成员个数
- set 添加键值对(返回Set实例)
- get 返回值(无则undefined)
- has() 返回布尔值
- delete() 删除某个值(返回布尔值)
- clear() 清除所有成员(没有返回值)
- keys() 返回键名的遍历器
- values() 返回键值的遍历器
- entries() 返回所有成员的遍历器
- forEach() 遍历Map的所有成员
WeakMap
- WeakMap结构与Map结构类似,也是用于生成键值对的集合,没有size和length属性
- WeakMap只接受对象作为键名(null除外),不接受其他类型的值作为键名
- WeakMap的键名都是弱引用,键名所指向的对象不计入垃圾回收机制
- WeakMap用于储存dom节点的临时数据时,如果节点从文档移除,会自动进行垃圾回收
- 实例属性和方法
- get() 得到对象
- set() 添加对象(返回实例)
- delete() 删除某个值(返回布尔值)
- has() 返回布尔值
Proxy
概述
- Proxy用于修改某些操作的默认行为,等同于在语言层面做出修改,属于一种“元编程”
- new Proxy(target, handler)接收2个参数,target代表目标对象,handler代表参数对象,用于定制行为
- 如果handler没有设置任何拦截,那就等同于直接指向原对象
- 如果一个属性不可配置和不可写,则该属性不能被代理,通过Proxy对象操作该属性会报错
实例方法
- get()
- set()
- apply()
- has()——对象是否具有某个属性
- a.对for...in无效
- construct()——针对new命令
- a.方法返回的必须是一个对象,否则会报错
- deleteProperty()——delete操作
- a.方法返回false,属性就无法被delete删除
- defineProperty()
- getOwnPropertyDescriptor()
- getPrototypeOf()
- a.getPrototypeOf方法的返回值必须是对象或者null,否则会报错
- isExtensible()——是否锁定[不可拓展属性]
- a.该方法只能返回布尔值,否则返回值会被自动转为布尔值
- b.proxy(拦截函数返回值)与target的Object.isExtensible()结果必须一致,否则报错
- ownKeys()——对象自身属性的读取操作
- a.主要拦截Object.keys(),Object.getOwnPropertyNames()和Object.getOwnPropertySymbols()函数
- b.ownKeys方法返回的数组成员,只能是字符串,否则会报错
- c.拦截Object.keys时,有三类属性会被ownKeys方法自动过滤——不存在的属性,不可遍历的属性和Symbol值属性
- d.如果目标对象是不可扩展的,ownKeys返回的数组之中必须包含原对象的所有属性,且不能包含多余的属性,否则报错
- preventExtensions()——锁定操作
- a.该方法只能返回布尔值,否则返回值会被自动转为布尔值
- b.只有目标对象不可扩展时,返回值才能为true,否则会报错
- setPrototypeOf()——设置原型属性
- a.该方法只能返回布尔值,否则返回值会被自动转为布尔值
- b.如果目标对象不可扩展,则setPrototypeOf方法不得改变目标对象的原型
静态方法
- Proxy.revocable()
- a.返回一个可取消的实例
-
b.执行实例的revoke方法后,proxy实例不可再访问,否则会报错
this问题
- 在Proxy代理的情况下,目标对象内部的this关键字会指向proxy实例
- 有些原生对象的内部属性,只有通过正确的this才能拿到,所以Proxy也无法代理这些原生对象的属性
Reflect
概述
- 将Object对象的一些明显属于语言内部的方法放到Reflect对象上
- 现阶段,某些方法同时在Object和Reflect对象上部署,未来的新方法将只部署在Reflect对象上
- 修改某些Object方法的返回结果,让其变得更合理
- Object.defineProperty在无法定义属性时,会抛出一个错误,而Reflect.defineProperty则会返回false
- 让之前是命令式的Object操作行为变成函数行为
"a" in obj,delet obj['a']
变成Reflect.has(obj, name),Reflect.deleteProperty(obj, name)
-
Reflect对象的方法与Proxy对象的方法一一对应,只要是Proxy对象的方法,就能在Reflect对象上找到对应的方法
静态方法
- get()
- set()
- a.如果Proxy对象和Reflect对象联合使用,通过proxy对Reflect传入context会触发proxy的defineProperty拦截
- has()
- deleteProperty()
- a.如果删除成功或者被删除的属性不存在,返回true;删除失败,返回false
- construct()
- a.等同于new target(...args),提供了一种不使用new,来调用构造函数的方法
- getPrototypeOf()
- a.如果参数不是对象,Object.getPrototypeOf会将这个参数转为对象,然后再运行,而Reflect.getPrototypeOf会报错
- setPrototypeOf()
- a.用于设置对象的__proto__属性,返回第一个参数对象
- b.如果第一个参数不是对象,Object.setPrototypeOf会返回这个参数,而Reflect.setPrototypeOf会报错
- **apply()
- defineProperty()
- getOwnPropertyDescriptor()
- a.如果第一个参数不是对象,Object.getOwnPropertyDescriptor会返回undefined,而Reflect.getOwnPropertyDescriptor会抛出错误
- isExtensible()
- a.对象是否可以拓展
- b.如果参数不是对象,Object.isExtensible会返回false,因为非对象本来就是不可扩展的,而Reflect.isExtensible会报错
- preventExtensions()
- a.设置对象为不可拓展
- b.如果参数不是对象,Object.preventExtensions在es5环境报错,在es6环境返回传入的参数,而Reflect.preventExtensions会报错
- ownKeys()
- a.返回对象的所有属性(可枚举和不可枚举,可读和不可读)
Promise对象
Promise含义
- Promise是一个可以获取异步操作消息的对象,它提供了统一的API,使得各种异步操作都可以用同样的方法进行处理
- Promise有三种状态Pending,Resolved和Rejected,只有异步操作的结果,可以决定当前是哪一种状态
- Promise对象的状态不受外界影响,一旦状态改变,就不会再变,任何时候得到的都是这个结果
- Promise实例之间进行传递的时候,被传递实例会等待传递实例的状态改变后才进行回调状态操作
- 优点:
- a.可以将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数
- b.统一的接口使得控制异步操作更加容易
- 缺点:
- a.无法取消Promise,一旦新建它就会立即执行,无法中途取消
- b.如果不设置回调函数,Promise内部抛出的错误不会反应到外部
- c.当处于Pending状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)
基本用法
var promise = new Promise(function(resolve, reject) {
if (true){
resolve(value);
} else {
reject(error);
}
});
promise.then(function(value) {
// success
}, function(error) {
// failure
});
Promise.prototype.then
- then方法会默认返回一个新Promise实例,因此可以进行链式操作
- then方法主动return的值会作为下一个then方法的参数
- then方法主动return的new Promise实例会被加入异步堆栈,只有其状态改变才会执行其链式的then回调
Promise.prototype.catch
- Promise.prototype.catch方法是.then(null,Rejected)的别名,用于指定发生错误时的回调函数
- 如果异步操作抛出错误,状态就会变为Rejected,就会调用catch方法指定的回调函数
- then方法指定的回调函数,如果运行中抛出错误,也会被catch方法捕获
- 在Promise构造函数回调中直接调用Rejected方法会触发catch方法
- catch方法返回的还是一个Promise对象,因此后面还可以接着调用then方法
- catch方法之中,还能再抛出错误,当还存在下一个catch的时候就会捕获并执行
Promise.all
- 用于将多个Promise实例,包装成一个新的Promise实例
- Promise.all方法接受一个数组作为参数
- a.如果数组由Promise实例组成,则会等待其中的Promise都完成时才会触发Promise.all实例的状态变化
- b.如果数组不由Promise实例组成,就会直接调用Promise.resolve方法,将参数转为Promise实例,再进一步处理
- 只有p1、p2、p3的状态都完成,组合p的状态才会完成
- 只要p1、p2、p3之中有一个被rejected,组合p的状态就变成rejected(此时第一个被reject的实例返回值会传递给p的回调函数)
Promise.race
- 同上
- 只要有一个Promise参数实例完成,就会调用Promise.race实例的状态变化,将率先完成的子Promise参数传递给Promise.race回调
Promise.resolve
- 将现有对象转为Promise对象
- a.当参数为Promise对象时,会原封不动返回该对象
- b.当参数为带"then"键名方法的对象时,会将这个对象转为Promise对象,然后就立即执行该对象的then方法
- c.当参数为非带"then"键名方法的对象时,Promise.resolve方法返回一个新的Promise对象,状态为Resolved
- d.不带参数时,Promise.resolve方法直接返回一个新的Promise对象,状态为Resolved
Promise.reject
- 返回一个新的Promise实例,状态为rejected,参数为错误信息
- Promise.reject()方法的参数,会原封不动地作为reject或catch的回调参数
Promise.try提案
- 对于那种可能是同步可能是异步的返回操作提供统一的处理方式,动态执行对应的同步/异步状态
- database.users.get({id: userId})有可能报同步错误,有可能报异步错误
Promise.try(database.users.get({id: userId})).then(...).catch(...)
Iterator和for...of循环
Iterator的作用
- 为各种数据结构,提供一个统一的、简便的访问接口(for...of)
- 使得数据结构的成员能够按某种次序排列
- 当使用for...of循环遍历某种数据结构时,该循环会自动去寻找Iterator接口
默认Iterator接口
- 部署了Symbol.iterator属性的数据结构,就称为部署了遍历器接口
- 原生具备Iterator接口的数据结构有:Array,Map,Set,String,TypedArray和函数的arguments对象
调用场合
- 解构赋值,扩展运算符,yield*,for..of
遍历器对象的return和throw方法
- return方法
- 调用场景——如果for...of循环提前退出(通常是因为出错,或者有break语句或continue语句)
- 部署场景——如果一个对象在完成遍历前,需要清理或释放资源,就可以部署return方法
- throw方法
- 主要是配合Generator函数使用,一般的遍历器对象用不到这个方法
for...of循环
- 一个数据结构只要部署了Symbol.iterator属性,就被视为具有iterator接口,就可以用for...of循环遍历它的成员
- for...of循环内部调用的就是数据结构的Symbol.iterator方法
- 拥有iterator接口的数据结构——字符串,数组,类数组(arguments和DOM NodeList),Generator对象
- for...of更常用于数组循环,for...in更常用于对象循环
Generator函数的语法
基本概念
- 语法上,function关键字与函数名之间有一个星号*,函数体内部使用yield表达式
- Generator属于普通函数,调用不会立即执行,而是返回一个遍历器对象,需要调用next()才能执行yield状态
- Generator函数就是遍历器生成函数,因此可以把Generator赋值给对象的Symbol.iterator属性,从而使得该对象具有Iterator接口,可以被for...of循环和扩展运算符转换
yield表达式
- yield表达式如果用在一个表达式中,必须放在圆括号里面;如果用作函数参数或放在赋值表达式右边,可以不加括号
- yield表达式本身没有返回值,总是返回undefined;
- next方法可以带一个参数,该参数就会被当作上一个yield表达式的返回值
- 由于next方法的参数表示上一个yield表达式的返回值,所以在第一次使用next方法时,传递参数是无效的
Generator.prototype.throw()
- Generator函数返回的遍历器对象,都有一个throw方法,可以在函数体外抛出错误,然后在Generator函数体内捕获
- throw方法可以接受一个参数,该参数会被catch语句接收,建议抛出Error对象的实例
- 一旦Generator执行过程中抛出错误,且没有被内部捕获,就不会再执行下去了,Generator函数默认结束
Generator.prototype.return()
- 调用return方法后会终结Generator函数,返回值的value属性就是return方法的参数,没有即为undefined
- 如果Generator函数内部有try...finally代码块,那么return方法会推迟到finally代码块执行完再执行
yield* 表达式
- yield* obj,如果obj是遍历器对象,将会遍历该对象的yield,增加步长
- 任何数据结构只要有Iterator接口,就可以被yield*遍历
Generator函数的异步应用
- Generator函数将异步操作表示得很简洁,但是流程管理却不方便
- 解决方案
- Thunk函数
- a.js版本的Thunk函数方案是将多参数函数转换为单参数函数版本
- b.可引入node模块,也可以自己书写,用于管理Generator函数流程
- co模块
- a.js版本的co函数方案是对promise的包装
- Thunk函数
async函数
- Generator函数的语法糖
- 改进特点
- a.内置执行器 ——自动执行完,无需写thunk和co自执行方案
- b.更好的语义
- c.更广的适用性 ——异步等待执行,同步直接执行
- d.返回promise ——可用then方法指定下一步操作
- 基本语法
- a.async函数返回一个Promise对象,可以使用then方法添加回调函数
- b.async函数的return值会成为返回的Promise对象的值,then方法的参数
- c.遇到await就会等待异步/同步操作完成,然后接着执行函数体
- d.await命令后是一个Promise对象,如果不是则会被转成一个立即resolve的Promise对象
- 错误处理
- a.async函数内抛出错误,会导致返回的promise对象变为reject状态
- b.只要一个await语句后面的Promise变为reject,那么整个async函数都会中断执行,错误信息会传入catch方法
- c.如果异步操作失败,却不希望中断后续异步操作,方法有:
- 1).使用try...catch语句,将await放入try,catch捕捉后会继续执行后续代码
- 2).对await后的promise对象增添catch方法进行错误捕捉,然后程序会继续执行后续代码
- 异步遍历器(提案)
- a.异步遍历器的最大的语法特点就是,用遍历器的next方法,能返回一个Promise对象
- b.对象的异步遍历器接口,部署在Symbol.asyncIterator属性上面
- c.next方法可以连续调用,不必等到上一步Promise对象resolve以后再调用;next方法会累积起来,自动按照每一步的顺序运行下去
- for await...of(提案)
- a.for await...of循环的一个用途,是部署了asyncIterable操作的异步接口,可以直接放入这个循环
- b.for...of自动调用遍历器的next方法,得到一个Promise对象;await用来处理这个Promise对象,一旦resolve,就把得到的值传入循环体
- c.for await...of循环也可以用于同步遍历器
- 异步Generator函数(提案)
- a.async函数与Generator函数的结合,await后面的操作会返回Promise对象
- b.普通的async函数返回的是一个Promise对象,而异步Generator函数返回的是一个异步Iterator对象,通过调用next方法来返回可操作的Promise对象
- c.yield *后面同样可以继续跟异步Generator函数
Class
Class基本语法
- 类的数据类型就是函数,类本身指向构造函数
- 类内部所有定义的方法都是不可枚举的
- 类本身和内部的属性方法可以采用变量来声明和表示
- 不使用new的类调用会报错
- 当constructor未被显示添加,空的constructor会被默认添加
- class声明不存在变量提升
- 采用class表达式,可以写出立即执行的class
- 类和模块的内部,默认就是严格模式
- class的get和set函数也定义在原型上
Class的静态方法
- 父类的静态方法,可以被子类继承——子类调用父类静态方法
- 子类也可以通过super,在静态方法中调用父类的静态方法
Class的静态属性和实例属性
[es6用法]
// 实例属性
class MyClass {
constructor() {
this.myProp=42;
}
}
// 静态属性
MyClass.属性=值;
[es7提案]
// 实例属性——实例可以取到
class MyClass {
myProp=42;
constructor() {
console.log(this.myProp); // 42
}
}
// 静态属性
class MyClass {
static myProp=42;
constructor() {
console.log(MyClass.myProp); // 42
}
}
class的私有属性
- es7提案
- a.私有属性用#表示,也用于表示私有方法,在类的外包无法访问
- b.私有属性可以指定初始值,在构造函数执行时进行初始化
class Foo { #a; #b; #c=0; #sum() { return #a + #b; } printSum() { console.log(#sum()); } constructor(a, b) { #a = a; #b = b; } }
new.target属性
- 返回new命令作用的那个构造函数,如果是class内部调用则返回当前class
- new.target只适用于构造函数或class内部的constructor方法
- 如果构造函数不是通过new命令调用的,则new.target会返回undefined
- 可以用来确定构造函数是怎么调用的,也可以用其做不可实例化的抽象类
Class的继承
Class继承
- 基本用法
- 1.存在继承关系后,constructor内必须执行super()操作,否则会报无this的错误
- 2.子类实例的构建是基于对父类实例加工,super()相当于对子类进行父类.call(this)
- 3.super返回父类实例后,植入子类原型属性和constructor,然后再接入到子类原型上
- super关键字
- 1.super用作函数时,必须用在constructor之内,否则会报错
- 2.super用作对象时,在普通方法中指向父类原型对象,在静态方法中指向父类
- 3.通过super调用父类的方法时,相当于父级原型调用该方法,但是super会绑定子类的this
- 4.通过super对某个属性赋值时,因为super绑定了子类的this,因而会赋值到子类属性上,但是调用时依然会在父级原型查找
- 5.super并不是动态绑定的,而是在声明时“静态”绑定的
- 原生构造函数的继承
- 1.es5之前原生构造函数无法用this去绑定,导致拿不到其内部实例属性,无法实现真正继承
- 2.es6通过extends继承可以自定义原生数据结构,实现子类的真正继承和拓展能力
3.super传参对Object原生类型无效,es6规定Object构造函数会忽略参数
Decorator
基本语法
- 书写上,置于要修改的类和方法之上
- 只能用于类和类的方法,不能用于函数,因为存在函数提升
- 不管是修饰类还是修饰方法,都支持多个修饰器
- 修饰器对行为的改变,发生在编译器,而不是运行时,其本质是编译时执行函数
类的修饰
- 当用于修饰类的时候,它的第一个参数代表所要修饰的目标类
方法的修饰
- 修饰器不仅可以修饰类,还可以修饰类的方法
- 修饰方法的时候,接受三个参数(target, name, descriptor)
- 当多个修饰器一起用时,遵循先从外到内进入,然后由内向外执行
Module的语法
export命令
- export命令规定的是对外的接口,因此必须与模块内部的变量建立一一对应关系
export { 变量名 }
- export语句输出的接口,与其对应的值是动态绑定关系,即通过该接口,可以取到模块内部实时的值一一定时器动态改变值的情况
- export命令可以出现在模块的任何位置,只要处于模块顶层就可以,如果处于块级作用域内就会报错一一import命令同样如此
import命令
- import命令具有提升效果,会提升到整个模块的头部首先执行,因为import命令是属于编译阶段执行
- 由于import是静态执行,所以不能使用表达式和变量这些只有在运行时才能得到结果的语法结构
- import语句会执行所加载的模块,因此可以有如下的写法
import 'a';
import '1';
- 多次重复执行同一句import语句,那么只会执行一次,而不会执行多次
import { foo } from 'my_module';
import { bar } from 'my_module';
模块的整体加载
import * as 模块名 from './文件名';
export default命令
- 为模块指定默认输出时,import命令可以为模块指定任意名字,且不需要用{}括号包起来
- 模块内部的声明函数在外部是无效的,加载的时候视同为匿名函数进行加载
- 一个模块只能有一个默认输出
- export default本质上就是一个叫做default的变量或方法,因此可以用as语句进行改名
var a = 1; export default a;
——将变量a的值赋给变量default,因此export default 1
也是可以的- 同时输入默认方法和其他变量
import abc,{ each } from 'lodash'
export与import的复合写法
如果在一个模块之中,先输入后输出同一个模块,import语句可以与export语句写在一起
- 写法1·默认用法
export { foo, bar } from 'my_module';
//等同于
import { foo, bar } from 'my_module';
export { foo, bar };
- 写法2·整体输出
export * from 'my_module'; ——会忽略my_module模块的default
- 写法3·默认接口
export { default } from 'foo';
- 写法4·接口改名
export { foo as myFoo } from 'my_module';
- 写法5·具名接口改为默认接口
export { es6 as default } from './someModule';
- 写法6·默认接口改为具名接口
export { default as es6 } from './someModule';
import()提案
- 属于运行时执行的动态加载,区别于import的静态加载
- import()函数可以用在任何地方,不仅仅是模块,非模块的脚本也可以使用
- import()函数与所加载的模块没有静态连接关系,这点也是与import语句不相同
- import()类似于Node的require方法,区别主要是前者是异步加载,后者是同步加载
- import()返回一个Promise对象,并允许模块路径动态生成——import(f()).then(...)
Module的加载实现
游览器加载
- script标签中defer和async的区别——defer是渲染完再执行,async是下载完就执行——即不能保证执行顺序
- 浏览器加载ES6模块也使用
<script>
标签,但要加入type="module"属性,效果等同于defer- a.代码运行在模块作用域,顶层变量对外不可见
- b.默认采用严格模式,不管有无"use strict"
- c.模块之中,import和export指令对应模块时,.js后缀不能省略
- d.模块顶层this为undefined
es6模块与commonjs模块的差异
- commonjs模块输出的是一个值的拷贝,es6模块输出的是值的引用
- commonjs模块是运行时加载,es6模块是编译时输出接口
- commonjs顶层this指向当前模块,es6顶层this指向undefined
- es6模块是动态引用,不会缓存值,模块里面的变量绑定其所在的模块,意味着可以获取模块的动态变化
- es6输入的模块变量,只是一个“符号连接”,属于只读的,对它进行重新赋值会报错
- export通过接口输出的是同一个值,因此不同的脚本加载这个接口,得到的都是同样的实例
node加载
- node中采用两套方案进行加载,es6模块和commonjs采用各自的加载方案
- 如果不输出任何接口,但希望被node认为是es6模块,可以在脚本中写"export {}"
import加载commonjs模块
- import加载commonjs模块,node会自动将module.exports属性当作模块的默认输出,即等同于export default
- import加载commonjs模块时,commonjs模块的输出缓存机制依然有效,被引入模块内部的变化不会更新到引入模块
- import {readfile} from 'fs'报错
原因——fs是commonjs格式,只有在运行时才能确定readfile接口,而import命令要求编译时就确定这个接口
解决办法——改为整体输入
require加载es6模块
- 采用require命令加载es6模块时,es6模块的所有输出接口会成为输入对象的属性
- require加载es6模块依然存在缓存机制,被引入模块内部的变化不会更新到引入模块
循环加载·commonjs
- commonjs的重要特性就是加载时执行,即脚本代码在require的时候就会执行,然后在内存生成一个对象
- commonjs模块无论加载多少次,都只会在第一次加载时运行一次,以后再执行加载,都只会到缓存中取值,返回第一次运行结果
循环加载·es6
- es6模块是动态引用,如果使用import从一个模块加载变量(即import foo from 'foo'),那些变量不会被缓存,而是成为一个
指向被加载模块的引用,意味着可以取到值得变化