Ratel 是与 SWC 差不多同期出现的、同样是用 Rust 写的 JavaScript 编译器,可惜现在已无人问津。
https://github.com/ratel-rust/ratel-core
https://github.com/ratel-rust/ratel-core
GitHub
GitHub - ratel-rust/ratel-core: High performance JavaScript to JavaScript compiler with a Rust core
High performance JavaScript to JavaScript compiler with a Rust core - ratel-rust/ratel-core
😭3
尽管
-
-
两者的顺序刚好相反。(虽然两个方法的参数数量不同,但不影响这里的逻辑,并且实际中基本是以
在绝大多数时候,这个顺序上的差别不会对实际使用造成影响。不过,通过刻意的设计还是可以观测到两者的区别:
CodeSandbox
提示:对 null 或 undefined 执行
参考:
1. ES 标准中关于 Object.hasOwn 的说明
2. ES 标准中关于 Object.prototype.hasOwnProperty 的说明
感谢 @JackWorks
Object.hasOwn 被作为 Object.prototype.hasOwnProperty 的替代品,但实际上两者对于参数的 normalize 在顺序上是有差别的:-
Object.hasOwn 会先对第一个参数执行 ToObject 再对第二个参数执行 ToPropertyKey;-
Object.prototype.hasOwnProperty 则会先对第一个参数(即传入的 key)执行 ToPropertyKey 再对 this 执行 ToObject。两者的顺序刚好相反。(虽然两个方法的参数数量不同,但不影响这里的逻辑,并且实际中基本是以
Object.prototype.hasOwnProperty.call 的形式来使用的,这也勉强算是两个参数)在绝大多数时候,这个顺序上的差别不会对实际使用造成影响。不过,通过刻意的设计还是可以观测到两者的区别:
const fakeKey = {
[Symbol.toPrimitive]() {
console.log('type conversion')
return 'key'
},
}
function callHasOwn() {
try {
// @ts-expect-error
Object.hasOwn(null, fakeKey)
} catch {
//
}
}
function callHasOwnProperty() {
try {
// @ts-expect-error
Object.prototype.hasOwnProperty.call(null, fakeKey)
} catch {
//
}
}
console.log('Using Object.hasOwn:')
callHasOwn()
console.log('====================')
console.log('Using Object.prototype.hasOwnProperty:')
callHasOwnProperty()
CodeSandbox
提示:对 null 或 undefined 执行
ToObject 会抛出错误。参考:
1. ES 标准中关于 Object.hasOwn 的说明
2. ES 标准中关于 Object.prototype.hasOwnProperty 的说明
感谢 @JackWorks
Babel 和 SWC 都有 core-js 相关的选项,用于精确控制要注入的 polyfill。对于 Babel,它在
不管是 Babel 还是 SWC,这个字段允许一个类型为数字和字符串的值,用于表示版本。如果是字符串,可以指定具体的版本,比如
但如果指定的是数字,比如
这会导致一些新的 polyfill 不被会注入到代码中。以
由于 Babel 的 REPL 不允许以 JSON 方式任意修改 Babel 的配置,所以下面将以 SWC 为例。但这个行为对于 Babel 和 SWC 都是一样,也就是两边都存在这个问题:
- 指定为
- 指定为
- 指定为
实际上,Babel 和 SWC 的文档都明确建议这个选项指定 semver-minor 以避免这个问题。
参考:
[1] Babel 文档
[2] SWC 文档
感谢 @Austaras
@babel/preset-env 的选项下[1];对于 SWC,它在配置文件的 env.coreJs 下[2]。不管是 Babel 还是 SWC,这个字段允许一个类型为数字和字符串的值,用于表示版本。如果是字符串,可以指定具体的版本,比如
"3.34" ,这没什么问题。但如果指定的是数字,比如
2 或 3 ,以及没有具体版本的字符串,比如 "2" 或 "3" ,这时 core-js 的版本解析将是反直觉的:它指定的不是最新版本(比如本文撰写时最新是 3.34.0),而是 3.0.0。这会导致一些新的 polyfill 不被会注入到代码中。以
Object.hasOwn 为例,它的 polyfill 是在 core-js 3.17.0 版本 中稳定的。如果我们在项目中给这个 core-js 选项指定了 3 或 "3" ,那么将不包含这个 polyfill,从而导致出现意外。由于 Babel 的 REPL 不允许以 JSON 方式任意修改 Babel 的配置,所以下面将以 SWC 为例。但这个行为对于 Babel 和 SWC 都是一样,也就是两边都存在这个问题:
- 指定为
3 时,输出代码不包含 polyfill 的 import 语句:playground;- 指定为
"3.16" 时,这个版本的 core-js 未稳定 Object.hasOwn 的 polyfill,所以还是没有它的 polyfill:playground;- 指定为
"3.17" 时就有它的 polyfill 了:playground。实际上,Babel 和 SWC 的文档都明确建议这个选项指定 semver-minor 以避免这个问题。
参考:
[1] Babel 文档
[2] SWC 文档
感谢 @Austaras
👍4
pnpm 有一个名为 resolution-mode 的配置项(在 .npmrc 文件中配置),但无论是 resolution-mode 这个名字还是配置项的值的名字,都相当的不直观,更不方便记忆。
背景:resolution-mode 影响的是 pnpm 的版本解析。适当的配置可以减少子依赖所带来的供应链攻击,并且在缓存的作用下可以加快安装速度。但请不要看到「安全」「快速」就盲目配置这个选项——阅读完下面的内容,并明确自己的意图再去动这个配置。
在解释之前,先做一些约定:
1. 不管在不同模式下,版本解析会有怎样的不同,它们都遵循语义化版本。这是大前提。
2. 下文的「子依赖」表示的是依赖的依赖,也可以被称为「间接依赖」。
目前 resolution-mode 允许设为下面三个值之一:
- highest(当前的默认值)这个模式下所有依赖(包括项目的直接依赖以及子依赖)都会被解析到最新版本;
- time-based 这个模式下项目的直接依赖会被解析到最老版本,而子依赖则会被解析到该直接依赖发布时间之前的最新版本;
- lowest-direct 这个模式下仅仅是项目的直接依赖会被解析到最老版本,子依赖会被解析到最新版本。
下面以这样的一个项目为例子:(测试时所使用的 pnpm 版本为 8.11.0)
ora 库有多个依赖,但我们在这里只需要关注 ora 本身以及它的其中一个依赖 chalk 的版本,并通过 pnpm 生成的 lock file 来得知它们被解析到哪个版本。
- 当 resolution-mode 设为 highest 时:ora 的版本被解析为 6.3.1,chalk 的版本被解析为 5.3.0。两个库都被解析到最新版本。
- 当 resolution-mode 设为 time-based 时:ora 的版本被解析为 6.2.0,chalk 的版本被解析为 5.2.0。ora 能满足语义化版本的最老版本是 6.2.0,而 ora 6.2.0 的发布时间为 2023/3/19 18:13:22,在此时间之前 chalk 最新版本是 5.2.0。(尽管 ora 的 package.json 文件里声明的是
- 当 resolution-mode 设为 lowest-direct 时:ora 的版本被解析为 6.2.0,chalk 的版本被解析为 5.3.0。可以看到即使 ora 作为项目的直接依赖被解析为 6.2.0,但它的依赖 chalk 被解析到最新版本即 5.3.0。
参考:
1. pnpm 关于 resolution-mode 的文档
2. ora 的所有版本
3. chalk 的所有版本
背景:resolution-mode 影响的是 pnpm 的版本解析。适当的配置可以减少子依赖所带来的供应链攻击,并且在缓存的作用下可以加快安装速度。但请不要看到「安全」「快速」就盲目配置这个选项——阅读完下面的内容,并明确自己的意图再去动这个配置。
在解释之前,先做一些约定:
1. 不管在不同模式下,版本解析会有怎样的不同,它们都遵循语义化版本。这是大前提。
2. 下文的「子依赖」表示的是依赖的依赖,也可以被称为「间接依赖」。
目前 resolution-mode 允许设为下面三个值之一:
- highest(当前的默认值)这个模式下所有依赖(包括项目的直接依赖以及子依赖)都会被解析到最新版本;
- time-based 这个模式下项目的直接依赖会被解析到最老版本,而子依赖则会被解析到该直接依赖发布时间之前的最新版本;
- lowest-direct 这个模式下仅仅是项目的直接依赖会被解析到最老版本,子依赖会被解析到最新版本。
下面以这样的一个项目为例子:(测试时所使用的 pnpm 版本为 8.11.0)
{
"dependencies": {
"ora": "^6.2.0"
}
}
ora 库有多个依赖,但我们在这里只需要关注 ora 本身以及它的其中一个依赖 chalk 的版本,并通过 pnpm 生成的 lock file 来得知它们被解析到哪个版本。
- 当 resolution-mode 设为 highest 时:ora 的版本被解析为 6.3.1,chalk 的版本被解析为 5.3.0。两个库都被解析到最新版本。
- 当 resolution-mode 设为 time-based 时:ora 的版本被解析为 6.2.0,chalk 的版本被解析为 5.2.0。ora 能满足语义化版本的最老版本是 6.2.0,而 ora 6.2.0 的发布时间为 2023/3/19 18:13:22,在此时间之前 chalk 最新版本是 5.2.0。(尽管 ora 的 package.json 文件里声明的是
"chalk": "^5.0.0")- 当 resolution-mode 设为 lowest-direct 时:ora 的版本被解析为 6.2.0,chalk 的版本被解析为 5.3.0。可以看到即使 ora 作为项目的直接依赖被解析为 6.2.0,但它的依赖 chalk 被解析到最新版本即 5.3.0。
参考:
1. pnpm 关于 resolution-mode 的文档
2. ora 的所有版本
3. chalk 的所有版本
TypeScript 新特性抢先看:新的内置类型
正如其名,
playground
在这个例子中,参数 a 的类型将被自动推断,此时
如果上面没有使用
另外,
playground
类型
在参与类型计算时,
这里
值得注意的是,在使用
playground
它跟上面的
PR: https://github.com/microsoft/TypeScript/pull/56794
NoInfer<T> 。它与 Uppercase 等类型都属于 marker type(即 lib.d.ts 里的实现只有一个 intrinsic 关键字),编译器对于这类 marker type 都会特殊对待。正如其名,
NoInfer<T> 类型用于防止类型被自动推断。也就是说,在某些地方(通常是函数泛型等),如果不希望该处的类型被自动推断,就可以使用。例如:(下面的例子由官方示例简化而来)declare function test<T extends string>(a: T, b: NoInfer<T>): void
test('a', 'b')
playground
在这个例子中,参数 a 的类型将被自动推断,此时
T 为 'a' ;而对于参数 b,因为我们使用 NoInfer 类型,那么它也就不会参与自动推断,由于前面 T 被推断为 'a' ,而此处的实际参数却是 'b' ,因此类型不兼容进而报错。如果上面没有使用
NoInfer , T 将会被推断为 'a' | 'b' ,读者可以打开 playground 链接自行修改测试。另外,
NoInfer<T> 不会对 T 产生影响,例如:type Result = NoInfer<'s'>
playground
类型
Result 仍为 's' 。在参与类型计算时,
NoInfer<T> 其实只是被替换为 unknown 。以上面的 test 函数为例,它相当于:declare function test<T extends string>(a: T, b: unknown): void
这里
T 仍为 'a' ,只不过因为使用了 unknown 而无法对参数 b 进行约束。值得注意的是,在使用
NoInfer<T> 时,一定要留意 T 在可被推断的地方被推断为了什么类型。例如:declare function test2<T>(a: T, b: NoInfer<T>): void
test2('a', 'b')
playground
它跟上面的
test 函数相比, T 没有了 extends string 的泛型约束。这时,参数 a 将被推断为 string 而不是 'a' ,也就是 T 推断为 string 。但因为 'b' 满足 string 的要求,因此这段代码不会报错。PR: https://github.com/microsoft/TypeScript/pull/56794
👍3
位于全局的
-
-
请尽可能使用
参考:
1. MDN 上的示例代码
2. ECMAScript 标准中关于 isNaN 的说明
3. ECMAScript 标准中关于 Number.isNaN 的说明
isNaN 与 Number.isNaN 虽然函数名字一样,但具体行为是有差别的:-
isNaN 会对传入的参数执行类型转换,转换为数字类型后再去判断;-
Number.isNaN 则不会执行类型转换,对于数字类型以外的值均返回 false 。请尽可能使用
Number.isNaN 以避免因类型转换而导致意外发生。参考:
1. MDN 上的示例代码
2. ECMAScript 标准中关于 isNaN 的说明
3. ECMAScript 标准中关于 Number.isNaN 的说明
👏7👎1
