前言:
当前兄弟们对“js过时了吗”可能比较关切,我们都想要分析一些“js过时了吗”的相关知识。那么小编也在网摘上搜集了一些关于“js过时了吗””的相关资讯,希望兄弟们能喜欢,姐妹们一起来了解一下吧!原文:
近年来,JavaScript的规模已经有很大的增长,这篇博客探讨了它仍然缺少的内容。
注意:
我仅仅列出那些我认为最重要的特性。许多其他的可能很有用,但是也存在添加太多的风险。这些选择带有主观意愿。这篇博客提到的几乎所有内容都受到TC39的关注。也就是说,它还可以作为将来可能使用的JavaScript预览。一、值按值比较对象
目前,JavaScript仅按值比较原始值,例如字符串--通过查看它们的内容。
> 'abc' === 'abc'true
相比之下,对象是按地址引用来比较的(每一个对象有一个唯一的地址,并且它仅仅和它自己相等)。
> {x: 1, y: 4} === {x: 1, y: 4}false> ['a', 'b'] === ['a', 'b']false> const obj = {x: 1, y: 4};> obj === objtrue
如果有一种方法可以创建按值比较的对象,那就太好了:
> #{x: 1, y: 4} === #{x: 1, y: 4}true> #['a', 'b'] === #['a', 'b']true
另外一种可能性是引入一种新的类(具体细节待定):
@[ValueType]class Point { // ···}
此外:这种类装饰器的语法,将类标记为值类型,也是基于一个draft proposal(提议草案)。
2、将对象放入数据结构
由于对象是按引用比较的,将他们放入(非弱引用)ECMAScript的数据结构(例如Map)中几乎没有任何意义。
const m = new Map();m.set({x: 1, y: 4}, 1);m.set({x: 1, y: 4}, 2);assert.equal(m.size, 2);
此问题可以通过自定义值类型解决。另外,Set元素和Map键的管理可以自定义,例如:
通过Hash Table管理的Map:检查是否相等需要一项操作,创建hash码也需要一项单独的操作。如果你使用的是hash码管理的Map,你应该是希望你的对象是不可变的,否则它太容易破坏数据结构了。通过排序树管理的Map:为了管理它存储的值,它需要一个比较两个值的操作。
3、大整数(ECMAScript2020)
JavaScript的数字总是64位双精度,这个最大只能给你53位带符号的整数。总之,你不能任意的表达任意整数。
> 2 ** 539007199254740992> (2 ** 53) + 1 // can’t be represented9007199254740992> (2 ** 53) + 29007199254740994
在某些情况下,它是一个比较大的限制。对于BigInts目前有个提案(ES2020已纳入标准),精度根据需要增长的实整数。
> 2n ** 53n9007199254740992n> (2n ** 53n) + 1n9007199254740993n
BigInts还支持强制转换,从而为您提供具有固定位数的值。
const int64a = BigInt.asUintN(64, 12345n);const int64b = BigInt.asUintN(64, 67890n);const result = BigInt.asUintN(64, int64a * int64b);
4、小数计算
JavaScript的数字是64位的浮点数(双精度),基于IEEE754标准。在底层,它是基于二进制表示的,因此当你处理小数的时候,总是会出现舍入错误:
> 0.1 + 0.20.30000000000000004
在科学计算和金融技术(一个快速增长的领域)中,这尤其是个问题。一个基于10进制的数字提案目前在stage-0。它们最终可能会像下面这样调用(请注意后缀m为小数):
> 0.1m + 0.2m0.3m
5、值分类
当前,在JavaScript中归类值是相当笨重的。
首先,你得决定是使用typeof还是instanceof第二,typeof有一个大家都知道的怪癖,就是把null归类为'object',并且还需要考虑它把函数归类为'function'这一怪癖。
> typeof null'object'> typeof function () {}'function'> typeof []'object'第三,instanceof在其他上下文不一定能正常工作(例如frame)
这个可能会通过一个库来修复(一旦我有时间,我就会创建概念来证明)。
二、函数式编程
1、Do表达式
C风格的语言在表达式和语句之间做了不好的区分。
// Conditional expressionlet str1 = someBool ? 'yes' : 'no';// Conditional statementlet str2;if (someBool) { str2 = 'yes';} else { str2 = 'no';}
特别是在函数式语言中,所有的东西都是表达式。Do表达式能够让你在表达式上下文中使用语句。
let str = do { if (someBool) { 'yes' } else { 'no' }};
`switch`同样在Do表达式中工作:
const n = 3;let str = do { switch (n) { case 1: 'one'; break; case 2: 'two'; break; case 3: 'three'; break; }};assert.equal(str, 'three');
Do表达式有助于消除最后一个使用立即执行函数表达式的主要原因(IIFEs):在一个函数上绑定静态数据。
做一个示例,下面就是使用IIFE的代码:
const func = (() => { // open IIFE let cache; return () => { if (cache === undefined) { cache = compute(); } return cache; }})(); // close IIFE
通过Do表达式,你不再需要IIFE:
const func = do { let cache; () => { if (cache === undefined) { cache = compute(); } return cache; };};
2、匹配一个解构的`switch`
JavaScript中直接处理对象很容易。但是,没有基于对象结构的内置方式去匹配案例。如下所示(示例来源于提案):
const resource = await fetch(jsonService);case (resource) { when {status: 200, headers: {'Content-Length': s}} -> { console.log(`size is ${s}`); } when {status: 404} -> { console.log('JSON not found'); } when {status} if (status >= 400) -> { throw new RequestError(res); }}
正如你看到的那样,在某种程度上新的case语句有点和switch相同,但是使用解构匹配案例。每当使用嵌套数据结构(例如在编译器中),这种功能就非常有用。模式匹配的提案目前已经进入stage-1。
3、管道操作符
对于管道操作符,目前有两个相互竞争的提案。在这里,我们看一下`Smart`管道(另外一个称作F#管道)。
管道操作符的基本思想如下。考虑如下的嵌套函数调用。
const y = h(g(f(x)));
然而,这种表示法不能反映出我们对计算步骤的想法。直观的,我们将它描述为:
值x开始对x调用f对上一步的结果调用g对上一步的结果调用h把值赋值给y
这个管道操作符让我们能够很直观表达:
const y = x |> f |> g |> h;
换一种说法,下面两个表达式是等价的:
f(123)123 |> f
此外,这个管道操作符支持`partial application`(类似于函数的bind方法):下面两个表达式是等价的:
123 |> f('a', #, 'b')123 |> (x => f('a', x, 'b'))
管道操作符一个重要的好处是你可以像使用方法一样使用函数,不需要去改变任何原型:
import {filter, map} from 'array-tools';const result = arr |> filter(#, x => x >= 0) |> map(#, x => x * 2);三、并发
JavaScript一直以来对并发的支持有限。并发进程事实上的标准是Worker Api,它在浏览器和NodeJs(在11.7版本之后不需要加flag)上都是可用的。
在NodeJs上使用它看起来像下面这样:
const { Worker, isMainThread, parentPort, workerData} = require('worker_threads');if (isMainThread) { const worker = new Worker(__filename, { workerData: 'the-data.json' }); worker.on('message', result => console.log(result)); worker.on('error', err => console.error(err)); worker.on('exit', code => { if (code !== 0) { console.error('ERROR: ' + code); } });} else { const {readFileSync} = require('fs'); const fileName = workerData; const text = readFileSync(fileName, {encoding: 'utf8'}); const json = JSON.parse(text); parentPort.postMessage(json);}
很遗憾的是,Worker是相当的“重”,每一个都有自己的环境上下文(全局变量等等)。我希望在将来能够看到更轻巧的结构。
四、标准库
JavaScript一个明显落后于其他语言的地方是它的标准库。保持它体量较小确实有意义,因为外部库更加容易发展和适应,然而有少量的核心功能是很有用的。
1、模块代替对象命名空间
在JavaScript语言有模块功能之前,它的标准库就已经被创建了。因此,所有的功能方法都被放进了对象的命名空间中,例如Math、JSON、Object和Reflect。
Math.max()JSON.parse()Object.keys()Reflect.ownKeys()
如果这些功能能被放进模块中,那就太好了。它们必须通过一个特殊的URLs去访问,例如使用伪协议`std:`
// New:import {max} from 'std:math';assert.equal( max(-1, 5), 5);// Old:assert.equal( Math.max(-1, 5), 5);
优点:
JavaScript将会变得更加模块化(这个将会加快启动时间和减少内存消耗)。比起调用存储在对象上的方法,调用通过模块导入的方法速度更快。
2、更加有助于可迭代对象(同步和异步)
迭代器的优势在于按需求值和支持大量数据源。但是JavaScript当前只提供了很少的工具用于迭代器。例如,如果你想filter、map、reduce一个迭代器,你必须把它转换为列表:
const iterable = new Set([-1, 0, -2, 3]);const filteredArray = [...iterable].filter(x => x >= 0);assert.deepEqual(filteredArray, [0, 3]);
如果JavaScript有针对于迭代器的工具函数,你就可以直接filter迭代对象:
const filteredIterable = filter(iterable, x => x >= 0);assert.deepEqual( // Only convert iterable to Array to check what’s in it [...filteredIterable], [0, 3]);
这里有少量针对迭代器的示例:
// Count elements in an iterableassert.equal(count(iterable), 4);// Create an iterable over a part of an existing iterableassert.deepEqual( [...slice(iterable, 2)], [-1, 0]);// Number the elements of an iterable// (producing another – possibly infinite – iterable)for (const [i,x] of zip(range(0), iterable)) { console.log(i, x);}// Output:// 0, -1// 1, 0// 2, -2// 3, 3
注意:
有关迭代器工具函数的示例,请查阅Python的itertools。对于JavaScript,任何针对可迭代对象的工具函数应该有两个版本:一个支持同步迭代,同一个支持异步迭代。
3、不可变数据
如果对无损转换数据有着更多的支持,那将会非常nice。如下是两个相关的库:
Immer是一个相对较轻的库,并且很好的与正常对象和数组协调工作。Immutable.js 是一个更加重量级并且功能更加丰富,且带有自己的数据结构。
4、对日期时间更好的支持
JavaScript内置的日期时间支持有很多怪癖。这就是为什么除了最基本的功能外都推荐使用第三方库。
谢天谢地,一个更好的日期时间API正在到来:
const dateTime = new CivilDateTime(2000, 12, 31, 23, 59);const instantInChicago = dateTime.withZone('America/Chicago');五、可能不需要的功能
1、可选链的优缺点
可选链是一个非常受欢迎的功能提案。下面那两个表达式是等价的:
obj?.prop(obj === undefined || obj === null) ? undefined : obj.prop
这个功能对于链式属性读取非常方便:
obj?.foo?.bar?.baz
但是这个功能也有它的缺点:
深度嵌套的结构更难管理。例如,如果有许多的属性名称序列,则重构将会更加困难。每个 属性都会限制多个对象的结构。在访问数据时如此宽容容易隐藏问题,并且这个问题出现的很慢,而且很难调试。例如,在一系列可选属性的名称中,早期的拼写错误比正常的方式取值的拼写错误更加不容易被发现。
一个可替代可选链的方式是在一个地方一次性提取信息:
您可以编写一个辅助函数来提取数据或者你能够写一个函数,它的输入是深层次嵌套的数据并且它的输出是简单的、正常化的数据。
通过以上任意一个方法,都可以执行检查,并且在出现问题的时候尽早失败。
2、我们需要运算符重载吗?
对于运算符重载,一些早期工作正在进行。但可能infix函数应用就已经足够了(尽管目前没有关于它的提案)。
import {BigDecimal, plus} from 'big-decimal';const bd1 = new BigDecimal('0.1');const bd2 = new BigDecimal('0.2');const bd3 = bd1 @plus bd2; // plus(bd1, bd2)
infix函数应用的好处:
你可以创建不被内置支持的JavaScript操作符与普通函数相比,嵌套表达式仍然具有很高的可读性
下面是一个嵌套表达式的例子:
a @plus b @minus c @times dtimes(minus(plus(a, b), c), d)
有趣的是,管道操作符也帮助这个具有很高的可读性
plus(a, b) |> minus(#, c) |> times(#, d)六、其他小功能
这里还有些许我遗漏的功能点,但是我认为它们是不如我之前提到的那些重要。
链式异常:允许你捕获一个异常,并且可以包装一些额外的信息,然后再次抛出它。
new ChainedError(msg, origError);可组合的正则表达式:
const regex = re`/^${RE_YEAR}-${RE_MONTH}-${RE_DAY}$/u`;转译正则表达式的文本(对.replace是很重要的)
> const re = new RegExp(RegExp.escape(':-)'), 'ug');> ':-) :-) :-)'.replace(re, '')' 'Array.prototype.get支持负数索引
> ['a', 'b'].get(-1)> 'b'用于匹配和解构的模式
function f(...[x, y] as args) { if (args.length !== 2) { throw new Error(); } // ···}检查对象的深度相等(可能可选择使用第二个参数式的形式以支持自定义的数据结构)
assert.equal( {foo: ['a', 'b']} === {foo: ['a', 'b']}, false);assert.equal( deepEqual({foo: ['a', 'b']}, {foo: ['a', 'b']}), true);枚举:给JavaScript添加枚举类型的一个好处就是减少了与TypeScript(已经有枚举类型了)的差异。当前已经有两个草案(还未处于正式阶段)。在这两个草案中,简单的语法都类似下面这样:
enum WeekendDay { Saturday, Sunday}const day = WeekendDay.Sunday;标记集合类型(由KatMarchán提出和撤销)。允许你像下面这样使用Set和Map:
const myMap = Map!{1: 2, three: 4, [[5]]: 6} // new Map([1,2], ['three',4], [[5],6])const mySet = Set!['a', 'b', 'c']; // new Set(['a', 'b', 'c'])七、FAQ:未来的JavaScriptJavaScript是否会支持静态类型?
短时间内是不会的。目前这种在开发期间进行类型检查(TypeScript,Flow),在运行期间是纯JavaScript的状况,协调得很好。因此目前没有任何理由去立即改变任何东西。
为什么我们不能通过删除怪癖和过时的功能来清理JavaScript?
web的一个关键要求是永远不要破坏向后兼容。
这个语言的缺点就是有许多传统的功能但利大于弊的点在于:大型代码库都是用的相同的特性。迁移到新版本是比较简单的;引擎仍然保持的比较小巧(无需支持多个版本)等等。
通过引入现有功能的更好版本,仍然可能修复一些错误。
八、思考语言设计
作为一个语言设计者,不管你做什么,总有人开心,也总有人会难受。因此,设计未来JavaScript特性的挑战不是让所有人都高兴,而是去尽可能的保持语言的不变性。
然而,在"不变性"是什么的问题上依然存在着分歧。因此,我们能够做的最好的事就是建立一个不变的“风格”,由一小群人(最多三个)构思和执行。
这并没有排除他们会受到其他许多人们的建议和帮助,但他们应该保持一致的风格。
引用弗雷德·布鲁克斯的话:
稍作回顾可以看出,不管委员会设计多么美好,有用的软件系统,也不管它是否是大型项目的一部分。能激起粉丝热情的软件系统总是那些一个或几个设计师设计的产品。
这些核心设计者的一个重要的职责就是对新特性说"no",防止JavaScript变得太大。
他们也需要一个强壮的支持系统,作为语言设计者往往会遭受大量的辱骂(因为人们在意自己并且不喜欢被拒绝)。最近的一个例子是Guido van Rossum因受到谩骂而辞去了首席Python语言设计师的工作。
其他想法
这些想法可能也会帮助设计和记录JavaScript:
创建一个前景图,用来描述JavaScript的未来。这样的前景图不仅可以讲述故事,还可以将许多不同的部分连接成一个连贯的整体。我知道的最后一个前景图是,Brendan Eich的“Harmony Of My Dreams”。记录设计原理。现在的ECMAScript规范记录了它们会怎么工作,但是却没有讲述为什么。一个例子就是,可枚举的目的是什么?一个权威的解释。这一半的正式规范其实是已经实现了。如果他们能够像编程语言那样直接运行那就非常棒。(你可能需要一个规范,去使得可以从非规范的帮助函数中把规范代码区分开来。)
标签: #js过时了吗