-
Notifications
You must be signed in to change notification settings - Fork 3.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
精读《TC39, ECMAScript, and the Future of JavaScript》 #21
Comments
TC39是什么?包括哪些人?一个推动JavaScript发展的委员会,由各个主流浏览器厂商的代表构成。 为什么会出现这样一个组织?从标准到落地是一个漫长的过程,相信大家上次阅读web components就能体会到标准到浏览器支持是一个漫长的过程。 TC39 这群人主要的工作是什么?制定ECMAScript标准,标准生成的流程,并实现。 标准的流程是什么样的?包括五个步骤:
目前每个stage 包含什么特性呢?
https://github.com/tc39/proposals/blob/master/stage-0-proposals.md 额外注意的是: Cancellation API 已经成熟要录入到 stage1,它提供程序主动终止 asyn 操作的API。
一般可以去哪里查看TC39标准的进程呢?stage0 的提案 https://github.com/tc39/proposals/blob/master/stage-0-proposals.md 我们怎么在程序中应用这些新特性呢?babel的插件 babel-presets-stage-0 babel-presets-stage-1 babel-presets-stage-2 babel-presets-stage-3 babel-presets-stage-4 |
粗看了一遍,搞明白 TC39,ECMA 与 stage0~4 之间的关系了。后续会整理出来给懒人阅读。 文章末尾提到了对 babel webpack prepack 的积极看法,我也对这些新东西持续关注中,不过本次精读主要会讨论一下全套 stage0~stage4 的提案,纯属个人看法,以后若被打脸,麻烦忘了这期精读。。 |
今天看到 stage0 又新增一员——Pattern Matching Syntax,突然兴奋,这绝对是我最期待的语法之一了。当然也不能高兴太早,按照这期文章中的说法,stage0 还只是 “strawman”,镜中花水中月。 当然从 README.md 中不难发现,模式匹配是有 Object rest 的,而这个语法现在是在 stage3,已经有了 babel 的 Plugin,不太清楚这种依赖性的提案会怎么处理,不过如果要等到 Object rest 并入 master 才会去允许模式匹配提案的进行的话,那未免有点扫兴了(摊手 |
Stage 4 大家庭Array.prototype.includesassert([1, 2, 3].includes(2) === true);
assert([1, 2, 3].includes(4) === false);
assert([1, 2, NaN].includes(NaN) === true);
assert([1, 2, -0].includes(+0) === true);
assert([1, 2, +0].includes(-0) === true);
assert(["a", "b", "c"].includes("a") === true);
assert(["a", "b", "c"].includes("a", 1) === false); 这个 api 很方便,没有悬念的进入了草案中。 曾争议过是否使用 Array.prototype.contains,但由于 不兼容因素 而换成了 includes。 Exponentiation operator// x ** y
let squared = 2 ** 2;
// same as: 2 * 2
let cubed = 2 ** 3;
// same as: 2 * 2 * 2 列表中进入了 stage4,但其 git 仓库 readme 还停留在 stage3。。 虽然已经有 Object.values/Object.entriesObject.values({
a: 1,
b: 2,
c: Symbol(),
}) // [1, 2, Symbol()]
Object.entries({
a: 1,
b: 2,
c: Symbol(),
}) // [["a", 1], ["b", 2], ["c", Symbol()]] 也没有什么争议,Object.keys 都有了,获取 values、entries 也是合理的。 TC39 会议中有争辩过为何不返回迭代器,原因挺有意思,因为 Object.keys 返回的是数组,所以这两个 api 还是与老大哥统一吧。 String.prototype.padStart / String.prototype.padEnd"foo".padStart(5, "bar") // bafoo
"foo".padEnd(5, "bar") // fooba 解决了字符串补齐需求,很棒! Object.getOwnPropertyDescriptorsObject.getOwnPropertyDescriptors({ a: 1})
// { a: {
// configurable: true,
// enumberable: true,
// value: 1,
// writable: true
// } } 特别是 babel 与 typescript 处理 class property decorator 方式不同的时候(typescript 处理得更成熟一些),会导致 babel 处理装饰器时,成员变量不设置默认值时,configurable 默认为 false,通过这个函数检查变量的配置很方便。 Trailing commas in function parameter lists and callsfunction clownPuppiesEverywhere(
param1,
param2, // Next parameter that's added only has to add a new line, not modify this line
) { /* ... */ } js 终于原生支持了,以前不支持的时候多加逗号还会报错,需要预编译工具删除最后一个逗号,现在终于名正言顺了。 Async functions这个不用多说了,都说好用。 Shared memory and atomics这是 ECMAScript 共享内存与 Atomics 的规范,涉及内容非常多,主要涉及到 asm.js。 asm.js 是一种性能解决方案,比如可以定义一个精确的 64k 堆: var heap = new ArrayBuffer( 0x10000 ) Lifting template literal restrictionstyled.div`
background-color: red;
`
|
padStart padEnd 早点写入规范说不定就没当年的 leftpad 事件了 |
cool |
Stage 3 大家庭Function.prototype.toString revision对函数的 toString 规则进行了修改:http://tc39.github.io/Function-prototype-toString-revision/#sec-function.prototype.tostring 当调用内置函数或 global为 ECMAScript 规范添加 var getGlobal = function () {
// the only reliable means to get the global object is
// `Function('return this')()`
// However, this causes CSP violations in Chrome apps.
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');
}; 虽然前端环境与 nodejs 区别很大,但既然提案进入了 stage3,说明大家非常关注 js 整体的生态,只要整体方向良性发展,相信不久将会进入 stage4。 Rest/Spread Propertieslet { x, y, ...z } = { x: 1, y: 2, a: 3, b: 4 };
x; // 1
y; // 2
z; // { a: 3, b: 4 } 不得不说,非常常用,而且 babel,jsTransform,typescript 均支持,感觉很快会进入 stage4. Asynchronous Iterationconst { value, done } = syncIterator.next();
asyncIterator.next().then(({ value, done }) => /* ... */); for await (const line of readLines(filePath)) {
console.log(line);
} async function* readLines(path) {
let file = await fileOpen(path);
try {
while (!file.EOF) {
yield await file.readLine();
}
} finally {
await file.close();
}
} 异步迭代器实现了 async await 与 generator 的结合。 语法的修改一定不能为了方便(在 ECMAScript 中可能出现),但这种混杂的方式容易让人混淆 await 与 generator 之间的关系,是否进入 stage4 还需仔细斟酌。 import()import(`./section-modules/${link.dataset.entryModule}.js`)
.then(module => {
module.loadPageInto(main);
})
.catch(err => {
main.textContent = err.message;
}); 这个提案主要增加了函数调用版的 import,而 webpack 等构建工具也在积极实现此规范,并作为动态加载的最佳范例。希望这种“官方 Amd”可以早日加入草案。 RegExp Lookbehind Assertionsjavascript 正则表达式一直不支持后行断言,不过现在已经进入 stage3,相信不久会进入 stage4. 前向断言: /\d+(?=%)/.exec("100% of US presidents have been male") // ["100"]
/\d+(?!%)/.exec("that’s all 44 of them") // ["44"] 后向断言: /(?<=\$)\d+/.exec("Benjamin Franklin is on the $100 bill") // ["100"]
/(?<!\$)\d+/.exec("it’s is worth about €90") // ["90"] 后向断言会获取某个字符后面跟的内容,在获取美刀等货币单位上有很大用途。chrome 可以使用 RegExp Unicode Property Escapesconst regexGreekSymbol = /\p{Script=Greek}/u;
regexGreekSymbol.test('π');
// → true 以上 虽然可以通过引用希腊字符(或者其他编码)表做正则处理,当每当更新表时,更新起来会非常麻烦,不如让浏览器原生支持 RegExp named capture groupslet re = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/u;
let result = re.exec('2015-01-02');
// result.groups.year === '2015';
// result.groups.month === '01';
// result.groups.day === '02';
// result[0] === '2015-01-02';
// result[1] === '2015';
// result[2] === '01';
// result[3] === '02'; let {groups: {one, two}} = /^(?<one>.*):(?<two>.*)$/u.exec('foo:bar');
console.log(`one: ${one}, two: ${two}`); // prints one: foo, two: bar 同时,还支持 反向引用能力,可以通过 let duplicate = /^(?<half>.*).\k<half>$/u;
duplicate.test('a*b'); // false
duplicate.test('a*a'); // true 总体来看非常给力,毫无意义的下标也是正则反人类的原因之一,这个提案通过的话,正则会变得更加可读。 s (dotAll) flag for regular expressions/foo.bar/s.test('foo\nbar');
// → true 通过添加了新的标识符 /foo[^]bar/.test('foo\nbar');
// → true
/foo[\s\S]bar/.test('foo\nbar');
// → true 从保守派角度来看,可能因为掌握了 Legacy RegExp features in JavaScript该提案主要针对 RegExp 遗留的静态属性进行梳理。平时很少接触,希望了解的人解读一下。 |
Stage2 大家庭function.sent metapropertygenerator 的第一个 function *adder(total=0) {
let increment=1;
while (true) {
switch (request = yield total += increment) {
case undefined: break;
case "done": return total;
default: increment = Number(request);
}
}
}
let tally = adder();
tally.next(0.1); // argument will be ignored
tally.next(0.1);
tally.next(0.1);
let last=tally.next("done");
console.log(last.value); //1.2 instead of 0.3 当引入 function *adder(total=0) {
let increment=1;
do {
switch (request = function.sent){
case undefined: break;
case "done": return total;
default: increment = Number(request);
}
yield total += increment;
} while (true)
}
let tally = adder();
tally.next(0.1); // argument no longer ignored
tally.next(0.1);
tally.next(0.1);
let last=tally.next("done");
console.log(last.value); //0.3 这是个很棒的特性,也不存在语意兼容问题,但 api 还是比较怪,而且自此 yield 接收参数也变得没有意义,况且如今 async await 逐渐成为主流,这种修正没有强烈刚需。而且 yield 的语意本身没有错误,这个提案比较危险。 String.prototype.{trimStart,trimEnd}既然 Class Fieldsclass Counter extends HTMLElement {
x = 0;
#y = 1;
} 类成员变量,有了它 js 就完整了。虽然觉得似有变量符号很难看,但成员变量绝对是非常有用的语法,在 react 中已经很常用了: class Todo extends React.Component {
state = { //.. }
} Promise.prototype.finally就像 但是库实现不足以使其纳入标准,只有当这些需求足够常用和通用时才会考虑。第三方库可能从竞争力角度考虑,多支持一种功能、少些一行代码就是多一份筹码,但语言规范是不能在乎这些的。 Class and Property Decorators类级别的装饰器已经进入 stage2 了,但现代前端开发中已经非常常用,很可能会进一步进入 stage3. 如果这个提案被废弃,那么大部分现代 js 代码将面临大量使用不存在语法的窘境。不过乐观的是,目前还找不到更好的装饰器替代方案,而在 python 中也存在装饰器模式可以参考。 Intl.Segmenter// Create a segmenter in your locale
let segmenter = new Intl.Segmenter("fr", {granularity: "word"});
// Get an iterator over a string
let iterator = segmenter.segment("Ceci n'est pas une pipe");
// Iterate over it!
for (let {segment, breakType} of iterator) {
console.log(`segment: ${segment} breakType: ${breakType}`);
break;
}
// logs the following to the console:
// segment: Ceci breakType: letter
虽然不是刚需,但 js 作为网页交互的语言,确实需要解决分析用户输入的问题。 Arbitrary-precision Integers新增了基本类型:整数类型,以及 Integer api 与字面语法 1234n。 目前 js 使用 64 位浮点数处理所有计算,直接导致了运算效率低下,这个提案弥补了 js 的计算缺点,希望可以早日进入草案。 提案名称由 Integer 改为 BigInt。 import.meta提出了使用 目前 js 可以通过如下方式获取脚本信息: const theOption = document.currentScript.dataset.option; 这样污染了全局变量,脚本信息应当存储在脚本作用域中,因此提案希望将脚本信息存储在脚本的 (async () => {
const response = await fetch(new URL("../hamsters.jpg", import.meta.url));
const blob = await response.blob();
const size = import.meta.scriptElement.dataset.size || 300;
const image = new Image();
image.src = URL.createObjectURL(blob);
image.width = image.height = size;
document.body.appendChild(image);
})(); |
重点关注了 stage0 的 cancellation API, 这个特性我感觉会升级的特别快... 为啥会出现这个api
关键的类
尝鲜方法和例子 |
Stage1 大家庭Date.parse fallback semantics通过字符串格式化日期一直是跨浏览器的痛点,本提案希望通过新增
正如提案所说,“如果字符串不满足 ISO 8601 格式,可以返回你想返回的任何值” 这样迷惑开发者是没有任何意义的,这样只会让开发者越来越不相信 js 是跨平台的语言。 这么重要的规范居然才 stage1,必须要顶上去。 export * as ns from "mod"; statementsexport * as someIdentifier from "someModule"; 很方便的 api,很多时候希望导出某个模块的全部接口,又不希望命名冲突,可以少写一行 import。 export v from "mod"; statements这个提案与 export * as ns from "mod"; statements Observable可观察类型可以从 dom 事件、轮询等触发事件中创建监听并订阅: function listen(element, eventName) {
return new Observable(observer => {
// Create an event handler which sends data to the sink
let handler = event => observer.next(event);
// Attach the event handler
element.addEventListener(eventName, handler, true);
// Return a cleanup function which will cancel the event stream
return () => {
// Detach the event handler from the element
element.removeEventListener(eventName, handler, true);
};
});
}
// Return an observable of special key down commands
function commandKeys(element) {
let keyCommands = { "38": "up", "40": "down" };
return listen(element, "keydown")
.filter(event => event.keyCode in keyCommands)
.map(event => keyCommands[event.keyCode])
}
let subscription = commandKeys(inputElement).subscribe({
next(val) { console.log("Received key command: " + val) },
error(err) { console.log("Received an error: " + err) },
complete() { console.log("Stream complete") },
}); 这个名字和 Object.observe 很像,不过没什么关系。该功能已经被 RxJS、XStream 等库实现。 String#matchAll目前正则表达式想要匹配全部的语法不够语义化,提案希望通过 现在匹配全部只能使用 WeakRefs有点像 OC 的弱引用,当对象被释放时,当前持有弱引用的对象也会被 GC 回收,但似乎还没有开始讨论,js 越来越底层了? Frozen Realms增强了 Realms 提案,利用不可变结构,实现结构共享。 Math ExtensionsMath 函数的拓展包含的函数:https://rwaldron.github.io/proposal-math-extensions/ 这个函数拓展很给力,特别是设计游戏,计算角度的时候: Math.DEG_PER_RAD // Math.PI / 180
of and from on collection constructors该提案设计了 Set、Map 类型的 问题由于: Reflect.construct(Array, [1,2,3]) // [1,2,3]
Reflect.construct(Set, [1,2,3]) // Uncaught TypeError: undefined is not a function 因为 Set 接收的参数是数组,而 construct 会调用 Set、Map 在国内环境用的比较少,也很少有人计较这个问题,不过从技术角度来看,确实需要修复。。 Generator arrow functions (=>*)还是挺有必要的,毕竟都出箭头函数了,也要支持一下箭头函数的 generator 语法。 Promise.try同理,各大库都有实现,好处是所有错误都可以通过 Null Propagation超级有用,看代码就知道了: const firstName = message.body?.user?.firstName || 'default' 该功能完全等同: const firstName =
()message &&
message.body &&
message.body.user &&
message.body.user.firstName) || 'default' 希望立刻进入 stage4. Math.signbit: IEEE-754 sign bit当值为 负数 或 -0 时返回 Error stacks提案建议将 do expressionsreturn (
<nav>
<Home />
{
do {
if (loggedIn) {
<LogoutButton />
} else {
<LoginButton />
}
}
}
</nav>
)
Realmslet realm = new Realm();
let outerGlobal = window;
let innerGlobal = realm.global;
let f = realm.evalScript("(function() { return 17 })");
f() === 17 // true
Reflect.getPrototypeOf(f) === outerGlobal.Function.prototype // false
Reflect.getPrototypeOf(f) === innerGlobal.Function.prototype // true
Temporal与 var ldt = new temporal.LocalDateTime(2017, 12, 31, 23, 59);
var ldt = new temporal.LocalDateTime(2017, 12, 31, 23, 59, options);
var ldt = new temporal.LocalDateTime(2017, 12, 31, 23, 59, 59);
var ldt = new temporal.LocalDateTime(2017, 12, 31, 23, 59, 59, options);
var ldt = new temporal.LocalDateTime(2017, 12, 31, 23, 59, 59, 123);
var ldt = new temporal.LocalDateTime(2017, 12, 31, 23, 59, 59, 123, options);
var ldt = new temporal.LocalDateTime(2017, 12, 31, 23, 59, 59, 123, 456789);
var ldt = new temporal.LocalDateTime(2017, 12, 31, 23, 59, 59, 123, 456789, options);
// add/subtract time (Dec 31 2017 23:00 + 2h = Jan 1 2018 01:00)
var addHours = new temporal.LocalDateTime(2017, 12, 31, 23, 00).add(2, 'hours');
// add/subtract months (Mar 31 - 1M = Feb 28)
var addMonths = new temporal.LocalDateTime(2017,03,31).subtract(1, 'months');
// add/subtract years (Feb 29 2020 - 1Y = Feb 28 2019)
var subtractYears = new temporal.LocalDateTime(2020, 02, 29).subtract(1, 'years'); 还自带时区转换 api 等等,如果进入草案,可以放弃 moment 这个重量级库了。 Float16 on TypedArrays, DataView, Math.hfround由于大多数 WebGL 纹理需要半精度以上的浮点数计算,推荐了 4 个 api:
Atomics.waitNonblockingvar sab = new SharedArrayBuffer(4096);
var ia = new Int32Array(sab);
ia[37] = 0x1337;
test1();
function test1() {
Atomics.waitNonblocking(ia, 37, 0x1337, 1000).then(function (r) { console.log("Resolved: " + r); test2(); });
}
var code = `
var ia = null;
onmessage = function (ev) {
if (!ia) {
console.log("Aux worker is running");
ia = new Int32Array(ev.data);
}
console.log("Aux worker is sleeping for a little bit");
setTimeout(function () { console.log("Aux worker is waking"); Atomics.wake(ia, 37); }, 1000);
}`;
function test2() {
var w = new Worker("data:application/javascript," + encodeURIComponent(code));
w.postMessage(sab);
Atomics.waitNonblocking(ia, 37, 0x1337).then(function (r) { console.log("Resolved: " + r); test3(w); });
}
function test3(w) {
w.postMessage(false);
Atomics.waitNonblocking(ia, 37, 0x1337).then(function (r) { console.log("Resolved 1: " + r); });
Atomics.waitNonblocking(ia, 37, 0x1337).then(function (r) { console.log("Resolved 2: " + r); });
Atomics.waitNonblocking(ia, 37, 0x1337).then(function (r) { console.log("Resolved 3: " + r); });
} 该 api 可以在多线程操作中,有顺序的操作同一个内存地址,如上代码变量 Numeric separators1_000_000_000 // Ah, so a billion
101_475_938.38 // And this is hundreds of millions
let fee = 123_00; // $123 (12300 cents, apparently)
let fee = 12_300; // $12,300 (woah, that fee!)
let amount = 12345_00; // 12,345 (1234500 cents, apparently)
let amount = 123_4500; // 123.45 (4-fixed financial)
let amount = 1_234_500; // 1,234,500 提案希望 js 支持分隔符使大数字阅读性更好(不影响计算),很多语言都有实现,很人性化。 |
搞清楚 TC39, ECMAScript 之间的关系,以及 js 最近又有什么新动静了。
The text was updated successfully, but these errors were encountered: