本专栏(FE weekly news)文章随缘更新,由于精力有限,每篇周报的内容可能不多,欢迎大家关注
1. TC39会议提案
TC39会议在6月11号召开,会议上讨论了很多提案,介绍几个比较重要的
Deferred Import Evaluation - Stage 2.7
背景介绍
通常来说,模块的加载会在其他逻辑代码执行前完成
import operation from 'operation'
const doSomething = function (target) {
return operation(target);
}
但是大量的模块加载可能会导致页面初始化阶段的性能问题,为了解决该问题,我们可以通过异步加载来导入部分模块。
// CommonJS
exports.doSomething = function (target) {
const operation = require('operation');
return operation(target);
}
// ES modules
export async function doSomething (target) {
const { operation } = await import('operations');
return operation(target);
}
现有的异步加载方案有两个缺点:
- 对代码的侵入性较大,如上面所见,对项目进行优化时工作量较大。
- 在使用前才加载对应模块,很可能会产生卡顿,降低用户体验。
提案介绍
为了解决上述问题,TC39提出了一种新的模块异步导入方法。
使用提案中的方法,上面的异步导入代码可以改写为:
// defer关键字声明异步导入
import defer * as operations from "operations"
exports.doSomething = function (target) {
return operations.operation(target);
}
此处的operations脚本会在最初被加载,但不会执行(比如在operations的最外层打个debugger断点,该断点此时也不会被执行)。只有执行operations.operation(target)时,才会触发operations内脚本的执行。
Error.isError() - Stage2
提案介绍
通过Error.isError()来判定当前值是否异常(比如说除数为0等)。在我看来就类似于try catch吧,不过他的错误定位范围更小一些,可读性也更好。
const func = (a: number) => {
isError(6/a) {
console.log('an error occurred')
} else {
console.log('result is ' + 6/a)
}
}
RegExp Escaping - Stage 2.7
背景介绍
假设我们现在有一个这样的需求: 写一个函数,需要把一个长文本中的指定字符串剔除。
我们通过以下代码实现
const replaceFunc = (longStr:string, regxStr: string) => {
return longStr.replace(new RegExp(regxStr, 'g'), '')
}
replaceFunc('somestrhellosomestr', 'hello.') // somestromestr
上面的代码在某些情况下,会得到预期外的结果。就如同示例一般,因为hello.中的.是正则表达式中的特殊字符,代表“换行符 \n 之外的任何单字符”,因此匹配到的不是hello.字符串,而是hello + \n以外任意单字符
提案介绍
新提案提出了一种转义api,
可以通过RegExp.escape,将指定字符串转义,这样便可以避免regxStr中特殊字符导致的匹配结果非预期的问题
const replaceFunc = (longStr:string, regxStr: string) => {
const escapeStr = RegExp.escape(regxStr) // 即'hello\\.'
return longStr.replace(new RegExp(escapeStr, 'g'), '')
}
replaceFunc('somestrhellosomestr', 'hello.') // somestrhellosomestr
Promise.try - Stage3
背景介绍
对于异常的捕获,同步和异步有着不同的方案。 比如说下面这段代码
const fixedPromise = v => new Promise((resolve, reject) =>{
setTimeout(() =>{
resolve(v.toFixed())
}, 3000)
})
const func1 = num => {
if (typeof num !== "number") {
throw new Error("id must be a number");
}
return fixedPromise(num);
}
func1('a string').catch(err => {
// 未触发此处
console.log('error occurred')
})
Promise.catch是无法捕获func1中抛出的异常的。想要捕获这个异常,还要用try catch包裹
try {
func1('a string').catch(err => {
console.log('I am promise.catch', err)
})
}.catch(e){
console.log('I am try catch', e)
}
// 输出 I am try catch Error: id must be a number
提案介绍
新提案提出了Promise.try方法,来实现对同步异步代码异常处理方案的统一,
我们使用Promise.try包裹func1函数
const func1 = num => Promise.try(() =>{
if (typeof num !== "number") {
throw new Error("id must be a number");
}
return fixedPromise(num);
})
func1('a string').catch(err => {
// 异常在此处被捕获
// I am proimse.catch Error: id must be a number
console.log('I am promise.catch', err)
})
包裹后函数内发生的异常,无论触发异常的代码是同步还是异步,都可以在Promise.catch中被捕获。
2. State of JavaScript 2023 出炉
23年的state of js调查出炉了(是的,在24年年中才出炉),挑几个点说一下:
-
通过这个调查我发现了之前没关注过的ts生态的一些库,比如Zod、yup,这些库的作用是为开发者提供类型校验功能,开发者可以根据校验结果来实现不同代码逻辑。
-
开发者对Vite的评价真的高,1.34%的负面评价,前所未见。(作为对比,webpack负面评价为48%),下图为今年state of js的最后总结,可以看到满屏的vite,恭喜尤大。
-
相比于js,ts的开发者占比进一步增加。
-
调查还对于一些比较新的featrues使用占比进行了统计,大概挑几个feature说一下:
-
左侧操作数为
null或undefined时,返回右侧操作数,否则返回左侧操作数。const foo = null ?? 'default string'; console.log(foo); // Expected output: "default string" const baz = 0 ?? 42; console.log(baz); // Expected output: 0 -
await不仅限于在async函数内部使用,还可以用于模块顶级,当一个模块引用了使用await的子模块时,将会等待子模块执行完再执行下面的代码(但不会阻塞其他子模块的加载)。// a.ts const colors = fetch("../data/colors.json").then( response => response.json() ); export default await colors;import colors from './a' // 等待fetch返回结果后再执行此处代码 console.log(colors ) -
普通的import指令会在最开始就导入模块并执行。
import()则可以让你在需要的时候再导入并执行对应模块。该指令会返回一个Promise, 若没有成功执行/导入模块,会通过reject抛出异常以下为一些适用
import()的场景:- 这个被导入的模块有很大可能用不到,且静态导入该模块会产生副作用或极大增加的你的应用负担。
- 要导入的模块可能在最开始并不存在。
- 要导入的模块字符串需要动态拼接
- 脚本处于非module环境下(比如scripts标签上没有
type="module"时)想要导入模块
// 静态导入 import * as mod from "/my-module.js"; // 动态导入 import("/my-module.js").then((mod2) => { console.log(mod === mod2); // true });注意: 非必要时不要使用动态导入,该导入方式不利于依赖树的构建,并且可能会影响分析工具对网站的评估结果。
-
用于在类中声明私有属性,私有属性在以下情况下,将抛出语法错误:
- 在类外部引用
- 在类内部引用未声明的私有属性
- 使用
delete移除私有属性 - 访问对象中不存在的私有属性
class ClassWithPrivateField { #privateField; constructor() {; delete this.#privateField; // Syntax error this.#undeclaredField = 42; // Syntax error } } const instance = new ClassWithPrivateField(); instance.#privateField; // Syntax errorclass C { #x; static getX(obj) { return obj.#x; } } console.log(C.getX(new C())); // undefined console.log(C.getX({})); // TypeError: Cannot read private member #x from an object whose class did not declare it -
逻辑与赋值
x &&= y:仅在x为真时为其赋值逻辑或赋值
||=: 仅在x为假时为其赋值const a = { duration: 50, title: '' }; a.duration ||= 10; console.log(a.duration); // Expected output: 50 a.title ||= 'title is empty.'; console.log(a.title); // Expected output: "title is empty"
-