参考规范:https://tc39.es/ecma262/。
Es6(es2015)
概述:
- 增强功能 包括模块、类声明、词法块作用域、迭代器和生成器,异步编程,解构模式以及适当的尾部调用。
- 内置库的扩展 支持数据抽象,包括Map, 集合Set, 以及二进制数值的数组。对象以及数组功能的扩展。
第六版的重点开发工作始于2009年,并于2015年6月通过,是经过了15年的努力的结果。作为常规版本存在,对增量语言和库增强功能提供了基础。
因为Es6已经是大家耳熟能详的,所以这里就不花篇幅了描述了,详细见:es6教程
Es7(es2016)
在es6提出后,TC-39决议认为耗时过长不利于可持续发展,于是将发布周期转换为每年发布一个版本,以确保新的语言特性能够更快的发展。
- Array.prototype.includes
- 新的指数运算符
**
- 新的语法错误
因为每年都会出一个版本,所以改动不会像es6一样那么大,一个个来看一下:
-
Array.prototype.includes
规定: 其实在es6中已经有 Array.prototype.includes 了, 只不过仍未完善,所以出现在了es7的版本中。
两个参数:第一个要搜索的值,第二个开始搜索的索引位置(可选,默认为0)。
返回值:找到返回true,找不到返回false。
let nums = [7, 8, 9]; // test console.log(nums.includes(7)); // true console.log(nums.includes(10)); // false console.log(nums.includes(7, 1)); // false
原因: 我们都知道在es6中存在多个类似功能的函数,比如:
findIndex(function)
、find(function)
、indexOf(value)
那么为什么还会有includes
呢?
-
首先,改名风波:
Array.prototype.includes 的前身提案名是Array.prototype.contains ,但是没有正式推出,于是很多网站自行hack了。
假如规范实现
contains
, 会出现contains
无法被for..in
读出来(因为js中所有原生提供的方法属性都是不可枚举的,只能通过属性的属性描述符获取getOwnPropertyDescriptor(Array.prototype, 'indexOf')
),而之前自行hack的contains的是可以被读出来的。会出现代码没变动,在新规范推出后产生bug的情况。因此初稿阶段,考虑新规范会使得许多现有网站出问题,所以改名
includes
。 -
其次,虽然我们用
indexOf
可以模拟includes
的行为,但出于语义描述不清楚的原因,所以要改。includes
表达了 是否包含该项的意思。indexOf
表达了查找数组中第一次出现对应元素的索引是什么,在针对返回的索引进一步处理的意思。has
为什么不叫has呢,因为has一般表示key中是否有(Map.prototype.has
),includes表示value中是否有。 -
那么他的判断是否相等的方式是什么呢?答案是:SameValueZero ,es6中有4种相等算法
松散相等比较:实现接口
==
严格相等比较:实现接口
===
,使用Array.prototype.indexOf
,Array.prototype.lastIndexOf
和case-matching
SameValue: 实现接口
Object.is()
-
SameValueZero: 实现接口借用了
Map
和Set
的方法。// 注意 // SameValue 中 +0 和 -0 是false Object.is(-0, +0); // false // SameValueZero 中 +0 和 -0 是true 和Set的判断很像不是 const s = new Set(); s.add(0); s.has(-0); // true
思考: 从探究整个提案的流程,可以看出一个新特性的出现需要考虑很多方面的问题。
类比之下,我们的项目在修改的时候 ,也需要考虑是否会对其他的东西产生影响,以及当前的方案是否最优。
思考一下,我们现在有用到查询数组中是否包含某个元素的情况,我猜测大多数都是用的indexOf
, 不过既然更优的api,官方已经提出来了 ,我们是否可以考虑换一下,毕竟可以减少码量,同时语义更加清晰不是。
let arr = [1, 2, 3];
if (arr.indexOf(1) > -1) {...}
// ==> 等价于
if (arr.includes(1)) {...}
-
语法变化:新的指数运算符
**
规定: 虽然Math.pow() 是已经支持了指数运算的方法,但是正式的运算符
**
看起来会会让开发者更容易阅读和理解。**
的左边是基数,右边是指数。 如下:let result = 10 ** 2; console.log(result); // 100 console.log(result === Math.pow(10, 2)) // true // 当然 支持 **= 即 a = a**b <==> a **= b
原因: 没错 ,就是为了更容易书写和阅读。
思考: 官方都时刻关心,可见容易书写和阅读多么的重要,我以为我们的代码可以更加的简洁和易读。推荐一个代码规范地址:https://github.com/ryanmcdermott/clean-code-javascript
-
新的语法错误
规定:在参数被解构或者有默认参数的函数中,禁止使用严格模式"use strict"指令。否则会抛出错误
// 正确 function good(first, second) { return first} // 抛出错误 原因:有默认参数并且使用了"use strict" function notGood(first, second = first) { "use strict"; return first; } // 抛出错误 原因:参数被解构并且使用了"use strict" function notGood2({first, second}) { "use strict"; return first; }
原因:实现运行在严格模式下的参数非常困难,比如:
function foo(first = this) {
"use strict";
return first
}
在严格模式下first
被认为是undefined,可是参数的默认值也可以是函数(通过apply , call指定上下文的调用方式),这就导致大多数的JavaScript引擎均不实现此功能,而是将其等同于全局对象。
思考:当我们遇到一个不好实现或者解决的问题的时候,做个智者,未尝不是一种明智的选择(绕过它/抛异常)。
Es8(es2017)
- Object、String的静态方法
- 在函数参数列表中尾随逗号并调用
- 异步功能、共享内存和原子
具体如下:
-
Object、String的静态方法
规定: 扩展了 Object和 String的功能
-
Object.values/Object.entries/Object.getOwnPropertyDescriptors()
Object.values
接受一个对象并返回所有值构成的数组,其顺序和for … in
的顺序相同。Object.entries
接受一个对象并返回数组,每个元素都是一个包含两个元素的数组。第一个是对象的key, 第二个是对象的value, 其顺序和for … in
的顺序相同。-
Object.getOwnPropertyDescriptors
获取属性描述符,即对象所有自己的属性描述符。包括value/writable/enumerable/configurable
let obj = { one: 'hh', second: 'dd', thrid: 'zz' } Object.values(obj); // ["hh", "dd", "zz"] Object.entries(obj); // [["one", "hh"], ["second", "dd"], ["thrid", "zz"]] Object.getOwnPropertyDescriptor(obj, 'one'); // {value: "one", writable: true, enumerable: true, configurable: true}
-
String padding
用另一个字符串填充填充当前字符串,直到结果字符串达到给定的长度为止,第二个参数默认为空格。String.padStart(长度, 填充字符串|' ')
从开头填充, 填充字符从左往右填充-
String.padEnd(长度, 填充字符串|' ')
从末尾向后填充,填充字符从左往右填充'abc'.padStart(10); // " abc" 'abc'.padStart(10, 'foo'); // "foofoofabc" 'abc'.padStart(6, '1234565'); // "123abc" 'abc'.padStart(8, '0'); // '00000abc' 'abc'.padStart(1); // 'abc' 'abc'.padEnd(10); // "abc " 'abc'.padEnd(10, 'foo');// "abcfoofoof" 'abc'.padEnd(6, '123456'); // 'abc123' 'abc'.padEnd(1); // 'abc'
-
思考:
Object.values
和 Object.keys
对应使用,
在对一些私密信息进行处理的时候,需要将一些数字进行加*隐藏,之前的写法可能是进行遍历数组逐个替换,现在有了padStart,可以这样试一下:
// 对身份进行隐藏显示
const fullNumber = '320321197803069527';
// old
const hideLen = fullNumber.length - 4;
let maskedNumber = ''
for (let i = 0; i < fullNumber.length; i++) {
if (i < hideLen) {
maskedNumber += '*';
}else {
maskedNumber += fullNumber[i]
}
}
console.log(maskedNumber); // **************9527
// new
const last4Digits = fullNumber.slice(-4);
const maskedNumberNew = last4Digits.padStart(fullNumber.length, '*');
console.log(maskedNumberNew); // **************9527 很简洁,思路很清晰不是
还有一种情形就是 给定字符串长度,超出的以省略号表示 (这里不考虑超出指定长度省略号表示),试试看?
const str = 'Tom Jackson';
// 期望输出: 'Tom...'
-
在函数参数列表中尾随逗号并调用
规定:在函数的参数列表中,最后一个参数以逗号结尾,如:
function myFun( p1, p2, ) {} myFun( 'param1', 'param2', )
原因:
- 如果最后一项改变了位置,则不必添加和删除逗号。
- 它可以帮助版本控制系统跟踪实际更改的内容。
-
异步功能
规定: 异步功能,指的是定义一个异步函数,也就是一个返回异步对象的函数,返回
Promise
或者函数上加async
标记。
原因:当异步功能有多个的时候,在一个函数中就会难以维护,违背了单一职责原则, 就需要拆分出不同的方法,因此就有了异步功能。
思考: 异步功能很好的将业务封装在了功能函数之中,调用者只需要关心成功和失败两种状态(resolve和reject),这样会使得代码更具有可读性。
这种场景目前用的最多的是在接口函数的编写中,它让我们不用太过关心接口的发送以及对返回数据的处理,只需要使用即可。
-
共享内存和原子
规定: 引入新的构造函数
SharedArrayBuffer
和 具有辅助函数的命名空间对象Atomics
, 用原子操作进行通信的程序,即使在并行CPU上也能确保定义良好的执行顺序。全局变量Atomics
,它的方法具有三个主要用例。- 同步化
- 等待通知
- 原子操作
Atomics.store() // 写入 Atomics.load() // 读取 Atomics.wait() // 等待 Atomics.wake() // 唤醒
原因: 虽然用到的地方很少,但是作为一门语言,对于并行操作还是要支持的。Web workers 将任务并行引入了 JavaScript 。还记得PWA吗?它的核心就在与本地开启了一个service worker,并对数据进行了缓存以及代理。
思考: 要实现并行,需要满足两点:一个是数据的并行,一个是代码的并行。这里
SharedArrayBuffer
作为共享阵列缓冲区是更高抽象的基本构建块。 它允许多个workers和主线程之间共享SharedArrayBuffer
对象的字节。同时Atomics
的 方法可以用来与其他 workers 进行同步。详情可以了解
https://www.html.cn/archives/7724
Es9(es2018)
- 解除模板字面量限制
- 正则表达式相关
- 对象解构中的rest操作符(...)/对象字面量中的spread操作符(...)
- Promise.prototype.finally 和 异步的迭代
具体如下:
-
解除模版字面量的限制
规定: 对于模版字面量中,如果存在非法转义字符 ,则将模版字面量设置为undefined。不过可以通过.raw访问原始值。
function tag(strs) { console.log(strs[0] === undefined); console.log(strs.raw[0] === '\\unicode and \\u{55}') } tag`\unicode and \u{55}`; // true true const badSequence = `bad escape sequence: \unicode`; // Uncaught SyntaxError: Invalid Unicode escape sequence
原因:解决模版字面量中存在非法转义字符报错的问题。
思考:在处理字符串的时候,我们多是需要注意处理需要进行转义的字符,此时有了这个特性 ,使的我们可以封装个公共的方法,放心的使用模版字面量,没必要进行而外的处理了。
-
正则表达式相关
-
.
的使用规定: es9支持了/s模式下,
.
可以匹配换行符。console.log(/./s.test("\n")); // true console.log(/./s.test("\r")); // true
原因:正则中
.
看似可以匹配任何字符,但换行符除外,当时大家用[\w\W]替代了.
。这个终究是个缺陷。于是增加了在 /s 模式下,.
=>[\w\W]
。
-
-
对捕获组进行命名:
规定: 使用捕获组需要通过指定捕获组名称来进行引用,每个名称是唯一的,避免冲突。语法:
?
const regExp = /(?
\d{4})-(? \d{2})-(? \d{2})/u; const result = regExp.exec('2020-10-21'); console.log(result.groups.year === '2020'); //true => result[1] === '2020' console.log(result.groups.month === '10'); //true => result[2] === '10' console.log(result.groups.day === '21');//true => result[3] === '21' // 其中 result[0] === '2020-10-21' // 命名捕获组这块 建议结合解构字面量一起使用 会更加清晰 const { groups: { year, month, day } } = regExp.exec('2020-10-21'); 原因:对捕获组命名可以让调用更加清晰。
-
支持了后行断言:
规定: es9之前只支持先行断言,现在支持了后行断言。
正向后行断言:(?<=...) 表示之前的字符串能匹配
const re = /(?<=\d{3}) meters/; // 表示 meters 之前 匹配三个数字 console.log(re.test('10 meters')); // false console.log(re.test('100 meters')); // true
负向后行断言:(?之前的字符串不能匹配
const re = /(?
原因: 弥补之前只能支持先行断言的缺陷。
思考: 正则中完整的断言定义:正/负向断言 与 先/后行断言 的笛卡尔积组合(互相组合得到4种结果)。
补充一下
正向先行断言
和负向先行断言
// 正向先行断言 const re = /meters(?= 10)/; console.log(re.test('meters 10')); // true console.log(re.test('meters 5')); // false // 负向先行断言 const re2 = /meters(?! 10)/; console.log(re.test('meters 10')); // false console.log(re.test('meters 5')); // true
-
Unicode 属性转义
规定:正则支持了更加强大的unicode匹配方式。在
/u
(修饰符u指unicode)模式下,通过\p{}大括号内提及unicode字符属性来匹配字符。可以用
\p{Number}
匹配所有数字可以用
\p{Alphabetic}
匹配所有Alphabetic元素,包括汉字,字母等可以用
\p{White_Space}
匹配空格、制表符、换行符。等等。
const regex = /^\p{Number}+$/u; regex.test("²³¹¼½¾"); // true regex.test("㉛㉜㉝"); // true regex.test("ⅠⅡⅢⅣⅤⅥⅦⅧⅨⅩⅪⅫ"); // true const str = "漢"; console.log(/\p{Alphabetic}/u.test(str)); // true // the \w shorthand cannot match 漢 console.log(/\w/u.test(str)); // false /^\p{White_Space}+$/u.test('\n \r \t'); // true
原因: 创建支持unicode的正则表达式不再麻烦,更加易读。使用Unicode属性转义符的代码将保持“最新”状态:每当Unicode标准得到更新时,ECMAScript引擎就会更新其数据。
-
对象解构中的rest操作符(...)/对象字面量中的spread操作符(...)
// 对象解构中的rest操作符(...) const {one, two, ...others} = { one: 'one', two: 'two', three: 'three', four: 'four'}; console.log(others); // { three: 'three', four: 'four' } // 对象字面量中的spread操作符(...) const nums = {one, two, ...others}; console.log(nums);// { one: 'one', two: 'two', three: 'three', four: 'four'};
**思考: **需要注意的是 在使用对象字面量的时候 如果有key重复,那么对象中后面的key的值会覆盖前面的。
-
Promise.prototype.finally 和 异步的迭代
-
Promise.prototype.finally
规定:一旦执行了Promise的resolve回调或者reject回调,最终都会执行finally回调
思考:总有一些相同的处理需要在resolve回调或者reject回调后处理,比如:资源的回收或者网络请求时loading的状态修改等。 平时我们会在resolve回调或者reject回调中都添加或者提取一个公共方法。现在会让我们更省力和美观。
-
异步的迭代
规定:这个是
for...of
的一种变体,他允许在由异步组成的可迭代对象上进行迭代。async function asyncFun() { const promises = [ fetch('f1.json'), fetch('f2.json'), fetch('f3.json'), ]; // 正常的迭代 for (const promiseItem of promises) { console.log(item); // => 输出 promise } // 正常的迭代 for await (const promiseItem of promises) { console.log(item); // => 输出 resolved response } }
思考: 适合的场景会是 多个异步同步执行,并对每个结果有统一的处理方式。比如:对文件的处理。
-
Es10(es2019)
Array.prototype.flat 和 Array.prototype.flatMap
Object.fromEntries
String.prototype.trimStart, String.prototype.trimEnd
Symbol的描述访问器
try { } catch {} 可省略catch参数的写法
对行分隔符
\u2028
和段落分隔符\u2029
的支持,JSON.stringify对特殊字符的转义处理
Array.prototype.sort
Function.prototype.toString
具体如下:
-
Array.prototype.flat 和 Array.prototype.flatMap
规定:
Array.prototype.flat(Number) ,降纬度数组,具体下降多少维度视参数而定,知道无法继续降维为止。默认为1
Array.prototype.flatMap(Function) ,flat和map功能的组合,接受一个函数, 返回一个处理后的降维了1个的数组。
const fruits = [['apple', 'banana'], ['苹果', '香蕉']]; console.log(fruits.flat()); // ['apple', 'banana', '苹果', '香蕉'] const fruits2 = [['apple'], ['banana'], ['苹果'], ['香蕉']]; const mappedAndFlatten = fruits2.flatMap((fruit, index) => [fruit + '_fruit']); console.log(mappedAndFlatten); // ["apple_fruit", "banana_fruit", "苹果_fruit", "香蕉_fruit"]
思考:
对比我们平常使用的解构方法, 需要递归遍历:
function reduceDimension(arr){ let ret = []; let targetArr = function(arr){ arr.forEach(function(item){ item instanceof Array ? targetArr(item) : ret.push(item); }); } targetArr(arr); return ret; } // 等同于 arr.flat(Infinity)
发现
flat(Infinity)
无疑更简洁(Infinity 作为数值中的无限大,可以保证数组能够完全解构)。
-
Object.fromEntries
规定: 将 由键值对组成的数组转换成一个对象, 与
Object.entries
功能相反。const fruits = { apple: '苹果', banana: '香蕉', orange: '橘子'}; const entries = Object.entries(fruits);// [["apple", "苹果"],["banana", "香蕉"] ,["orange", "橘子"]] const fromEntries = Object.fromEntries(entries); // { apple: '苹果', banana: '香蕉', orange: '橘子'}
思考: 这是针对这种特定数据结构的格式化 函数。对于一个对象如果需要同时修改对象的key-value, 直接修改不方便,我们可以先将一个对象通过
Object.entries
转换成数组,进行修改,完成后通过Object.fromEntries
转换回来。
-
String.prototype.trimStart, String.prototype.trimEnd
trimStart :去除字符串前面的空格
trimEnd: 去除字符串后面的空格
-
Symbol的描述访问器
规定: 通过 Symbol.prototype.description可以获取定义Symbol时的具体数值
const OPEN = Symbol('STATUS_OPEN'); // old 可以通过 toString() 来获取描述信息 console.log(OPEN.toString()); // "Symbol('STATUS_OPEN')" // new 通过 description属性获取 console.log(OPEN.description); // "STATUS_OPEN"
思考: Symbol的出现 让我们在常量的使用中大量使用,不过有时侯需要获取它的描述值来做一些判断。
const BANNER = { LEFT: Symbol('left'), RIGHT: Symbol('right'), } // 使用时候 if (BANNER.LEFT.description === 'left') { // do something }
-
try { } catch {} 可省略catch参数的写法
规定: catch后面的括号以及参数在不用的时候可以省略
try { } catch(err) {} try { } catch {} // 不会报错
思考:有些时候我们不需要对catch 的err 数据进行操作,这个时候如果传递了,但是未使用,那么eslint 可能会警告我们代码中存在可优化。本着不用不写的原则,省略无疑是很好的一种方式。
-
对行分隔符
\u2028
和段落分隔符\u2029
的支持规定: es10之前
'\u2028'
和'\u2020'
这种写法会报错,现在不会啦。
-
JSON.stringify对特殊字符的转义处理
规定: JSON.stringify 在处理
\uD800
到\DFFF
之间的字符,如果找不到对应的,直接返回转义后的字符。JSON.stringify('\uDF06\uD834'); // '"\\udf06\\ud834"'
-
Array.prototype.sort
规定: es10之前 V8对于包含10个以上的数组排序时采用了不稳定的快速排序算法,这个算法可能会改变原来数组里面相同数值的元素的先后关系。 通过采用原地排序保证数组中相同数值元素的先后关系。避免了顺序被改变的问题。
-
Function.prototype.toString
规定:返回一个表示函数源代码的字符串,包括格式和注释,比较精确。
function add(num1, num2) { /* add */ return num1 + num2; } add.toString(); // "function add(num1, num2) { // /* add */ // return num1 + num2; // }"
Es11(es2020)
String.prototype.matchAll
Promise.allSettled
export * as ns from 'module'
for-in 对于迭代对象顺序的统一
链合并运算符
?.
以及 Null判断运算符??
新增原始类型 BigInt ,可以表示任意精度的整数
import 动态导入
import.meta
标准化的全局对象
具体如下:
-
String.prototype.matchAll
规定: 返回所有与正则表达式相匹配字符串的结果的迭代器,包括捕获组。相比于match, 它提供的信息更全。
const regexp = /t(e)(st(\d?))/g; const str = 'test1test2'; // 曾经 我们通过 while 和 exec配合来获取比较全的匹配信息 let result = null; while((result = regexp.exec(str)) !== null) { console.log(result); } // > ["test1", "e", "st1", "1", index: 0, input: "test1test2", groups: undefined] // > ["test2", "e", "st2", "2", index: 5, input: "test1test2", groups: undefined] // 现在 我们用matchAll 就好了 const array = [...str.matchAll(regexp)]; for (result of array) { console.log(result); } // > ["test1", "e", "st1", "1", index: 0, input: "test1test2", groups: undefined] // > ["test2", "e", "st2", "2", index: 5, input: "test1test2", groups: undefined]
-
Promise.allSettled
规定:对比Promise.all 方法,Promise.allSettled 当每个Promise 无论失败还是成功都会走回调函数。
const promiseArr = [ Promise.resolve(100), Promise.reject(null), Promise.reject(new Error('Oh No')), ] Promise.all(promiseArr).then(results => { // 不会执行 console.log('All Promise Settled', results); }); Promise.allSettled(promiseArr).then(results => { console.log('All Promise Settled', results); }); // [{status: "fulfilled", value: 100}, // {status: "rejected", reason: null}, // {status: "rejected", reason: Error: Oh No at
:4:20}] 思考:当我们在进行多个接口请求的时候,如果通过Promise.all ,不能保证回调一定会执行,这时候如果有loading状态的,无疑会出现问题,无法在回调中更新。如果我们并不关心接口是否调用成功,这个时候Promise.allSettled 无疑是更合适的。
-
export * as ns from 'module'
规定: 在一个文件中引用另外一个文件并导出他的一种缩写。
export * as utils from './utils.mjs' // 等价于 ==> import * as utils from './utils.mjs' export { utils }
思考: 在react中使用组件的时候,我们会对一个目录下的多个文件 通过
index.js
导出。好处在于当前文件中并不会真的导入文件对象的引用,比如以上的utils为undefined。// index.js old import * as Header from './header.jsx' import * as Footer from './footer.jsx' // ... export { Header } export { Footer } // ... // index.js new export * as Header from './header.jsx'; export * as Footer from './footer.jsx'; export * as Body from './body.jsx'; export * as Nav from './nav.jsx';
-
for-in 对于迭代对象顺序的统一
规定:虽然浏览器自己保持了
for in
的顺序,但是在es11中进行了标准化。
-
链合并运算符
?.
以及 Null判断运算符??
规定:链合并运算符 避免了调用时左侧的对象是否为null或undefined的判断,Null判断运算符 为属性值为null或undefined时,指定默认值
const userName = (list && list.info && list.info.base && list.info.base.userName ) || 'userName'; // => 等价于 const userName = list?.info?.base?.userName ?? 'userName';
思考:
??
类似于||
,区别在于:??
只当左侧为null/undefined
的时候才会指定右侧默认值-
||
当左侧所有转换后boolean值为false 的时候会指定右侧默认值(包括 0 )在某些情况下 我们并不需要这种结果,因此结合业务考虑使用哪一种。
-
新增原始类型 BigInt ,可以表示任意精度的整数
规定: 顾名思义,就是用来存放Number放不下的数字
let maxNum = 9007199254740992; // Number的最大值 pow(2, 53) - 1 console.log(maxNum + 1); // 9007199254740992 maxNum = 9007199254740992n; console.log(maxNum + 1n); // 9007199254740993n console.log(typeof maxNum === 'bigint'); // true console.log(maxNum + 1); // Uncaught TypeError: Cannot mix BigInt and other types
思考: 在进行大数计算的时候,之前需要通过竖式运算,一种从个位往前一个一个相加求和的方式,比较麻烦。现在直接用BigInt就可以了
-
import 动态导入
规定: 返回所请求模块的模块名称空间对象的Promise,所以也可以使用async/await
const modulePath = './file.js'; import(modulePath) .then((module) => { // do something }); // or (async function() { const modulePath = './file.js'; const module = await import(modulePath); // do somthing })();
思考:import 的动态导入支持 一方面可以让我们按需加载,另一方面的可能会出现滥用的可能,对静态文件分析造成一定程度上的阻碍(比如:tree-shaking 就是依赖于 import的静态导入,如果都做成了动态导入,那么该功能的能力会收到很大的影响)。
-
import.meta
规定: 可以通过import.meta获取module的信息
// module.js console.log(import.meta) // 得到一个对象 包含模块的信息{ url: "file:///home/user/module.js" }
思考:在使用一个模块时,有时需要知道模板本身的一些信息(比如模块的路径)。这是import.meta的用处。
-
标准化的全局对象
规定:全局变量之前在各个平台未统一,需要自行标准化。
var getGlobal = function () { if (typeof self !== 'undefined') { return self; } if (typeof window !== 'undefined') { return window; } if (typeof global !== 'undefined') { return global; } throw new Error('unable to locate global object'); } // getGlobal() 等同于 globalThis
其他可能的提案
do表达式
双冒号运算符
管道运算符
#!
命令
具体如下:
-
do表达式
规定: 通过在块级作用域前添加do , 让块级作用域可以变为表达式。也就是说可以返回值。do 也提供了单独的作用域。do表达式的逻辑:封装什么就会返回什么
let x = do { let t = f(); t * t + 1; }; // x的值等于 t*t+1的结果 // 等同于 <表达式> do { <表达式>; } // 等同于 <语句> do { <语句> }
思考:我们在写jsx的时候,do很好用。以下避免了三元运算符的使用,同时代码意图比较清晰。
return ( )
-
双冒号运算符
规定:即“函数绑定”(function bind)运算符,用来取代
call
、apply
、bind
调用。左边是一个对象,右边是一个函数foo::bar; // 等同于 bar.bind(foo); foo::bar(...arguments); // 等同于 bar.apply(foo, arguments)
-
管道运算符
规定:JavaScript 的管道是一个运算符,写作
|>
。它的左边是一个表达式,右边是一个函数。支持await。x |> f // 等同于 f(x) x |> await f // 等同于 await f(x)
思考:管道运算符最大的好处,就是可以把嵌套的函数,写成从左到右的链式表达式。
function doubleSay (str) { return str + ", " + str; } function capitalize (str) { return str[0].toUpperCase() + str.substring(1); } function exclaim (str) { return str + '!'; } let msg = 'hello'; const result = msg |> doubleSay |> capitalize |> exclaim; // 等同于 const resultNew = exclaim(capitalize(doubleSay(msg)));
-
#!
命令规定: JavaScript 脚本引入了
#!
命令,写在脚本文件或者模块文件的第一行。Unix 命令行就可以直接执行脚本。同时对于JavaScript 引擎来说,会把#!
理解成注释,忽略掉这一行。// 写在脚本文件第一行 hello.js #!/usr/bin/env node 'use strict'; console.log(1); # 以前执行脚本的方式 $ node hello.js # 现在执行 的方式 $ ./hello.js