编程语言
首页 > 编程语言> > 过去 3 年的所有 JavaScript 和 TypeScript 功能

过去 3 年的所有 JavaScript 和 TypeScript 功能

作者:互联网

ECMAScript

过去(仍然相关的旧介绍)

假设我们想编写一种方法来记录包含数字的任意字符串,但格式化数字。
我们可以为此使用标记模板。
函数格式数字(字符串:模板字符串数组,数字:数字):字符串{返回字符串
[0] +数字。到固定(2) + 字符串[1];
}
控制台。log(formatNumbers'这是值: ${0}, 这很重要。这是值:0.00,这很重要。

或者,如果我们想“翻译”(此处更改为小写)字符串中的翻译键。
函数翻译键(键:字符串):字符串{ 
 返回键。toLocaleLowerCase();
}
function translate(strings: TemplateStringsArray, ...表达式:字符串[]):字符串{
返回字符串。reduce((累加器, currentValue, index) => 累加器 + currentValue + translateKey(expressions[index] ??“”)、“);
}
控制台。log(翻译'Hello, this is ${'NAME'} 表示${'MESSAGE'}.');您好,这是要说消息的名字。
const obj: { [index: string]: string } = {}; 

常量符号A = 符号('a');
常量符号B = 符号。对于(“b”);

控制台。log(符号 A.描述);// “a”

obj[symbolA] = 'a';
obj[symbolB] = 'b';
obj['c'] = 'c';
obj.d = 'd';

控制台。log(obj[symbolA]);“a”
控制台。log(obj[symbolB]);// “b”
// 密钥不能使用任何其他符号或没有符号访问。
控制台。log(obj[Symbol('a')]);未定义的
控制台。log(obj['a']);// 未定义

// 使用 ...在。
for (const i in obj) {
 console.日志(i);“c”, “d”
}

ES2020

// 以前:
// 如果我们有一个对象变量(或任何其他结构),我们不确定是否定义,
// 我们无法轻松访问该属性。
常量对象: { 名称: 字符串 } |未定义 = 数学。随机() > 0.5 ?未定义 : { 名称: '测试' };
常量值 = 对象。姓名;类型错误:“对象”可能是“未定义”。

我们可以先检查它是否已定义,但这会影响可读性,并且对于嵌套对象来说变得复杂。
const objectOld: { name: string } |未定义 = 数学。随机() > 0.5 ?未定义 : { 名称: '测试' };
常量值老 = 对象老 ?对象旧。名称 : 未定义;

相反,
我们可以使用可选的链接。
const 对象新: { 名称: 字符串 } |未定义 = 数学。随机() > 0.5 ?未定义 : { 名称: '测试' };
常量值新 = 对象新?。姓名;

这也可以用于索引和函数。
常量数组:字符串[] |未定义 = 数学。随机() > 0.5 ?未定义:[“测试”];
常量项目 = 数组?。[0];
常量函数: (() => 字符串) |未定义 = 数学。随机() > 0.5 ?未定义 : () => '测试';
常量结果 = 函数?。();
const value: string | undefined = Math.random() > 0.5 ? undefined : 'test';

// PREVIOUSLY:
// When we want to conditionally assign something else if a value is undefined or null, we could use the "||" operator.
const anotherValue = value || 'hello';
console.log(anotherValue); // "test" or "hello"

// This works fine when using truthy values, but if we were to compare to 0 or an empty string, it would also apply.
const incorrectValue = '' || 'incorrect';
console.log(incorrectValue); // always "incorrect"
const anotherIncorrectValue = 0 || 'incorrect';
console.log(anotherIncorrectValue); // always "incorrect"

// NEW:
// Instead we can use the new nullish coalescing operator. It only applies to undefined and null values.
const newValue = value ?? 'hello';
console.log(newValue) // always "hello"

// Now falsy values are not replaced.
const correctValue = '' ?? 'incorrect';
console.log(correctValue); // always ""
const anotherCorrectValue = 0 ?? 'incorrect';
console.log(anotherCorrectValue); // always 0
let importModule;
if (shouldImport) {
  importModule = await import('./module.mjs');
}
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"
}
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"
  }
}
// PREVIOUSLY:
// JavaScript stores numbers as floats, so there is always a bit of inaccuracy
// but more importantly, there start to be inaccuracies for integer operations after a certain number.
const maxSafeInteger = 9007199254740991;
console.log(maxSafeInteger === Number.MAX_SAFE_INTEGER); // true

// If we compare numbers bigger than it, there can be inaccuracies.
console.log(Number.MAX_SAFE_INTEGER + 1 === Number.MAX_SAFE_INTEGER + 2);

// NEW:
// Using the new BigInt datatype we can in theory store and operate on infinitely big (whole) numbers.
// Use it by using the BigInt constructor or appending "n" at the end of a number.
const maxSafeIntegerPreviously = 9007199254740991n;
console.log(maxSafeIntegerPreviously); // 9007199254740991

const anotherWay = BigInt(9007199254740991);
console.log(anotherWay); // 9007199254740991

// If we use the constructor using numbers, we can not safely pass integers bigger than the MAX_SAFE_INTEGER.
const incorrect = BigInt(9007199254740992);
console.log(incorrect); // 9007199254740992
const incorrectAgain = BigInt(9007199254740993);
console.log(incorrectAgain); // 9007199254740992
// Oops, they convert to the same value.

// Instead use strings or better the other syntax.
const correct = BigInt('9007199254740993');
console.log(correct); // 9007199254740993
const correctAgain = 9007199254740993n;
console.log(correctAgain); // 9007199254740993

// hex, octal and binary numbers can also be passed as strings.
const hex = BigInt('0x1fffffffffffff');
console.log(hex); // 9007199254740991
const octal = BigInt('0o377777777777777777');
console.log(octal); // 9007199254740991
const binary = BigInt('0b11111111111111111111111111111111111111111111111111111');
console.log(binary); // 9007199254740991

// Most arithmetic operations work just like you would expect them to,
// though the other operator needs to also be a BigInt. All operations return BigInts as well.
const addition = maxSafeIntegerPreviously + 2n;
console.log(addition); // 9007199254740993

const multiplication = maxSafeIntegerPreviously * 2n;
console.log(multiplication); // 18014398509481982

const subtraction = multiplication - 10n;
console.log(subtraction); // 18014398509481972

const modulo = multiplication % 10n;
console.log(modulo); // 2

const exponentiation = 2n ** 54n;
console.log(exponentiation); // 18014398509481984

const exponentiationAgain = 2n^54n;
console.log(exponentiationAgain); // 18014398509481984

const negative = exponentiation * -1n;
console.log(negative); // -18014398509481984

// Division works a bit differently, since BigInt can only store whole numbers.
const division = multiplication / 2n;
console.log(division); // 9007199254740991
// For whole numbers that are divisible, this works just fine.

// But for numbers that are not divisible, this will act like integer division (rounded down).
const divisionAgain = 5n / 2n;
console.log(divisionAgain); // 2

// There is no strict equality (but loose equality) to non-BigInt numbers.
console.log(0n === 0); // false
console.log(0n == 0); // true

// But comparisons work as expected.
console.log(1n < 2); // true
console.log(2n > 1); // true
console.log(2 > 2); // false
console.log(2n > 2); // false
console.log(2n >= 2); // true

// They are of the type "bigint"
console.log(typeof 1n); // "bigint"

// They can be converted back to regular numbers (signed and unsigned (no negative numbers)).
// Though this of course sacrifices the accuracy. The number of significant digits can be specified.

console.log(BigInt.asIntN(0, -2n)); // 0
console.log(BigInt.asIntN(1, -2n)); // 0
console.log(BigInt.asIntN(2, -2n)); // -2
// Usually you would use a higher number of bits.

// Negative numbers will be converted to the 2's-complement when converting to an unsigned number.
console.log(BigInt.asUintN(8, -2n)); // 254
console.log(globalThis.Math); // Math Object
console.log(import.meta.url); // "file://..."
export * as am from 'another-module'
import { am } from 'module'

ES2021

const testString = 'hello/greetings everyone/everybody';
// PREVIOUSLY:
// Only replaces the first instance
console.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'
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"]
}
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"
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"
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"
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

async function asyncFuncSuccess() {
  return 'test';
}
async function asyncFuncFail() {
  throw new Error('Test');
}
// PREVIOUSLY:
// Whenever we want to await a promise, this is only possible inside async functions.
// await asyncFuncSuccess(); // SyntaxError: await is only valid in async functions
// So we had to wrap it inside one, thereby losing error handling and top-level concurrency.
try {
  (async () => {
    console.log(await asyncFuncSuccess()); // "test"
    try {
      await asyncFuncFail();
    } catch (e) {
      // This is needed because otherwise the errors are never caught (or way too late without a proper trace).
      console.error(e); // Error: "Test"
      throw e;
    }
  })();
} catch (e) {
  // This is never triggered (or way too late without a proper trace) because the function is async.
  console.error(e);
}
// This is logged before the promise result because the async function is not awaited (because it couldn't).
console.log('Hey'); // "Hey"
// NEW:
// If the file is an ES module (set in package.json, has exports, named ".mts") we can just await at the top level instead.
console.log(await asyncFuncSuccess()); // "test"
try {
  await asyncFuncFail();
} catch (e) {
  console.error(e); // Error: "Test"
}
// This is logged after the promise result because all async calls are awaited.
console.log('Hello'); // "Hello"
class ClassWithPrivateField {
  #privateField;
  #anotherPrivateField = 4;

  constructor() {
    this.#privateField = 42; // Valid
    this.#privateField; // Syntax error
    this.#undeclaredField = 444; // Syntax error
    console.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); // undefined
console.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"
import json from './foo.json' assert { type: 'json' };
console.log(json.answer); // 42
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]
}
console.log([4, 5].at(-1)) // 5

const array = [4, 5];
array.at(-1) = 3; // SyntaxError: Assigning to rvalue
const obj = { name: 'test' };

console.log(Object.hasOwn(obj, 'name')); // true
console.log(Object.hasOwn(obj, 'gender')); // false
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
}

Future (can already be used with TypeScript 4.9)

class Person {
  accessor name: string;

  constructor(name: string) {
    this.name = name;
    console.log(this.name) // 'test'
  }
}

const person = new Person('test');

TypeScript

Basics (Context for further introductions)

// 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'.

Past (Still relevant older introductions)

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.type
type 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.
// 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
// 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 number

type Str = GetReturnType<(x: string) => string>; // typed as string

type 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 number
list[1] // typed as number | undefined
list[2] // typed as boolean | undefined
list[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 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 Test
const anotherObj = makeObj(AnotherTest); // Type error: Argument of type 'typeof AnotherTest' is not assignable to parameter of type 'ConstructsMyInterface'.
// 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]); // 23
const 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'.
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
  }
}
/** @deprecated message */
type Test = string;

const test: Test = 'dfadsf'; // Type error: 'Test' is deprecated.

TypeScript 4.1

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>}`;
// 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 string
type P2 = Awaited<Promise<string>>; // typed as string
type P3 = Awaited<Promise<Promise<string>>>; // typed as string
const originalValue = 1;
/**
  * Copy of another value
  * @see originalValue
  */
const value = originalValue;
tsc --explainFiles

<<output
../../.asdf/installs/nodejs/16.13.1/.npm/lib/node_modules/typescript/lib/lib.es5.d.ts
  Library 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.ts
  Library 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.ts
  Library 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
const [_first, second] = [3, 5];
console.log(second);

// Or even shorter
const [_, value] = [3, 5];
console.log(value);

TypeScript 4.3

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);
  }
}
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';
  }
}
以前:
类测试 {}

测试。测试 = '';类型错误:类型“类型测试”上不存在属性“test”。

 NEW:class NewTest {
 static [key: string]:
 string;
} 

新测试。测试 = '';
常量原始值 = 1;
/** * {@link originalValue} 的副本

 */
const value = originalValue;

打字稿 4.4

类测试 {
 名称?: 字符串;
 年龄:数字 |未定义;
}

const test = new Test();
测试。名称 = 未定义;类型错误:类型“undefined”不能分配给带有“exactOptionalPropertyTypes: true”的类型“string”。请考虑将“未定义”添加到目标类型。
测试。年龄 = 未定义;
控制台。日志(测试。年龄);//定义

打字稿 4.5

假设我们想要一个通用的等待值。
我们可以为此使用 Awaited 实用程序类型(它的源代码是前面示例的一部分),
// 因此无限嵌套的 Promise 都解析为其值。
类型 P1 = 等待<字符串>;键入为字符串
类型 P2 = 等待<承诺<字符串>>;键入为字符串
类型 P3 = 等待<承诺<承诺<字符串>>>;键入为字符串
导入类型的最佳方法是使用“import type”关键字来防止它们在编译后实际导入。
从“./file”导入 { 某物 };
从“./文件”导入类型 { SomeType };
这需要同一个文件的两个导入语句。

// 新:
// 现在这可以合并到一个语句中。
从“./file”导入 { 某些内容,键入 SomeType };
以前:
const obj = { name: 'foo', value: 9, toggle: false };键入为 { name: string; value: number; toggle: boolean; }
可以分配任何值,因为它们通常是键入的。
obj.name = 'bar';

常量元组 = ['名称', 4, 真];键入为 (string | number | boolean)[]
// 长度和确切类型无法从类型中确定。可以在任何地方分配任何值。
元组[0] = 0;
元组[3] = 0;

// NEW:const objNew = { name: 'foo', value: 9, toggle: 
false } as const;键入为 { readonly name: “foo”; readonly value: 9; readonly toggle: false; }
不能分配任何值(因为它被定义为“foo”(并且也是只读的))。
objNew.名称 = “酒吧”;类型错误:无法分配给“name”,因为它是只读属性。

const tupleNew = ['name', 4, true] as const;键入为只读 [“name”, 4, true]
// 长度和确切类型现在已定义,无法分配任何内容(因为它被定义为文字(并且也是只读的))。
元组新[0] = 0;类型错误:无法分配给“0”,因为它是只读属性。
元组新[3] = 0;类型错误:类型“只读 [”name“, 4, true]”中的索引签名仅允许读取。
 

打字稿 4.6

接口 允许类型 {
 '数字': 数字;
 “字符串”:字符串;
 “布尔值”:布尔值;
}

// 记录指定允许的类型中的种类和值类型。
type UnionRecord<AllowedKeys extensions keyof AllowedTypes> = { [Key in AllowedKeys]:{ 
 kind:
 Key;
 值:允许的类型[键];
 logValue: (value: AllowedTypes[Key]) => void;
}}
[允许的密钥];

函数 logValue 只接受记录的值。
function processRecord<Key extensions keyof AllowedTypes>(record: UnionRecord<Key>) {
 record.logValue(record.值);
}

processRecord({
 kind: 'string', value: 'hello!', // 用于隐式具有字符串 | 数字 | 布尔值类型的值,


 // 但现在被正确地推断为字符串。
 日志值:值 => {
 控制台。日志(值。到大写());
  }
});
tsc --generateTrace trace

 cat 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”:“/...”}},
...
 output cat 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”:“未解析
“,”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”:[“...”]},


...
输出

TypeScript 4.7

...
"compilerOptions": [
  ...
  "module": "es2020"
]
...
...
"type": "module"
...
类 列表<T> {
 私有列表: T[] = [];

 get(key: number): T {
 返回这个。列表[键];


} push(value: T): void {
 this.列表。推送(值);
 }}


function makeList<T>(items: T[]): List<T> {
 const list = new List<T>();
 items. 对于每个(项目 => 列表。推送(项目));
 返回列表;
}

// 假设我们想要一个函数来创建一个列表,但只允许某些值。
之前:
// 我们需要手动定义一个包装函数并传递参数。
function makeStringList(text: string[]) {
 return makeList(text);
}

// NEW:
// 使用实例化表达式,这要容易得多。
const makeNumberList = makeList<number>;
假设我们要键入一个类型,如果它是字符串,则仅获取数组的第一个元素。
我们可以为此使用条件类型。

以前:
类型 FirstIfStringOld<T> =
 T 扩展 [推断 S, ...未知[]]
 ?S 扩展字符串?S : 从不 : 从不
;

但这需要两个嵌套的条件类型。我们也可以合二为一。
类型 FirstIfString<T> =
 T 扩展 [string, ...未知[]]
 // 从“T”
中获取第一种类型?T[0]
 : 从不;

这仍然是次优的,因为我们需要为数组索引正确的类型。

新:
// 使用推断类型变量的扩展约束,可以更轻松地声明这一点。
类型 FirstIfStringNew<T> =
 T 扩展 [推断 S 扩展字符串,...未知[]]
 ?S
 : 从不;
请注意,键入之前的工作方式相同,这只是一种更干净的语法。

类型 A = FirstIfStringNew<[string, number, number]>;键入为字符串
类型 B = FirstIfStringNew<[“hello”, number, number]>;键入为 “hello”type C = FirstIfStringNew<[“hello”
 |“世界”,布尔值]>;输入为“你好” |“world”
类型 D = FirstIfStringNew<[boolean, number, string]>;键入为从不
假设我们有一个接口/一个扩展另一个接口的类。
接口  动物 {
 动物的东西: 任何;
}

接口  狗扩展动物 {
 狗的东西: 任何;
}

我们有一些通用的“getter”和“setter”。
类型吸气<T> = () => T;

类型二传手<T> = (值: T) => 空;

如果我们想找出 Getter<T1> 是否与 Getter<T2> 或 Setter<T1> 匹配 Setter<T2>,这取决于协方差。
function useAnimalGetter(getter: Getter<Animal>) {
 getter();
}

现在我们可以将 Getter 传递到函数中。
useAnimalGetter((() => ({ animalStuff: 0 }) as Animal));
这显然有效。

但是,如果我们想使用返回狗的 Getter 怎么办?
useAnimalGetter((() => ({ animalStuff: 0, dogStuff: 0 }) as Dog)  );
这也有效,因为狗也是动物。

function useDogGetter(getter: Getter<Dog>) {
 getter();
}

如果我们对useDogGetter函数尝试相同的操作,我们将不会得到相同的行为。
useDogGetter((() => ({ animalStuff: 0 }) as Animal));类型错误:属性“dogStuff”在类型“动物”中缺失,但在类型“狗”中是必需的。
这不起作用,因为需要狗,而不仅仅是动物。

useDogGetter((() => ({ animalStuff: 0, dogStuff: 0 }) as Dog)  );
但是,这有效。

直觉上,我们可能期望二传者的行为相同,但他们没有。
function setAnimalSetter(setter: Setter<Animal>, value: Animal) {
 setter(value);
}

如果我们传递相同类型的 Setter,它仍然有效。
setAnimalSetter((value: Animal) => {}, { animalStuff: 0 });

function setDogSetter(setter: Setter<Dog>, value: Dog) {
 setter(value);
}

这里也一样。
setDogSetter((value: Dog) => {}, {动物的东西: 0, 狗的东西: 0 }  );

但是,如果我们将 Dog Setter 传递到 setAnimalSetter 函数中,则行为与 Getter 相反。
setAnimalSetter((value: Dog) => {}, { animalStuff: 0, dogStuff: 0 }); 类型错误:类型“(值:狗)=> void”的参数不能分配给类型为“Setter<Animal>”的参数。

这一次它以相反的方式工作。
setDogSetter((value: Animal) => {}, { animalStuff: 0, dogStuff: 0 }); 


要向 TypeScript 发出此信号(不需要,但有助于提高可读性),请使用新的类型参数可选方差注释。
类型 GetterNew<out T> = () => T;
类型二传手新<在 T> = (值: T) => 空;
...
“编译器选项”: [
 ...
 “模块后缀”: [“.ios”, “.native”, “”]
]
...
从“./foo”导入 * 作为 foo;
首先检查 ./foo.ios.ts、./foo.native.ts,最后检查 ./foo.ts。
   

打字稿 4.9

假设我们有一个对象/地图/字典,它存储各种项目及其颜色。
const obj = { fireTruck: [255, 0, 0], bush: '#00ff00', ocean: [0, 0,

 255]} // 键入为 {
 fireTruck: number[]; bush: string; ocean: number[]
; }

这隐式类型化了属性,因此我们可以对数组和字符串进行操作。
const rgb1 = obj.fireTruck[0];键入为数字
常量十六进制 = obj.bush;键入为字符串

// 假设我们只想允许某些对象。
我们可以使用 Record 类型。
常量旧对象: 记录<字符串, [数字, 数字, 数字] | string> = {
 fireTruck: [255, 0, 0], bush: '#00ff00', ocean: [0, 0, 255]} // 键入为 Record<string, [number, number,

 number]
 | string>
// 但是现在我们失去了属性的类型。
const oldRgb1 = oldObj.消防车[0];键入为字符串 | 数字
常量 oldHex = oldObj。灌木丛;键入为字符串 | 数字

// 新:
// 使用 satifies 关键字,我们可以检查与类型的兼容性,而无需实际分配它。
const newObj = {
 fireTruck: [255, 0, 0], bush: '#00ff00', ocean: [0, 0, 255]} 满足记录<字符串,

 [数字, 数字, 数字] | 
string> // 键入为 { fireTruck: [number, number, number]; bush: string; ocean: [number, number, number]; }
我们仍然有属性的类型,数组甚至通过成为元组变得更加准确。
const newRgb1 = newObj.消防车[0];键入为 number
const newRgb4 = newObj。消防车[3];类型错误:长度为“3”的元组类型“[数字,数字,数字]”在索引“3”处没有元素。
const newHex = newObj.灌木丛;键入为字符串
 

标签:JavaScript,网站开发,编程
来源: