ES6 PART3

214 阅读25分钟

ES6

1.Class的基本语法

1.1 简介
1.1.1 类的由来

传统的生成实例对象的方法是通过构造函数。

function Point(x, y) {
  this.x = x;
  this.y = y;
}
​
Point.prototype.toString = function () {
  return '(' + this.x + ', ' + this.y + ')';
};
​
var p = new Point(1, 2);

但是这种写法与其他面向对象的编程语言写法不太一样,所以ES6提供了更接近传统语言的写法,引入了类。类的大部分功能,ES5都可以做到,只是用class的写法让对象原型的写法更加清晰,更像传统语言的写法。上述代码可以改写为:

class Point {
  constructor(x, y) {//构造函数
    this.x = x;
    this.y = y;
  }
​
  toString() {
    return '(' + this.x + ', ' + this.y + ')';
  }
}

类的所有方法都定义在类的原型上。

class Point {
  constructor() {
    // ...
  }
​
  toString() {
    // ...
  }
​
  toValue() {
    // ...
  }
}
​
// 等同于Point.prototype = {
  constructor() {},
  toString() {},
  toValue() {},
};

需要注意的是constructor()属性是指向类本身的,这一点和ES5一致。而且类内部定义的所有方法都是不可枚举的,但是ES5的写法中,方法是可枚举的。

1.1.2 constructor方法

类的默认方法,生成实例对象时自动调用。

1.1.3 类的实例

通过new来创建一个新的类对象,需要注意的是,和ES5一样,实例的属性并不是定义在this对象上,而是定义在class中。

1.1.4 getter和setter

可以用get和set关键字,来拦截某个属性的存值函数和取值函数。

1.1.5 属性表达式
let methodName = 'getArea';
​
class Square {
  constructor(length) {
    // ...
  }
​
  [methodName]() {
    // ...
  }
}
1.1.6 Class表达式
const MyClass = class Me {
  getClassName() {
    return Me.name;
  }
};
1.1.7 注意
  1. 类和模块的内部默认严格模式,不需要使用use strict指定运行模式。
  2. 不存在变量提升。
  3. 有name属性。(好像没什么用)
  4. 可以在类内部定义Generator方法。
  5. 类的方法内部如果存在this,就默认指向类的实例。(少用,容易报错)
1.2 静态方法

类相当于一个实例的原型,所有类中定义的方法都会被实例继承。如果不希望实例继承某个方法,就可以在方法前加上static,只能通过类来调用,而不能用实例来调用。

1.3 实例属性的新写法

原本只能定义在constructor()方法内部的this上面,先在也可以直接定义到类的最顶层。

1.4 静态属性

只能通过类来访问。

1.5 私有方法和私有属性

如何实现私有化?(只能在类的内部访问的方法,外部不能访问)

  1. 定义私有方法时在变量命名前加上下划线,但是这种方法并不保险。
  2. 将私有方法移出类。(类内部的方法都是对外可见的)
  3. 利用Symbol值的唯一性,将私有方法命名为一个Smybol值。

私有属性的提案:在属性名前加上#。

in运算符:

原本可以用try...catch结构来判断是否存在某个私有属性。后面引入in来判断私有属性。

class A {
  use(obj) {
    if (#foo in obj) {
      // 私有属性 #foo 存在
    } else {
      // 私有属性 #foo 不存在
    }
  }
}
1.6 静态块

ES2022引入,用来初始化静态属性。

class C {
  static x = ...;
  static y;
  static z;
​
  static {
    try {
      const obj = doSomethingWith(this.x);
      this.y = obj.y;
      this.z = obj.z;
    }
    catch {
      this.y = ...;
      this.z = ...;
    }
  }
}
1.7 new.target属性

用来确定构造函数时怎么调用的,如果不是通过new或者Reflect.cobnnstruct()调用的,就会返回undefined。

Class内部调用new.target时,会返回当前的Class,子类继承父类时,new.target会返回子类。函数外部调用new.target就会报错。

2.Class的继承

2.1 简介

通过extends关键字实现继承。子类必须在构造函数中调用super方法来调用父类的构造函数建造父类的this对象。

ES5中的继承,本质是创造子类的实例对象this,然后再将父类的方法添加到this上面。而ES6设计是将父类实例对象的属性和方法,加到this上面,再用子类的构造函数修改this,与ES5相反。

父类的静态方法也会被子类继承。

2.2 Object.getPrototypeOf()

通过此方法从子类获取父类。

Object.getPrototypeOf(ColorPoint) === Point
// true
2.3 super关键字

super当函数调用时,代表父类的构造函数,ES6要求,子类的构造函数必须执行一次super函数。(哪怕子类不定义构造函数,默认的构造函数也会调用super)

super作为对象是,在普通方法中,指向父类的原型对象;在静态方法中,指向父类。

2.4 类的prototype属性和__proto__属性
  1. 子类的__proto__属性,表示构造函数的继承,总是指向父类。
  2. 子类的prototype属性的__proto__属性,表示方法的继承,总是指向父类的prototype属性。

实例的__proto__属性: 子类实例的__proto__属性的__proto__属性,指向父类实例的__proto__属性。意思就是子类的原型的原型是父类的原型。

2.5 原生构造函数的继承

以前原生构造函数是无法继承的。Boolean(),Number(),String(),Array(),Date(),Function(),RegExp(),Error(),Object()。继承原生构造函数后,类的行为与原生构造函数完全不一致。

ES6允许继承原生构造函数定义子类,ES6先新建父类的实例对象this,然后再用子类的构造函数修饰this,使服了爹所有行为都可以继承。

注意: 继承Object的子类有一个行为差异,无法通过super向父类Object传参。

2.6 Mixin模式的实现

多个对象合成一个新的对象,新的对象由各个组成成员的接口。

3.Module的语法

3.1 概述

为了更加静态化,让编译时就能确定模块的依赖关系,以及输入和输出的变量,ES6模块应运而生。ES6模块不是对象,而是通过export命令显示指定输出的代码,再通过import命令输入。

优点:

  1. 是编译时加载,所有可以静态分析,引入宏、类型检测。
  2. 不在需要UMP模块格式。
  3. 将来浏览器的新API能用模块格式提供,不再必须做成全局变量或者navigator对的属性。
  4. 不再需要对象作为命名空间。
3.2 严格模式

自动使用严格模式。

严格模式:

  1. 变量必须声明后再使用
  2. 函数的参数不能有同名属性,否则报错
  3. 不能使用with语句
  4. 不能对只读属性赋值,否则报错
  5. 不能使用前缀 0 表示八进制数,否则报错
  6. 不能删除不可删除的属性,否则报错
  7. 不能删除变量delete prop,会报错,只能删除属性delete global[prop]
  8. eval不会在它的外层作用域引入变量
  9. eval和arguments不能被重新赋值
  10. arguments不会自动反映函数参数的变化
  11. 不能使用arguments.callee
  12. 不能使用arguments.caller
  13. 禁止this指向全局对象
  14. 不能使用fn.caller和fn.arguments获取函数调用的堆栈
  15. 增加了保留字(比如protected、static和interface)
3.3 export 命令

用于规定模块的对外接口。可以导出对象、函数和类,但是必须与模块内部的变量建立一一对应的关系。

3.4 import 命令

用于输入其他模块提供的功能。

重命名:output as newName

不允许在加载模块的脚本中改写接口,但是可以改对象的属性,但是这样其他脚本里引用这个接口里的对象时,是被改写后的值了,所以不推荐改导入的接口。

3.5 模块的整体加载

写法:import * from xxx

3.6 export default 命令

默认导出,这样就可以在脚本中给导出的模块命名。

3.7 export 与 import 的复合写法
export { foo, bar } from 'my_module';
​
// 可以简单理解为
import { foo, bar } from 'my_module';
export { foo, bar };
3.8 模块的继承

在导出时,先export * from 父模块。但是这样会忽略父模块的default方法。

3.9 跨模块常量

用来写跨模块的常量,因为const是块级作用域,这里可以用export const PI = 3.14来定义常量。

如果需要的常量比较多,就建一个专门的constants目录,将写有很多常量的文件放在此目录下。

3.10 import()

简介: import语句实在编译时处理的,所以import放在if里面无意义,是静态加载的。但ES2020提案中引入了import()函数,支持动态加载模块。import(加载的模块的位置)。

使用场合:

  1. 按需加载:在需要的时候及在某个模块。

  2. 条件加载:放在if代码块中,根据不同情况加载。

  3. 动态的模块路径:和then一起用,根据参数加载不同的模块。

    import(f())
    .then(...);
    

注意点:

用import()加载后,这个模块会作为一个对象,当作then方法的参数,所以可以使用对象解构赋值的语法获取输出的接口。

4.Module的加载实现

4.1 浏览器加载

传统方法:

HTML用

defer要等到整个页面在内存中正常渲染结束(DOM 结构完全生成,以及其他脚本执行完成),才会执行;async一旦下载完,渲染引擎就会中断渲染,执行这个脚本以后,再继续渲染。、defer是“渲染完再执行”,async是“下载完就执行”。另外,如果有多个defer脚本,会按照它们在页面出现的顺序加载,而多个async脚本是不能保证加载顺序的。

加载规则:

浏览器加载模块是,也是用

async属性也可以用于模块加载中,只要加载完成,渲染引擎就会中断渲染立即执行。执行完成后再次渲染。

对于外部的模块脚本的注意点:

  1. 代码是在模块作用域之中运行,而不是在全局作用域运行。模块内部的顶层变量,外部不可见。
  2. 模块脚本自动采用严格模式,不管有没有声明use strict。
  3. 模块之中,可以使用import命令加载其他模块(.js后缀不可省略,需要提供绝对 URL 或相对 URL),也可以使用export命令输出对外接口。
  4. 模块之中,顶层的this关键字返回undefined,而不是指向window。也就是说,在模块顶层使用this关键字,是无意义的。
  5. 同一个模块如果加载多次,将只执行一次。
4.2 ES6 模块与 CommonJS 模块的差异
  1. CommonJS 模块输出的是一个值的拷贝,ES6 模块输出的是值的引用。
  2. CommonJS 模块是运行时加载,ES6 模块是编译时输出接口。
  3. CommonJS 模块的require()是同步加载模块,ES6 模块的import命令是异步加载,有一个独立的模块依赖的解析阶段。
4.3 Node.js 的模块加载方法

Js的两种模块,ES6和CommoJs模块。前者使用import和export,后者用require()和module.exports,后者是Node.js专用,与ES6模块不兼容。

ES6要求模块必须用.mjs后缀文件名,Node.js遇到.mjs文件就会认为他是ES6模块,默认启用严格模式。

package.json的main字段:

用来指定模块加载的入口文件。如果一个模块的后缀不是.mjs,格式又是ES6模块,就要在package.json里将type改成module。

package.json的exports字段(优先级高于main):

  1. 指定脚本或者子目录的别名。

    // ./node_modules/es-module-package/package.json
    {
      "exports": {
        "./features/": "./src/features/"
      }
    }
    ​
    import feature from 'es-module-package/features/x.js';
    // 加载 ./node_modules/es-module-package/src/features/x.js
    
  2. main的别名。

    {
      "exports": {
        ".": "./main.js"
      }
    }// 等同于
    {
      "exports": "./main.js"
    }
    
  1. 条件加载。
{
  "type": "module",
  "exports": {
    ".": {
      "require": "./main.cjs",
      "default": "./main.js"
    }
  }
}
  1. CommonJS模块加载ES6模块。
(async () => {
  await import('./my-app.mjs');
})();
  1. ES6模块加载CommonJS。

    import packageMain from 'commonjs-package';
    
  1. 同时支持两种格式的模块。

    import cjsModule from '../index.js';
    export const foo = cjsModule.foo;
    //方法二
    "exports":{
      "require": "./index.js",
      "import": "./esm/wrapper.js"
    }
    
  2. Node.js的内置模块

    // 整体加载
    import EventEmitter from 'events';
    const e = new EventEmitter();
    ​
    // 加载指定的输出项
    import { readFile } from 'fs';
    readFile('./foo.txt', (err, source) => {
      if (err) {
        console.error(err);
      } else {
        console.log(source);
      }
    });
    
  1. 加载路径:ES6必须给出脚本的完整路径,而且不能省略后缀名。
  2. 内部变量:ES6内,顶层的this指向undefined,CommonJS指向当前模块。
4.4 循环加载

耦合度过高,但无避免。

// a.js
var b = require('b');
​
// b.js
var a = require('a');

CommonJS模块的加载原理: 每一个模块都是一个脚本文件,require命令第一次加载该脚本就会执行整个脚本,然后在内存生成一个对象。用到这个模块的时候就会去缓存里取值。

CommonJS模块的循环加载: 由于CommonJS是加载时执行,所以循环加载时,只输出已执行的部分,未执行的部分不会输出。

ES6模块的循环加载: 因为ES6是动态引用。可以把可能会用到的参数改写成函数形式(不是函数表达式),因为函数具有提升作用,这样就不会报错了。

5.编程风格

5.1 块级作用域
  1. let代替var。
  2. let和const之间,优先用const。
5.2 字符串

一律用单引号或反引号,不要用双引号。动态字符串使用反引号。

5.3 解构赋值

优先使用解构赋值的情况:

  1. 用数组成员对变量赋值。
  2. 函数的参数是对象的成员。
  3. 函数返回多个值。
5.4 对象
  1. 单行定义的对象,最后一个成员不以逗号结尾。多行定义的对象,最后一个成员以逗号结尾。
  2. 对象尽量静态化,不要随便添加新的属性。
  3. 对象的属性名如果是动态的,创造对象的时候可以使用属性表达式定义。
5.5 数组
  1. 使用扩展运算符拷贝数组。
  2. 使用Array.from方法,将类似数组的对象转为数组。
5.6 函数、
  1. 立即执行的函数可以写成箭头函数。
  2. 匿名函数作参数时,尽量用箭头函数代替。
  3. 简单的、单行的、不会复用的函数,用箭头函数代替。
  4. 不要在函数体内使用arguments变量,使用rest运算符(...)代替。因为rest可以提供一个真的数组。
  5. 使用默认值语法设置函数参数的默认值。
5.7 Map 结构

只有模拟现实世界的实体对象才会使用Object,如果只是需要简单映射,Map结构就足够了。

5.8 Class
  1. 在需要prototype的操作时,使用Class。
  2. 使用extends实现继承,这样更简单,不会破坏instanceof运算。
5.9 模块
  1. 用import取代require()
  2. 用export取代module.exports
  3. 如果模块只有一个输出值,就使用export default,如果有多个值,尽量不要使用export default。
  4. 如果模块默认输出一个函数,函数名的首字母应该小写。
  5. 如果模块默认输出一个对象,对象名的首字母应该大写。
5.10 ESLint 的使用

6.读懂ECMAScript规格

6.1 术语

​ **抽象操作:**引擎内部的一些方法,外部不能调用。

​ **Record和field:**键值对的数据结构就是Record,每一组键值对称为field。

​ **[[Notation]]:**用[[Value]]、[[Writable]]等写法来指代field的键名。例如Obj就是一个Record,有一个Prototype属性,ES6中一般都用obj.[[Prototype]]这种写法啦替代obj.Prototype。所以使用[[Notation]]这种写法的属性,都是对象的内部属性。所有的JavaScript函数都有一个内部属性[[call]]用来允许该函数。

​ **Completion.Record:**每个语句都会返回一个Completion Record表示运行结果,每个Completion Record都有一个[[Type]]属性,表示运行结果的类型。

​ normal:运行正常。return、throw、break、continue都是abrupt completion,只用关注[[Type]]为throw的情况,就是运行出错了。其他三个都只出现在特定的个场景,不用考虑。

6.2 抽象操作的标准流程
  1. Let result be AbstractOp()
  2. ReturnIfAbrupt(result)
  3. return result

​ 先调用抽象操作得到result,如果时abrupt completion就直接返回,如果没有返回就是normal completion,将result的值设为resultCompletionRecord.[[value]],然后返回result。

6.3 相等运算符

​ x==y是如何实现的?

  1. 如果x不是正常值,中断执行。
  2. 如果y不是正常值,中断执行。
  3. 如果Type(x)与Type(y)相同,执行严格相同运算x===y。
  4. 如果x是null,y是undefined,返回true。
  5. 如果x是undefined,y是null,返回true。
  6. 如果Type(x)是数值,Type(y)是字符串,返回x == ToNumber(y)的结果。
  7. 如果Type(x)是字符串,Type(y)是数值,返回ToNumber(x) == y的结果。
  8. 如果Type(x)是布尔值,返回ToNumber(x) == y的结果。
  9. 如果Type(y)是布尔值,返回x == ToNumber(y)的结果。
  10. 如果Type(x)是字符串或数值或Symbol值,Type(y)是对象,返回x == ToPrimitive(y)的结果。
  11. 如果Type(x)是对象,Type(y)是字符串或数值或Symbol值,返回ToPrimitive(x) == y的结果。
  12. 返回false。
6.4 数组的空位

​ 数组的空位会反映在length属性,但是这个位置的值是未定义,如果读取,结果就是undefined。所以in运算符、数组的hasOwnProperty方法、Object.keys方法都取不到空位额属性名。

6.5 数组的map方法

​ 为什么数组的map方法会跳过空位?

  1. 得到当前数组的this对象。
  2. 如果报错就返回。
  3. 求出当前数组的length属性。
  4. 如果报错就返回。
  5. 如果map方法的参数callbackfn不可执行,就报错。
  6. 如果map方法的参数之中,指定了this,就让T等于该参数,否则T为undefined。
  7. 生成一个新的数组A,跟当前数组的length属性保持一致。
  8. 如果报错就返回。
  9. 设定K等于0。
  10. 只要k小于当前数组的length属性,就重复以下步骤:
    1. 设定Pk等于ToString(k),即将k转为字符串。
    2. 设定kPresent等于HasProperty(O, Pk),即求当前数组有没有指定属性
    3. 如果报错就返回
    4. 如果kPresent等于true,则进行下面步骤
      1. 设定kValue等于Get(O, Pk),取出当前数组的指定属性
      2. 如果报错就返回
      3. 设定mappedValue等于Call(callbackfn, T, «kValue, k, O»),即执行回调函数
      4. 如果报错就返回
      5. 设定status等于CreateDataPropertyOrThrow (A, Pk, mappedValue),即将回调函数的值放入A数组的指定位置
      6. 如果报错就返回
    5. k增加
  11. 返回A

​ 上述算法中,当处理一个全是空位的数组时,在10.2步,KPresent会报错,因为空位对应的属性名对于数组来说是不存在的,因此就会返回,就没有后面的操作了。

7.异步遍历器

7.1 同步遍历器的问题

image-20211122145050657.png

Iterator接口遍历的next方法必须是同步的,如果要用来处理异步操作可以把next()方法的返回值的value属性变为一个Thunk函数或者Promise对象,然后等待返回真正的值,但是done属性还是同步产生的。

7.2 异步遍历的接口

​ 调用遍历器的next方法,返回一个Promise对象。然后用then方法指定这个Promise对象的状态变为resolve以后的回调函数。这个回调函数的参数就是一个具有value和done两个属性的对象,和同步遍历器一样。

​ 异步遍历器的接口部署在Symbol.asyncIterator属性上。如果一个对象的Symbol.asyncIterator有值,就应该对它进行异步遍历。

​ **注意:**异步遍历器的next方法可以连续调用,不用等到上一步的Promise对象resolve以后再调用。此时next方法会累积起来,可以把next方法放在promise.all方法里面或者一次性调用所有的next方法,然后await最后一步操作。

7.3 for await...of

​ 用于遍历异步的Iterator接口。

async function f() {
  for await (const x of createAsyncIterable(['a', 'b'])) {
    console.log(x);
  }
}
// a
// b

​ 部署了asyncIterable操作的异步接口可以直接放入这个循环。

let body = '';

async function f() {
  for await(const data of req) body += data;
  const parsed = JSON.parse(body);
  console.log('got', parsed);
}

​ 如果Promise对象被reject,for await...of就会报错,要用try...catch捕捉。此循环也可遍历同步遍历器。

7.4 异步Generator函数

​ 异步Generator就是返回一个异步遍历器对象。语法上就是async函数和Generator函数结合。异步遍历器的设计目的之一,**就是Generator函数在处理同步操作和异步操作时,可以用同一套接口。**此函数也可以和for await...of一起用。

​ 此时函数有四种函数形式:普通函数,async函数,Generator函数和异步Generator函数。

​ 异步Generator函数也可以通过next方法的参数,接受外部传入的数据。

7.5 yield*函数
async function* gen1() {
  yield 'a';
  yield 'b';
  return 2;
}

async function* gen2() {
  // result 最终会等于 2
  const result = yield* gen1();
}

8.ArrayBuffer

​ ArrayBuffer对象、TypedArray视图和DataView视图是JavaScript操作二进制数据的一个接口。

​ 二进制数组由三类对象组成。

(1)ArrayBuffer对象:代表内存之中的一段二进制数据,可以通过“视图”进行操作。“视图”部署了数组接口,这意味着,可以用数组的方法操作内存。

(2)TypedArray视图:共包括 9 种类型的视图,比如Uint8Array(无符号 8 位整数)数组视图, Int16Array(16 位整数)数组视图, Float32Array(32 位浮点数)数组视图等等。

(3)DataView视图:可以自定义复合格式的视图,比如第一个字节是 Uint8(无符号 8 位整数)、第二、三个字节是 Int16(16 位整数)、第四个字节开始是 Float32(32 位浮点数)等等,此外还可以自定义字节序。

8.1 ArrayBuffer 对象

​ 该对象代表二进制数据的一段内存,不能直接读写,只能通过视图来读写。DataView视图的创建需要提供ArrayBuffer对象实例作为参数,另一种TypedArray视图不是一个构造函数,而是一组构造函数,代表不同的数据格式。

​ 几个属性和方法:

​ **ArrayBuffer.prototype.byteLength:**ArrayBuffer实例的byteLength属性,返回所分配的内存区域的字节长度。

​ **ArrayBuffer.prototype.slice() :**允许将内存区域的一部分,拷贝生成一个新的ArrayBuffer对象。

​ **ArrayBuffer.isView():**有一个静态方法isView,返回一个布尔值,表示参数是否为ArrayBuffer的视图实例。

8.2 TypedArray 视图

​ 一共有9种类型的视图:Int8Array、UintArray、Uint8ClampedArray、Int16Array、Uint16Array、Int32Array、Uint32Array、Float32Array、Float64Array。

​ 四种构造函数:TypedArray(buffer, byteOffset=0,length?)、TypedArray(length)、TypedArray(typedArray)、TypedArray(arrayLikeObject)。

​ 普通数组的操作方法和属性,对TypedArray数组完全适用。

​ **BYTES_PER_ELEMENT属性:**每一种视图的构造函数都有这个属性,来表示着中国数据类型占据的字节数。

​ **ArrayBuffer与字符串的相互转换:**使用原生的TextEncoder和TextDecoder方法。

​ **溢出:**TypedArray数组的溢出处理规则就是抛弃溢出的位,然后按照视图类型进行解释。、

​ **TypedArray.prototype.buffer:**TypedArray实例的buffer属性,返回整段内存区域对应的ArrayBuffer对象。

​ **TypedArray.prototype.byteLength, TypedArray.prototype.byteOffset:**前者返回TypedArray数组占据的内存长度,单位为字节。后者返回TypedArray数组从底层ArrayBuffer对象的哪个字节开始。

​ **TypedArray.prototype.lentgh:**表示TypedArray数组含有多少个成员。

​ **TypedArray.prototype.set():**用于复制数组。

​ **TypedArray.prototype.subarray():**对于TypedArray数组的一部分再建立一个新的视图。

​ **TypedArray.prototype.slice():**返回一个指定位置的新的TypedArray实例。

​ **TypedArray.of():**将参数转为TypedArray实例。

​ **TypedArray.from:**接受一个可遍历的数据结构作为参数,返回一个基于这个结构的TypedArray实例。

8.3 复合视图

​ 该视图的构造函数可以指定起始位置和长度,在同一段内存中可以依次存放不同类型的数据,这就是“复合视图”。

8.4 DataView 视图

​ 用来操作一段包括多种类型的数据。

8.5 二进制数组的应用

​ **AJAX:**传统上,服务器通过AJAX操作只能返回文本数据,responseType默认位text,因为XMLHttpRequest第二版XHR2允许服务器返回二进制数据,如果明确知道返回的二进制数据类型可以把返沪类型设为arraybuffer,不知道就设为blob。

​ **Canvas:**该元素输出的二进制像素数据,就是TypedArray数组。

​ **WebSocket:**通过ArrayBuffer,发送或接受二进制数据。

​ **Fetch API:**取回的数据就是ArrayBuffer对象。

​ **File API:**如果知道一个文件的二进制数据类型,也可以将这个文件读取为ArrayBuffer对象。

8.6 SharedArrayBuffer

​ Js是单线程的,但是Web worker是多线程的,所以ES2017引入了SharedArrayBuffer,允许Worker线程与主线程共享一块内存。

8.7 Atomics 对象

​ 用来防止两个线程同时修改某个地址,保证所有共享内存的操作都是原子性的,并且可以再所有线程内同步。

(1)Atomics.store(),Atomics.load():前者用向共享内存写入数据,后者用来从向共享内存读出数据。保证了读写操作的原子性。

(2)Atomics.exchange():写入数据的第二种方法,此方法是返回被替换的值,上面的方法是返回写入的值。

(3)Atomics.wait(),Atomics.notify():用于等待通知,相当于锁内存。

​ Atomics.wait(sharedArray,index,value,timeout):参数分别是共享内存的视图数组,试图数据的位置,该位置的预期值,表示休眠的时间(默认Infinity,整数,只能被Atomics.notify()唤醒)。

​ Atomics.notify(sharedArray,index,count):参数分别是共享内存的视图数组,视图数据的位置,需要唤醒的Worker线程的数( 默认Infinity)。

(4)运算方法:

Atomics.add(sharedArray, index, value)

Atomics.add用于将value加到sharedArray[index],返回sharedArray[index]旧的值。

Atomics.sub(sharedArray, index, value)

Atomics.sub用于将value从sharedArray[index]减去,返回sharedArray[index]旧的值。

Atomics.and(sharedArray, index, value)

Atomics.and用于将value与sharedArray[index]进行位运算and,放入sharedArray[index],并返回旧的值。

Atomics.or(sharedArray, index, value)

Atomics.or用于将value与sharedArray[index]进行位运算or,放入sharedArray[index],并返回旧的值。

Atomics.xor(sharedArray, index, value)

Atomic.xor用于将vaule与sharedArray[index]进行位运算xor,放入sharedArray[index],并返回旧的值。

(5)其他方法:

​ Atomics.compareExchange(sharedArray, index, oldval, newval):如果sharedArray[index]等于oldval,就写入newval,返回oldval。

​ Atomics.isLockFree(size):返回一个布尔值,表示Atomics对象是否可以处理某个size的内存锁定。如果返回false,应用程序就需要自己来实现锁定。

9.最新提案

9.1 do 表达式

​ 使块级作用域变为表达式,就可以返回值了。封装什么就返回什么。

9.2 throw 表达式

​ 允许throw用于表达式。

9.3 函数的部分执行

​ 绑定参数返回一个新函数。

注意点:

  1. 函数的部分执行使基于原函数的,如果原函数发生变化,部分执行生成的新函数夜壶立即反映这种变化。
  2. 如果预先提供的那个值是一个表达式,那这个表达式不会在定义时求值,而是再每次调用时求值。
  3. 如果新函数的参数多于占位符的数量,那么多余的参数将被忽略。
  4. ...只会被采集一次,如果函数的部分执行使用了多个...,那么每个...的值都将相同。
9.4 管道运算符

​ 将前一个操作的值传给后一个操作。

​ x|>f 等同于 f(x)

9.5 Math.signbit()

​ 用来判断一个值的正负。

​ 参数为NaN,返回false;为-0,返回true;为负值,返回true;其他情况都是false。

9.6 双冒号运算符

​ “函数绑定”运算符,用来取代call、apply、bind调用。

​ foo::bar 等同于 bar.bind(foo)

9.7 Realm API

​ 提供沙箱功能,允许隔离代码,防止被隔离的代码拿到全局对象。以前常用作为沙箱。

9.8 #!命令

​ 放在脚本的第一行,用来指定脚本的执行器。

9.9 import.meta
  1. import.meta.url:返回当前模块的URL路径。
  2. import.meta.scriptElement:返回加载模块的那个元素
9.10 JSON 模块

​ 目前只能使用fetch()加载JSON模块,现在允许用Import直接加载。

10.Decorator(用@来表示

10.1 类的装饰

​ 装饰器可以用来装饰整个类。

​ **注意:**装饰器对类的行为的改变,是代码编译时发生的,而不是在运行时。这意味着,装饰器能在编译阶段运行代码。也就是说,装饰器本质就是编译时执行的函数。

10.2 方法的装饰

​ 装饰器还可以装饰类的属性。

10.3 为什么装饰器不能用于函数?

​ 因为存在函数提升。

10.4 core-decorators.js
  1. @autobind:使得方法中的this对象绑定原始对象。
  2. @readonly:使属性或方法不可写。
  3. @override:检查子类的方法,是否正确覆盖了父类的同名方法。
  4. @deprecate:显示一条警告,表示该方法将被废除。
  5. @suppressWarnings:一致deprecated装饰器而导致的console.warn()调用。
10.5 使用装饰器实现自动发布事件

​ 使对象的方法被调用时,自动发出一个事件。

10.6 Mixin

​ 混入,在一个对象之中混入另一个对象的方法。

10.7 Trait

​ 与mixin相似,可以防止同名方法的冲突、排除混入某些方法、为混入的方法起别名等等。