title: ES6 - 最新提案
date: 2020.12.13
top:

categories:

tags:


do 表达式
throw 表达式
函数的部分执行
管道运算符
数值分隔符
Math.signbit()
双冒号运算符
Realm API

#!命令
import.meta

2020.12.13 星期日 11:25

本章介绍一些尚未进入标准、但很有希望的最新提案。

1 do 表达式

现在有一个提案,使得块级作用域可以变为表达式,也就是说可以返回值,办法就是在块级作用域之前加上do,使它变为do表达式,然后就会返回内部最后执行的表达式的值。
do表达式的逻辑非常简单:封装的是什么,就会返回什么。
do表达式的好处是可以封装多个语句,让程序更加模块化

// 值得一提的是,do表达式在 JSX 语法中非常好用。
return (
  <nav>
    <Home />
    {
      do {
        if (loggedIn) {
          <LogoutButton />
        } else {
          <LoginButton />
        }
      }
    }
  </nav>
)

2 throw 表达式

JavaScript 语法规定throw是一个命令,用来抛出错误,不能用于表达式之中。

// 报错
console.log(throw new Error());

上面代码中,console.log的参数必须是一个表达式,如果是一个throw语句就会报错。

现在有一个提案,允许throw用于表达式。

语法上,throw表达式里面的throw不再是一个命令,而是一个运算符。
为了避免与throw命令混淆,规定throw出现在行首,一律解释为throw语句,而不是throw表达式。

3 函数的部分执行

多参数的函数有时需要绑定其中的一个或多个参数,然后返回一个新函数。

function add(x, y) { return x + y; }
function add7(x) { return x + 7; }

// bind 方法
const add7 = add.bind(null, 7);
// 箭头函数
const add7 = x => add(x, 7);

上面两种写法都有些冗余。其中,bind方法的局限更加明显,它必须提供this,并且只能从前到后一个个绑定参数,无法只绑定非头部的参数。
现在有一个提案,使得绑定参数并返回一个新函数更加容易。这叫做函数的部分执行(partial application)。

const add = (x, y) => x + y;
const addOne = add(1, ?);

const maxGreaterThanZero = Math.max(0, ...);

根据新提案,?是单个参数的占位符,…是多个参数的占位符。以下的形式都属于函数的部分执行。

?和…只能出现在函数的调用之中,并且会返回一个新函数。
函数的部分执行,也可以用于对象的方法。

注意点

4 管道运算符

JavaScript 的管道是一个运算符,写作|>。它的左边是一个表达式,右边是一个函数。管道运算符把左边表达式的值,传入右边的函数进行求值。
管道运算符最大的好处,就是可以把嵌套的函数,写成从左到右的链式表达式。

// 传统的写法
exclaim(capitalize(doubleSay('hello')))
// "Hello, hello!"

// 管道的写法
'hello'
  |> doubleSay
  |> capitalize
  |> exclaim
// "Hello, hello!"

管道运算符只能传递一个值,这意味着它右边的函数必须是一个单参数函数。
如果是多参数函数,就必须进行柯里化,改成单参数的版本。

管道运算符对于await函数也适用。

5 数值分隔符

现在有一个提案,允许 JavaScript 的数值使用下划线(_)作为分隔符。
JavaScript 的数值分隔符没有指定间隔的位数,也就是说,可以每三位添加一个分隔符,也可以每一位、每两位、每四位添加一个。

数值分隔符有几个使用注意点。

除了十进制,其他进制的数值也可以使用分隔符。
注意,分隔符不能紧跟着进制的前缀0b、0B、0o、0O、0x、0X。

下面三个将字符串转成数值的函数,不支持数值分隔符。主要原因是提案的设计者认为,数值分隔符主要是为了编码时书写数值的方便,而不是为了处理外部输入的数据。

6 Math.signbit()

Math.sign()用来判断一个值的正负,但是如果参数是-0,它会返回-0。
JavaScript 内部使用 64 位浮点数(国际标准 IEEE 754)表示数值,IEEE 754 规定第一位是符号位,0表示正数,1表示负数。所以会有两种零,+0是符号位为0时的零值,-0是符号位为1时的零值。
实际编程中,判断一个值是+0还是-0非常麻烦,因为它们是相等的。

该方法的算法如下。
如果参数是NaN,返回false
如果参数是-0,返回true
如果参数是负值,返回true
其他情况返回false

7 双冒号运算符

但是,箭头函数并不适用于所有场合,所以现在有一个提案,提出了“函数绑定”(function bind)运算符,用来取代call、apply、bind调用。

函数绑定运算符是并排的两个冒号(::),双冒号左边是一个对象,右边是一个函数。该运算符会自动将左边的对象,作为上下文环境(即this对象),绑定到右边的函数上面。

如果双冒号左边为空,右边是一个对象的方法,则等于将该方法绑定在该对象上面。
如果双冒号运算符的运算结果,还是一个对象,就可以采用链式写法。

8 Realm API

Realm API 提供沙箱功能(sandbox),允许隔离代码,防止那些被隔离的代码拿到全局对象。

Realm 沙箱里面只能运行 ECMAScript 语法提供的 API,不能运行宿主环境提供的 API。
如果要解决这个问题,可以使用下面的代码。
globalTwo.console = globalOne.console;

Realm()构造函数可以接受一个参数对象,该参数对象的intrinsics属性可以指定 Realm 沙箱继承原始顶层对象的方法。

正常情况下,沙箱的JSON方法不同于原始的JSON对象。但是,Realm()构造函数接受{ intrinsics: ‘inherit’ }作为参数以后,就会继承原始顶层对象的方法。

9 #!命令

// 写在脚本文件第一行
// 写在模块文件第一行
#!/usr/bin/env node


# 以前执行脚本的方式
$ node hello.js
# hashbang 的方式
$ ./hello.js

有了这一行以后,Unix 命令行就可以直接执行脚本。
对于 JavaScript 引擎来说,会把#!理解成注释,忽略掉这一行。

10 import.meta

为 import 命令添加了一个元属性import.meta,返回当前模块的元信息。

import.meta只能在模块内部使用,如果在模块外部使用会报错。

这个属性返回一个对象,该对象的各种属性就是当前运行的脚本的元信息。具体包含哪些属性,标准没有规定,由各个运行环境自行决定。一般来说,import.meta至少会有下面两个属性。

(1)import.meta.url

注意,Node.js 环境中,import.meta.url返回的总是本地路径,即是file:URL协议的字符串,比如file:///home/user/foo.js。

(2)import.meta.scriptElement
import.meta.scriptElement是浏览器特有的元属性,返回加载模块的那个<script>元素,相当于document.currentScript属性。

11:52