TypeScript语言进阶

Bigint

BigInt是在2019年9月被正式纳入ECMAScript标准中的特性。虽然BigInt不是频繁使用的特性, 但其特殊性在于它是一种新的原始数据类型,同时又属于数值类型的一种。由于BigInt类型的加入, JavaScript中共支持两种数值类型,即Number类型和BigInt类型。目前,JavaScript语言中共有以下七种原始数据类型:

  • Undefined
  • Null
  • Strng
  • Boolean
  • Number
  • Symbal
  • Nigint

JavaScript语言使用双精度64位浮点数格式来表示Number类型的值。Number类型能够安全表示的最大整数为2^53^-1,该数值能够使用内置的Number对象上的MAX_SAFE_INTEGER属性来表示。BigInt类型能够表示任意精度的整数,尤其是大于2^53^-1的整数,这也正是引入BigInt类型的原因。

创建Bigint

我们可以使用以下两种方式来创建BigInt类型的值:

  • 使用BigInt字面量。

  • 使用BigInt()函数。

BigInt字面量的语法是在一个整数后面添加一个小写字母“n”。字母“n”必须紧随数字之后,两者之间不允许存在空白字符。示例如下:

const unit0 = 1n; // Bigint字面量
const unit1 = BigInt(1) // BigInt()函数   1n 

BigInt 与Number

BigInt类型的值能够与Number类型的值进行大小及相等关系的比较。在进行严格相等===比较时, BigInt类型的值与Number类型的值永远不相等。在进行非严格相等==比较及大小关系比较时,BigInt 类型的值与Number类型的值将进行数学意义上的比较。

虽然BigInt类型的值可以与Number类型的值进行比较,但是BigInt类型的值不允许与Number类型的值一起进行混合数学运算。示例如下:

1+1n //类型错误!无法混合使用BigInt和其他类型

通过内置的Number()函数能够将BigInt类型的值转换为Number类型的值。但要注意,在BigInt 类型与Number类型之间进行强制类型转换时有可能损失精度。示例如下:

Number(1n); // 1

展开运算符

展开运算符是ECMAScript 2015中定义的运算符,可以用在多种上下文中,比如对象字面量、 数组字面量和函数调用语句等。展开运算符使用连续的三个点符号“...”来表示。展开运算符的后面是一个表达式,表达式的求值结果为要展开的值。

展开数组字面量

开数组字面量(Spread Syntax)来展开一个数组,将其元素逐个插入到另一个数组中。展开数组字面量使用三个连续的点(...)作为操作符。

下面是展开数组字面量的示例:

const arr1 = [1, 2, 3];
const arr2 = [...arr1, 4, 5, 6];

console.log(arr2); // 输出: [1, 2, 3, 4, 5, 6]

在上述示例中,...arr1表示展开数组 arr1,将其元素逐个插入到新的数组 arr2 中。

展开对象字面量

展开对象字面量(Spread Syntax)来展开一个对象,将其属性逐个插入到另一个对象中。展开对象字面量使用三个连续的点(...)作为操作符。

下面是展开对象字面量的示例:

const obj1 = { foo: 1, bar: 2 };
const obj2 = { ...obj1, baz: 3 };

console.log(obj2); // 输出: { foo: 1, bar: 2, baz: 3 }

在上述示例中,...obj1表示展开对象 obj1,将其属性逐个插入到新的对象 obj2 中。

展开函数参数

展开函数参数(Spread Syntax)来传递一个数组或对象作为函数的参数,并将其展开为单独的参数。

如果要展开数组作为函数参数,可以使用展开操作符(...)在函数调用时将数组展开为单独的参数。示例如下:

function myFunction(a: number, b: number, c: number) {
  console.log(a, b, c);
}

const arr = [1, 2, 3];
myFunction(...arr); // 等同于 myFunction(1, 2, 3)

在上述示例中,使用展开操作符 ... 将数组 arr 展开为单独的参数,从而调用 myFunction 函数并传递参数 1, 2, 3

如果要展开对象作为函数参数,可以使用展开操作符(...)在函数调用时将对象的属性展开为单独的参数。示例如下:

function myFunction(a: number, b: number, c: number) {
  console.log(a, b, c);
}

const obj = { a: 1, b: 2, c: 3 };
myFunction(...Object.values(obj)); // 等同于 myFunction(1, 2, 3)

在上述示例中,使用 Object.values() 方法获取对象 obj 的所有属性值,然后使用展开操作符 ... 将这些属性值展开为单独的参数,从而调用 myFunction 函数并传递参数 1, 2, 3

展开函数参数可以方便地传递数组或对象的值作为函数参数,使代码更简洁易读。需要注意的是,展开函数参数只能用于函数调用,不能用于函数定义。

解构

JavaScript中的解构是指将数组或对象在结构上进行分解,将其拆分成独立的子结构,然后可以访问那些拆分后的子结构。解构提供了一种更加便利的数据访问方式,使用解构语法能够极大地简化代码。

数组解构

数组解构(Array Destructuring)语法从数组中提取值并将其赋给变量。

数组解构赋值使用了类似于数组字面量的表示方式。赋值运算符右侧为需要解构的数组,赋值运算符左侧是解构赋值的目标,在解构赋值的同时也支持声明新的变量。

数组解构使用方括号([])来指定要解构的数组,并使用等号(=)将解构的值赋给变量。示例如下:

const arr = [1, 2, 3];

// 解构数组
const [a, b, c] = arr;

console.log(a); // 输出 1
console.log(b); // 输出 2
console.log(c); // 输出 3

在上述示例中,通过将数组 [1, 2, 3] 解构为 [a, b, c],可以将数组中的元素分别赋给变量 abc

对象解构

象解构(Object Destructuring)语法从对象中提取值并将其赋给变量。

对象解构使用花括号({})来指定要解构的对象,并使用等号(=)将解构的值赋给变量。示例如下:

const obj = { x: 1, y: 2, z: 3 };

// 解构对象
const { x, y, z } = obj;

console.log(x); // 输出 1
console.log(y); // 输出 2
console.log(z); // 输出 3

在上述示例中,通过将对象 { x: 1, y: 2, z: 3 } 解构为 { x, y, z },可以将对象中的属性值分别赋给变量 xyz

可选链运算符

可选链运算符是2019年12月纳入ECMAScript标准中的新特性。TypeScript 3.7版本增加了对可选链运算符的支持,因此我们可以在TypeScript 3.7及以上的版本中直接使用该运算符。

当尝试访问对象属性时,如果对象的值为undefined或null,那么属性访问将产生错误。为了提高程序的健壮性,在访问对象属性时通常需要检查对象是否已经初始化,只有当对象不为undefined 和null时才去访问对象的属性。可选链运算符旨在帮助开发者省去冗长的undefined值和null值检查代码,增强了代码的表达能力。

可选链运算符由一个问号和一个点号组成,即“ ?. ”。可选链运算符有以下三种语法形式:

  • 可选的静态属性访问。

  • 可选的计算属性访问。

  • 可选的函数调用或方法调用。

可选的静态属性访问

const obj = {
  prop1: {
    prop2: {
      prop3: 'value'
    }
  }
};

console.log(obj?.prop1?.prop2?.prop3); // 输出 'value'
console.log(obj?.prop1?.prop2?.prop4); // 输出 undefined

在该语法中,如果obj的值为undefined或null,那么表达式的求值结果为undefined;否则,表达式的求值结果为obj.prop

可选的计算属性访问

obj?.[expr]

在该语法中,如果obj的值为undefined或null,那么表达式的求值结果为undefined;否则, 表达式的求值结果为obj[expr]。

可选的函数调用或方法调用

const obj2: MyObject = {};

obj2.func1?.(); // 不会调用任何函数,没有输出
obj2.obj1?.func2?.(); // 不会调用任何函数,没有输出

通过使用可选链运算符 ?.,可以安全地调用函数 func1func2,即使它们可能不存在或为 undefined。如果函数存在,则会执行相应的函数调用操作,否则整个调用表达式的结果为 undefined

短路求值

如果可选链运算符左侧操作数的求值结果为undefined或null,那么右侧的操作数不会再被求值,我们将这种行为称作短路求值。在下例中,由于变量a的值为undefined,因此第4行中的变量x 将不会执行自增运算:

let x= 0;

let a = undefined;

a?.[++x];//undefined X; 0

值得一提的是,二元逻辑运算符“&&”和“|”也具有短路求值的特性。

空值合并运算符

空值合并运算符在2019年11月成为ECMAScript标准中的候选特性。虽然还不是最终的标准, 但核心功能已经基本确定。TypeScript 3.7版本增加了对空值合并运算符的支持,因此我们可以在 TypeScript 3.7以上的版本中直接使用该运算符。

空值合并运算符与可选链运算符一样都具有短路求值的特性。当空值合并运算符左侧操作数的值不为undefined和null时,右侧操作数不会被求值,而是直接返回左侧操作数。

空值合并运算符与逻辑或运算符 || 有些相似,但在处理空值时有区别。逻辑或运算符会对左侧的操作数进行强制类型转换,将类似 false0"" 等 Falsy 值也视为假,因此如果左侧的操作数为 Falsy 值时,会返回右侧的操作数。而空值合并运算符只会对 nullundefined 进行判断,其他 Falsy 值仍然会被视为真。

下面是使用空值合并运算符的示例:

const foo = null ?? 'default'; // foo 的值为 'default',因为左侧的操作数为 null
const bar = undefined ?? 'default'; // bar 的值为 'default',因为左侧的操作数为 undefined
const baz = 0 ?? 42; // baz 的值为 0,因为左侧的操作数不为 null 或 undefined
const qux = false ?? true; // qux 的值为 false,因为左侧的操作数不为 null 或 undefined

空值合并运算符可以用于给变量提供默认值,当变量为 nullundefined 时,可以使用空值合并运算符提供一个备选的默认值。这在处理函数参数、对象属性等情况下特别有用。

function greet(name?: string) {
  const userName = name ?? 'Anonymous';
  console.log(`Hello, ${userName}!`);
}

greet(); // 输出 "Hello, Anonymous!"
greet('Alice'); // 输出 "Hello, Alice!"

Falsy 值是指在布尔上下文中被视为假(false)的值。在 JavaScript 和 TypeScript 中,以下值被视为 Falsy 值:

  • false:布尔类型的 false
  • 0:数值类型的零
  • '':空字符串
  • null:表示空值或无效对象的特殊关键字
  • undefined:表示未定义的值
  • NaN:表示非数值的特殊值

这些值在条件判断、逻辑运算和类型转换中被视为假,当它们作为条件表达式的结果时,会被自动转换为 false。其他非 Falsy 值都被视为真(true)。