Skip to content

ES6标准入门(第三版)

😀对《ES6标准入门(第三版)》一书的笔记总结

第1章 ECMAScript 6简介(略)

第2章 let和const命令

ES6 中声明变量的6种方法:varfunctionletconstimportclass

let

  • 声明的变量只在其代码块中有效

  • 不存在变量提升

  • 暂时性死区:只要块级作用域内存在let命令,它所声明的变量就绑定这个区域,不再受外部的影响。在代码块中,使用let命令声明变量之前,该变量都是不可用的,这在语法上称为暂时性死区

  • 不允许在相同作用域中重复声明同一个变量

块级作用域

  • ES5没有块级作用域不合理的场景:

    • 内层变量可能会覆盖外层变量

    • 用来计数的循环变量泄露为全局变量

  • ES6中的 let 实际上为 javascript 新增了块级作用域,块级作用的出现使得立即执行匿名函数不再必要

const

  • 声明一个只读的常量,一旦声明,常量的值就不能改变,只声明不赋值会报错

  • const的本质:实际上保证的并不是变量的值不可改动,而是变量指向的那个内存地址不得改动,对于简单数据类型而言,值就保存在变量指向的那个内存地址不得改动,而对于复杂类型而言,变量指向的内存地址保存的只是一个指针,const只能保证这个指针是固定的,至于它指向的数据结构是不是可变的则不能控制,例如可以为const声明的常量添加属性,但不能将其直接改为其他对象

第3章 变量的解构赋值

  • 数组的解构赋值(按次序):

    • 若解构不成功,则变量的值等于 undefined

    • 若等号右边的不是数组(严格来说是不可遍历结构),则会报错

    • 只要某种数据结构具有 Iterator 接口,则都可以采用数组形式的解构赋值

    • 允许指定默认值

  • 对象的解构赋值(按同名属性名):

    • 数组的解构是与数组中元素的顺序保持一致的,但对象不同,对象解构的变量名必须与属性同名才能取得值

      let { foo, bar } = { foo: 'aaa', bar: 'bbb' }
      foo // 'aaa'
      bar // 'bbb'
      
      // 实际上是(上面是应用了对象的属性简写)
      let { foo: foo, bar: bar } = { foo: 'aaa', bar: 'bbb' }
      foo // 'aaa'
      bar // 'bbb'
      
      // 若变量名和属性名不一致,则
      let { foo: a, bar: b }  = { foo: 'aaa', bar: 'bbb' }
      a // 'aaa'
      b // 'bbb'
      foo // error: foo is not defined
      bar // error: bar is not defined
      

      对象的解构赋值的内部机制是先找到同名属性,然后再赋值给对应的变量(上述代码中的a、b),真正被赋值的是后者,而不是前者

    • 允许指定默认值

  • 字符串的解构赋值

  • 数值和布尔值的解构赋值:若等号右边是数值或者布尔值,则会先转为对象

  • 函数参数的解构赋值

第4章 字符串的扩展

  • Unicode 相关:codePonitAt()codePointAt()String.fromCodePonit()at()normalize()

  • 字符串实现遍历器接口:可使用 for...of 循环遍历

  • 判断一个字符串是否包含在另一个字符串中:

    • includes() :返布尔值,表示是否找到了参数字符串

    • startsWith() :返布尔值,表示参数字符串是否在源字符串的头部

    • endsWith() :返布尔值,表示参数字符串是否在源字符串的尾部

  • repeat() :返新字符串,表示将原字符串重复n次,但参数不同时表现不同,参数为:

    • 正整数:最佳实践

    • 0:返空字符串

    • 字符串:会先转换为数字

    • 小数:向下取整

    • 负数或无穷:报错

    • 0到-1:等用于取0,返空字符串

  • 字符串补全:

    • padStart() :头部补全

    • padEnd():尾部补全

    • 均接受两个参数,第一个参数用来指定字符串的最小长度,第二个参数则是用来补全的字符串

  • 模板字符串(``):常用于拼接,反斜杠 \ 转义,${}<%= ... %>放置js代码

第5章 正则的扩展(略)

  • 字符串中可使用正则表达式的方法:

    • match() === RegExpexec() 方法 => 返回匹配项的数组,无匹配项返回 null

    • replace()

    • search() => 返回第一个匹配项的索引,没找到返回-1

    • split()

  • RegExp的方法

    • exec()

    • test() => 返布尔值

第6章 数值的扩展

Number方法的扩展

  • Number.isFinite() :判断一个数值是否是有限的,返布尔值

  • Number.isNaN() :判断一个数值是否是NaN,返布尔值

这两个方法与传统的全局方法 isFinite()isNaN() 的差别在于,传统方法先调用 Number() 将非数值转化为数值,再进行判断,而方法只对数值,对于非数值一律返回 false

  • Number.parseInt() 、Number.parseFloat() :将全局方法 parseInt()parseFloat() 移植到Number对象上,行为不变

  • Number.isInteger() :判断一个值是否为整数,返布尔值,对25.0返回true,对非数值一律返回false

  • Number.EPSILON :极小的常量,本质是一个可以接受的误差范围

  • Number.MAX_SAFE_INTEGERNumber.MIN_SAFE_INTEGER 表示安全数值的上下限

  • Number.isSafeInteger() 判断一个整数是否落在安全范围内,返布尔值,对 25.0 返回 true ,对非数值一律返回 false

Math 对象的扩展

  • Math.trunc() :去除小数部分取整,对于非数值,先用 Number() 转为数值,对于空值和无法截取整数的值,返回 NaN

  • Math.sign() :判断一个数值是正数、负数还是0,对于非数值,会先用 Number() 转为数值,返回值有5种情况:

    • 参数为正数,返回+1

    • 参数为负数,返回-1

    • 参数为0,返回0

    • 参数为-0,返回-0

    • 其他值,返回NaN

  • Math.cbrt() :用于计算一个数的立方根,对于非数值,会先用Number()转为数值

第7章 函数的扩展

  • 设置函数参数的默认值,和解构赋值默认值结合使用

  • 当设置了参数默认值时,传入 undefined 会触发默认值,传入 null 则不会触发默认值

  • 当设置了参数默认值时,函数的 length 属性等于全部参数的数量减去设置了默认值参数的数量,不包含 rest 多余参数

  • 函数参数的作用域:当设置了参数默认值时,参数会形成一个单独的作用域,等到初始化结束,这个作用域会消失,这种语法行为在不设置参数默认值时是不会出现的

  • 使用 rest 参数(形式为"...变量名"),用于获取函数的多余参数,后面不能再其他参数,搭配的变量是一个数组,可替代内部的 arguments 对象,如:

fucntion add(...values) {
  let sum = 0
  for(var val of values){
    sum += val
  }
  return sum
}
add(1,2,3) // 6
  • 箭头函数注意事项

    • 函数体内的 this 对象就是定义时所在的对象,而不是使用时所在的对象

    • 不可以当做构造函数,不能使用new命令

    • 不可以使用 arguments 对象,如果要用可用rest参数代替

    • 不可以使用 yield 命令,不能用做 Generator 函数

  • 尾调用、尾递归及其优化(略)

第8章 数组的扩展

扩展运算符...

将一个数组转化为用逗号分隔的参数序列

  • 替代数组的apply方法,如
// ES5
Math.max.apply(null,[1,2,3])
// ES6
Math.max(...[1,2,3])

// ES5
var arr1 = [0,1,2]
var arr2 = [3,4,5]
Array.propotype.push.apply(arr1,arr2)
// ES6
var arr1 = [0,1,2]
var arr2 = [3,4,5]
arr1.push(...arr2)
  • 合并数组
// ES5
[1,2].concat(more)
// ES6
[1,2,...more]

// ES5
arr1.concat(arr2,arr3)
// ES6
[...arr1,...arr2,...arr3]
  • 与解构赋值结合,如
// ES5
var a = list[0]
var rest = list.slice(1)
// ES6
[a,...rest] = list

用于数组赋值时候,只能置于参数的最后一位

  • 任何实现了 Iterator 接口的对象都可以用扩展运算符转为真正的数组,扩展运算符内部调用的就是数据结构的Iterator接口

Array.from()

定义:将类数组对象(常见有NodeListarguments)和可遍历对象(部署了 Iterator 接口)转为真正的对象

// 类数组
var arrayLike = {
    '0': 'a',
    '1': 'b',
    '2': 'c',
    length: 3
}
// ES5
var arr = [].slice().call(arrayLike) // ['a','b','c']
// ES6
var arr = Array.from(arrayLike) // ['a','b','c']

任何具有 length 属性的对象及部署了 Iterator 接口的对象,都可以通过 Array.from() 转为真正的数组

接受第2个参数,用来对每个元素进行处理,将处理后的值放入返回的数组

Array.of()

定义:将一组值转为数组,弥补 Array() 因参数个数不同导致的行为差异

数组实例的方法

copyWithin()

find()

用于找到第一个符合条件的数组成员,若无返回 undefined

findIndex()

用于找到第一个符合条件的数组成员的索引,无则返回 -1

find()findIndex()第1个参数均为回调函数,回调函数中的3个参数分别为当前的值、当前的索引、原数组;第2个参数均为用来绑定回调函数的 this 对象

findIndex() 配合 Object.is() 方法可弥补 indexOf() 不能发现NaN的不足

[NaN].indexof(NaN) // -1
[NaN].findIndex(y => Object.is(NaN,y)) // 0

fill()

使用给定值填充一个数组

entries()、keys()、values()

均返回一个遍历器对象,可用 for...of 循环遍历,其中 entries() 返回的是单项是数组形式

  let arr = ['a','b']
  for(let index of arr.keys()){
      console.log(index)
  }
  // 0
  // 1
  for(let value of arrr.values()){
      console.log(value)
  }
  // 'a'
  // 'b'
  for(let [index,value] of arr.entries()){
      console.log(index,value)
  }
  // 0 'a'
  // 1 'b'
  for(let item of arr.entries()){
      console.log(item)
  }
  // [0,'a']
  // [1,'b']

includes()

表示数组是否包含给定的值,返布尔值,相比于 indexOf() 更直观,解决了 indexOf()NaN的误判

Maphas()

用来查找键名

Sethas()

用来查找键值

第9章 对象的扩展

  • 属性的简写:只写属性名,不写属性值,此时属性值等于属性名所代表的的变量

  • 属性名可使用表达式:以方括号 [] 表示的属性名中可使用表达式

Object.is()

比较两值是否相等,可看做时 === 的增强版

  • == 会自动转换类型

  • === 比较 +0-0 时返回 true ,比较 NaN 时会返回 false

    +0 === -0 // true
    NaN === NaN // false
    
    Object.is(+0-0) // false
    Object.is(NaN,NaN) // true
    

Obejct.assign(target,source1,source2)

用于将源对象( source )的所有可枚举属性复制到目标对象( target )上,同名属性时后面属性会覆盖前面属性,若属性值为对象,则仅复制其引用值(浅拷贝)

用途:为对象添加属性、方法,克隆对象、合并多个对象、为属性指定默认值

属性的遍历

  • for...in :自身和继承的可枚举属性(不含Symbol属性)

  • Object.keys(obj) :返回数组,自身的可枚举属性(不含 Symbol 属性)

  • Object.getOwnPropertyNames(obj) :返回数组,自身的可枚举属性和不可枚举属性(不含 Symbol 属性 )

  • Object.getOwnPropertySymbols(obj) :返回数组,自身 Symbol 属性

  • Reflect.ownKeys(obj) :返回数组,自身可枚举属性和不可枚举属性和 Symbol 属性

读取、设置对象的 prototype 对象

Object.getPrototypeOf(obj)Object.setPrototypeOf(obj,prototype) ,尽量不使用 __proto__

Object.keys()、Object.values()、Object.entries()

均返回数组

  • Object.keys() :数组每一项为键名

  • Object.values() :数组每一项为键值`

  • Object.entries() :数组每一项为键值对数组

    let obj = {
        id: '1',
        name: 'ljx'
    }
    let arr = Object.entries(obj); // [['id','1'],['name','ljx']] 
    for(let [k,v] of arr){
        console.log(k,v)
    }
    // 'id' '1'
    // 'name' 'ljx'
    

for...offor...in 的区别:

for...in :遍历对象

for...of :遍历数组、字符串等任何部署了 Iterator 接口的数据结构,不能用于普通对象,会报错

解构赋值(浅复制):

  • 数组中:

    let [a,...b] = [1,2,3]
    a // 1
    b // [2,3]
    
  • 对象中:

    let {x,y,...z} = {x: 1,y: 2,a: 3,b: 4}
    x // 1
    y // 2
    z // {a: 3,b: 4}
    

扩展运算符:

  • 克隆对象:

    let a = {id: '1',name: 'ljx'}
    let b = {...a};
    b // {id: '1',name: 'ljx'}
    
    // 相当于
    let c = Object.assign({},a)
    c // {id: '1',name: 'ljx'}
    
  • 合并对象:

    let a = {id: '1'}
    let b = {name: 'ljx'}
    let c = {...a,...b}
    c // {id: '1',name: 'ljx'}
    
    // 相当于
    let d = Object.assign({},a,b)
    d // {id: '1',name: 'ljx'}
    

Object.getOwnPropertyDescriptor(obj,key)

返回对象指定属性的描述对象

let obj = {
  id: '1'
}
let desc = Object.getOwnPropertyDescriptor(obj,'id')
desc // {value: '1',writable: true,enumberable: true,configurable: true}

Object.getOwnPropertyDescriptors(obj)

返回对象所有属性的描述对象

let obj = {
  id: '1',
  name: 'ljx'
}
let descs = Object.getOwnPropertyDescriptors(obj)
descs 
// {
//	id: {value: '1',writable: true,enumberable: true,configurable: true},
//	name: {value: 'ljx',writable: true,enumberable: true,configurable: true}
// }

Null 传导运算符(提案):?.

解决读取对象内部属性的时候,往往需要先判断该对象是否存在,若改为 Null 传导运算符则只要其中一个返回 nullundefined ,则不再继续运算

let result = (a && a.b && a.b.c) || 'default'

// Null运算符表示
let result = a?.b?.c || 'default'

第10章 Symbol

ES6 新引入的原始数据类型,类似于字符串,表示独一无二的值,使用 Symbol 函数生成,前面不能用 New ,可接受一个字符串作为参数(仅表示描述,即使相同参数返回值还是不相等的),若传参为对象,会先调用对象的 toString() 方法,作为对象属性名的时候不能使用点运算符

  • Symbol 作为对象属性是公有属性,但不会被常规方法遍历得到,可利用这个特性定义对象的非私有但又希望只用于内部的属性方法

  • Symbol 作为对象属性遍历的方法:

    • Object.getOwnPropertySymbols() :放回对象所有作为属性名的 Symbol 值的数组

    • Reflect.ownkeys() :返回数组,自身可枚举属性和不可枚举属性和 Symbol 属性

Symbol.for(key)

接受一个字符串作为参数,然后搜索有没有以该参数作为名称的 Symbol 值,若有,则返回这个 Symbol 值,若无则新建 ,并登记在全局环境中供搜索(搜索的范围是用 Symbol.for() 创建的 Symbol 值,因为 Symbol() 生成的 Symbol 值是不会被登记在全局环境中供搜索的,每次调用都是生成一个不同的 Symbol 值)

let a = Symbol.for('name')
let b = Symbol.for('name')
a === b // true

Symbol.keyFor()

返回一个已登记的 Symbol 类型值的 key ,若未登记则返回 undefined

let a = Symbol.for('name')
Symbol.keyFor(a) // 'name'

内置 Symbol

指向语言内部使用的方法

  • Symbol.hasInstance

  • Symbol.isConcatSpreadable

  • Symbol.species

  • Symbol.match

  • Symbol.replace

  • Symbol.search

  • Symbol.split

  • Symbol.iterator

  • Symbol.toPrimitive

  • Symbol.toStringTag

  • Symbol.unscopables

第11章 Set和Map数据结构

Set

​ 类似数组,但值唯一,没有重复,构造函数接收数组或类数组对象作为参数,想 Set 中加入值时不会发生类型转换,认为 NaN 等于 NaN,且两个对象总是不相等的,即使是两个空对象

​ **利用 Set 去重:[...new Set(arr)]Array.from(new Set(arr)) **

let arr1 = [1,1,2,3]

let arr2 = [...new Set(arr1)] // [1,2,3]

let arr3 = Array.from(new Set(arr1)) // [1,2,3] 

实例属性

  • size:成员总数

实例操作方法

  • add(value) :增值,返回 Set 结构本身

  • delete(value) :删值,返回表示删除成功与否的布尔值

  • has(value):返回是否包含 value 的布尔值

  • clear() :清空,无返回值

实例遍历方法(优先使用 values()):

  • keys()/values() :返回键名/键值的遍历器(由于 Set 键名和键值是同一个值,故这两个方法行为完全一致)

  • entries() :返回键值对数组的遍历器

    let a = new Set(['a','b','c'])
    for(let item of a.entries){
    	console.log(item)
    }
    // ['a','a']
    // ['b','b']
    // ['c','c']
    
  • forEach() :使用调函数遍历每个成员,无返回值

  • for...of :由于 Set 实例默认可遍历,其默认遍历器生成函数就是 values() 方法,所以直接用 for..of 循环 Set 就行,不用加上values() 方法

使用 Set 实现并集(Union)、交集(Intersect)和差集(Difference)

let arr1 = [1,2]
let arr2 = [2,3]

// 并集
let union = new Set([...arr1,...arr2]) // Set {1,2,3}
// 交集
let intersect = new Set(arr1.filter(x => arr2.includes(x))) // Set {2}
// 差集
let difference = new Set(arr1.filter(x => !arr2.includes(x))) // Set {1,3}

let arr3 = new Set([1,2])
let arr4 = new Set([2,3])

// 并集
let union = new Set([...arr3,...arr4]) // Set {1,2,3}
// 交集
let intersect = new Set([...arr3].filter(x => arr4.has(x))) // Set {2}
// 差集
let difference = new Set([...arr3].filter(x => !arr4.has(x))) // Set {1,3}

WeakSet

​ 与 Set 类似,但成员只能是对象,且成员对象都是弱引用的,即垃圾回收机制不考虑 WeakSet 对该对象的引用,所以 WeakSet 成员不适合引用,会随时消失,不可遍历

方法:add(value)delete(value)has(value)

Map

​ 类似于对象,但解放了对象只能使用字符串作为键名的限制,相当于对象提供了 “字符串 — 值 ” 的对应,而 Map 提供了 “ 值 — 值 ” 的对应

​ 构造函数的参数,可以是数组,类似 [['id','1'],['name','ljx']] 的结构,也可以是任何具有 Interator 接口且每个成员都是一个双元素数组的数据结构,所以 SetMap 都可以做参数,多次赋值同一个键则覆盖,只有对同一对象的引用,Map 结构才将其视为同一个键(即根据内存地址判断)

实例属性

  • size :成员数量

实例操作方法

  • set(key,value) :设置键名、键值,返回该 Map 结构,可链式调用

  • get(key) :获取 key 对应的键值,找不到返回 undefined

  • has(key) :返回是否包含 key 的布尔值

  • delete(key) :删除键,返回删除成功与否的布尔值

  • clear() :清空,无返回值

实例遍历方法

  • keys() :返回键名的遍历器

  • values() :返回键值的遍历器

  • entries() :返回所有成员的遍历器

  • forEach() :使用调函数遍历每个成员,无返回值

  • for...of:由于 Map 实例默认可遍历,其默认遍历器生成函数就是 entries() 方法,所以直接用 for..of 循环 Map 就行,不用加上entries() 方法

WeakMap

​与 Map 类似,但键名只能是对象( null 除外),且键名对象都是弱引用的,键值是正常引用的,即垃圾回收机制不考虑 WeakMap 对该对象的引用,不可遍历,典型的应用场景是,在网页的 DOM 元素上添加数据时使用 Map 结构,当该 DOM 元素被清除时,其对应的 WeakMap 记录就会自动被移除,总之,就是适用于键名对象可能会会在将来消失的场景,有助于防止内存泄露

方法:get(key)set(key,value)has(key)delete(key)

第12章 Proxy(代理)

Proxy 可以理解成在目标对象前架设一个 “拦截” 层,外界对该对象的访问都必须通过这层拦截,提供了一种机制可以对外界的访问进行过滤和改写

​生成 Proxy 实例:let proxy = new Proxy(target,handler),其中 target 表示所要拦截的目标对象, handler 参数也是一个对象,用来定制拦截行为

​要想 Proxy 起作用,必须针对 Proxy 实例进行操作,而不是针对目标对象进行操作,若 handler 上无设任何拦截,等同于直接通向原对象

​存在 this 指向问题,在 Proxy 代理的情况下,目标对象内部的 this 关键字会指向 Proxy 代理,而并非目标对象

Proxy 支持的所有拦截操作(13种) ,作为 handler 的方法

  • get(target,propKey,receiver)

拦截对象属性的读取,比如 proxy.fooproxy['foo']

  • set(target,propKey,value,receiver)

拦截对象属性的设置,比如 proxy.foo = vproxy['foo'] = v ,返回一个布尔值

  • has(target,propKey)

拦截 propKey in proxy 的操作,返回一个布尔值

  • deleteProperty(target,propKey)

拦截 delete proxy[propKey] 的操作

  • ownKeys(target)

拦截 Object.getOwnPropertyNames(proxy)Object.getOwnPropertySymbols(proxy)Object.keys(proxy) ,返回一个数组。该方法返回目标对象所有自身属性的属性名,而 Object.keys() 的返回结果仅包括目标对象自身的可遍历属性

  • getOwnPropertyDescriptor(target,propKey)

拦截 Object.getOwnPropertyDescriptor(proxy,propKey) ,返回属性的描述对象

  • defineProperty(target,propKey,propDesc)

拦截 Object.defineProperty(proxy,propKey,propDesc)object.defineProperties(proxy,propDesc) ,返回一个布尔值

  • preventExtensions(target)

拦截 Object.preventExtensions(proxy) ,返回一个布尔值

  • getPrototypeOf(target)

拦截 Object.getPrototypeOf() ,返回一个对象

  • isExtensible(target)

拦截 Object.isExtensible(proxy) ,返回一个布尔值

  • setPrototypeOf(target,proto)

拦截 Object.setPrototypeOf(proxy,proto) ,返回一个布尔值,如果目标对象是函数,那么还有两种额外操作可以拦截

  • apply(target,object,args)

拦截 Proxy 实例,并将其作为函数调用的操作,比如 proxy(...args)proxy.call(object,...args)proxy.apply(...)

  • construct(target,args)

拦截 Proxy 实例作为构造函数调用的操作,比如 new proxy(...args)

Proxy.revocable()

  • 返回一个对象,其 proxy 属性是 Proxy 实例,revoke 属性是一个函数,可以取消 Proxy 实例,当执行完 revoke 函数再访问 Proxy 实例,会报错
  • 使用场景:目标对象不允许直接访问,必须通过代理访问,一旦访问结束,就收回代理权,不允许下次再次访问
let target = {}
let handler = {}

let { proxy, revoke } = Proxy.revocable(target, handler)

proxy.foo = 123
proxy.foo // 123

revoke()
proxy.foo // TypeError: Revoked

第13章 Reflect

​为了优化操作对象而生,可以看做是 Object 对象方法的优化扩展版,设计目的:

  • Object 对象的一些明显属于语言内部的方法(如 Object.defineProperty() )放在 Reflect 上,现在 ObjectReflect 上均存在,但将来新方法只在 Reflect 上部署

  • 修改某些 Object 方法的返回结果,让其变得更合理,如 Object.defineProperty(obj,name,desc) 在无法定义属性时会报错,但 Reflect.defineProperty(obj,name,desc) 则会返回 false

  • Object 操作都变成函数行为,如 name in objdelete obj[name] 分别变成 Reflect.has(obj,name)Reflect.deleteProperty(obj,name)

  • Reflect 对象的方法与 Proxy 对象的方法一一对应,只要是 Proxy 对象的方法,就能在 Reflect 对象上找到对应的方法,使得 Proxy 对象能方便地调用对应的 Reflect 方法来完成默认行为

Reflect 对象的静态方法(13个):

​ **对于一些需要传入对象作为参数的方法,Reflect 对象方法表现的更为严谨,即当参数不是对象时,会直接报错,而非像对应的 Obejct 方法一样会将参数先转化为对象 **

  • Reflect.get(target,name,receiver) :返回 targetname 属性,若无返回 undefined

  • Reflect.set(target,name,value,receiver) :设置 targetname 属性等于 value ,返回设置成功与否的布尔值

  • Reflect.has(obj,name) :判断 Obj 是否包含 name 属性,返布尔值

  • Reflect.deleteProperty(obj,name) :删除 Objname 属性,返布尔值

  • Reflect.construct(target,args) :等同于 new target(...args) ,提供了一种不使用 new 来调用构造函数的方法,如

    function A(name){
    	this.name = name
    }
    // new 的写法
    const instance = New A('ljx')
    
    // Reflect.construct的写法
    const instance = Reflect.construct(A,['ljx'])
    
  • Reflect.getPrototypeOf(obj) :用于读取对象的 __proto__ 属性,对应 Object.getPrototypeOf(obj)

  • Reflect.setPropertypeOf(obj,newProto) :用于设置对象的 __proto__ 属性,返回第一个参数对象,对应 Object.setPrototypeOf(obj,newProto)

  • Reflect.apply(func,thisArg,args) :等同于 Function.prototype.apply.call(func,thisArg,args) ,用于绑定 this 对象后执行给定函数

  • Reflect.defineProperty(target,propertyKey,attributes) :等同于 Object.defineProperty() ,用来为对象定义属性

  • Reflect.getOwnPropertyDescriptor(target,propertyKey) :等同于 Object.getOwnPropertyDescriptor() ,用于获得指定属性的描述对象

  • Reflect.isExtensible(target) :对应 Object.isExtensible() ,返回一个布尔值,表示当前对象是否可扩展

  • Reflect.preventExtensions(target) :用于使目标对象变为不可扩展的,返回一个布尔值,百世是否操作成功

  • Reflect.ownKeys(target):用于返回对象的所有属性,基本等同于 Object.getOwnPropertyNames()Object.getOwnPropertySymbols() 之和

使用 Proxy 实现观察者模式

第14章 Promise对象

Promise 是异步编程的一种解决方案,简单来说,是一个容器,保存着某个未来才会结束的事件(通常是一个异步操作)的结果,从语法上来说,Promise 是一个对象,从它可以获取异步操作的消息

特点

  • 对象的状态不受外界影响,有3种状态,分别是 Pending(进行中)Fulfilled(已成功)Rejected(已失败) ,只有异步操作的结果可以决定当前是哪一种状态,任何其他操作都无法改变这个状态

  • 一旦状态改变就不会再变,任何时候都可以得到这个结果,Promise 对象的状态改变只有两种可能,从 Pending 变为 Fulfilled 和从 Pending 变为 Rejected ,只要这两种情况发生,状态就凝固定型(Resolved)了,不会再变,此时再对其添加回调函数,也是会立即得到这个结果

  • 无法取消 Promise ,一旦新建就会立即执行,无法中途取消

构造函数生成 Promise 实例

let promise = new Promise((resolve,reject) => {
    if(/* 异步操作成功 */){
       resolve(value)
    }else{
       reject(error)
    }
})

Promise.prototype.then()

作用是为 promise 实例添加状态改变时的回调函数,第一次参数是 Resolved 状态的回调函数,第二个参数(可选)是 Rejected 状态的回调函数,then() 方法返回的是一个新的 Promise 实例,因此可以使用链式写法,即 .then().then()

Promise.prototype.catch()

.then(null,rejection) 的别名,用于指定发生错误时的回调函数,**Promise 对象的错误具有 “冒泡” 性质,会一直向后传递,直到被捕获位置,也就是说,错误总会被下一个 catch 语句捕获。一般来说,不要在 then() 方法中定义 Rejected 状态的回调函数(即 then 的第二个参数),而应总使用 catch() 方法

Promise.all()

用于将多个 Promise 实例包装成一个新的 Promise 实例,接受一个具有 Iterator 接口的包含多个 Promise 实例的集合(通常为数组),若集合中参数非 Promise 实例,会先调用 Promise.resolve() 方法将其转化为 Promise 实例

let p = Promise.all([p1,p2,p3])

上述代码中,p 的状态由 p1p2p3 决定,分两种情况

  • 只有 p1p2p3 的状态都变为 Fulfilledp 的状态才会变成 Fulfilled ,此时 p1p2p3 的返回值组成一个数组,传递给 p 的回调函数

  • 只要 p1p2p3 中有一个被 Rejectedp 的状态就变成 Rejected ,此时第一个被 Rejected 的实例的返回值会传递给 p 的回调函数(如果作为参数的 Promise 实例自身定义了 catch 方法,那么它被 rejected 时并不会触发 Promise.all().catch() 方法

Promise.race()

同样是将多个 Promise 实例包装成一个新的 Promise 实例,参数处理和 Promise.all() 一致,但返回的状态取决于最先改变状态的实例,只要 Promise 实例中有一个率先改变状态,返回的状态就跟着改变,并将返回值传出到新的 Promise 的回调函数

Promise.resolve()

将现有对象转化为 Promise 对象

  • 参数为 Promise 实例时:不做任何修改,原封不动的返回这个实例

  • 参数为 thenable 对象时:thenable 对象是指具有 then() 方法的对象,会将这个对象转为 Promise 对象,然后立即执行 thenable 对象的 then 方法

  • 参数不是 thenable 对象或根本不是对象或不带任何参数时:返回一个 Resolved 状态的 Promise 对象(回调函数立即执行)

Promise.reject()

返回一个状态为 RejectedPromise 对象,参数会原封不动的作为 reject 的理由变成后续方法的参数

Promise.done()

总是处于回调链的尾端,保证抛出任何可能出现的错误

Promise.finally()

接受一个回调函数作为参数,该函数不管最终状态怎样都必须执行

第15章 Iterator(遍历器) 和 for...of 循环

Iterator 是一种接口,为各种不同的数据结构提供统一的访问机制。任何数据结构,只要部署 Iterator 接口,就可以完成遍历操作,此时称该数据结构是可遍历的

作用:

  • 为各种数据接口提供一个统一的、简便的访问接口

  • 使得数据结构的成员能够按某种次序排列

  • for...of 消费

默认的 Iterator 接口部署在数据结构的 [Symbol.ietrator] 属性上,换句话说,数据结构只要具有 [Symbol.iterator] 属性,就可认为是可遍历的,执行 [Symbol.iterator] 方法,会返回一个遍历器对象,该对象的根本特征是具有 next() 方法,每次调用 next() 都会返回一个代表当前成员的信息对象,具有 valuedone 两个属性,此外还具有 return() 方法和 throw() 方法

原生具有 Iterator 接口的数据结构

  • Array

  • String

  • Set

  • Map

  • TypeArray

  • 函数的 arguments 对象

  • NodeList 对象

默认调用 Iterator 接口场合

  • 解构赋值:对数组和 Set 结构进行解构赋值时

  • 扩展运算符(...

  • yield*yield* 后面跟的是一个可遍历的结构,它会调用该结构的遍历器接口

其他调用 Iterator 接口场合

由于数组的遍历会调用遍历器接口,所以任何接受数组作为参数的场合其实都调用了遍历器接口,比如:

  • for...of

  • Array.from()

  • Map()Set()WeakMap()WeakSet()

  • Promise.all()

  • Promise.race()

部署 Iterator 接口

最简易的方法:使用 Generator 函数

let obj = {}
obj[Symbol.iterator] = function* (){
    yield 'a'
    yield 'b'
    yield 'c'
}
for(let value of obj){
    console.log(value)
}
// a
// b
// c

第16章 Generator(生成器) 函数的语法

Generator 函数是一种异步编程解决方案( async 函数就是 Generator 函数的语法糖),可以理解为一个状态机,封装了多个内部状态,执行 Generator 函数会返回一个遍历器对象

特点

  • function 命令和函数名之间有一个 * 星号

  • 函数内部使用 yield(产出) 语句定义不同的内部状态

  • 调用 Generator 函数后,该函数并不执行,而是返回一个指向内部状态的遍历器对象,之后需要调用 next() 方法使指针移向下一个状态,也就是说,每一次调用 next() 方法,内部指针就从函数头部或上一次停下来的地方继续执行,直到遇到下一条 yiled 语句(或 return 语句),换句话说,Generator 函数是分段执行的, yield 语句是暂停执行的标记,而 next() 方法可以恢复执行

function* abc(){
    yield 'a'
    yield 'b'
    return 'c'
}
let gen = abc() // 返回遍历器对象
gen.next() // {value: 'a', done: false}
gen.next() // {value: 'b', done: false}
gen.next() // {value: 'c', done: true}
gen.next() // {value: undefined, done: true}

遍历器对象的 next() 方法的运行逻辑:

  • 遇到 yield 语句就暂停执行后面的操作,并将紧跟在 yield 后面的表达式的值作为返回的对象的 value 属性值

  • 下一次调用 next() 方法时再继续往下执行,直到遇到下一条 yield 语句

  • 如果没有遇到新的 yield 语句,就一直运行到函数结束,直到 return 语句为止,并将 return 语句后面的表达式的值作为返回对象的 value 属性值

  • 如果该函数没有 return 语句,则返回对象的 value 属性值为 undefined

yield 表达式为惰性求值,若 yield 表达式用在另一个表达式中,必须放在圆括号内

Generator 函数内调用另一个 Generator 函数,默认是无效的,需要使用 yield* 表达式

Generator 函数作为对象属性时的简写:

// 原写法
let obj = {
	gen: function* (){
        
    }
}
// 缩写
let obj = {
    * gen() {
        
    }
}

Generator 函数的应用:

  • 异步操作的同步化表达

  • 控制流管理

  • 部署 Iterator 接口

  • 作为数据结构

第17章 Generator 函数的异步应用(略)

第18章 async 函数

Generator 函数的语法糖

async 函数对 Generator 函数的改进

  • 内置执行器,即与普通函数的执行一样,不需要调用 next() 方法

  • 明确的语义,用 async 代替 *await 代替 yield

  • 更好的适用性,await 命令后面可以是 Promise 对象和原始类型的值(数值、字符串和布尔值,但此时等同于同步操作,因为当不是 Promise 对象时,会将其转为一个立即 resolvePromise 对象

  • 返回值是 Promise ,比起 Generator 函数的返回值是 Iterator 对象更方便,可以用 then() 方法指定下一步的操作

用法

  • async 函数返回一个 Promise 对象,可以使用 then() 方法添加回调函数。当函数执行的时候,一旦遇到 await 就会先返回(取决于 await 后面是否为异步操作),等到异步操作完成,再接着执行函数体内后面的语句

  • async 函数内部的 return 语句返回的值,会成为 then() 方法回调函数的参数

  • async 函数返回的 Promise 对象必须等到内部所有的 await 命令后面的 Promise 对象执行完才会发生状态改变,除非遇到 return 语句或抛出错误,也就是说,只有 async 函数内部的异步操作执行完,才会执行 then() 方法指定的回调函数

  • 正常情况下,await 命令后面是一个 Promise 对象,如果不是,会转为一个立即 resolvePromise 对象,如果 await 命令后面的 Promise 对象变为 reject 转态,则 reject 的参数会被 catch 方法的回调函数接收到,且只要一个 await 后面的 Promise 对象变为 reject,那么整个 async 函数都会中断执行,相当于 async 返回的 Promise 对象被 reject

进阶用法

  • 由于 await 后面的 Promiserejected 时会中断后续操作,所以最好将 await 命令放在 try...catch 代码块中(可放置多个)
async function func() {
    try{
        await doSomething1()
        await doSomething2()
    }catch(err){
        console.log(err)
    }
}
  • 若多个 await 命令后面的异步操作不存在继发关系,则最好同时触发
// 继发关系,只有等 getFoo 完成以后才会执行 getBar
let foo = await getFoo()
let bar = await getBar()

// 同时触发写法1
let [foo,bar] = await Promise.all([getFoo(),getBar()])
// 同时触发写法2
let fooPromise = getFoo()
let barPromise = getBar()
let foo = await fooPromise
let bar = await barPromise

第19章 Class的基本语法(略)

第20章 Class的继承(略)

第21章 修饰器(略)

第22章 Module的语法

ES6 之前的模块加载方案,主要有用于服务器的 CommonJS 和 用于浏览器的 AMDES6 模块的设计思想是尽量静态化,使得编译时就能确定模块的依赖关系,以及输入和输出的变量。CommonJSAMD 模块都是只能在运行时确定这些东西,如 CommonJS 模块就是对象,输入时必须查找对象属性

// CommonJS 模块
let { stat, exists, readFile } = require('fs')

// 等同于
let _fs = require('fs')
let stat = _fs.stat
let exists = _fs.exists
let readFile = _fs.readfile

​ 上述代码实质是整体加载 fs 模块(即加载 fs 的所有方法),生成一个对象 (fs),然后再从这个对象上面读取3个方法,这种加载称为 ""运行时加载",因为只有运行时才能得到这个对象,导致完全没办法在编译时进行 “静态优化”

ES6 模块不是对象,而是通过 export 命令显示指定输出的代码,再通过 import 命令输入

// ES6 模块
import { stat, exists, readfile } from 'fs'

​ 上述代码实质上是从 fs 模块加载3个方法,而不是加载其他方法。这种加载称为 编译时加载静态加载,即 ES6 可以在编译时就完成模块加载,效率比 CommonJS 模块的加载方式高

ES6模块中,顶层的 this 指向 undefined ,即不应该在顶层代码中使用 this

export

// 写法1
export const a = 'a'
export function b(){}

// 写法2
const a = 'a'
function b(){}
export { a, b }

// 写法3 (重命名 old as new)
const a = 'a'
function b(){}
export {
	a as renameA,
    b as renameB
}

export 语句输出的接口与其对应的值是动态绑定关系,即通过该接口可以获取模块内部实时的值,而 CommonJS 模块输出的是值的缓存,不存在动态更新

import

// 写法1
import { a, b } from "模块路径(相对/绝对)"

// 写法2 (重命名 old as new)
import { a as renameA } from "模块路径(相对/绝对)"

// 写法3 (整体加载,all为自己的重命名)
import * as all from "模块路径(相对/绝对)"
all.a
all.b
// 整体加载所在的对象是可以静态分析的,所以不允许运行时改变,故以下代码是不允许的
all.a = '666'
all.b = function() {}

import 命令会有提升效果,会提升到整个模块的头部并首先执行,因为 import 命令是在编译阶段执行的,在代码运行之前,如

foo()
import { foo } from "模块路径(相对/绝对)"

export default 和对应的 import :当 export default 时其中函数或变量的名称变得不再重要(其实是输出一个叫做 default的变量,后面不能跟变量声明语句),此时 import 时可取任意名,且不需要大括号,如

export default function a() {}
// 此时等同于
// export { a as default }

import b from '模块路径(相对/绝对)'
// 此时等同于
// import { default as b } from "模块路径(相对/绝对)" 

import() 函数(提案):由于是静态加载,所以无法在代码运行时加载,即不能在代码块中动态加载,该函数就是解决此类动态加载问题的(按需加载、条件加载、动态的模块路径等)