Python 还能承担下一个时代的发展重任吗?Mojo 语言的横空出世对 AI 研发生态有什么影响? 了解详情
写点什么

三年回顾:JavaScript 与 TypeScript 最新特性汇总

作者: Linus Schlumberger

  • 2023-04-03
    北京
  • 本文字数:20655 字

    阅读完需:约 68 分钟

三年回顾:JavaScript与TypeScript最新特性汇总

全文 7739 字,建议收藏。


本文涵盖了过去三年中发布的最新 JavaScript 和 TypeScript 特性,包括异步迭代器、nullish 合并、可选链、私有字段等等。对于每个特性,文章提供了简单的解释、示例代码以及使用场景,以便开发者能够更好地理解和应用这些新特性。同时,文章还介绍了如何在项目中使用这些特性,以及如何通过 polyfill 或者 Babel 等工具使旧的浏览器支持这些新特性。


本文将带大家回顾过去三年(乃至更早)以来,JavaScript/ECMAScript 以及 TypeScript 经历的一系列功能变化。


当然,这里提到的很多功能也许跟大家的日常工作八竿子打不着。但关注功能的发展变化,应该能帮助您加深对这些语言的理解。


有很多 TypeScript 功能并没有被记录在内,因为它们总体上可以概括为“之前它的运作效果跟期望不同,但现在相同了”。所以如果大家之前对某些问题有所诟病,现在不妨重试一次。


概述


●  JavaScript / ECMAScript (按时间排序)


●  TypeScript (按时间排序)


ECMAScript

更早(更早发布但仍然重要)


●  标记模板字面量:通过在模板字面量之前添加函数名,可以将函数传递至模板字面量和模板值的某些部分。这项功能有不少有趣的用途。


// Let's say we want to write a way to log arbitrary strings containing a number, but format the number.// We can use tagged templates for that.function formatNumbers(strings: TemplateStringsArray, number: number): string {  return strings[0] + number.toFixed(2) + strings[1];}console.log(formatNumbers`This is the value: ${0}, it's important.`); // This is the value: 0.00, it's important.// Or if we wanted to "translate" (change to lowercase here) translation keys within strings.function translateKey(key: string): string {  return key.toLocaleLowerCase();}function translate(strings: TemplateStringsArray, ...expressions: string[]): string {  return strings.reduce((accumulator, currentValue, index) => accumulator + currentValue + translateKey(expressions[index] ?? ''), '');}console.log(translate`Hello, this is ${'NAME'} to say ${'MESSAGE'}.`); // Hello, this is name to say message.

复制代码


●  Symbols(之前被错误归类为 ES2022):对象的唯一键:Symbol(“foo”) === Symbol(“foo”); // false。内部使用。


const obj: { [index: string]: string } = {};const symbolA = Symbol('a');const symbolB = Symbol.for('b');console.log(symbolA.description); // "a"obj[symbolA] = 'a';obj[symbolB] = 'b';obj['c'] = 'c';obj.d = 'd';console.log(obj[symbolA]); // "a"console.log(obj[symbolB]); // "b"// The key cannot be accessed with any other symbols or without a symbol.console.log(obj[Symbol('a')]); // undefinedconsole.log(obj['a']); // undefined// The keys are not enumerated when using for ... in.for (const i in obj) {  console.log(i); // "c", "d"}
复制代码


ES2020


●  可选链:要访问一个可能未定义的对象的值(通过索引),可以通过在父对象名称后添加? 来使用可选链。可选链也可用于索引 ([…]) 或者函数调用。


// PREVIOUSLY:// If we have an object variable (or any other structure) we don't know for certain is defined,// We can not easily access the property.const object: { name: string } | undefined = Math.random() > 0.5 ? undefined : { name: 'test' };const value = object.name; // type error: 'object' is possibly 'undefined'.// We could first check if it is defined, but this hurts readability and gets complex for nested objects.const objectOld: { name: string } | undefined = Math.random() > 0.5 ? undefined : { name: 'test' };const valueOld = objectOld ? objectOld.name : undefined;// NEW:// Instead we can use optional chaining.const objectNew: { name: string } | undefined = Math.random() > 0.5 ? undefined : { name: 'test' };const valueNew = objectNew?.name;// This can also be used for indexing and functions.const array: string[] | undefined = Math.random() > 0.5 ? undefined : ['test'];const item = array?.[0];const func: (() => string) | undefined = Math.random() > 0.5 ? undefined : () => 'test';const result = func?.();
复制代码


●  import(): 动态导入,例如 import … from …,但在运行上且使用变量。


let importModule;if (shouldImport) {importModule = await import('./module.mjs');}
复制代码


●  String.matchAll: 获取正则表达式的多个匹配项,包括其捕获组,且不使用循环。


const stringVar = 'testhello,testagain,';// PREVIOUSLY:// Only gets matches, but not their capture groups.console.log(stringVar.match(/test([\w]+?),/g)); // ["testhello,", "testagain,"]// Only gets one match, including its capture groups.const singleMatch = stringVar.match(/test([\w]+?),/);if (singleMatch) {  console.log(singleMatch[0]); // "testhello,"  console.log(singleMatch[1]); // "hello"}// Gets the same result, but is very unintuitive (the exec method saves the last index).// Needs to be defined outside the loop (to save the state) and be global (/g),// otherwise this will produce an infinite loop.const regex = /test([\w]+?),/g;let execMatch;while ((execMatch = regex.exec(stringVar)) !== null) {  console.log(execMatch[0]); // "testhello,", "testagain,"  console.log(execMatch[1]); // "hello", "again"}// NEW:// Regex needs to be global (/g), also doesn't make any sense otherwise.const matchesIterator = stringVar.matchAll(/test([\w]+?),/g);// Needs to be iterated or converted to an array (Array.from()), no direct indexing.for (const match of matchesIterator) {  console.log(match[0]); // "testhello,", "testagain,"  console.log(match[1]); // "hello", "again"}
复制代码


●  Promise.allSettled(): 与 Promise.all() 类似,但需要等待所有 Promises 完成,且不会在第一次 reject/throw 时返回。它能让降低错误处理的难度。


async function success1() {return 'a'}async function success2() {return 'b'}async function fail1() {throw 'fail 1'}async function fail2() {throw 'fail 2'}// PREVIOUSLY:console.log(await Promise.all([success1(), success2()])); // ["a", "b"]// but:try {  await Promise.all([success1(), success2(), fail1(), fail2()]);} catch (e) {  console.log(e); // "fail 1"}// Notice: We only catch one error and can't access the success values.// PREVIOUS FIX (really suboptimal):console.log(await Promise.all([ // ["a", "b", undefined, undefined]  success1().catch(e => { console.log(e); }),  success2().catch(e => { console.log(e); }),  fail1().catch(e => { console.log(e); }), // "fail 1"  fail2().catch(e => { console.log(e); })])); // "fail 2"// NEW:const results = await Promise.allSettled([success1(), success2(), fail1(), fail2()]);const sucessfulResults = results  .filter(result => result.status === 'fulfilled')  .map(result => (result as PromiseFulfilledResult<string>).value);console.log(sucessfulResults); // ["a", "b"]results.filter(result => result.status === 'rejected').forEach(error => {  console.log((error as PromiseRejectedResult).reason); // "fail 1", "fail 2"});// OR:for (const result of results) {  if (result.status === 'fulfilled') {    console.log(result.value); // "a", "b"  } else if (result.status === 'rejected') {    console.log(result.reason); // "fail 1", "fail 2"  }}
复制代码


●  globalThis: 在全局上下文中访问变量,与环境无关(浏览器、NodeJS 等)。仍被视为较差实践,但在某些情况下是必要的。类似于浏览器上的 this。


console.log(globalThis.Math); // Math Object
复制代码


●import.meta: 在使用 ES-modules 时,获取当前模块的 URL import.meta.url。


console.log(import.meta.url); // "file://..."
复制代码


●  export * as … from …: 轻松将默认值重新导出为子模块。


export * as am from 'another-module'import { am } from 'module'
复制代码


ES2021


●  String.replaceAll(): 替换掉某字符串内某一子字符串的所有实例,无需始终使用带有全局标志(/g)的正则表达式。


const testString = 'hello/greetings everyone/everybody';// PREVIOUSLY:// Only replaces the first instanceconsole.log(testString.replace('/', '|')); // 'hello|greetings everyone/everybody'// Instead a regex needed to be used, which is worse for performance and needs escaping.// Not the global flag (/g).console.log(testString.replace(/\//g, '|')); // 'hello|greetings everyone|everybody'// NEW:// Using replaceAll this is much clearer and faster.console.log(testString.replaceAll('/', '|')); // 'hello|greetings everyone|everybody'
复制代码


●  Promise.any: 当只需要获取 promises 列表中的一个结果时,则返回第一个结果;仅在所有 promises 均被拒绝时才返回 AggregateError,而非立即拒绝的 Promise.race。


async function success1() {return 'a'}async function success2() {return 'b'}async function fail1() {throw 'fail 1'}async function fail2() {throw 'fail 2'}// PREVIOUSLY:console.log(await Promise.race([success1(), success2()])); // "a"// but:try {  await Promise.race([fail1(), fail2(), success1(), success2()]);} catch (e) {  console.log(e); // "fail 1"}// Notice: We only catch one error and can't access the success value.// PREVIOUS FIX (really suboptimal):console.log(await Promise.race([ // "a"  fail1().catch(e => { console.log(e); }), // "fail 1"  fail2().catch(e => { console.log(e); }), // "fail 2"  success1().catch(e => { console.log(e); }),  success2().catch(e => { console.log(e); })]));// NEW:console.log(await Promise.any([fail1(), fail2(), success1(), success2()])); // "a"// And it only rejects when all promises reject and returns an AggregateError containing all the errors.try {  await Promise.any([fail1(), fail2()]);} catch (e) {  console.log(e); // [AggregateError: All promises were rejected]  console.log(e.errors); // ["fail 1", "fail 2"]}
复制代码


●  Nullish coalescing assignment (??=): 仅在之前为 “nullish”(null 或 undefined)时才分配值。


let x1 = undefined;let x2 = 'a';const getNewValue = () => 'b';// Assigns the new value to x1, because undefined is nullish.x1 ??= 'b';console.log(x1) // "b"// Does not assign a new value to x2, because a string is not nullish.// Also note: getNewValue() is never executed.x2 ??= getNewValue();console.log(x1) // "a"
复制代码


●  Logical and assignment (&&=): 仅在之前为“truhty”(true 或可以转换为 true 的值)时才分配值。


let x1 = undefined;let x2 = 'a';const getNewValue = () => 'b';// Does not assign a new value to x1, because undefined is not truthy.// Also note: getNewValue() is never executed.x1 &&= getNewValue();console.log(x1) // undefined// Assigns a new value to x2, because a string is truthy.x2 &&= 'b';console.log(x1) // "b"
复制代码


●  Logical or assignment (||=): 仅在之前为“falsy”(false 或转换为 false)时才分配值。


let x1 = undefined;let x2 = 'a';const getNewValue = () => 'b';// Assigns the new value to x1, because undefined is falsy.x1 ||= 'b';console.log(x1) // "b"// Does not assign a new value to x2, because a string is not falsy.// Also note: getNewValue() is never executed.x2 ||= getNewValue();console.log(x1) // "a"
复制代码


●  WeakRef: 保留对一个对象的“weak”引用,但不阻止对象被垃圾回收。


const ref = new WeakRef(element);// Get the value, if the object/element still exists and was not garbage-collected.const value = ref.deref;console.log(value); // undefined// Looks like the object does not exist anymore.
复制代码


●  数字分隔符 (_): 使用 _ 分隔数字以提高可读性。不会对功能造成影响。


const int = 1_000_000_000;const float = 1_000_000_000.999_999_999;const max = 9_223_372_036_854_775_807n;const binary = 0b1011_0101_0101;const octal = 0o1234_5670;const hex = 0xD0_E0_F0;
复制代码


ES2022


●  #private: 通过以 # 开头的命名,使类成员(属性和方法)私有,即只能通过类本身进行访问。其无法被删除或动态分配。任何不正确行为都会导致 JavaScript(注意,不是 TypeScript)语法错误。不推荐在 TypeScript 项目中这样做,而应用直接使用 private 关键字。


class ClassWithPrivateField {#privateField;#anotherPrivateField = 4;constructor() {this.#privateField = 42; // Validthis.#privateField; // Syntax errorthis.#undeclaredField = 444; // Syntax errorconsole.log(this.#anotherPrivateField); // 4}}const instance = new ClassWithPrivateField();instance.#privateField === 42; // Syntax error
复制代码


●  静态类成员: 将任意类字段(属性和方法)标记为静态。


class Logger {static id = 'Logger1';static type = 'GenericLogger';static log(message: string | Error) {console.log(message);}}class ErrorLogger extends Logger {static type = 'ErrorLogger';static qualifiedType;static log(e: Error) {return super.log(e.toString());}}console.log(Logger.type); // "GenericLogger"Logger.log('Test'); // "Test"// The instantiation of static-only classes is useless and only done here for demonstration purposes.const log = new Logger();ErrorLogger.log(new Error('Test')); // Error: "Test" (not affected by instantiation of the parent)console.log(ErrorLogger.type); // "ErrorLogger"console.log(ErrorLogger.qualifiedType); // undefinedconsole.log(ErrorLogger.id); // "Logger1"// This throws because log() is not an instance method but a static method.console.log(log.log()); // log.log is not a function
复制代码


●  类中的静态初始化块: 类初始化时运行的块,基本属于静态成员的“构造函数”。


class Test {static staticProperty1 = 'Property 1';static staticProperty2;static {this.staticProperty2 = 'Property 2';}}console.log(Test.staticProperty1); // "Property 1"console.log(Test.staticProperty2); // "Property 2"
复制代码


●  导入断言(非标准,在 V8 中实现):以 import … from … assert { type: ‘json’ }的形式对导入类型做断言,可用于在不解析 JSON 的前提下将其导入。


import json from './foo.json' assert { type: 'json' };console.log(json.answer); // 42
复制代码


●  RegExp 匹配索引:获取正则表达式匹配和捕获组的开始和结束索引。适用于 RegExp.exec(), String.match() 和 String.matchAll()。


const matchObj = /(test+)(hello+)/d.exec('start-testesthello-stop');// PREVIOUSLY:console.log(matchObj?.index);// NEW:if (matchObj) {// Start and end index of entire match (before we only had the start).console.log(matchObj.indices[0]); // [9, 18]// Start and end indexes of capture groups.console.log(matchObj.indices[1]); // [9, 13]console.log(matchObj.indices[2]); // [13, 18]}
复制代码


●  负索引 (.at(-1)): 在索引数组或字符串时,可以使用 at 从末尾开始索引。相当于 arr[arr.length - 1)


console.log([4, 5].at(-1)) // 5
复制代码


●  hasOwn: 推荐使用的新方法,用于查找对象具有哪些属性,用于替代 obj.hasOwnProperty()。在某些特殊情况下效果更好。


const obj = { name: 'test' };console.log(Object.hasOwn(obj, 'name')); // trueconsole.log(Object.hasOwn(obj, 'gender')); // false
复制代码


●  错误原因(Error cause): 现在可以为错误指定可选原因,允许在重新抛出时指定原始错误。


try {try {connectToDatabase();} catch (err) {throw new Error('Connecting to database failed.', { cause: err });}} catch (err) {console.log(err.cause); // ReferenceError: connectToDatabase is not defined}
复制代码


之后(已可在 TypeScript 4.9 中使用)


●  Auto-Accessor: 自动将属性设为私有,并为其创建 get/set 访问器。


class Person {accessor name: string;constructor(name: string) {this.name = name;console.log(this.name) // 'test'}}const person = new Person('test');
复制代码


TypeScript

基础(进一步介绍上下文)


●  泛型: 将类型传递至其他类型,负责在对类型进行泛化后仍保证类型安全。应始终优先使用泛型,而非 any 或 unknown。


// WITHOUT:function getFirstUnsafe(list: any[]): any {return list[0];}const firstUnsafe = getFirstUnsafe(['test']); // typed as any// WITH:function getFirst<Type>(list: Type[]): Type {return list[0];}const first = getFirst<string>(['test']); // typed as string// In this case the parameter can even be dropped because it is inferred from the argument.const firstInferred = getFirst(['test']); // typed as string// The types accepted as generics can also be limited using `extends`. The Type is also usually shortened to T.class List<T extends string | number> {private list: T[] = [];get(key: number): T {return this.list[key];}push(value: T): void {this.list.push(value);}}const list = new List<string>();list.push(9); // Type error: Argument of type 'number' is not assignable to parameter of type 'string'.const booleanList = new List<boolean>(); // Type error: Type 'boolean' does not satisfy the constraint 'string | number'.
复制代码


更早(更早发布但仍然重要)


●  实用程序类型: TypeScript 中包含多种实用程序类型,这里解释其中最重要的几种。


interface Test {  name: string;  age: number;}// The Partial utility type makes all properties optional.type TestPartial = Partial<Test>; // typed as { name?: string | undefined; age?: number | undefined; }// The Required utility type does the opposite.type TestRequired = Required<TestPartial>; // typed as { name: string; age: number; }// The Readonly utility type makes all properties readonly.type TestReadonly = Readonly<Test>; // typed as { readonly name: string; readonly age: string }// The Record utility type allows the simple definition of objects/maps/dictionaries. It is preferred to index signatures whenever possible.const config: Record<string, boolean> = { option: false, anotherOption: true };// The Pick utility type gets only the specified properties.type TestLess = Pick<Test, 'name'>; // typed as { name: string; }type TestBoth = Pick<Test, 'name' | 'age'>; // typed as { name: string; age: string; }// The Omit utility type ignores the specified properties.typetype TestFewer = Omit<Test, 'name'>; // typed as { age: string; }type TestNone = Omit<Test, 'name' | 'age'>; // typed as {}// The Parameters utility type gets the parameters of a function type.function doSmth(value: string, anotherValue: number): string {  return 'test';}type Params = Parameters<typeof doSmth>; // typed as [value: string, anotherValue: number]// The ReturnType utility type gets the return type of a function type.type Return = ReturnType<typeof doSmth>; // typed as string// There are many more, some of which are introduced further down.
复制代码


●  条件类型: 根据某种类型是否匹配 / 扩展另一种类型,来对类型做有条件设置。可以按照 JavaScript 中条件(三元)运算符的方式理解。


// Only extracts the array type if it is an array, otherwise returns the same type.type Flatten<T> = T extends any[] ? T[number] : T;// Extracts out the element type.type Str = Flatten<string[]>; // typed as string// Leaves the type alone.type Num = Flatten<number>; // typed as number
复制代码


●  使用条件类型进行推断: 并非所有泛型类型都需要由用户指定,有些也可以从代码中推断得出。要实现基于类型推断的条件逻辑,必须有 infer 关键字,它会以某种方式定义临时推断类型变量。


// Starting with the previous example, this can be written more cleanly.type FlattenOld<T> = T extends any[] ? T[number] : T;// Instead of indexing the array, we can just infer the Item type from the array.type Flatten<T> = T extends (infer Item)[] ? Item : T;// If we wanted to write a type that gets the return type of a function and otherwise is undefined, we could also infer that.type GetReturnType<Type> = Type extends (...args: any[]) => infer Return ? Return : undefined;type Num = GetReturnType<() => number>; // typed as numbertype Str = GetReturnType<(x: string) => string>; // typed as stringtype Bools = GetReturnType<(a: boolean, b: boolean) => void>; // typed as undefined
复制代码


●  元组可选元素与其余元素: 使用 ? 声明元组中的可选元素,使用 … 声明元组中的其余元素。


// If we don't yet know how long a tuple is going to be, but it's at least one, we can specify optional types using `?`.const list: [number, number?, boolean?] = [];list[0] // typed as numberlist[1] // typed as number | undefinedlist[2] // typed as boolean | undefinedlist[3] // Type error: Tuple type '[number, (number | undefined)?, (boolean | undefined)?]' of length '3' has no element at index '3'.// We could also base the tuple on an existing type.// If we want to pad an array at the start, we could do that using the rest operator `...`.function padStart<T extends any[]>(arr: T, pad: string): [string, ...T] {  return [pad, ...arr];}const padded = padStart([1, 2], 'test'); // typed as [string, number, number]
复制代码


●  抽象类和方法: 类和类中的各方法可以被声明为 abstract,以防止其被实例化。


abstract class Animal {  abstract makeSound(): void;  move(): void {    console.log('roaming the earth...');  }}// Abstract methods need to be implemented when extended.class Cat extends Animal {} // Compile error: Non-abstract class 'Cat' does not implement inherited abstract member 'makeSound' from class 'Animal'.class Dog extends Animal {  makeSound() {    console.log('woof');  }}// Abstract classes cannot be instantiated (like Interfaces), and abstract methods cannot be called.new Animal(); // Compile error: Cannot create an instance of an abstract class.const dog = new Dog().makeSound(); // "woof"
复制代码


●  构造函数签名: 在类声明之外,定义构造函数的类型。在大多数情况下不应使用,建议用抽象类代替。


interface MyInterface {  name: string;}interface ConstructsMyInterface {  new(name: string): MyInterface;}class Test implements MyInterface {  name: string;  constructor(name: string) {    this.name = name;  }}class AnotherTest {  age: number;}function makeObj(n: ConstructsMyInterface) {    return new n('hello!');}const obj = makeObj(Test); // typed as Testconst anotherObj = makeObj(AnotherTest); // Type error: Argument of type 'typeof AnotherTest' is not assignable to parameter of type 'ConstructsMyInterface'.
复制代码


●  ConstructorParameters Utility 类型: 属于 TypeScript 辅助函数,能够根据构造函数类型(但不是类)获取构造函数参数。


// What if we wanted to get the constructor argument for our makeObj function.interface MyInterface {  name: string;}interface ConstructsMyInterface {  new(name: string): MyInterface;}class Test implements MyInterface {  name: string;  constructor(name: string) {    this.name = name;  }}function makeObj(test: ConstructsMyInterface, ...args: ConstructorParameters<ConstructsMyInterface>) {  return new test(...args);}makeObj(Test); // Type error: Expected 2 arguments, but got 1.const obj = makeObj(Test, 'test'); // typed as Test
复制代码


TypeScript 4.0


●  可变元组类型: 元组中的其余元素现在是通用的,且允许使用多个其余元素。


// What if we had a function that combines two tuples of undefined length and types? How can we define the return type?// PREVIOUSLY:// We could write some overloads.declare function concat(arr1: [], arr2: []): [];declare function concat<A>(arr1: [A], arr2: []): [A];declare function concat<A, B>(arr1: [A], arr2: [B]): [A, B];declare function concat<A, B, C>(arr1: [A], arr2: [B, C]): [A, B, C];declare function concat<A, B, C, D>(arr1: [A], arr2: [B, C, D]): [A, B, C, D];declare function concat<A, B>(arr1: [A, B], arr2: []): [A, B];declare function concat<A, B, C>(arr1: [A, B], arr2: [C]): [A, B, C];declare function concat<A, B, C, D>(arr1: [A, B], arr2: [C, D]): [A, B, C, D];declare function concat<A, B, C, D, E>(arr1: [A, B], arr2: [C, D, E]): [A, B, C, D, E];declare function concat<A, B, C>(arr1: [A, B, C], arr2: []): [A, B, C];declare function concat<A, B, C, D>(arr1: [A, B, C], arr2: [D]): [A, B, C, D];declare function concat<A, B, C, D, E>(arr1: [A, B, C], arr2: [D, E]): [A, B, C, D, E];declare function concat<A, B, C, D, E, F>(arr1: [A, B, C], arr2: [D, E, F]): [A, B, C, D, E, F];// Even just for three items each, this is really suboptimal.// Instead we could combine the types.declare function concatBetter<T, U>(arr1: T[], arr2: U[]): (T | U)[];// But this types to (T | U)[]// NEW:// With variadic tuple types, we can define it easily and keep the information about the length.declare function concatNew<T extends Arr, U extends Arr>(arr1: T, arr2: U): [...T, ...U];const tuple = concatNew([23, 'hey', false] as [number, string, boolean], [5, 99, 20] as [number, number, number]);console.log(tuple[0]); // 23const element: number = tuple[1]; // Type error: Type 'string' is not assignable to type 'number'.console.log(tuple[6]); // Type error: Tuple type '[23, "hey", false, 5, 99, 20]' of length '6' has no element at index '6'.
复制代码


●  标记元组元素: 元组元素现可被命名为 [start: number, end: number] 的形式。如果命名其中一个元素,则所有元素必须均被命名。


type Foo = [first: number, second?: string, ...rest: any[]];// This allows the arguments to be named correctly here, it also shows up in the editor.declare function someFunc(...args: Foo);
复制代码


●  从构造函数推断类属性: 在构造函数中设置属性时,现可推断其类型,不再需要手动设置。


class Animal {// No need to set types when they are assigned in the constructor.name;constructor(name: string) {this.name = name;console.log(this.name); // typed as string}}
复制代码


●JSDoc @deprecated 支持: TypeScript 现可识别 JSDoc/TSDoc @deprecated 标签。


/** @deprecated message */type Test = string;const test: Test = 'dfadsf'; // Type error: 'Test' is deprecated.
复制代码


TypeScript 4.1


●  模板字面量类型: 在定义字面量类型时,可以通过 ${Type}等模板指定类型。这样可以构造复杂的字符串类型,例如将多个字符串字面量组合起来。


type VerticalDirection = 'top' | 'bottom';type HorizontalDirection = 'left' | 'right';type Direction = `${VerticalDirection} ${HorizontalDirection}`;const dir1: Direction = 'top left';const dir2: Direction = 'left'; // Type error: Type '"left"' is not assignable to type '"top left" | "top right" | "bottom left" | "bottom right"'.const dir3: Direction = 'left top'; // Type error: Type '"left top"' is not assignable to type '"top left" | "top right" | "bottom left" | "bottom right"'.// This can also be combined with generics and the new utility types.declare function makeId<T extends string, U extends string>(first: T, second: U): `${Capitalize<T>}-${Lowercase<U>}`;
复制代码


●  在映射类型中重新映射键: 为已映射的类型重新分配类型,但仍使用其值,例如 [K in keyof T as NewKeyType]: T[K]。


// Let's say we wanted to reformat an object but prepend its IDs with an underscore.const obj = { value1: 0, value2: 1, value3: 3 };const newObj: { [Property in keyof typeof obj as `_${Property}`]: number }; // typed as { _value1: number; _value2: number; value3: number; }
复制代码


●  递归条件类型: 在定义之内使用条件类型,这种类型允许以有条件方式解包无限嵌套值。


type Awaited<T> = T extends PromiseLike<infer U> ? Awaited<U> : T;type P1 = Awaited<string>; // typed as stringtype P2 = Awaited<Promise<string>>; // typed as stringtype P3 = Awaited<Promise<Promise<string>>>; // typed as string
复制代码


●  JSDOC @see 标签的编辑器支持: JSDoc/TSDoc @see variable/type/link 标签现可在编辑器中受到支持。


const originalValue = 1;/*** Copy of another value* @see originalValue*/const value = originalValue;
复制代码


●  tsc — explainFiles:  --explainFiles 选项可被 TypeScript CLI 用于解释哪些文件是编译的一部分、为什么会这样。这一点对于调试非常重要。警告:对于大型项目或较为复杂的设置,这会生成大量输出,建议改用 tsc --explainFiles | less 或其他类似功能。


tsc --explainFiles<<output../../.asdf/installs/nodejs/16.13.1/.npm/lib/node_modules/typescript/lib/lib.es5.d.tsLibrary referenced via 'es5' from file '../../.asdf/installs/nodejs/16.13.1/.npm/lib/node_modules/typescript/lib/lib.es2015.d.ts'Library referenced via 'es5' from file '../../.asdf/installs/nodejs/16.13.1/.npm/lib/node_modules/typescript/lib/lib.es2015.d.ts'../../.asdf/installs/nodejs/16.13.1/.npm/lib/node_modules/typescript/lib/lib.es2015.d.tsLibrary referenced via 'es2015' from file '../../.asdf/installs/nodejs/16.13.1/.npm/lib/node_modules/typescript/lib/lib.es2016.d.ts'Library referenced via 'es2015' from file '../../.asdf/installs/nodejs/16.13.1/.npm/lib/node_modules/typescript/lib/lib.es2016.d.ts'../../.asdf/installs/nodejs/16.13.1/.npm/lib/node_modules/typescript/lib/lib.es2016.d.tsLibrary referenced via 'es2016' from file '../../.asdf/installs/nodejs/16.13.1/.npm/lib/node_modules/typescript/lib/lib.es2017.d.ts'Library referenced via 'es2016' from file '../../.asdf/installs/nodejs/16.13.1/.npm/lib/node_modules/typescript/lib/lib.es2017.d.ts'...output
复制代码


●  解构变量可被显式标记为未使用: 在解构时,可使用下划线将变量标记为未使用,从而防止 TypeScript 抛出“未使用的变量”错误。


const [_first, second] = [3, 5];console.log(second);// Or even shorterconst [_, value] = [3, 5];console.log(value);
复制代码


TypeScript 4.3


●  属性上的单独写入类型: 在定义 set/get 访问器时,write/set 类型现可不同于 read/get 类型。意味着设置器能够接受相同值的多种格式。


class Test {private _value: number;get value(): number {return this._value;}set value(value: number | string) {if (typeof value === 'number') {this._value = value;return;}this._value = parseInt(value, 10);}}
复制代码


●  override: 使用 override,会将继承的类方法显式标记为覆写。因此当父类发生变化时,TypeScript 会提醒父方法已不存在,从而实现更安全的复杂继承模式。


class Parent {getName(): string {return 'name';}}class NewParent {getFirstName(): string {return 'name';}}class Test extends Parent {override getName(): string {return 'test';}}class NewTest extends NewParent {override getName(): string { // Type error: This member cannot have an 'override' modifier because it is not declared in the base class 'NewParent'.return 'test';}}
复制代码


●  静态索引签名: 在类上使用静态属性时,现在也可以使用 static [propName: string]: string 设置索引签名。


// PREVIOUSLY:class Test {}Test.test = ''; // Type error: Property 'test' does not exist on type 'typeof Test'.// NEW:class NewTest {static [key: string]: string;}NewTest.test = '';
复制代码


●对 JSDOC@link 标签提供编辑器支持: JSDoc/TSDoc {@link variable/type/link} 内联标签现可在编辑器中显示和解析。


const originalValue = 1;/*** Copy of {@link originalValue}*/const value = originalValue;
复制代码


TypeScript 4.4


●  精确的可选属性类型 ( — exactOptionalPropertyTypes): 使用编译器标志 --exactOptionalPropertyTypes 时(或在 tsconfig.json 中),隐式允许 undefined(例如 property?: string)的属性将不允许被分配为 undefined。相反,undefined 必须经过明确许可,例如 property: string | undefined。


class Test {name?: string;age: number | undefined;}const test = new Test();test.name = 'test'; // Type error: Option 'exactOptionalPropertyTypes' cannot be specified without specifying option 'strictNullChecks'.test.age = 0;
复制代码


TypeScript 4.5


●  Awaited 类型与 Promise 改进: 新的 Awaited<>实用程序类型能从无限嵌套的 Promises 中提取值类型(类似于 await 对该值的操作)。这也改进了 Promise.all() 的类型推断。


// Let's say we want to have a generic awaited value.// We can use the Awaited utility type for this (its source code was part of a previous example),// so infinitely nested Promises all resolve to their value.type P1 = Awaited<string>; // typed as stringtype P2 = Awaited<Promise<string>>; // typed as stringtype P3 = Awaited<Promise<Promise<string>>>; // typed as string
复制代码


●  导入名称上的类型修饰符: 在普通(非 import type)导入语句中,关键字 type 可用于表示该值只应在类型编译时导入(且可以去除)。


// PREVIOUSLY:// The optimal way to import types is to use the `import type` keyword to prevent them from actually being imported after compilation.import { something } from './file';import type { SomeType } from './file';// This needs two import statements for the same file.// NEW:// Now this can be combined into one statement.import { something, type SomeType } from './file';
复制代码


●  const 断言: 在将常量定义为 as const 时,即可将其准确归类为字面量类型。这项功能有多种用例,可以轻松进行准确分类。此功能还会令对象和数组成为 readonly,防止常量对象发生突变。


// PREVIOUSLY:// The optimal way to import types is to use the `import type` keyword to prevent them from actually being imported after compilation.import { something } from './file';import type { SomeType } from './file';// This needs two import statements for the same file.// NEW:// Now this can be combined into one statement.import { something, type SomeType } from './file';
复制代码


●  类中各方法的片段补全: 当一个类继承多个方法类型时,编辑器现可为各类型提供建议片段。



TypeScript 4.6


●  索引访问推断改进:当直接在键内直接索引一个类型时,如果该类型位于同一对象上,现在其准确率会更高。这也是 TypeScript 现代化特性的良好体现。


interface AllowedTypes {'number': number;'string': string;'boolean': boolean;}// The Record specifies the kind and value type from the allowed types.type UnionRecord<AllowedKeys extends keyof AllowedTypes> = { [Key in AllowedKeys]:{kind: Key;value: AllowedTypes[Key];logValue: (value: AllowedTypes[Key]) => void;}}[AllowedKeys];// The function logValue only accepts the value of the Record.function processRecord<Key extends keyof AllowedTypes>(record: UnionRecord<Key>) {record.logValue(record.value);}processRecord({kind: 'string',value: 'hello!',// The value used to implicitly have the type string | number | boolean,// but now is correctly inferred to just string.logValue: value => {console.log(value.toUpperCase());}});
复制代码


●  TypeScript Trace Analyzer ( — generateTrace):  --generateTrace


选项可在 TypeScript CLI 当中用于生成一个文件,其中包含关于类型检查和编译过程的详情信息。这有助于优化复杂类型。


tsc --generateTrace tracecat trace/trace.json<<output[{"name":"process_name","args":{"name":"tsc"},"cat":"__metadata","ph":"M","ts":...,"pid":1,"tid":1},{"name":"thread_name","args":{"name":"Main"},"cat":"__metadata","ph":"M","ts":...,"pid":1,"tid":1},{"name":"TracingStartedInBrowser","cat":"disabled-by-default-devtools.timeline","ph":"M","ts":...,"pid":1,"tid":1},{"pid":1,"tid":1,"ph":"B","cat":"program","ts":...,"name":"createProgram","args":{"configFilePath":"/...","rootDir":"/..."}},{"pid":1,"tid":1,"ph":"B","cat":"parse","ts":...,"name":"createSourceFile","args":{"path":"/..."}},{"pid":1,"tid":1,"ph":"E","cat":"parse","ts":...,"name":"createSourceFile","args":{"path":"/..."}},{"pid":1,"tid":1,"ph":"X","cat":"program","ts":...,"name":"resolveModuleNamesWorker","dur":...,"args":{"containingFileName":"/..."}},...outputcat trace/types.json<<output[{"id":1,"intrinsicName":"any","recursionId":0,"flags":["..."]},{"id":2,"intrinsicName":"any","recursionId":1,"flags":["..."]},{"id":3,"intrinsicName":"any","recursionId":2,"flags":["..."]},{"id":4,"intrinsicName":"error","recursionId":3,"flags":["..."]},{"id":5,"intrinsicName":"unresolved","recursionId":4,"flags":["..."]},{"id":6,"intrinsicName":"any","recursionId":5,"flags":["..."]},{"id":7,"intrinsicName":"intrinsic","recursionId":6,"flags":["..."]},{"id":8,"intrinsicName":"unknown","recursionId":7,"flags":["..."]},{"id":9,"intrinsicName":"unknown","recursionId":8,"flags":["..."]},{"id":10,"intrinsicName":"undefined","recursionId":9,"flags":["..."]},{"id":11,"intrinsicName":"undefined","recursionId":10,"flags":["..."]},{"id":12,"intrinsicName":"null","recursionId":11,"flags":["..."]},{"id":13,"intrinsicName":"string","recursionId":12,"flags":["..."]},...output
复制代码


TypeScript 4.7


●  在 Node.js 中支持 ECMAScript 模块: 在使用 ES Modules 替代 CommonJS 时,TypeScript 现可支持指定默认值。具体指定在 tsconfig.json 中实现。


..."compilerOptions": [..."module": "es2020"]...
复制代码


●  package.json 中的类型:  package.json 中的 type 字段可被设定为"module",以供 ES Modules 使用 node.js。在大多数情况下,这种方式对 TypeScript 已经足够,不需要前面提到的编译器选项。


..."type": "module"...
复制代码


●  实例化表达式: 实例化表达式允许在引用一个值时,指定类型参数。这样可以在不创建包装器的前提下,收窄泛型类型。


class List<T> {private list: T[] = [];get(key: number): T {return this.list[key];}push(value: T): void {this.list.push(value);}}function makeList<T>(items: T[]): List<T> {const list = new List<T>();items.forEach(item => list.push(item));return list;}// Let's say we want to have a function that creates a list but only allows certain values.// PREVIOUSLY:// We need to manually define a wrapper function and pass the argument.function makeStringList(text: string[]) {return makeList(text);}// NEW:// Using instantiation expressions, this is much easier.const makeNumberList = makeList<number>;
复制代码


●  扩展对推断类型变量的约束: 在条件类型中推断类型变量时,现在可以使用 extends 直接将其收窄 / 约束。


// Let's say we want to type a type that only gets the first element of an array if it's a string.// We can use conditional types for this.// PREVIOUSLY:type FirstIfStringOld<T> =T extends [infer S, ...unknown[]]? S extends string ? S : never: never;// But this needs two nested conditional types. We can also do it in one.type FirstIfString<T> =T extends [string, ...unknown[]]// Grab the first type out of `T`? T[0]: never;// This is still suboptimal because we need to index the array for the correct type.// NEW:// Using extends Constraints on infer Type Variables, this can be declared a lot easier.type FirstIfStringNew<T> =T extends [infer S extends string, ...unknown[]]? S: never;// Note that the typing worked the same before, this is just a cleaner syntax.type A = FirstIfStringNew<[string, number, number]>; // typed as stringtype B = FirstIfStringNew<["hello", number, number]>; // typed as "hello"type C = FirstIfStringNew<["hello" | "world", boolean]>; // typed as "hello" | "world"type D = FirstIfStringNew<[boolean, number, string]>; // typed as never
复制代码


●  类型参数的可选变体注释: 泛型在检查是否“匹配”时可以有不同行为,例如对 getter 和 setter,对是否允许继承的判断是相反的。为了明确起见,现在用户可以明确指定。


// Let's say we have an interface / a class that extends another one.interface Animal {animalStuff: any;}interface Dog extends Animal {dogStuff: any;}// And we have some generic "getter" and "setter".type Getter<T> = () => T;type Setter<T> = (value: T) => void;// If we want to find out if Getter<T1> matches Getter<T2> or Setter<T1> matches Setter<T2>, this depends on the covariance.function useAnimalGetter(getter: Getter<Animal>) {getter();}// Now we can pass a Getter into the function.useAnimalGetter((() => ({ animalStuff: 0 }) as Animal));// This obviously works.// But what if we want to use a Getter which returns a Dog instead?useAnimalGetter((() => ({ animalStuff: 0, dogStuff: 0 }) as Dog));// This works as well because a Dog is also an Animal.function useDogGetter(getter: Getter<Dog>) {getter();}// If we try the same for the useDogGetter function we will not get the same behavior.useDogGetter((() => ({ animalStuff: 0 }) as Animal)); // Type error: Property 'dogStuff' is missing in type 'Animal' but required in type 'Dog'.// This does not work, because a Dog is expected, not just an Animal.useDogGetter((() => ({ animalStuff: 0, dogStuff: 0 }) as Dog));// This, however, works.// Intuitively we would maybe expect the Setters to behave the same, but they don't.function setAnimalSetter(setter: Setter<Animal>, value: Animal) {setter(value);}// If we pass a Setter of the same type it still works.setAnimalSetter((value: Animal) => {}, { animalStuff: 0 });function setDogSetter(setter: Setter<Dog>, value: Dog) {setter(value);}// Same here.setDogSetter((value: Dog) => {}, { animalStuff: 0, dogStuff: 0 });// But if we pass a Dog Setter into the setAnimalSetter function, the behavior is reversed from the Getters.setAnimalSetter((value: Dog) => {}, { animalStuff: 0, dogStuff: 0 }); // Type error: Argument of type '(value: Dog) => void' is not assignable to parameter of type 'Setter<Animal>'.// This time it works the other way around.setDogSetter((value: Animal) => {}, { animalStuff: 0, dogStuff: 0 });// NEW:// To signal this to TypeScript (not needed but helpful for readability), use the new Optional Variance Annotations for Type Parameters.type GetterNew<out T> = () => T;type SetterNew<in T> = (value: T) => void;
复制代码


●  使用 moduleSuffixes 实现分辨率自定义: 在使用具有自定义文件后缀的环境时(例如,.ios 用于原生应用构建),现在您可以为 TypeScript 指定这些后缀以正确对导入进行解析。具体指定在 tsconfig.json 中实现。


..."compilerOptions": [..."module": [".ios", ".native", ""]]...import * as foo from './foo';// This first checks ./foo.ios.ts, ./foo.native.ts, and finally ./foo.ts.
复制代码


●  在编辑器内转到源定义: 在编辑器中,开放新的“转到源定义”菜单项。其功能类似于“转到定义”,但更多指向.ts 和 .js 文件,而非类型定义 (.d.ts)。




TypeScript 4.9


●  satisfies 运算符: satisfies 运算符允许检查与类型间的兼容性,且无需实际分配该类型。这样可以在保持兼容性的同时,获得更准确的类型推断。


// PREVIOUSLY:// Let's say we have an object/map/dictionary which stores various items and their colors.const obj = {fireTruck: [255, 0, 0],bush: '#00ff00',ocean: [0, 0, 255]} // typed as { fireTruck: number[]; bush: string; ocean: number[]; }// This implicitly types the properties so we can operate on the arrays and the string.const rgb1 = obj.fireTruck[0]; // typed as numberconst hex = obj.bush; // typed as string// Let's say we only want to allow certain objects.// We could use a Record type.const oldObj: Record<string, [number, number, number] | string> = {fireTruck: [255, 0, 0],bush: '#00ff00',ocean: [0, 0, 255]} // typed as Record<string, [number, number, number] | string>// But now we lose the typings of the properties.const oldRgb1 = oldObj.fireTruck[0]; // typed as string | numberconst oldHex = oldObj.bush; // typed as string | number// NEW:// With the satisfies keyword we can check compatibility with a type without actually assigning it.const newObj = {fireTruck: [255, 0, 0],bush: '#00ff00',ocean: [0, 0, 255]} satisfies Record<string, [number, number, number] | string> // typed as { fireTruck: [number, number, number]; bush: string; ocean: [number, number, number]; }// And we still have the typings of the properties, the array even got more accurate by becoming a tuple.const newRgb1 = newObj.fireTruck[0]; // typed as numberconst newRgb4 = newObj.fireTruck[3]; // Type error: Tuple type '[number, number, number]' of length '3' has no element at index '3'.const newHex = newObj.bush; // typed as string
复制代码


●  编辑器中的“删除未使用的导入”与“排序导入”命令: 在编辑器中,新命令(及自动修复)“删除未使用的导入”和“排序导入”让导入管理更加轻松易行。



原文链接:

https://medium.com/@LinusSchlumberger/all-javascript-and-typescript-features-of-the-last-3-years-629c57e73e42


相关阅读:

TypeScript 与 JavaScript:你应该知道的区别

JavaScript 框架太多了?相反,是太少了

最佳的 18 个 JAVASCRIPT 前端开发框架和库

我踩过了 TypeScript 的坑,只想告诉你快来

2023-04-03 12:345472

评论

发布
暂无评论
发现更多内容

大厂面试必备!字节大佬刷Leetcode总结的算法笔记

做梦都在改BUG

Java 数据结构 算法 LeetCode

【体验有奖】玩转 AIGC,函数计算 x 通义千问预体验,一键部署AI应用赢Airpods

Serverless Devs

函数计算FC AIGC

揭秘阿里云Flink智能诊断利器——Fllink Job Advisor

阿里云大数据AI技术

大数据 flink 企业号 6 月 PK 榜

探究核心技术&最佳实践,云原生OLAP论坛火热开启!

阿里云大数据AI技术

云原生

打造高可用的微服务架构:Spring Cloud 的优化与实践

xfgg

Java 微服务 SpringCloud 6 月 优质更文活动

百度离线资源治理

百度Geek说

数据库 大数据 离线 企业号 6 月 PK 榜 6 月 优质更文活动

深入探究Flink:实时处理与批量处理的完美结合

xfgg

Java flink 6 月 优质更文活动

深入了解mock.js,打造出类似真实数据的模拟数据

Apifox

程序员 前端 前端开发 API Mock

Openjob 1.0.2 重磅发布,新一代分布式任务调度框架

stelin

分布式架构 Java 分布式

深度学习应用篇-计算机视觉-图像分类[3]:ResNeXt、Res2Net、Swin Transformer、Vision Transformer等模型结构、实现、模型特点详细介绍

汀丶人工智能

人工智能 深度学习 计算机视觉 图像分类 6 月 优质更文活动

Amazon CodeWhisperer代码提示体验本文带你了解

我叫于豆豆吖.

云计算 亚马逊 亚马逊云

深度学习应用篇-计算机视觉-目标检测[4]:综述、边界框bounding box、锚框(Anchor box)、交并比、非极大值抑制NMS、SoftNMS

汀丶人工智能

人工智能 深度学习 计算机视觉 目标检测 6 月 优质更文活动

2023世界人工智能大会“AI生成与垂直大语言模型”论坛重磅来袭!

NLP资深玩家

NFTScan 成为 CMC 官方 NFT 数据合作伙伴

NFT Research

crypto NFT

瞄准“量效”难题,百度营销创新推出大健康线索营销解决方案-医效通

说山水

咸阳有没有等保测评机构?在哪里?怎么联系?

行云管家

等保 等保测评 等保测评机构 咸阳

OpenHarmony 4.0 Beta1发布,邀您体验

OpenHarmony开发者

OpenHarmony

10分钟了解Kubernetes网络

俞凡

架构 Kubernetes 云原生

用户组是什么意思?怎么容易理解?有什么作用?

行云管家

运维 权限 用户组

让ChatGPT来写今年的高考作文,能得几分?

楚少AI

ChatGPT4 2023高考 ChatGPT写作

2022百度ESG报告发布:年度答卷展现安全信任承诺

百度安全

AI老师的作者:17岁的高中生,可能是你想要孩子成为的样子

无人之路

AI 教育 ChatGPT

Jogger慢跑者链游系统开发NFT技术

薇電13242772558

NFT 链游

问道价值互联网,区块链的下一个十年 | 2023开放原子全球开源峰会区块链分论坛即将启幕

开放原子开源基金会

区块链 开源 开放原子全球开源峰会

什么是双机热备技术?华为和思科如何实现双机热备?

做梦都在改BUG

Java 网络 双机热备

最强AIGC实战应用速成指南来了!14天掌握核心技术

飞桨PaddlePaddle

人工智能 深度学习 百度飞桨

来了解Amazon CodeWhisperer的强大吧

初学者

云计算 亚马逊 亚马逊云

“变脸的秘密”!直播源码app开发技术特效功能的实现

山东布谷科技

源码剖析 APP开发 软件开发、 源码搭建 直播源码

等待还是转行?GitHub爆赞的10W字Java八股文,你没得选择

做梦都在改BUG

Java java面试 Java八股文 Java面试题 Java面试八股文

浅谈微服务异步解决方案

做梦都在改BUG

Java 微服务 异步

赋能生态合作 共话数字创新 | 2023开放原子全球开源峰会软硬协同开源分论坛即将启幕

开放原子开源基金会

开源 开放原子全球开源峰会 开放原子 软硬协同开源

  • 扫码加入 InfoQ 开发者交流群
三年回顾:JavaScript与TypeScript最新特性汇总_大前端_InfoQ精选文章