跳至主要內容

变量声明

樱桃茶大约 26 分钟

变量声明

在 TypeScript 中,变量声明的概念扩展了 JavaScript 的声明方式。以下是 TypeScript 变量声明的一些关键点:

  • let 和 const: TypeScript 推荐使用 letconst 来声明变量而不是 varlet 用于变量值可能会改变的情况,const 用于变量值不应该改变的常量。

    • 例子:

      let mutableValue = 10; // 这个值可以被重新赋值
      mutableValue = 15;
      
      const immutableValue = "Hello"; // 这个值不可更改
      // immutableValue = "World"; // 错误:不能给常量重新赋值
      
  • 类型注解: 在 TypeScript 中你可以给变量添加类型注解来指定变量的类型。

    • 例子:
      let username: string = "Alice";
      let score: number = 75;
      
  • 类型推断: TypeScript 能够自动推断出变量的类型,所以并非所有变量声明都需要显式的类型注解。

    • 例子:
      let isDone = false; // 类型推断为 boolean
      
  • 解构: TypeScript 支持对象和数组的解构赋值,从而可以提取出所需的信息。

    • 解构对象例子:

      let user = { name: "Bob", age: 27 };
      let { name, age } = user; // 提取user对象中的name和age属性
      
    • 解构数组例子:

      let scores = [80, 90, 100];
      let [score1, score2] = scores; // 提取scores数组中的前两个元素
      
  • 展开: TypeScript 允许使用展开操作符(...)来拷贝对象或数组中的所有属性到另一个对象或数组。

    • 对象展开例子:

      let originalUser = { name: "Alice", age: 25 };
      let clonedUser = { ...originalUser }; // 创建一个originalUser的拷贝
      
    • 数组展开例子:

      let firstArray = [1, 2, 3];
      let secondArray = [...firstArray, 4, 5]; // 创建一个包含firstArray所有元素加上4和5的新数组
      

学习 TypeScript 变量声明时,理解这些基本概念对于平滑地过渡从 JavaScript 到 TypeScript 至关重要。记住,TypeScript 是 JavaScript 的超集,它增加了类型系统和其他特性,帮助你编写更健壮、易维护的代码。

变量声明

变量声明在 TypeScript 中是如何定义和使用变量的基础。这与 JavaScript 很相似,但 TypeScript 增加了类型注解来帮助我们更好地预防错误和理解代码结构。

  • let 和 const TypeScript 支持使用letconst来声明变量,这与 ES6 的用法一致。

    • let用于声明一个可以重新赋值的变量。
      let userName: string = "Alice";
      userName = "Bob"; // 正确
      
    • const用于声明一个常量,即一旦赋值后就不可以再被改变。
      const userAge: number = 30;
      // userAge = 31; // 错误:不能给const变量重新赋值
      
  • 类型注解 TypeScript 让你能够给变量指定具体的类型,如果尝试赋值错误的类型,编译器会报错。

    let isStudent: boolean = true;
    // isStudent = "Yes"; // 错误:Type 'string' is not assignable to type 'boolean'.
    
  • 类型推断 当你没有明确指定类型时,TypeScript 会尝试推断出一个类型。

    let courseName = "Introduction to TypeScript"; // 类型推断为string
    // courseName = 42; // 错误:Type 'number' is not assignable to type 'string'.
    
  • var 关键字(不推荐) 尽管 TypeScript 支持 JavaScript 的var关键字,但不建议使用,因为它有作用域提升等问题,可能导致难以发现的 bug。

    var globalVar = "This is a global variable";
    
  • 块级作用域 使用letconst声明的变量都有块级作用域,即它们只在定义它们的块或 for-loop 内有效。

    function doSomething() {
      let someValue = 10;
      if (someValue > 5) {
        let hello = "Hi!"; // 块级作用域
        console.log(hello); // 输出 Hi!
      }
      // console.log(hello); // 错误:Cannot find name 'hello'. 'hello' is not defined outside the if-block.
    }
    
  • 解构赋值 TypeScript 也支持 ES6 的解构赋值,允许你从数组或对象中快速提取数据。

    • 解构数组
      let input: [number, number] = [1, 2];
      let [first, second] = input; // first = 1, second = 2
      
    • 解构对象
      let obj = { a: "foo", b: 12, c: "bar" };
      let { a, b } = obj; // a = "foo", b = 12
      
  • 展开运算符 展开运算符(...)允许一个数组或对象的所有可枚举属性被包含到当前位置。

    let firstArr = [1, 2];
    let secondArr = [3, 4];
    let combinedArr = [...firstArr, ...secondArr]; // 结果为 [1, 2, 3, 4]
    

通过以上的例子,你可以看出 TypeScript 的变量声明非常类似于 JavaScript,唯一显著的区别在于类型注解,这些类型注解为代码提供了额外的安全性和可维护性。当从 JavaScript 过渡到 TypeScript 时,重要的是逐步习惯在变量声明时添加类型注解。

var 声明

  • var 声明是在 JavaScript 中创建变量的一种方式,也可以在 TypeScript 中使用。它会声明一个函数作用域或全局作用域的变量,而不是块级作用域(即大括号{}内部)的变量。

  • 问题点:var声明出来的变量可能引起变量提升(hoisting)问题,意味着无论在函数的哪个位置声明变量,变量都会被提升到函数的顶部,就好像是在函数开始的地方声明的一样。

    function foo() {
      if (true) {
        var bar = "hello"; // bar 是函数作用域内的变量
      }
      console.log(bar); // 输出 "hello",因为bar在整个函数范围内都有效
    }
    foo();
    
  • var声明的另一个问题是它允许你多次声明同一个变量,这可能会导致代码中的逻辑错误。

    var baz = "baz";
    // ... 其他代码 ...
    var baz = "qux"; // 没有错误,但是变量baz已经被重新声明和赋值
    
  • 在 TypeScript 中,建议尽可能避免使用var来声明变量,转而使用letconst。这两者都支持块级作用域,更加安全,并且能够帮助你避免上述var可能带来的问题。

    function foo() {
      if (true) {
        let bar = "hello"; // bar 只在这个 if 语句块中有效
      }
      console.log(bar); // 错误: Cannot find name 'bar'.
    }
    foo();
    

作用域规则

变量声明中的var声明和作用域规则与 TypeScript 紧密相关,因为 TypeScript 扩展自 JavaScript,并保留了其大部分语法和特性。以下是关于var声明的作用域规则的解释:

  • 函数作用域: var声明的变量具有函数作用域,这意味着在函数内部声明的变量只能在该函数内部访问。

    function func() {
      var insideVar = "I exist within func";
    }
    console.log(insideVar); // 错误:insideVar is not defined
    
  • 全局作用域: 如果var声明在函数外部进行,则该变量拥有全局作用域,可以在代码的任何地方被访问。

    var outsideVar = "I'm global";
    function func() {
      console.log(outsideVar); // 正确,会输出:"I'm global"
    }
    console.log(outsideVar); // 正确,会输出:"I'm global"
    
  • 提升(Hoisting): 使用var声明的变量会被提升至其当前作用域的顶部,这意味着变量可以在声明之前被引用,但这样做会得到undefined值。

    function func() {
      console.log(hoistedVar); // 输出"undefined",而不是报错
      var hoistedVar = "I am hoisted";
    }
    
  • 重复声明: 在同一个作用域内,使用var可以重复声明同一个变量,后续声明会无视掉,但是如果包含初始化,则会重新赋值。

    function func() {
      var repeatedVar = "first declaration";
      var repeatedVar; // 没有错误,但这个声明无效
      console.log(repeatedVar); // 输出"first declaration"
    
      var repeatedVar = "second declaration"; // 这里会更新变量的值
      console.log(repeatedVar); // 输出"second declaration"
    }
    

学习 TypeScript 时,理解var如何在 JavaScript 中工作是重要的,因为虽然 TypeScript 推荐使用letconst来声明变量以避免上述问题,但在阅读或维护现有 JavaScript 代码时,你仍然可能会遇到var

捕获变量怪异之处

变量声明中的 "捕获变量怪异之处" 指的是在 JavaScript 和 TypeScript 中使用 var 关键字声明变量时发生的作用域相关的行为,这有时会造成混乱。var 声明的变量具有函数作用域,而不是块级作用域(块级作用域是指只在如 if 语句或 for 循环这样的代码块内有效的作用域),这意味着即使在一个代码块内声明了变量,它也可以在外部被访问。

  • 问题: 在循环中使用 var 声明变量可能会导致预期之外的结果。
  • 例子:
for (var i = 0; i < 10; i++) {
  setTimeout(function () {
    console.log(i);
  }, 100 * i);
}

在上面的例子中,你可能期望输出的是 0 到 9 的数字。但实际上将会打印出数字 10 十次。这是因为 setTimeout 函数创建了一个函数(闭包),它捕获了变量 i 的引用,而不是循环中每一步的值。由于 var 声明的变量具有函数作用域,并且循环完成后 i 的值为 10,所以每个函数都引用相同的 i 变量。

  • 解决方法: 使用 let 关键字代替 var,因为 let 提供了块级作用域。
for (let i = 0; i < 10; i++) {
  setTimeout(function () {
    console.log(i);
  }, 100 * i);
}

在这个修改后的例子中,每次迭代都会创建一个新的 i 变量,因此 setTimeout 的回调函数能够捕获到当前迭代步骤中的 i 的值,最终正确地打印出从 0 到 9 的数字。

总结起来,在使用 var 声明变量时,需要注意它的函数作用域特性,这可能会在类似于循环等场景下带来预期之外的行为。在 TypeScript(和现代的 JavaScript)中,更推荐使用 letconst 来避免这类问题,它们提供了块级作用域,并且更加符合编程直觉。

let 声明

  • let 声明是一种用于声明变量的方法,它具有块作用域(block scope)特性。这意味着使用 let 定义的变量只能在其定义的代码块内部访问。

    let greeting = "Hello, World!";
    if (greeting) {
      let greet = "Hi there!";
      console.log(greet); // 正常工作
    }
    console.log(greeting); // 正常工作
    // console.log(greet); // 这会抛出一个错误,因为 `greet` 只在上面的 if 语句块中可见
    
  • let 声明与 var 声明不同,后者是函数作用域(function scope),而不是块作用域。

    var greeter = "hey hi";
    for (var i = 0; i < 5; i++) {
      console.log(greeter); // 正常工作
    }
    console.log(i); // 也正常工作,并输出 5
    

    对比使用 let:

    let greeting = "say Hi";
    for (let i = 0; i < 5; i++) {
      console.log(greeting); // 正常工作
    }
    // console.log(i); // 抛出错误,因为 `i` 只在循环体内可见
    
  • 使用 let 可以避免一些由于变量提升(hoisting)导致的问题。变量提升是指在 JavaScript 中,函数内部声明的变量可以在整个函数范围内被访问,即使是在声明之前访问也不会报错,只是值为 undefined

    console.log(foo); // 输出 undefined,而不是抛出错误
    var foo = "Foo";
    

    使用 let 就不会发生这样的事情:

    // console.log(bar); // 如果取消注释,这会抛出错误
    let bar = "Bar";
    
  • 在一个块内部重复声明同一变量时,如果使用 let 会抛出错误。这有助于避免并发或者重复声明变量带来的问题,这在使用 var 时很容易发生。

    let greet = "hello world";
    // let greet = "welcome"; // 如果取消注释,TypeScript 会报错,因为已经声明了 `greet`
    
  • let 也支持在声明变量时添加类型注解,这是 TypeScript 特有的特性,可以确保变量中存放特定类型的数据。

    let age: number = 30;
    // age = 'thirty'; // 这会抛出一个错误,因为 'thirty' 不是数字类型
    

通过理解和使用 let 声明,你可以写出更加安全和容易理解的 TypeScript 代码,同时也能够减少潜在的作用域和变量提升等引发的问题。

块作用域

块作用域(Block Scope):

  • 在 JavaScript 中,var关键字声明的变量是函数作用域或全局作用域的,而不是块级作用域的。
  • 块级作用域意味着变量仅在包含它们的代码块(例如if语句或for循环)内部可见。
  • TypeScript 引入了let关键字来声明变量,这样声明的变量会拥有块级作用域。

举例说明块作用域:

if (true) {
  let blockScopedVariable = "I am inside an if block";
  console.log(blockScopedVariable); // 输出: 'I am inside an if block'
}

console.log(blockScopedVariable); // 错误: blockScopedVariable is not defined
  • 在上面的例子中,blockScopedVariable只能在if语句的块内被访问,在外部尝试访问时会导致错误。
  • 这与使用var声明的变量形成对比,如果用var代替let,那么在if语句外部也能够访问到blockScopedVariable

巩固理解:

function f(input: boolean) {
  let a = 100;

  if (input) {
    // 'b' 只存在于这个块里面
    let b = a + 1;
    return b;
  }

  // 错误: 'b' doesn't exist here
  return b;
}
  • 在这个例子中,a是函数作用域的,在整个函数体内都可以访问。
  • bif语句块中用let声明,所以它只能在这个if块中使用。在if语句外尝试返回b会导致一个错误。

重定义及屏蔽

变量声明中的“let 声明”部分提到的“重定义及屏蔽”是指在 TypeScript(以及 ES6 及以上版本的 JavaScript)中,当你使用 let 关键字来声明变量时,这个变量只能在它被声明的块或子块中被访问,这就是所谓的块级作用域。而“重定义”和“屏蔽”则涉及到在不同作用域下使用相同名称的变量。

  • 重定义: 通常,在同一作用域内不能用 let 重复声明同一个变量名;否则,会得到一个编译错误。但在不同的块作用域内,你可以使用相同的变量名来声明变量。

    let pet = "cat";
    // let pet = 'dog'; // 错误: 不能再次声明'pet'
    
  • 块级作用域: 使用 let 的变量仅在其声明的块(或子块)内有效。

    function foo() {
      let pet = "cat";
      if (true) {
        let pet = "dog"; // 这里的'pet'与外层的'pet'是不同的变量
        console.log(pet); // 输出: dog
      }
      console.log(pet); // 输出: cat
    }
    
  • 屏蔽: 当在一个嵌套的块作用域中声明了一个外层作用域已有的变量名时,内部的变量会“屏蔽”掉外部的变量。在内部作用域中访问该变量时,将引用到最接近作用域链中的那一个变量。

    let pet = "cat";
    
    function inner() {
      let pet = "dog"; // 内部作用域的'pet'屏蔽了外部作用域的'pet'
      return pet; // 返回的是内部作用域的'pet'
    }
    
    console.log(inner()); // 输出: dog
    console.log(pet); // 输出: cat
    

屏蔽现象说明了作用域链是如何工作的,从当前作用域开始搜索变量,如果没找到,就向上一层作用域继续查找,直到找到变量或达到全局作用域为止。因此,即使有相同名称的变量,只要它们处于不同的作用域(例如,一个在函数外,另一个在函数内),它们就是完全不同的变量,互不影响。

块级作用域变量的获取

块级作用域变量的获取是指在 TypeScript(和 ES6 中的 JavaScript)里,当你使用let关键字声明一个变量时,这个变量拥有所谓的“块级作用域”(Block Scope)。不同于使用var声明的变量有函数作用域(Function Scope),let声明的变量仅在它们被定义的代码块(如:循环、条件语句)内有效。

  • 使用var声明变量时,无论实际声明在何处,都会被视为在当前函数的顶部声明,这种行为称为变量提升(hoisting)。
  • let声明的变量具有块级作用域,意味着变量在声明它的代码块或任何嵌套的子块外面是不能访问的。
  • 这有助于减少运行时错误,因为变量不会在预期的作用域之外被意外使用。

举例:

function foo() {
  // i 在这里是不可见的
  for (let i = 0; i < 10; i++) {
    // i 在这个循环块内是可见的
  }
  // i 在这里也是不可见的,如果这里尝试访问 i, 会抛出一个错误
}

if (true) {
  let a = "hello";
  // a 在这个 if 块内是可见的
}
// a 在这个 if 块外是不可见的

在上面的例子中,ia都是用let声明的,只在它们各自的块(循环和if语句)中可见。如果你尝试在这些块的外面访问它们,TypeScript 编译器将会抛出一个错误。这比var声明变量的传统行为更加直观和安全,可以避免许多常见的编程错误。

const 声明

const 声明是 TypeScript (和 ES6) 中的一种变量声明方式,用于创建一个只读的常量引用。这意味着一旦一个变量被声明为 const,它的值就不能再被改变。

  • const 创建的是一个不可变的变量绑定,但并不意味着该变量所指向的值是不可变的;
    • 例如,如果 const 指向一个对象或数组,对象的属性或数组的元素还是可以修改的。
  • 使用 const 声明变量时,必须在声明时初始化它;
    • const pi = 3.14159;,你不能像 let 那样先声明后赋值,如 const pi; pi = 3.14159; 是错误的。
  • const 最好用于那些不需要改变引用的场景;
    • 对于基本数据类型(如数字、字符串、布尔值),const 确保值不可变;
    • 对于复合数据类型(如对象、数组),const 保证引用地址不变,但对象的属性或数组中的内容仍然可以改变。

举例说明:

// 正确的 const 声明及初始化
const greeting = "Hello, World!";
console.log(greeting); // 输出:Hello, World!

// 错误的 const 声明,没有初始化
// const name; // SyntaxError: Missing initializer in const declaration

// 如果尝试重新赋值,会得到一个错误
// greeting = "Hello, TypeScript!"; // TypeError: Assignment to constant variable.

// 对于数组和对象,它们的内容仍然可以被修改
const numbers = [1, 2, 3];
numbers.push(4);
console.log(numbers); // 输出:[1, 2, 3, 4]

// 不能重新分配新的数组给 const 变量
// numbers = [0, 0, 0]; // TypeError: Assignment to constant variable.

const user = {
  name: "Alice",
  age: 25,
};
user.age = 26;
console.log(user); // 输出:{ name: 'Alice', age: 26 }

// 同样,不能将整个对象重新赋值
// user = { name: "Bob" }; // TypeError: Assignment to constant variable.

通过以上例子可以看出,const 对于基本数据类型来说确实是常量,对于数组和对象则是其内容可变但引用地址不变。在使用 TypeScript 开发时,优先使用 const 来定义变量可以提升代码的稳定性和可预测性。

let vs. const

在 TypeScript 中,letconst是你用来声明变量的两种方式。它们都是 ES6(ECMAScript 2015)引入的新特性,TypeScript 作为 JavaScript 的超集,同样支持这些特性。以下是它们之间的区别:

  • let

    • let用于声明一个块作用域的局部变量,即这个变量只存在于它被定义的块中(比如一个循环或者一个 if 语句内部)。
    • 当你计划在以后的代码中改变这个变量的值时应该使用let
    • 与传统的var不同,let避免了某些因变量提升导致的常见问题。

    例子:

    let count = 0; // count可以在后面的代码中被重新赋值
    for (let i = 0; i < 5; i++) {
      count += i;
    }
    // 这里i是不可访问的,因为它只在for循环中有效
    
  • const

    • const也是用于声明变量,但它声明的是常量,一旦被赋值就不能再改变。
    • 使用const声明时必须立即初始化变量,因为之后无法再修改。
    • let相同,const也是块作用域的。
    • 尽管const表示常量,但如果它指向一个对象,对象内部的属性还是可以被修改的。

    例子:

    const pi = 3.14; // pi的值不能被改变
    // pi = 1; // 错误: 不能给pi重新赋值
    
    const obj = { name: "Alice" };
    obj.name = "Bob"; // 对象obj不可以被整体重新赋值,但是它的属性可以修改
    // obj = { name: 'Charlie' }; // 错误: 不能给obj重新赋值
    

在编写 TypeScript 代码时,推荐尽可能使用const来声明变量,这样可以防止在程序中无意间修改了变量值,增加代码的可靠性。当需要在后续的操作中改变变量的值时,再使用let

解构

解构(Destructuring)是 TypeScript 提供的一种语法,允许你从数组或对象中提取数据并赋值给独立的变量。这可以使代码更加清晰和简洁。

  • 数组解构:

    • 基本用法: [a, b] = [10, 20] 把数组中的第一个元素赋值给 a,第二个元素赋值给 b
    • 可以忽略不需要的元素: [a, , b] = [10, 20, 30] 只获取第一和第三个元素。
    • 使用剩余运算符获取其余元素: [a, ...rest] = [10, 20, 30, 40] 把数组前面的元素赋值给 a,其余的组成一个新的数组赋值给 rest
    • 也可以用于函数参数: function f([first, second]: [number, number]) {...} 函数接收一个元组作为参数,并解构它。
  • 对象解构:

    • 基本用法: {a, b} = {a:10, b:20} 把对象的属性 ab 提取出来分别赋值给同名变量。
    • 可以为属性设置不同的变量名: {a: newA, b: newB} = {a:10, b:20} 把属性 a 的值赋给了变量 newAb 的值赋给了 newB
    • 使用默认值: {a, b = 1000} = {a:10} 如果对象没有属性 b,则 b 默认为 1000
    • 使用剩余运算符获取其余属性: {a, ...rest} = {a:10, b:20, c:30} 属性 a 赋给变量 a,剩下的属性形成一个新的对象赋值给 rest
    • 解构可用于嵌套对象: {a: {b, c}} = {a: {b:10, c:20}} 提取嵌套对象中的属性 bc

使用解构可以大幅度减少代码冗余,让变量的声明和初始化更加直观和便捷。

解构数组

解构数组的基本概念是从数组中提取值,然后将这些值赋给变量,可以使得你的代码更加简洁和可读。

在 JavaScript 中,你可能已经见过诸如下面的代码:

let first = someArray[0];
let second = someArray[1];

在 TypeScript 中(就像在 ES6+的 JavaScript 中一样),你可以使用数组解构来达到同样的效果,但方法更为简洁:

let [first, second] = someArray;

以下是利用数组解构的一些实际例子:

  • 交换变量:
let a = 1;
let b = 3;

[a, b] = [b, a]; // 现在a = 3, b = 1
  • 函数返回多个值:
function getScores() {
  return [70, 80, 90];
}

let [math, science, art] = getScores();
// math = 70, science = 80, art = 90
  • 函数中使用剩余参数:
let [firstScore, ...remainingScores] = getScores();
// firstScore = 70, remainingScores = [80, 90]
  • 忽略不需要的值:
let [, , thirdScore] = getScores();
// thirdScore = 90

注意,在 TypeScript 中,解构也可以包括类型注解:

let [first, second]: [number, number] = [1, 2];

在这个例子中,firstsecond 都显式地被标注为 number 类型。当你使用 TypeScript 时,添加类型注解可以提供更多的编译时检查,并且可以让代码的意图更明确。

对象解构

对象解构是一种从对象中提取属性并将它们赋值给变量的快捷方式。在 JavaScript 中你可能已经知道如何通过点(.)运算符来访问对象的属性,但是当你需要从一个对象中提取很多属性时,代码可能会显得冗长和重复。TypeScript 沿用了 ES6 的解构特性,这使得这个过程更加简洁。

  • 基本例子:

    let obj = { a: 1, b: 2, c: 3 };
    
    // 没有解构
    let a = obj.a;
    let b = obj.b;
    let c = obj.c;
    
    // 使用对象解构
    let { a, b, c } = obj;
    
  • 默认值:如果对象没有某个属性,可以为解构的变量设置默认值。

    let { a, b, d = 4 } = obj; // obj没有d属性,所以d默认为4
    
  • 新的变量名:可以将对象的属性解构到不同名称的变量中。

    let { a: newA, b: newB } = obj; // 现在有了新变量newA和newB,分别对应obj的a和b
    
  • 函数参数中的对象解构:在函数参数定义中直接进行解构。

    function greet({ name, age }): void {
      console.log(`Hello, my name is ${name} and I am ${age} years old.`);
    }
    
    greet({ name: "Alice", age: 30 }); // 输出: Hello, my name is Alice and I am 30 years old.
    
  • 解构现存对象的部分属性:如果你只想获取对象中的几个属性,可以按需解构。

    let { a, c } = obj; // 只解构a和c,忽略b
    
  • 剩余属性(...):可以用三点(...)运算符来获取对象中剩余的所有属性。

    let { a, ...rest } = obj;
    console.log(rest); // 将输出:{ b: 2, c: 3 }
    

对象解构可以使代码更加简洁明了,尤其是处理具有多个属性的对象时,同时也让代码易于维护和理解。通过上述示例,你可以看到解构如何帮助你避免编写重复且繁琐的代码来访问或使用对象中的属性。

属性重命名

对象解构是一种快速获取对象属性的语法形式,在 TypeScript 中,这项技术不仅可以用于从对象中提取值,还可以在提取值时对其进行重命名。属性重命名允许你将对象的属性赋给一个新的变量名,以便在代码中使用更合适或可读性更强的名称。

举例说明:

假设我们有一个对象person

let person = {
  name: "Alice",
  age: 25,
};

如果你想获取这个对象的nameage属性,并且想要将它们分别赋给变量personNamepersonAge,你可以使用以下语法进行对象解构并同时重命名:

let { name: personName, age: personAge } = person;

现在,不是使用person.nameperson.age来引用这些值,我们可以直接使用personNamepersonAge

console.log(personName); // 输出:Alice
console.log(personAge); // 输出:25
  • 解构时重命名的语法是在冒号:后面放置新的变量名。
  • 原对象person不会因为解构而改变,仍然保持原有的属性和结构。
  • 重命名后,只有新的变量名(如personNamepersonAge)在当前作用域内有效。
  • 这种方法特别有用,当对象的属性名过于通用或可能与其他变量名冲突时,通过重命名可以避免命名冲突。

再举一个实际应用中的例子,比如处理 HTTP 响应:

function handleResponse({ status: statusCode, headers: responseHeaders }) {
  console.log(statusCode); // 使用重命名后的变量
  // ...可以继续使用responseHeaders等变量
}

// 假设getResponse()函数返回一个包含status和headers的对象
let response = getResponse();
handleResponse(response);

在上面的例子中,我们定义了一个函数handleResponse,它通过参数解构直接提取出状态码和头信息,并将它们重命名为statusCoderesponseHeaders,以便在函数内部更清晰明了地使用这些数据。

默认值

对象解构中的默认值指当解构赋值时,如果原对象中对应的属性不存在或其值为undefined,则可以为其设置一个默认值。这样,在使用某个属性时,即便该属性在原对象中缺失,也能保证变量有一个初始值,从而避免运行时错误。

  • 例子 1: 给一个不存在的属性设置默认值
let { a = 10, b = 5 } = { a: 3 };
console.log(a); // 输出 3,因为实际存在该属性
console.log(b); // 输出 5,因为实际上不存在b属性,所以使用了默认值
  • 例子 2: 解构赋值同时设置多个默认值
let { a: aa = 10, b: bb = 20 } = { a: 30 };
console.log(aa); // 输出 30,因为 { a: 30 } 中 a 的值被赋给了 aa
console.log(bb); // 输出 20,因为没有 b 属性,bb 使用了默认值
  • 例子 3: 函数参数中使用对象解构和默认值
function greet({ name, age = 25 } = { name: "Anonymous" }): void {
  console.log(`Hello, my name is ${name} and I am ${age} years old.`);
}
greet({ name: "Alice", age: 30 }); // 输出 "Hello, my name is Alice and I am 30 years old."
greet({ name: "Bob" }); // 输出 "Hello, my name is Bob and I am 25 years old.",age 使用了默认值
greet(); // 输出 "Hello, my name is Anonymous and I am 25 years old.",整个对象使用了默认值

注意,默认值只有在解构赋值时遇到 undefined 的情况下才会生效;如果属性值为 null 或其他 falsy 值(除了 undefined),默认值不会被使用。

  • 例子 4: null 不触发默认值
let { a = 10 } = { a: null };
console.log(a); // 输出 null,因为 a 属性存在且值为 null

函数声明

变量声明中的"解构 > 函数声明"部分,是指在 TypeScript 中如何在函数参数中使用解构赋值。解构赋值允许你直接从对象或数组中提取数值,赋给声明的变量。在函数参数中使用解构可以使代码更加简洁明了。

  • 使用对象解构: 当你想要传递一个对象到函数内部,并且只需要对象中的几个属性时,可以采用对象解构。
function greet({ name, age }: { name: string; age: number }) {
  console.log(`Hello, my name is ${name} and I'm ${age} years old.`);
}

const user = { name: "Alice", age: 25 };
greet(user); // 输出: Hello, my name is Alice and I'm 25 years old.
  • 使用默认参数: 在解构时,你可以设置默认值,在属性缺失时使用。
function setupProfile({ name, age = 30 }: { name: string; age?: number }) {
  console.log(`Profile: ${name}, Age: ${age}`);
}

setupProfile({ name: "Bob" }); // 输出: Profile: Bob, Age: 30
  • 使用剩余参数:有时候你可能想要收集剩余的属性,可以使用展开运算符 ...
function printCoordinates({
  x,
  y,
  ...rest
}: {
  x: number;
  y: number;
  [key: string]: any;
}) {
  console.log(`X: ${x}, Y: ${y}`);
  console.log(rest); // 这将是一个包含剩余属性的对象
}

printCoordinates({ x: 10, y: 20, z: 30, name: "pointA" });
// 输出: X: 10, Y: 20
// 输出: { z: 30, name: 'pointA' }
  • 嵌套解构: 解构不仅仅可以用于第一层属性,也适用于嵌套结构。
function getFullName({
  person: { firstName, lastName },
}: {
  person: { firstName: string; lastName: string };
}) {
  return `${firstName} ${lastName}`;
}

const userData = { person: { firstName: "John", lastName: "Doe" } };
console.log(getFullName(userData)); // 输出: John Doe

在学习 TypeScript 的过程中,理解解构和它在 TypeScript 中的类型注解是很重要的。这会帮助你写出更清晰、类型安全的代码。记住,在 TypeScript 中,解构不仅仅是一个语法便利,而且你可以在解构表达式中添加类型注解,增加代码的可维护性和稳定性。

展开

变量声明中的"展开"是指将一个数组或对象的所有可枚举属性,复制到当前对象或数组中。在 TypeScript 中,这通常用于复制对象或合并对象,以及复制数组或合并数组。

  • 对象展开

    • 实例:假设有两个对象,我们想创建一个新对象,它包含这两个对象的属性。

      let person = { name: "Jane", age: 22 };
      let job = { title: "Developer", company: "Google" };
      
      let employee = { ...person, ...job };
      console.log(employee);
      // 输出: { name: 'Jane', age: 22, title: 'Developer', company: 'Google' }
      
    • 注意点:如果两个对象有相同的属性,则后面的对象会覆盖前面的对象属性。

      let person = { name: "Jane", age: 22 };
      let anotherPerson = { name: "Tom", age: 30 };
      
      let combinedPerson = { ...person, ...anotherPerson };
      console.log(combinedPerson);
      // 输出: { name: 'Tom', age: 30 }
      
  • 数组展开

    • 实例:将两个数组合并成一个新数组。

      let firstArray = [1, 2, 3];
      let secondArray = [4, 5, 6];
      
      let combinedArray = [...firstArray, ...secondArray];
      console.log(combinedArray);
      // 输出: [1, 2, 3, 4, 5, 6]
      
    • 注意点:数组展开可以用于插入其他元素或组合多个数组。

      let numbers = [2, 3, 4];
      let newNumbers = [1, ...numbers, 5];
      console.log(newNumbers);
      // 输出: [1, 2, 3, 4, 5]
      

展开操作符提供了一种简洁的语法来合并和克隆数组和对象,而不用写循环或者使用Object.assign()之类的方法。在使用 TypeScript 时,它能让你更便捷地处理数据结构,尤其是在不改变原始对象或数组的情况下进行操作。