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

过去 3 年的所有 JavaScript 和 TypeScript 特性

作者:互联网

概述

ECMA脚本

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

// 假设我们要编写一种方法来记录包含数字的任意字符串,但要格式化数字。
// 我们可以为此使用标记模板。
function  formatNumbers ( strings: TemplateStringsArray, number : number ): string { 
  return strings[ 0 ] + number . toFixed ( 2 ) + 字符串[ 1 ]; 
}
控制台log (formatNumbers `这是值:${ 0 },这很重要。` ); // 这是值:0.00,很重要。

// 或者如果我们想“翻译”(在这里更改为小写)字符串中的翻译键。
function  translateKey ( key: string ): string {
  返回键。toLocaleLowerCase (); 
} 
function  translate ( strings: TemplateStringsArray, ...expressions: string [] ): string {
  返回字符串。reduce ( ( accumulator, currentValue, index ) => accumulator + currentValue + translateKey (expressions[index] ?? '' ), ''); 
}
控制台日志(将`你好,这是${ 'NAME' }翻译成${ 'MESSAGE' }。`);// 你好,这是消息的名称。
const  obj : { [ index : string ]: string } = {}; 

const symbolA =符号( 'a' ); 
常量符号 B =符号对于'b'); 

控制台日志(symbolA.description // "a"

 obj[symbolA] = 'a' ; 
obj[symbolB] = 'b' ; 
obj[ 'c' ] = 'c' ; 
对象。d = 'd' ; 

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

// 当使用 for ... in. 
for ( const i in obj) { 
  console . 日志(一);// "c", "d"
 }

ES2020

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

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

// 新:
// 相反,我们可以使用可选链接。
const  objectNew : {名称:字符串} | 未定义=数学随机() > 0.5 ? 不明确的:{名称'测试' }; 
const valueNew = objectNew?。姓名

// 这也可以用于索引和函数。
常量 数组字符串[] | 未定义=数学随机() > 0.5 ? 未定义:[ '测试' ];
const item = array?.[ 0 ]; 
常量 函数:(()=> 字符串)| 未定义=数学随机() > 0.5 ?未定义: () =>  '测试' ; 
const result = func?.();
 量值字符串| 未定义=数学随机() > 0.5 ? 未定义“测试”

// PREVIOUSLY: 
// 当我们想在值未定义或 null 时有条件地分配其他内容时,我们可以使用“||” 操作员。
const anotherValue = 值 || '你好' ; 
控制台日志(另一个值);// "test" 或 "hello" 

// 这在使用真值时工作正常,但如果我们要与 0 或空字符串进行比较,它也适用。
const incorrectValue = ''|| “不正确”
控制台日志(不正确的值);// 总是“不正确” 
const anotherIncorrectValue = 0 || “不正确”
控制台日志(另一个不正确的值);// 总是“不正确” 

// 新:
// 相反,我们可以使用新的无效合并运算符。它仅适用于未定义和空值。
const newValue = 值 ?? '你好' ; 
控制台log (newValue) // 始终为“hello” 

// 现在不替换虚假值。
??“不正确”
控制台日志(正确值);// 总是 "" 
const anotherCorrectValue = 0 ?? “不正确”
控制台日志(另一个正确值);// 总是 0
导入模块;
if (shouldImport) { 
  importModule = await  import ( './module.mjs' ); 
}
const stringVar = 'testhello,testagain,' ; 

// PREVIOUSLY: 
// 只获取匹配项,但不获取它们的捕获组。
控制台log (stringVar.match ( /test([\w]+?),/g ) ); // ["testhello,", "testagain,"] // 只得到一个匹配项,包括它的捕获组。const singleMatch = 字符串变量。匹配( /test([\w]+?),/ ); 如果(单一匹配){控制台日志(单匹配[ 0 ]);// “testhello”,控制台日志(单匹配[ 1




  
  ]); // "hello"
 } 

// 得到相同的结果,但是非常不直观(exec 方法保存最后一个索引)。
// 需要定义在循环外(保存状态)并且是全局的(/g),
// 否则会产生死循环。
const regex = /test([\w]+?),/g ; 
让执行匹配;
while ((execMatch = regex.exec ( stringVar)) !== null ) { 
  console . 日志(execMatch[ 0 ]);// "testhello,", "testagain,"
  控制台日志(execMatch[ 1 ]);// “再一次问好”
} 

// 新:
// 正则表达式需要是全局的 (/g),否则也没有任何意义。
const matchesIterator = stringVar. matchAll ( /test([\w]+?),/g ); 
// 需要迭代或转换为数组(Array.from()),不能直接索引。
for ( const match of matchesIterator) { 
  console . 日志(匹配[ 0 ]);// "testhello,", "testagain,"
  控制台日志(匹配[ 1 ]);// “你好”,“再次”
 }
async  function  success1 ( ) { return  'a' } 
async  function  success2 ( ) { return  'b' } 
async  function  fail1 ( ) { throw  'fail 1' } 
async  function  fail2 ( ) { throw  'fail 2' } 

// 上一个:
控制台log ( await  Promise .all ([ success1 (), success2 ( ) ])); // ["a", "b"] // 但是:

尝试{
  等待 承诺所有([成功1(),成功2(),失败1(),失败2()]); 
} catch (e) {
  控制台. 记录(e); // "fail 1"
 } 
// 注意:我们只捕获一个错误,无法访问成功值。

// PREVIOUS FIX(真的不是最理想的):
控制台log ( await  Promise .all ([ // ["a", "b", undefined, undefined] success1 ( ). catch ( e =>
  {控制台记录(e); }),
  成功2()。catch ( e => { console.log (e); }), fail1 
  ( ) . catch ( e => { console .log (e); }), // “失败 1” fail2 () catch ( e => { console.log (e); } )])) ; // "fail 2" // 新:const results = await Promise . 全部解决([
  


 success1 (), success2 (), fail1 (), fail2 ()]); 
const成功结果 = 结果
过滤器结果=>结果。状态=== '已完成'
map ( result => (result as  PromiseFulfilledResult < string >). value ); 
控制台日志(成功结果);// ["a", "b"]
结果。过滤器结果=>结果。状态=== '拒绝')。forEach ( error => { 
  console . log ((error as  PromiseRejectedResult ). reason ); // "fail 1", "fail 2"
 }); 
// 或:
for ( const result of results) { 
  if (result.status === ' fulfilled' ) { 
    console . 日志(结果。);// "a", "b"
   } else  if ( result.status ==='拒绝' ) {
    控制台. 日志(结果。原因);// “失败 1”,“失败 2”
   } 
}
// 以前:
// JavaScript 将数字存储为浮点数,因此总会有一些不准确
// 但更重要的是,在某个数字之后的整数运算开始出现不准确。
const maxSafeInteger = 9007199254740991 ; 
控制台日志(maxSafeInteger === Number。MAX_SAFE_INTEGER // true // 如果我们比较比它大的数字,可能会不准确。控制台日志数字。MAX_SAFE_INTEGER + 1 ===数字。MAX_SAFE_INTEGER + 2 _ _


); 

// 新:
// 使用新的 BigInt 数据类型,理论上我们可以存储和操作无限大(整数)的数字。
// 通过使用 BigInt 构造函数或在数字末尾附加“n”来使用它。
const maxSafeIntegerPreviously = 9007199254740991n ; 
控制台日志(maxSafeIntegerPreviously);// 9007199254740991 

const anotherWay = BigInt ( 9007199254740991 ); 
控制台日志(另一种方式);// 9007199254740991 

// 如果我们使用数字构造函数,我们不能安全地传递大于 MAX_SAFE_INTEGER 的整数。
常数不正确 = BigInt ( 9007199254740992 ); 
控制台日志(不正确);// 9007199254740992 
const incorrectAgain = BigInt ( 9007199254740993 ); 
控制台日志(再次不正确);// 9007199254740992 
// 糟糕,它们转换为相同的值。

// 而是使用字符串或更好的其他语法。
const correct = BigInt ( '9007199254740993' ); 
控制台日志(正确);// 9007199254740993 
const correctAgain =9007199254740993n ; 
控制台日志(再次正确);// 9007199254740993 

// 十六进制、八进制和二进制数也可以作为字符串传递。
const hex = BigInt ( '0x1ffffffffffffff' ); 
控制台日志(十六进制);// 9007199254740991 
const octal = BigInt ( '0o377777777777777777' ); 
控制台日志(八进制);// 9007199254740991 
const binary = BigInt ( '0b1111111111111111111111111111111111111111111111111111' ); 
安慰. 日志(二进制);// 9007199254740991 

// 大多数算术运算的工作方式与您预期的一样,
// 尽管另一个运算符也需要是 BigInt。所有操作也返回 BigInts。
常量加法 = maxSafeIntegerPreviously + 2n ; 
控制台日志(添加);// 9007199254740993 

const multiplication = maxSafeIntegerPreviously * 2n ; 
控制台日志(乘法);// 18014398509481982 

const减法 = 乘法 - 10n ; 
控制台日志(减法);// 18014398509481972 

const模 = 乘法 % 10n ; 
控制台日志(模数);// 2 

const求幂 = 2n ** 54n ; 
控制台日志(求幂);// 18014398509481984 

const exponentiationAgain = 2n ^ 54n ; 
控制台日志(再次取幂);// 18014398509481984 

const negative = exponentiation * - 1n ; 
控制台日志(消极的); // -18014398509481984 

// 除法的工作方式有点不同,因为 BigInt 只能存储整数。
const除法 = 乘法 / 2n ; 
控制台日志(除法);// 9007199254740991 
// 对于可整除的整数,这很好用。

// 但对于不可整除的数字,这将类似于整数除法(向下舍入)。
const divisionAgain = 5n / 2n ; 
控制台日志(再次划分);// 2 

// 非 BigInt 数字没有严格相等(但松散相等)。
控制台日志( 0n === 0 ); // 假
控制台日志0n == 0);// true 

// 但是比较按预期工作。
控制台对数( 1n < 2 ); // 真正的
控制台对数( 2n > 1 ); // 真正的
控制台对数( 2 > 2 ); // 假
控制台对数( 2n > 2); // 假
控制台日志( 2n >= 2 ); // true 

// 它们属于“bigint”类型
console日志类型为 1n);// "bigint" 

// 它们可以转换回常规数字(有符号和无符号(无负数))。
// 虽然这当然会牺牲准确性。可以指定有效位数。

控制台日志BigInt.asIntN0  -2n // 0
控制台记录(大整数asIntN ( 1 , - 2n )); // 0
控制台日志BigInt.asIntN2-2n // -2 // 通常你会使用更多的位数。// 负数在转换为无符号数时会被转换为2的补码。控制台日志BigInt.asUintN8  -2n // 254



控制台日志(globalThis。数学);// 数学对象
控制台日志导入。url // “文件://...”
 另一个模块”导出*
 “模块”导入{am}

ES2021

const testString = 'hello/greeting everybody/everybody' ; 
// PREVIOUSLY: 
// 仅替换第一个实例
控制台日志(testString。替换'/''|')); // 'hello|greetings everyone/everybody' 

// 相反,需要使用正则表达式,这对性能来说更糟,需要转义。
// 不是全局标志 (/g)。
控制台日志(testString.replace ( / \//g , '|' )); // '你好|大家好|大家' 

// 新:
// 使用 replaceAll 这会更清晰、更快速。
控制台日志(testString.replaceAll '/'  ' |')); // '你好|大家好|大家'
async  function  success1 ( ) { return  'a' } 
async  function  success2 ( ) { return  'b' } 
async  function  fail1 ( ) { throw  'fail 1' } 
async  function  fail2 ( ) { throw  'fail 2' } 

// 上一个:
控制台log ( await  Promise . race ([ success1 (), success2 ()])); // "a" 
// 但是:
尝试{
  等待 承诺race ([ fail1 (), fail2 (), success1 (), success2 ()]); 
} catch (e) {
  控制台. 记录(e); // "fail 1"
 } 
// 注意:我们只捕获一个错误,无法访问成功值。

// PREVIOUS FIX(真的不是最理想的):
控制台log ( await  Promise . race ([ // "a" 
  fail1 (). catch ( e => {控制台. 记录(e); }), // “失败 1” 
  fail2 (). catch ( e => { console .log (e); }), // “fail 2” 
  success1 (). catch ( e => { console.log (e); }), success2 ( ) . catch ( e => { console.log (e); } )])) ; // 新:控制台日志等待承诺任何([失败1
  


 (), fail2 (), success1 (), success2 ()])); // "a" 
// 并且它仅在所有承诺都拒绝时拒绝并返回包含所有错误的 AggregateError。
尝试{
  等待 承诺任何([ fail1 (), fail2 ()]); 
} catch (e) {
  控制台. 记录(e); // [AggregateError: 所有承诺都被拒绝] 
  console . 日志(例如错误);// ["失败 1", "失败 2"]
 }
x1 =未定义
x2 = 'a' ; 
const  getNewValue = ( ) => 'b' ; 

// 将新值赋给 x1,因为 undefined 是无效的。
x1 ??= 'b' ; 
控制台log (x1) // "b" 

// 不给 x2 赋新值,因为字符串不是空值。
// 另请注意:getNewValue() 永远不会执行。
x2 ??= getNewValue (); 
控制台日志(x1)//“a”
x1 =未定义
x2 = 'a' ; 
const  getNewValue = ( ) => 'b' ; 

// 不给 x1 赋新值,因为 undefined 不是 truthy。
// 另请注意:getNewValue() 永远不会执行。
x1 &&= getNewValue (); 
控制台log (x1) // undefined 

// 为 x2 分配一个新值,因为字符串是真实的。
x2 &&= 'b' ; 
控制台日志(x1)//“b”
x1 =未定义
x2 = 'a' ; 
const  getNewValue = ( ) => 'b' ; 

// 将新值赋给 x1,因为 undefined 是假的。
x1 ||= 'b' ; 
控制台log (x1) // "b" 

// 不给 x2 赋新值,因为字符串不是假的。
// 另请注意:getNewValue() 永远不会执行。
x2 ||= getNewValue (); 
控制台日志(x1)//“a”
const ref = new  WeakRef (元素); 

// 如果对象/元素仍然存在并且没有被垃圾回收,则获取值。
量值 = 参考。取消引用
控制台日志(值);// undefined 
// 看起来这个对象已经不存在了。
常数int = 1_000_000_000 ; 
const float = 1_000_000_000.999_999_999 ; 
const max = 9_223_372_036_854_775_807n ; 
常量二进制 = 0b1011_0101_0101 ; 
常量八进制 = 0o1234_5670 ; 
常量十六进制 = 0xD0_E0_F0 ;

ES2022

异步 函数 asyncFuncSuccess ( ) { 
  return  'test' ; 
} 
async  function  asyncFuncFail ( ) { 
  throw  new  Error ( 'Test' ); 
} 
// PREVIOUSLY: 
// 每当我们想要等待一个承诺时,这只能在异步函数中实现。
// 等待 asyncFuncSuccess(); // SyntaxError: await 仅在异步函数中有效
// 所以我们不得不将它包装在一个函数中,从而失去了错误处理和顶级并发性。
尝试{ 
  (异步() => {
    控制台日志(等待 asyncFuncSuccess ()); // “测试”
    尝试{ 
      await  asyncFuncFail (); 
    } catch (e) { 
      // 这是必需的,否则错误永远不会被捕获(或者在没有适当跟踪的情况下为时已晚)。
      控制台错误(e);// 错误:“测试”
      抛出e; 
    } 
  })(); 
} catch (e) { 
  // 这永远不会被触发(或者太晚没有适当的跟踪)因为函数是异步的。
  控制台错误(e);
}
// 这是在 promise 结果之前记录的,因为没有等待异步函数(因为它不能)。
控制台日志'嘿'); // "Hey" 
// NEW: 
// 如果文件是一个 ES 模块(在 package.json 中设置,有导出,名为“.mts”),我们可以在顶层等待。
控制台日志等待 asyncFuncSuccess());// “测试”
尝试{ 
  await  asyncFuncFail (); 
} catch (e) {
  控制台. 错误(e);// 错误:“测试”
 }
// 这是在承诺结果之后记录的,因为等待所有异步调用。
控制台日志'你好'); // “你好”
 ClassWithPrivateField { 
  #privateField; 
  #anotherPrivateField = 4 ; 

  构造函数( ) {
    这个.#privateField = 42 ; // 有效
    . #privateField; // 语法错误
    this .#undeclaredField = 444 ; // 语法错误
    控制台. 日志这个。#anotherPrivateField);// 4
   } 
}

常量实例 = new  ClassWithPrivateField (); 
实例.#privateField === 42 ;// 语法错误
 Logger { 
  static id = 'Logger1' ; 
  静态 类型= 'GenericLogger' ; 
  静态 日志消息:字符串|错误){
    控制台日志(消息);
  } 
} 

class  ErrorLogger  extends  Logger { 
  static  type = 'ErrorLogger' ; 
  静态限定类型;
  静态 日志e:错误){
    返回 超级log (e.toString ( )); 
  } 
}

控制台日志记录器类型);//“GenericLogger”
记录器日志'测试'); // "Test" 

// static-only 类的实例化是无用的,只是为了演示目的而在这里完成。
const log = new  Logger (); 

错误记录器日志 错误“测试”));// 错误:“测试”(不受父实例化的影响)
控制台日志错误记录器类型);// “ErrorLogger”
控制台. 日志ErrorLogger.qualifiedType _ // 未定义的
控制台日志ErrorLogger.id _ // "Logger1" 

// 这会抛出异常,因为 log() 不是实例方法而是静态方法。
控制台log (log.log ( )); // log.log 不是函数
 测试{ 
  static staticProperty1 = '属性 1' ; 
  静态静态属性2;
  静态{
    这个staticProperty2 = '属性 2' ; 
  } 
}

控制台日志测试。staticProperty1 //“属性 1”
控制台日志测试。staticProperty2 // "属性 2"
 './foo.json'导入json assert { type : 'json' }; 
控制台日志(json.answer // 42
const matchObj = /(test+)(hello+)/ d. exec ( '开始-testesthello-停止' ); 

// 以前:
控制台日志(匹配对象?索引);

// 新:
if (matchObj) { 
  // 整个匹配的开始和结束索引(之前我们只有开始)。
  控制台log (matchObj.indices [ 0 ] ); // [9, 18] 

  // 捕获组的开始和结束索引。
  控制台log (matchObj.indices [ 1 ] ); // [9, 13]
  控制台. log (matchObj.indices [ 2 ] ); // [13, 18]
 }
控制台log ([ 4 , 5 ].at ( - 1 )) // 5

常量数组 = [ 4 , 5 ]; 
大批。(- 1 ) = 3 处// 语法错误:分配给右值
const obj = {名称'测试' }; 

控制台log ( Object . hasOwn (obj, 'name' )); // 真正的
控制台log ( Object . hasOwn (obj, '性别' )); // 错误的
尝试{
  尝试{ 
    connectToDatabase (); 
  } catch (err) { 
    throw  new  Error ( '连接到数据库失败。' , { cause : err }); 
  } 
} catch (err) {
  控制台. 日志(错误。原因);// ReferenceError: connectToDatabase 未定义
}

Future(已经可以与 TypeScript 4.9 一起使用)

 Person {
  访问者名称字符串

  构造函数名称:字符串){
    这个名字=名字;
    控制台log ( this.name ) // 'test'   } } const person = new Person ( ' test' ) ;



 

打字稿

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

// WITHOUT: 
function  getFirstUnsafe ( list: any [] ): any { 
  return list[ 0 ]; 
} 

const firstUnsafe = getFirstUnsafe ([ 'test' ]); // 键入任何

// WITH: 
function getFirst< Type >( list : Type []): Type { 
  return list[ 0 ]; 
} 

const first = getFirst< string >([ 'test' ]); // 输入为字符串

// 在这种情况下,参数甚至可以被删除,因为它是从参数中推断出来的。
const firstInferred = getFirst ([ 'test' ]); // typed as string 

// 接受为泛型的类型也可以使用 `extends` 进行限制。Type 通常也缩写为 T。class 
List  < T extends  string | number > {
  私有 列表:T[] = []; 

  得到数字):T {
    返回 这个列表[键];
  }

  价值:T):无效{
    这个列表(值);
  } 
} 

const list = new  List <字符串>(); 
列表。( 9 ); // 类型错误:“数字”类型的参数不可分配给“字符串”类型的参数。
const booleanList = new  List < boolean >(); // 类型错误:类型 'boolean' 不满足约束 'string | 数字'。

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

接口 测试{
  名称字符串
  年龄人数
} 

// Partial 实用程序类型使所有属性都是可选的。
type  TestPartial = Partial <测试>; // 输入为 { name?: string | 不明确的; 年龄?:数字 | 不明确的; } 
// Required 实用程序类型做相反的事情。
类型 TestRequired = Required < TestPartial >; // 输入为 { name: string; 年龄:数字;} 
// Readonly 实用程序类型使所有属性只读。
类型 TestReadonly =只读<测试>;// 类型为 { readonly name: string; readonly age: string } 
// Record 实用程序类型允许简单定义对象/地图/字典。最好尽可能索引签名。
const  config : Record < string , boolean > = { option : false , anotherOption : true }; 
// Pick 实用程序类型仅获取指定的属性。
输入 TestLess = Pick < Test , 'name' >; // 输入为 { name: string; }
输入 TestBoth = Pick < Test , 'name' | '年龄' >; // 输入为 { name: string; 年龄:字符串;} 
// Omit 实用程序类型忽略指定的 properties.type 
type  TestFewer = Omit < Test , 'name' >; // 输入为 { age: string; } 
type  TestNone = Omit < Test , '名称' | '年龄' >; // typed as {} 
// Parameters 实用程序类型获取函数类型的参数。
 value: string , anotherValue: number ): string { 
  return  'test' ; 
} 
type  Params = Parameters < typeof doSmth>; // typed as [value: string, anotherValue: number] 
// ReturnType 实用程序类型获取函数类型的返回类型。
type  Return = ReturnType < typeof doSmth>; // typed as string 

// 还有很多,下面会介绍其中的一些。
// 如果是数组,则只提取数组类型,否则返回相同类型。
输入 Flatten <T> = T extends  any [] ? T[] : T; 

// 提取出元素类型。
类型 Str = Flatten < string []>; // 键入为字符串

// 单独保留类型。
type  Num = Flatten < number >; // 输入为数字
// 从前面的例子开始,这样可以写得更干净。
type  FlattenOld <T> = T extends  any [] ? T[] : T; 

// 除了索引数组之外,我们还可以从数组中推断出 Item 类型。
输入 Flatten <T> = T extends (infer Item )[] ? 项目: T; 

// 如果我们想写一个类型来获取函数的返回类型,否则是未定义的,我们也可以推断。
类型 GetReturnType < Type > = Type  extends (... args : any[]) => 推断返回返回未定义

输入 Num = GetReturnType < () =>  number >; // 输入为数字

type  Str = GetReturnType < ( x: string ) =>  string >; // 类型为字符串

类型 Bools = GetReturnType < ( a: boolean , b: boolean ) =>  void >; // 类型为未定义
// 如果我们还不知道一个元组有多长,但它至少是一个,我们可以使用 `?` 指定可选类型。
const  list : [ number , number ?, boolean ?] = []; 
list[ 0 ] // 输入为数字
list[ 1 ] // 输入为数字 | undefined
 list[ 2 ] // 类型为布尔值 | undefined
 list[ 3 ] // 类型错误:长度为“3”的元组类型“[number, (number | undefined)?, (boolean | undefined)?]”在索引“3”处没有元素。

// 我们也可以将元组基于现有类型。
// 如果我们想在开始时填充一个数组,我们可以使用剩余运算符 `...` 来做到这一点。
函数padStart<T extends  any []>( arr : T, pad : string ): [ string , ...T] { 
  return [pad, ...arr]; 
} 

const padded = padStart ([ 1 , 2 ], 'test' ); // 输入为 [string, number, number]
抽象  Animal { 
  abstract  makeSound (): void ; 

  移动():无效{
    控制台日志'漫游地球......'); 
  } 
} 

// 扩展时需要实现抽象方法。
class  Cat  extends  Animal {} // 编译错误:非抽象类“Cat”未实现从类“Animal”继承的抽象成员“makeSound”。

  扩展 动物{ 
  makeSound){
    控制台日志( 'woof' ); 
  } 
} 

// 抽象类不能被实例化(如接口),抽象方法也不能被调用。
 动物();// 编译错误:无法创建抽象类的实例。

const dog = new  Dog ()。制作声音();//“汪”
接口 MyInterface {
  名称字符串
} 

interface  ConstructsMyInterface { 
  new ( name : string ): MyInterface ; 
}

 测试 实现 MyInterface {
  名称字符串
  构造函数名称:字符串){
    这个名字=名字;
  } 
} 

class  AnotherTest { 
  age : number ; 
}

function  makeObj ( n: ConstructsMyInterface ) { 
    return  new  n ( 'hello!' ); 
} 

const obj = makeObj (测试); // 类型为 Test 
const anotherObj = makeObj ( AnotherTest ); // 类型错误:“typeof AnotherTest”类型的参数不可分配给“ConstructsMyInterface”类型的参数。
// 如果我们想获取 makeObj 函数的构造函数参数怎么办。
接口 MyInterface {
  名称字符串
} 

interface  ConstructsMyInterface { 
  new ( name : string ): MyInterface ; 
}

 测试 实现 MyInterface {
  名称字符串
  构造函数名称:字符串){
    这个名字=名字;
  } 
}

函数 makeObj (测试: ConstructsMyInterface, ...args: ConstructorParameters<ConstructsMyInterface> ) {
  返回  测试(...args); 
} 

makeObj (测试); // 类型错误:需要 2 个参数,但得到 1 个
。const obj = makeObj ( Test , 'test' ); // 类型为测试

打字稿 4.0

// 如果我们有一个组合两个未定义长度和类型的元组的函数怎么办?我们如何定义返回类型?

// PREVIOUSLY: 
// 我们可以写一些重载。
声明 函数 concat ( arr1: [], arr2: [] ): []; 
声明 函数concat<A>( arr1 : [A], arr2 : []): [A]; 
声明 函数concat<A, B>( arr1 : [A], arr2 : [B]): [A, B]; 
声明 函数concat<A, B, C>( arr1 : [A], arr2 : [B, C]): [A, B, C]; 
声明 函数concat<A, B, C, D>(arr1 : [A], arr2 : [B, C, D]): [A, B, C, D]; 
声明 函数concat<A, B>( arr1 : [A, B], arr2 : []): [A, B]; 
声明 函数concat<A, B, C>( arr1 : [A, B], arr2 : [C]): [A, B, C]; 
声明 函数concat<A, B, C, D>( arr1 : [A, B], arr2 : [C, D]): [A, B, C, D]; 
声明 函数concat<A, B, C, D, E>( arr1 : [A, B], arr2 : [C, D, E]): [A, B, C, D, E]; 
声明 函数concat<A, B, C>( arr1: [A, B, C], arr2 : []): [A, B, C]; 
声明 函数concat<A, B, C, D>( arr1 : [A, B, C], arr2 : [D]): [A, B, C, D]; 
声明 函数concat<A, B, C, D, E>( arr1 : [A, B, C], arr2 : [D, E]): [A, B, C, D, E]; 
声明 函数concat<A, B, C, D, E, F>( arr1 : [A, B, C], arr2 : [D, E, F]): [A, B, C, D, E, F ]; 
// 即使每个只有三个项目,这也不是最理想的。

// 相反,我们可以组合类型。
声明 函数concatBetter<T, U>( arr1 : T[], arr2: U[]): (T | U)[]; 
// 但这种类型为 (T | U)[] 

// 新:
// 使用可变元组类型,我们可以轻松定义它并保留有关长度的信息。
声明 函数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 ]作为[数字,数字,数字]); 
控制台日志(元组[ 0 ]);// 23 
const 元素: number = tuple[ 1 ]; // 类型错误:类型“string”不可分配给类型“number”。
控制台日志(元组[ 6 ]);// 类型错误:长度为 '6' 的元组类型 '[23, "hey", false, 5, 99, 20]' 在索引 '6' 处没有元素。
输入 Foo = [ first : number , second?: string , ... rest : any []]; 

// 这允许在这里正确命名参数,它也会显示在编辑器中。
声明 函数 someFunc ( ...args: Foo );
class  Animal { 
  // 在构造函数中赋值时不需要设置类型。
  姓名; 

  构造函数名称:字符串){
    这个名字=名字;
    控制台日志这个名字);// 类型为字符串
  } 
}
/** @deprecated消息 */ 
type  Test = string ; 

常量 测试测试= 'dfadsf'// 类型错误:“测试”已弃用。

打字稿 4.1

type  VerticalDirection = '顶部' | '底部' ; 
类型 Horizo​​ntalDirection = '左' | '对' ; 
输入 Direction = ` ${VerticalDirection}  ${Horizo​​ntalDirection} ` ; 

const  dir1 : Direction = '左上角' ; 
const  dir2 : Direction = 'left' ; // 类型错误:类型“left”不可分配给类型“top left” | “右上角” | “左下角” | 
 Direction = '左上角' ; // 类型错误:类型 '"left top"' 不可分配给类型 '"top left" | “右上角” | “左下角” | “右下”。

// 这也可以与泛型和新的实用程序类型结合使用。
declare  function makeId<T extends  string , U extends  string >( first : T, second : U): ` ${Capitalize<T>} - ${Lowercase<U>} ` ;
// 假设我们想重新格式化一个对象,但在其 ID 前加上下划线。
const obj = { value1 : 0 , value2 : 1 , value3 : 3 }; 
const  newObj : { [ keyof typeof obj中的属性 as `_ ${Property} ` ]: number }; // 输入为 { _value1: number; _value2:数字;值 3:数字;} 
输入 Awaited <T> = T extends  PromiseLike <infer U> ? 等待<U> : T; 

输入 P1 = Awaited <字符串>; // 输入为字符串
类型 P2 = Awaited < Promise < string >>; // 输入为字符串
类型 P3 = Awaited < Promise < Promise < string >>>; // 输入为字符串
const原始值 = 1 ; 
/** 
  * 另一个值的副本
  * @see  originalValue
   */ 
const value = originalValue;
tsc --explainFiles 

<< output 
../../.asdf/installs/nodejs/16.13.1/.npm/lib/node_modules/typescript/lib/lib.es5.d.ts
  通过文件中的“es5”引用的库'../../.asdf/installs/nodejs/16.13.1/.npm/lib/node_modules/typescript/lib/lib.es2015.d.ts'
  通过文件 '../.es5' 引用的库。 ./.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
  库通过'es2015'从文件'../../.asdf/installs/nodejs/16.13.1/.npm/lib/node_modules/ typescript/lib/lib.es2016.d.ts'
  通过文件 '../../.asdf/installs/nodejs/16.13.1/ 中的 'es2015' 引用的库。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
  库通过'es2016'从文件'../../ .asdf/installs/nodejs/16.13.1/.npm/lib/node_modules/typescript/lib/lib.es2017.d.ts'
  通过'es2016'从文件'../../.asdf/installs/引用的库nodejs/16.13.1/.npm/lib/node_modules/typescript/lib/lib.es2017.d.ts' 
...
输出
const [_first, second] = [ 3 , 5 ]; 
控制台日志(第二);

// 或者更短
const [_, value] = [ 3 , 5 ]; 
控制台日志(值);

打字稿 4.3

 测试{ 
  private  _value : number ; 

  获取 ():数字{
    返回 这个_值
  }

  设置 值:数字|字符串){
    如果类型值 === '数字'){
      _value = 值;
      返回
    }
    这个_value = parseInt(值,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 ():字符串{ // 类型错误:该成员不能有 'override' 修饰符,因为它未在基类 'NewParent' 中声明。
    返回 “测试”
  } 
}
// 以前:
 测试{}

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

// 新:
 NewTest { 
  static [ key : string ]: string ; 
}

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

const value = originalValue;

打字稿 4.4

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

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

打字稿 4.5

// 假设我们想要一个通用的等待值。
// 我们可以为此使用 Awaited 实用程序类型(它的源代码是前面示例的一部分),
// 因此无限嵌套的 Promises 都解析为它们的值。
输入 P1 = Awaited <字符串>; // 输入为字符串
类型 P2 = Awaited < Promise < string >>; // 输入为字符串
类型 P3 = Awaited < Promise < Promise < string >>>; // 输入为字符串
// PREVIOUSLY: 
// 导入类型的最佳方法是使用 `import type` 关键字来防止它们在编译后实际被导入。'./file'
导入{ something} './file'导入类型{ SomeType } // 对于同一个文件,这需要两个导入语句。// 新:// 现在这可以组合成一个语句。import { something, type SomeType } from './file' ; 
  




  
// PREVIOUSLY: 
const obj = { name : 'foo' , value : 9 , toggle : false }; // 输入为 { name: string; 值:数字;切换:布尔值;} 
// 任何值都可以赋值,因为它们通常是有类型的。
对象。名称= '酒吧' ; 

const tuple = [ 'name' , 4 , true ]; // typed as (string | number | boolean)[] 
// 无法从类型中确定长度和确切类型。可以在任何地方分配任何值。
元组[ 0 ] =0 ; 
元组[ 3 ] = 0 ; 

// 新:
const objNew = { name : 'foo' , value : 9 , toggle : false } as  const ; // 输入为 { readonly name: "foo"; 只读值:9;只读切换:假;} 
// 不能赋值(因为它被定义为“foo”(而且也是只读的))。
对象新建。名称= '酒吧' ; // 类型错误:无法分配给“名称”,因为它是只读属性。

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

打字稿 4.6

接口 AllowedTypes { 
  'number' : number ; 
  '字符串'字符串
  '布尔值'布尔值
} 

// Record 从允许的类型中指定种类和值类型。
type  UnionRecord < AllowedKeys  extends keyof AllowedTypes > = { [ Key  in  AllowedKeys ]: 
{ 
  kind : Key ; 
  AllowedTypes [];
  日志:(值:AllowedTypes[Key] ) =>  void ; 
}
}[允许键]; 

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

 processRecord ({
   kind : 'string' ,
   value : 'hello!' ,
   // 用于隐式具有类型 string | number | boolean 的值,
  // 但现在被正确地推断为字符串。
  日志值=> {
    控制台log (value.toUpperCase ( )); 
  } 
});
tsc --generateTrace trace 

cat trace/trace.json 
<<输出
[ 
{"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":"
禁用-by-default-devtools.timeline","ph":"M","ts":...,"pid":1,"tid":1}, {"pid":1,"tid" 
: 1,“酸碱度”:"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":"/..."}},"rootDir":"/..."}}, {"pid":1,"tid":1,"ph":"B","cat":"parse","ts":..., "name":"createSourceFile","args":{"path":"/..."}}, {"pid":1,"tid":1,"ph":"E","cat" :"解析","ts":...,"名称":"createSourceFile","args":{"路径":"/..."}},"rootDir":"/..."}}, {"pid":1,"tid":1,"ph":"B","cat":"parse","ts":..., "name":"createSourceFile","args":{"path":"/..."}}, {"pid":1,"tid":1,"ph":"E","cat" :"解析","ts":...,"名称":"createSourceFile","args":{"路径":"/..."}},cat":"parse","ts":...,"name":"createSourceFile","args":{"path":"/..."}},cat":"parse","ts":...,"name":"createSourceFile","args":{"path":"/..."}},
{“pid”:1,“tid”:1,“ph”:“X”,“cat”:“程序”,“ts”:...,“name”:“resolveModuleNamesWorker”,“dur”:。 ..,"args":{"containingFileName":"/..."}}, 
...
输出

cat trace/types.json 
<<输出
[{"id":1,"intrinsicName":"any", "recursionId":0,"flags":["..."]}, 
{"id":2,"intrinsicName":"any","re​​cursionId":1,"flags":["..." ]}, 
{"id":3,"intrinsicName":"any","re​​cursionId":2,"flags":["..."]}, {"id":4,"intrinsicName":" 
error ","recursionId":3,"flags":["... 
"]}, {"id":5,"intrinsicName":"unresolved","re​​cursionId":4,"flags":["..." ]}, 
{"id":6,"intrinsicName":"any","re​​cursionId":5,"flags":["..."]}, {"id":7,"intrinsicName":"
内在","re​​cursionId":6,"flags":["..."]},intrinsicName":"intrinsic","re​​cursionId":6,"flags":["..."]},intrinsicName":"intrinsic","re​​cursionId":6,"flags":["..."]},
{"id":8,"intrinsicName":"unknown","re​​cursionId":7,"flags":["..."]}, {"id":9,"intrinsicName":"unknown", 
" recursionId":8,"flags":["..."]}, 
{"id":10,"intrinsicName":"undefined","re​​cursionId":9,"flags":["..."] }, 
{"id":11,"intrinsicName":"undefined","re​​cursionId":10,"flags":["..."]}, {"id":12,"intrinsicName":"null 
" ,"recursionId":11,"flags":["..."]}, 
{"id":13,"intrinsicName":"string","re​​cursionId":12,"flags":["... “]}, 
...
输出

打字稿 4.7

...
“compilerOptions”  [
   ... 
  “模块”  “es2020” 
]
 ...
...
“类型”  “模块”
 ...
 列表<T> {
  私有 列表:T[] = []; 

  得到数字):T {
    返回 这个列表[键];
  }

  价值:T):无效{
    这个列表(值);
  } 
} 

function makeList<T>( items : T[]): List <T> { 
  const list = new  List <T>(); } 
  项目。forEach (项目=>列表。(项目));
  返回列表;
} 

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

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

// 以前:
type  FirstIfStringOld <T> = 
  T extends [infer S, ... unknown []] 
    ? S扩展 字符串? S:从不
从不

// 但这需要两个嵌套的条件类型。我们也可以合二为一。
type  FirstIfString <T> = 
  T extends [ string , ... unknown []] 
    // 从 `T` 中获取第一个类型
T[ 0] 
从不

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

// 新:
// 在推断类型变量上使用扩展约束,这可以更容易地声明。
输入 FirstIfStringNew <T> = 
  T extends [infer S extends  string , ... unknown []] 
    ? S 
从不
// 请注意,输入的工作方式与之前相同,这只是一种更简洁的语法。

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

interface  Dog  extends  Animal { 
  dogStuff : any ; 
} 

// 我们有一些通用的“getter”和“setter”。
输入 Getter <T> = () => T; 

类型 Setter <T> = ( value: T ) =>  void ; 

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

// 现在我们可以将 Getter 传递给函数。
useAnimalGetter (( () => ({ animalStuff : 0 })作为 动物)); 
// 这显然有效。

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

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

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

useDogGetter (( () => ({ animalStuff : 0 , dogStuff : 0 })作为 ));
// 但是,这是有效的。

// 直觉上,我们可能希望 Setter 的行为相同,但事实并非如此。
函数 setAnimalSetter ( setter: Setter<Animal>, value: Animal ) { 
  setter (value); 
} 

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

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

// 这里也一样。
设置狗设置器价值:狗)=> {},{ an​​imalStuff0dogStuff0 }); 

// 但如果我们将 Dog Setter 传递给 setAnimalSetter 函数,则行为与 Getters 相反。
setAnimalSetter ( ( value: Dog ) => {}, { animalStuff : 0 , dogStuff : 0 }); // 类型错误:'(value: Dog) => void' 类型的参数不可分配给'Setter<Animal>' 类型的参数。

// 这次它以相反的方式工作。
setDogSetter值:动物)=>{}, { animalStuff : 0 , dogStuff : 0 }); 

// 新:
// 要向 TypeScript 发出信号(不需要但有助于提高可读性),请使用新的类型参数可选方差注释。
输入 GetterNew <out T> = () => T; 
输入 SetterNew < in T> = ( value: T ) =>  void ;
...
"compilerOptions" :  [
   ... 
  "moduleSuffixes" :  [ ".ios" ,  ".native" ,  "" ] 
]
 ...
'./foo'导入*作为foo // 这首先检查 ./foo.ios.ts、./foo.native.ts,最后检查 ./foo.ts。 
   

打字稿 4.9

// PREVIOUSLY: 
// 假设我们有一个存储各种项目及其颜色的对象/地图/字典。
const obj = { 
  fireTruck : [ 255 , 0 , 0 ], 
  bush : '#00ff00' , 
  ocean : [ 0 , 0 , 255 ] 
} // 输入为 { fireTruck: number[]; 布什:字符串;海洋:数字[];} 

// 这会隐式键入属性,以便我们可以对数组和字符串进行操作。
const rgb1 = 对象。救火车[ 0 ];// 类型为数字
const十六进制=对象。灌木丛// 类型为字符串

// 假设我们只想允许某些对象。
// 我们可以使用 Record 类型。
const  oldObj : Record <字符串, [数字,数字,数字] | string > = { 
  fireTruck : [ 255 , 0 , 0 ], 
  bush : '#00ff00' , 
  ocean : [ 0 , 0 , 255 ] 
}// 输入为 Record<string, [number, number, number] | string> 
// 但现在我们丢失了属性的类型。
常量oldRgb1 = oldObj。救火车[ 0 ];// 类型为字符串 | 数字
常量oldHex = oldObj。灌木丛// 类型为字符串 | number 

// 新:
// 使用 satisfies 关键字,我们可以检查与类型的兼容性而无需实际分配它。
const newObj = { 
  fireTruck : [ 255 , 0 , 0 ], 
  bush : '#00ff00' , 
  ocean : [ 0 , 0, 255 ] 
} 满足Record < string , [ number , number , number ] | string > // 输入为 { fireTruck: [number, number, number]; 布什:字符串;海洋:[数字,数字,数字];} 
// 而且我们仍然有属性的类型,数组甚至通过成为元组变得更加准确。
const newRgb1 = newObj。救火车[ 0 ];// 输入为数字
const newRgb4 = newObj. 消防车[ 3 ];// 类型错误:长度为“3”的元组类型“[number, number, number]”在索引“3”处没有元素。
const newHex = newObj。灌木丛// 输入为字符串
 

标签:JavaScript, 编程,Web开发
来源: