书摘&心得

此处知识与红宝书重复部分不再记录,过于复杂的知识也不做记录,额外记录一些“你不知道”的知识。

1ES?

  • ES6只是ESMAScript的一个版本,后续的发展无穷无尽
  • 建议关注ESMAScript双月报告,靠谱的中文版整理。

    2 语法

  • 大多数内容都是老生常谈的,这边摘录了一些我觉得有趣的知识。

  • …操作符在函数声明的参数中使用被称为收集,在其他地方则是展开
  • undefined 意味着缺失。也就是说,undefined 和缺失是无法区别的,至少对于函数参数来说是如此。
  • 函数参数默认值甚至可以是表达式
    • 是惰性求值的——只在需要的时候运行
    • 函数参数默认值会优先搜寻函数体内的变量
  • 利用解构可以不借助临时变量,交换两个变量的值
    • image.png
  • 简写 ```javascript // 老办法: var o = { x: function(){ .. }, y: function(){ .. } };

// 简写: var o = { x() { .. }, y() { .. } }; var o = { foo() { .. } }; var o = { “f” + “oo” { .. } // 计算出的简洁方法 “b” + “ar” { .. } // 计算出的简洁生成器 };

  1. - 箭头函数总是函数表达式;并不存在箭头函数声明。
  2. - 单例(singleton)模式:只允许自己被创建一次
  3. <a name="Q43MM"></a>
  4. ## 标签模板字面量
  5. - 标签部分可以为任意结果为函数的表达式
  6. - 访问strings.raw属性可以拿到未处理的值
  7. ```javascript
  8. function foo(strings, ...values) {
  9. console.log( strings );
  10. console.log( values );
  11. }
  12. var desc = "awesome";
  13. foo`Everything is ${desc}!`; // 标签是foo
  14. // [ "Everything is ", "!"]
  15. // [ "awesome" ]
  16. function bar() {
  17. return function foo(strings, ...values) {
  18. console.log( strings );
  19. console.log( values );
  20. }
  21. }
  22. var desc = "awesome";
  23. bar()`Everything is ${desc}!`; // 标签是bar()
  24. // [ "Everything is ", "!"]
  25. // [ "awesome" ]

for of 与 for in

  • for..in 在数组的键 / 索引上循环,而 for..of 在数组的值上循环。
    • 谐音记忆:银(in)剑(键)
  • 只能在能够产生迭代器,供循环使用的对象上使用

    • 迭代器详见:第7章 迭代器与生成器
    • 字符串也可以
      1. var a = ["a","b","c","d","e"];
      2. for (var idx in a) {
      3. console.log( idx );
      4. }
      5. // 0 1 2 3 4
      6. for (var val of a) {
      7. console.log( val );
      8. }
      9. // "a" "b" "c" "d" "e"
  • 与for of 等价的for循环:

    1. for (var v, res; (res = it.next()) && !res.done; ) {
    2. v = res.value;
    3. console.log( v );
    4. }
    • 可以看到,每次循环时都会调用迭代器的.next()方法,所以for of只能在能够产生迭代器的对象上使用。

      正则表达式

  • RegExp.prototype.text()不会向前移动匹配,只会向后匹配

  • 新增标识

    • u 标识符表示正则表达式用 Unicode(UTF-16)字符来解释处理字符串

      • 用于特殊字符的匹配
        1. // 只匹配起始处并在普通文本 "-clef" 之前有一个字符的情形
        2. /^.-clef/ .test( " -clef" ); // false
        3. /^.-clef/u.test( " -clef" ); // true
    • y 标识为定点标识,指定正则开始匹配的位置

      1. var re2 = /foo/y, // <-- 注意定点标识y
      2. str = "++foo++";
      3. re2.lastIndex; // 0
      4. re2.test( str ); // false--0处没有找到"foo"
      5. re2.lastIndex; // 0
      6. re2.lastIndex = 2;
      7. re2.test( str ); // true
      8. re2.lastIndex; // 5--更新到前次匹配之后位置
      9. re2.test( str ); // false
      10. re2.lastIndex; // 0--前次匹配失败后重置

      3 代码组织

      3.1 迭代器

  • 关于迭代器,红宝书和本书都描述得很多,本书更偏向底层,红宝书更偏向应用。

  • 迭代器不是一个全新的主题
    • ES6把迭代器标准化了
  • 迭代器是一种有序的、连续的、基于拉取的用于消耗数据的组织方式
  • Symbol.iterator
    • 表示可以为这个对象产生迭代器的方法。
    • image.png
  • 迭代器的next()方法只能接受一个参数
    • 关于这点本书的描述存在问题
    • 应该和promise.resolve(),函数返回值保持一致——只有一个值
    • 是异步函数科里化的前提
    • 传递2个参数时TS报错:
      • image.png
  • IteratorResult 对 象

    • 指定了从任何迭代器操作返回的值必须是下面这种形式的对象
      • image.png
    • return(..)、next(..)会返回一个 IteratorResult 对 象

      3.2 生成器

  • yield*本质上是yield委托,将代码流程委托到另一个生成器上

    • image.png
    • dva的yield take本质上就是生成器委托
  • 生成器核心功能

    • 产生一系列值
    • 顺序执行任务队列

      3.3 模块

  • 在所有 JavaScript 代码中,唯一最重要的代码组织模式是模块,而且一直都是

  • ES6模块特性
    • 基于文件:一个文件一个模块
    • API是静态的:后续无法补充
    • 单例
      • 模块只有一个实例,其中维护了它的状态。
      • 每次向其他模块导入这个模块的时候,得到的是对单个中心实例的引用。
      • 模块的公开 API 中暴露的属性和方法是到内部模块定义中的标识符的实际绑定(几乎类似于指针)
  • 导入模块时使用一个字符串值表示去哪里获得这个模块(URL、文件路径等)
    • 这个值对于你的程序来说是透明的,只对加载器本身有意义。
  • 作用域
    • 没有用 export 标示的一切都在模块作用域内部保持私有。
    • 在模块内没有全局作用域。
    • 但是直接import xxxx可以将xxxx的作用域引入到当前作用域,因而可以访问另一个文件的全局变量。
  • 命名空间导入
    • import * as xxx fomr ‘yyyy’
    • 导入的模块有默认导出,它的名字是default
  • 导入不变性的重要性
    • 因为如果没有这样的保护,你的修改就会最终影响这个模块的所有其他用户(别忘了:单例)
    • 这会导致出乎意料的副作用!
  • 作为 import 结果的声明是“提升的”
  • 系统模块加载器

    • import 语句使用外部环境(浏览器、Node.js 等)提供的独立机制。
    • 把模块标识符字符串解析成可用的指令,用于寻找和加载所需的模块。
    • 本身不是由 ES6 指定的。它是独立的、平行的标准

      命名导出与默认导出的微妙区别

      1、默认导出
      1. function foo(..) {
      2. // ..
      3. }
      4. export default foo;
      上面的代码,导出的是此时到函数表达式值的绑定,而不是标识符 foo。换句话说,如果之后在你的模块中给 foo 赋一个不同的值,模块导入得到的仍然是原来导出的函数,而不是新的值。
      2、命名导出
      1. function foo(..) {
      2. // ..
      3. }
      4. export { foo as default };
      上面的代码,默认导出绑定实际上绑定到 foo 标识符而不是它的值,所以如果之后修改了 foo 的值,在导入一侧看到的值也会更新。
      导出时刻的值无关紧要。导入时刻的值也无关紧要。绑定是活连接,所以重要的是访问这个绑定时刻的当前值。

      3.4 类

  • javascript只能模拟类

    • class Foo表明创建一个(具体的)名为Foo的函数
    • constructor(..)指定Foo(..)函数的签名以及函数体内容。
      1. class Foo {
      2. constructor(a, b) {
      3. this.x = a;
      4. this.y = b;
      5. }
      6. gimmeXY() {
      7. return this.x * this.y;
      8. }
      9. }
      10. // 相当于
      11. function Foo(a,b) {
      12. this.x = a;
      13. this.y = b;
      14. }
      15. Foo.prototype.gimmeXY = funciton() {
      16. return this.x * this.y;
      17. }

      extends与super与“继承”

      1. class Bar extends Foo {
      2. constructor(a, b, c) {
      3. super(a,b);
      4. this.z = c;
      5. }
      6. gimmeXYZ() {
      7. return super.gimmeXY() * this.z;
      8. }
      9. }
  • extends提供了一个语法糖

    • 用来在两个函数、及其原型之间建立[[Prototype]]委托链接
    • 通常被误称为“继承”
    • 或者令人迷惑地标识为“原型继承”
  • 在构造器中,super自动指向“父构造器”
    • 在前面的例子中就是Foo(..)。
  • 在方法中,super会指向“父对象”
    • 这样就可以访问其属性/方法了,比如super.gimmeXY()。
  • super通常与类相关,但其实对普通对象的简洁方法一样有效

    1. var o1 = {
    2. foo() {
    3. console.log("o1:foo");
    4. }
    5. };
    6. var o2 = {
    7. foo() {
    8. super.foo();
    9. console.log("o2:foo");
    10. }
    11. }
    12. Object.setPrototypeOf(o2, o1);
    13. o2.foo();
    14. // o1:foo
    15. // o2:foo

    super恶龙

  • super是静态绑定

    • 看起来似乎super像this一样可以被函数上下文驱动
    • 不能重载
    • 写它继承谁,它就继承谁,定义的时候就定死了
    • 所以当动态改变类的继承关系时,super的指向会出乎意料
    • 所以最好不要动态改变类的继承关系,或者放弃类,转而拥抱基于原型链的继承模式
  • 子类构造器中调用super(..)之后才能访问this

    • 原因:创建/初始化你的实例this的实际上是父构造器。

      扩展原生类

      image.png
  • ES6之前手动连接原型对象并不能真正支持array的特有特性

    • 自如自动更新length属性
  • 真正的Error对象创建时,会自动捕获特殊的stack信息,包括生成错误时的行号和文件名。

    • 前ES6自定义error“子类”没有这样的特性

      static

  • static方法(不只是属性)

    • 直接添加到这个类的函数对象上的,而不是在这个函数对象的prototype对象上。 ```javascript class Foo { static cool() { console.log(“cool”); } } class Bar extends Foo { static awesome() { super.cool(); console.log(“awesome”); } } var b = new Bar(); Bar.awesome(); // cool awesome Bar.cool(); // cool b.awesome // undefined b.cool // undefined
  1. <a name="up4mM"></a>
  2. # 4 异步流程控制
  3. - 生成器可以yield一个promise出来恢复生成器
  4. - 使用运行器runner【下卷184】
  5. - 这种模式过于常用:async await诞生了:
  6. ```javascript
  7. // 使用运行器runner
  8. run( function *main() {
  9. var ret = yield step1();
  10. try {
  11. ret = yield step2( ret );
  12. }
  13. catch (err) {
  14. ret = yield step2Failed( err );
  15. }
  16. ret = yield Promise.all([
  17. step3a( ret ),
  18. step3b( ret ),
  19. step3c( ret )
  20. ]);
  21. yield step4( ret );
  22. } ).then(
  23. function fulfilled(){
  24. // *main()成功完成
  25. },
  26. function rejected(reason){
  27. // 哎呀,出错了
  28. }
  29. );
  30. // aysnc await
  31. async function main() {
  32. var ret = await step1();
  33. try {
  34. ret = await step2( ret );
  35. }
  36. catch (err) {
  37. ret = await step2Failed( err );
  38. }
  39. ret = await Promise.all( [
  40. step3a( ret ),
  41. step3b( ret ),
  42. step3c( ret )
  43. ] );
  44. await step4( ret );
  45. }
  46. main().then(
  47. function fulfilled(){
  48. // main()成功完成
  49. },
  50. function rejected(reason){
  51. // 哎呀,出错了
  52. }
  53. );

async function弊端

没有办法从外部取消一个正在运行的async funciton实例

image.png

更多异步流程控制方案移步:
js异步流程管理
第11章 期约与异步函数

5 集合

  • 要从 map 中得到一列值/键,可以使用 values(..)/keys(..),它会返回一个迭代器。
  • Set和Map相比Object的优势在于可以使用对象做键
  • Set 是一个值的集合,其中的值唯一(重复会被忽略)。
  • 更多详见第五、六章 引用类型

    6 新增API

    Array.of(..)

  • 取代了 Array(..) 成为数组的推荐函数形式构造器

  • 【陷阱】Array构造函数传入单个数字,会构造一个长度为传入数字的空数组

    1. var a = Array( 3 );
    2. a.length; // 3
    3. a[0]; // undefined
    4. var b = Array.of( 3 );
    5. b.length; // 1
    6. b[0]; // 3

    Array.from()

  • 转换(类)数组

    1. var arrLike = {
    2. length: 4,
    3. 2: "foo"
    4. };
    5. Array.from( arrLike );
    6. // [ undefined, undefined, "foo", undefined ]
  • 槽位值为undefined不代表是空槽位

  • 空槽位可能导致意想不到的Bug
  • 使用Array.from()永远不会产生空槽位
  • 第二个参数是映射

    1. var arrLike = {
    2. length: 4,
    3. 2: "foo"
    4. };
    5. Array.from( arrLike, function mapper(val,idx){
    6. if (typeof val == "string") {
    7. return val.toUpperCase();
    8. }
    9. else {
    10. return idx;
    11. }
    12. });
    13. // [ 0, 1, "FOO", 3 ]

    Array.prototype.fill()

    填充数组,也可以取代已有值

    1. var a = [ null, null, null, null ].fill( 42, 1, 3 );
    2. a; // [null,42,42,null]
    3. var b = [1, 2, 3].fill(3);
    4. b; // [3, 3, 3]

    Number.isNaN()

    修正了isNaN()的行为

    1. var a = NaN, b = "NaN", c = 42;
    2. isNaN( a ); // true
    3. isNaN( b ); // true --oops!
    4. isNaN( c ); // false
    5. Number.isNaN( a ); // true
    6. Number.isNaN( b ); // false--修正了!
    7. Number.isNaN( c ); // false

    Number.isInteger(..)

    JavaScript 的数字值永远都是浮点数

    1. Number.isInteger( 4 ); // true
    2. Number.isInteger( 4.0 ); // true
    3. Number.isInteger( 4.2 ); // false

    String.proptotype.repeat()

    1. "foo".repeat( 3 ); // "foofoofoo"

    7 元编程

  • 太复杂了想哭 T.T

  • 什么是元编程:操作目标是程序本身的行为特性的编程
    • 代码查看自身
      • 用for..in 循环枚举对象的键
      • 检查一个对象是否是某个“类构造器”的实例
    • 代码修改自身
    • 代码修改默认语言特性,以此影响其他代码
      • proxy
  • 出人意料的是内置Symbol符号是这一章的主力军
    • 元编程描述了javascirpt很多内置方法的底层原理
  • 特性测试也属于一种元编程
    • 就是检测这个方法能不能用,那个方法支持不支持的代码
  • 尾递归调用TCO也属于元编程

  • 可以把代理看作是对目标对象的“包装”。

  • 代理在先
    • 代理成为了代码交互的主要对象,而实际目标对象保持隐藏 / 被保护的状态。
    • 可能这么做是因为你想要把对象传入到某个无法被完全“信任”的环境
    • 传递对象的代理可能比传递对象本身更安全。
  • 代理在后
    • 把 proxy 对象放到主对象的[[Prototype]] 链中
    • 在对象本身上找不到的时候,访问对象的原型链,查询到对象的代理
    • 代理只作为最后的保障
  • 更多详见:第9章 代理与反射

    属性排序

  • 总之属性的顺序很难维持为你想要的顺序

  • 在 ES6 之前,一个对象键 / 属性的列出顺序是依赖于具体实现
    • 多数引擎按照创建的顺序进行枚举
  • ES6 之后,拥有属性的列出顺序是由 [[OwnPropertyKeys]] 算法定义

    • (1) 首先,按照数字上升排序,枚举所有整数索引拥有的属性;
    • (2) 然后,按照创建顺序枚举其余的拥有的字符串属性名;
    • (3) 最后,按照创建顺序枚举拥有的符号属性。

      应用

  • 匿名函数的函数名推导

  • 提供了构造器调用方式这样的信息的元属性
  • 通过公开符号可以覆盖原本特性,比如对象到原生类型的类型转换。
    • 代理可以拦截并自定义对象的各种底层操作,Reflect 提供了工具来模拟它们。
  • 特性测试
    • 可尾递归优化
    • 把焦点从你的程序转移到 JavaScript 引擎功能本身。
    • 通过更多地了解环境能力,你的程序可以在运行时调整自己达到最优效果。

      image.png

8 ES6之后