《JavaScript 高级程序设计》其他内容 学习记录

218 阅读7分钟

A、ES2018、ES2019

1、异步迭代

1、创建并使用异步迭代器

  • 同步迭代器
class Emitter {
  constructor(max) {
    this.max = max
    this.syncIdx = 0
  }
  *[Symbal.iterator]() {
    while(this.syncIdx < this.max) {
      yield this.syncIdx++
    }
  }
}

const emitter = new Emitter(5)

function syncCount() {
  const syncCounter = emitter[Symbol.iterator]()
  
  for(const x of syncCounter) {
    console.log(x)
  }
}
syncCount()
// 0
// 1
// 2
// 3
// 4
  • 要使用迭代器和生成器的异步版本定义了 Symbol.asyncIterator, 以便定义和调用输出期约的生成器函数。同时,这一版规范还为异步迭代器增加了 for-await-of 循环, 用于使用异步迭代器。
class Emitter {
  constructor(max) {
    this.max = max
    this.syncIdx = 0
    this.asyncIdx = 0
  }
  *[Symbal.iterator]() {
    while(this.syncIdx < this.max) {
      yield this.syncIdx++
    }
  }
  
  async *[Symbol.asyncIterator]() {
  // *[Symbol.asyncIterator]() {
    while(this.asyncIdx < this.max) {
      //yield new Promise((resolve) => resolve(this.asyncIdx++))
      yield this.asyncIdx++
    }
  }
}

const emitter = new Emitter(5)

function syncCount() {
  const syncCounter = emitter[Symbol.iterator]()
  
  for(const x of syncCounter) {
    console.log(x)
  }
}

async functoin asyncCount() {
	const asyncCounter = emitter[Symbol.asyncIterator]()

	for await(const x of asyncCounter) {
    console.log(x)
  }
}
syncCount()
// 0
// 1
// 2
// 3
// 4
asyncCount()
// 0
// 1
// 2
// 3
// 4

// 可以把前面例子中的同步生成器传给 for-await-of 循环:
async function asyncIteratorSyncCount() {
  const syncCounter = emitter[Symbol.iterator]()
  
  for await(const x of syncCounter) {
    console.log(x)
  }
}

2、理解异步迭代器队列

3、处理异步迭代器的reject()

class Emitter {
  constructor(max) {
    this.max = max;
    this.asyncIdx = 0;
  }
  async *[Symbol.asyncIterator]() {
    while (this.asyncIdx < this.max) {
      if (this.asyncIdx < 3) {
        yield this.asyncIdx++;
      } else {
        throw 'Exited loop';
      }
    }
  }
}
const emitter = new Emitter(5);
async function asyncCount() {
  const asyncCounter = emitter[Symbol.asyncIterator]();
  for await (const x of asyncCounter) {
    console.log(x);
  }
}
asyncCount();
// 0
// 1
// 2
// Uncaught (in promise) Exited loop 

4、使用next()手动异步迭代

  • for-await-of 循环特征
    • 一是利用异步迭代器队列保证按顺序执行
    • 二是隐藏 异步迭代器的期约

5、顶级异步循环

  • 一般来说,包括 for-await-of 循环在内的异步行为不能出现在异步函数外部。不过,有时候可 能确实需要在这样的上下文使用异步行为。为此可以通过创建异步 IIFE 来达到目的
class Emitter {
  constructor(max) {
    this.max = max;
    this.asyncIdx = 0;
  }
  async *[Symbol.asyncIterator]() {
    while (this.asyncIdx < this.max) {
      yield new Promise((resolve) => resolve(this.asyncIdx++));
    }
  }
}
const emitter = new Emitter(5);
(async function () {
  const asyncCounter = emitter[Symbol.asyncIterator]();
  for await (const x of asyncCounter) {
    console.log(x);
  }
})();
// 0
// 1
// 2
// 3
// 4 

6、实现可观察对象

  • 异步迭代器可以耐心等待下一次迭代而不会导致计算成本,那么这也为实现可观察对象(Observable)
class Observable {
  constructor() {
    this.promiseQueue = [];
    // 保存用于解决队列中下一个期约的程序
    this.resolve = null;
    // 把最初的期约推到队列
    // 该期约会解决为第一个观察到的事件
    this.enqueue();
  }
  // 创建新期约,保存其解决方法
  // 再把它保存到队列中
  enqueue() {
    this.promiseQueue.push(
      new Promise((resolve) => this.resolve = resolve));
  }
  // 从队列前端移除期约
  // 并返回它
  dequeue() {
    return this.promiseQueue.shift();
  }
  async *fromEvent(element, eventType) {
    // 在有事件生成时,用事件对象来解决 队列头部的期约
    // 同时把另一个期约加入队列
    element.addEventListener(eventType, (event) => {
      this.resolve(event);
      this.enqueue();
    });
    // 每次解决队列前面的期约
    // 都会向异步迭代器返回相应的事件对象
    while (1) {
      yield await this.dequeue();
    }
  }
}
(async function () {
  const observable = new Observable();
  const button = document.querySelector('button');
  const mouseClickIterator = observable.fromEvent(button, 'click');
  for await (const clickEvent of mouseClickIterator) {
    console.log(clickEvent);
  }
})();

2、对象字面量的剩余操作符和扩展操作符

1、剩余操作符

  • 可以在解构对象时将所有剩下未指定的可枚举属性收集到一个对象中。
const person = { name: 'Matt', age: 27, job: 'Engineer' };
const { name, ...remainingData } = person;

console.log(name); // 'Matt'
console.log(remainingData); // { age: 27, job: 'Engineer' }
  • 每个对象字面量中最多可以使用一次剩余操作符,且必须放到最后。
const person = { name: 'Matt', age: 27, job: { title: 'Engineer', level: 10 } };
const { name, job: { title, ...remainingJobData }, ...remainingPersonData } = person;

console.log(name); // Matt
console.log(title); // Engineer
console.log(remainingPersonData); // { age: 27 }
console.log(remainingJobData); // { level: 10 }

const { ...a, job } = person;
// SyntaxError: Rest element must be last element 
  • 剩余操作符在对象间执行浅复制,因此会复制对象的引用而不克隆整个对象。
const person = { name: 'Matt', age: 27, job: { title: 'Engineer', level: 10 } };

const { ...remainingData } = person;

console.log(person === remainingData); // false
console.log(person.job === remainingData.job); // true 
  • 剩余操作符会复制所有自有可枚举属性,包括符号
const s = Symbol();
const foo = { a: 1, [s]: 2, b: 3 }

const {a, ...remainingData} = foo;

console.log(remainingData);
// { b: 3, Symbol(): 2 } 

2、扩展操作符

  • 像拼接数组一样合并两个对象。应用到内部对象的扩展操作符会对所有自有可枚举属性执行浅复制到外部对象
const s = Symbol();
const foo = { a: 1 };
const bar = { [s]: 2 };

const foobar = {...foo, c: 3, ...bar};

console.log(foobar);
// { a: 1, c: 3 Symbol(): 2 }
  • 扩展对象的顺序很重要
    • 对象跟踪插入顺序。从扩展对象复制的属性按照它们在对象字面量中列出的顺序插入
    • 对象会覆盖重名属性。在出现重名属性时,会使用后出现属性的值
  • 与剩余操作符一样,所有复制都是浅复制

3、Promise.prototype.finally()

  • 有了 Promise.prototype.finally(),就可以统一共享的处理程序。finally()处理程序不传递任何参数,也不知道自己处理的期约是解决的还是拒绝的

4、正则表达式相关特性

1、dotAll标志

  • 正则表达式中用于匹配任意字符的点(.)不匹配换行符,比如\n 和\r 或非 BMP 字符,如表情符号
  • 规范新增了 s 标志(意思是单行,singleline),从而解决了这个问题

2、向后查找断言

3、命名捕获组

4、Unicode属性转义

5、数组打平方法

1、Array.prototype.flat()

  • 如果没有这两个新方法要打平数组的一个示例实现
function flatten(sourceArray, flattenedArray = []) {
  for(const element of sourceArray) {
		if(Array.isArray(element)) {
			flatten(element, flattenedArray)
    }else {
      flattenedArray.push(element)
    }
  }
  return flattenedArray
}
const arr = [[0], 1, 2, [3, [4, 5]], 6];
console.log(flatten(arr))
// [0, 1, 2, 3, 4, 5, 6] 
  • 指定打平到第几级嵌套
function flatten(sourceArray, depth, flattenedArray = []) {
  for(const element of sourceArray) {
    if(Array.isArray(element) && depth > 0) {
      flatten(element, depth - 1, flattenedArray)
    }else {
      flattenedArray.push(element)
    }
  }
  return flattenedArray
}
const arr = [[0], 1, 2, [3, [4, 5]], 6];
console.log(flatten(arr, 1));
// [0, 1, 2, 3, [4, 5], 6] 
  • Array.prototype.flat() 接收depth参数(默认1),返回一个对要打平 Array 实例的浅复制副本
  • 因为是执行浅复制,所以包含循环引用的数组在被打平时会从源数组复制值

2、Array.prototype.flatMap()

  • Array.prototype.flatMap()方法会在打平数组之前执行一次映射操作
  • 在功能上,arr.flatMap(f)arr.map(f).flat()等价
  • arr.flatMap()更高效,因为浏览器只需要执行一次遍历

6、Object.fromEntries()

  • 用于通过键/值对数组的 集合构建对象。这个方法执行与 Object.entries()方法相反的操作
const obj = {
	foo: 'bar',
	baz: 'qux'
};
const objEntries = Object.entries(obj);
console.log(objEntries);
// [["foo", "bar"], ["baz", "qux"]]
console.log(Object.fromEntries(objEntries));
// { foo: "bar", baz: "qux" } 
  • 接收一个可迭代对象参数,该可迭代对象可以包含任意数量的大小为 2 的可迭代对象
  • 这个方法可以方便地将 Map 实例转换为 Object 实例
const map = new Map().set('foo', 'bar');
console.log(Object.fromEntries(map));
// { foo: "bar" } 

7、字符串修理方法

  • trimStart() 删除字符串开头的空格
  • trimEnd() 删除字符串末尾的空格
  • 在只有一个空格的情况下,这两个方法相当于执行与 padStart()和 padEnd()相反的操作

8、Symbol.prototype.description

  • 用于取得可选的符号描述。 以前,只能通过将符号转型为字符串来取得这个描述
const s = Symbol('foo');
console.log(s.toString());
// Symbol(foo)

// 这个原型属性是只读的,可以在实例上直接取得符号的描述。如果没有描述,则默认为 undefined。
const s = Symbol('foo');
console.log(s.description);
// foo 

9、可选的catch绑定

B、严格模式

1、选择使用

  • "use strict";

2、变量

  • 不允许意外创建全局变量
  • 无法在变量上调用 delete
  • 不允许变量名为 implements、interface、let、 package、private、protected、public、static 和 yield。

3、对象

  • 给只读属性赋值会抛出 TypeError
  • 在不可配置属性上使用 delete 会抛出 TypeError
  • 给不存在的对象添加属性会抛出 TypeError
  • 使用对象字面量时,属性名必须唯一(ES6不抛错)

4、函数

  • 严格模式要求命名函数参数必须唯一
  • 严格模式下,命名参数和 arguments 是相互独立的
  • 去掉了 arguments.callee 和 arguments.caller
  • 读或写函数的 caller 或 callee 属性也会抛出 TypeError
  • 严格模式也限制了函数的命名,不允许函数名为 implements、interface、 let、package、private、protected、public、static 和 yield

1、函数参数

  • ES6 增加了剩余操作符、解构操作符和默认参数,为函数组织、结构和定义参数提供了强大的支持。
  • ECMAScript 7 增加了一条限制,要求使用任何上述先进参数特性的函数内部都不能使用严格模式,否则会抛出错误。不过,全局严格模式还是允许的

2、eval()

  • eval()不会再在包含上下文中创建变量或函数

3、eval与argumnets

  • 严格模式明确不允许使用 eval 和 arguments 作为标识符和操作它们的值

5、this强制转换

  • 使用函数的 apply()或 call()方法时
    • 在非严格模式下 null 或 undefined 值会被强制转型为全局对象。
    • 在严格模式下,则始终以指定值作为函数 this 的值,无论指定的是什么值。

6、类与模块

  • 对于类,这包括类声明和类表达式,构造函数、实例方法、静态方法、获取方法和设置方法都在严 格模式下
  • 对于模块,所有在其内部定义的代码都处于严格模式

7、其他变化

  • 消除 with 语句
  • 从 JavaScript 中去掉了八进制字面量
  • 修改了非严格模式下的parseInt(),将八进制字面量当作带前导0的十进制字面量

C、JavaScript库和框架

D、JavaScript工具