在 TypeScript 中,var 还是 var,let 还是 let,const 还是 const,这里简单温习一下 var、let 和 const。

  1. var
  2. let
  3. const
  4. 解构
  5. 展开

1. var

使用 var 声明变量并赋值时 var a = 1; 声明 var a 会提升,但赋值 a = 1 不会提升,var 可以重复声明,var 只分函数内和函数外,不存在块级作用域,除了下面 try...catch 块中的这个 e,但其实此处的 e 个人觉得看作是函数的形参或许更好理解:

  1. try {
  2. } catch(e) {
  3. }

2. let

let 是 ES6 出现的,是块级作用域,不存在变量提升,同一个块内不能重复声明,在声明之前存在暂时性死区,不能进行操作,如下所示,在声明 a 之前就尝试调用函数 foo() 会报错 Cannot access 'a' before initialization

  1. function foo() {
  2. return a
  3. }
  4. foo()
  5. let a

3. const

const 可以理解为 let 的增强,const 声明一个变量的同时要对其进行初始化,并且之后不能直接对声明变量再次赋值,但可以更改变量所指向的对象上的属性:

  1. const a = 18
  2. const n // 会报错
  3. const a = 25 // 会报错
  4. const people = {
  5. name: 'xiaofeng',
  6. age: a
  7. }
  8. // 会报错
  9. const people = {
  10. name: 'xiaoqian',
  11. age: a
  12. }
  13. // 对属性修改是ok的,因为并没有改变对 people 的引用
  14. people.age = 25

var、let、const 从最佳实践看来应该优先使用 const,不行再使用 let,而 var 几乎不需要使用,我一时间也想不出在可以写 ES6 的条件下,有什么特殊场景是不能用 let 非要用 var 的。

4. 解构和剩余参数

默认情况下 ts 文件在编译时会被转换为 ES3,看一下就能理解 ES6 语法了,或者到 babel 官网上 Try it out 练练手,看看是如何转换的。

4.1 数组解构

  1. let input = [1, 2]
  2. // 数组的解构赋值
  3. let [first, second] = input
  4. // 等价于
  5. // let first = input[0]
  6. // let second = input[1]
  7. // 解构也能用于函数的参数
  8. function foo([first, second]: [number, number]) {
  9. console.log(first)
  10. console.log(second)
  11. }
  12. // foo(input) // 使用 ts 编译时会报错,因为参数要求是 Tuple 元祖类型 [number, number],而实际上传入了 number[] 类型
  13. // 解决办法是把 input 定义为同样的元祖类型即可
  14. let input2: [number, number] = [1, 2]
  15. foo(input2) // ok
  16. // 解构 + 剩余参数
  17. let [first, ...rest] = [1, 2, 3, 4]
  18. console.log(first) // 1
  19. console.log(rest) // [2, 3, 4]
  20. // 还可以使用逗号分隔略过不关心的数据
  21. let [, second, , fourth] = [1, 2, 3, 4]
  22. console.log(second) // 2
  23. console.log(fourth) // 4

4.2 对象解构

解构对象时,要注意如果一个属性值是 null,而不是 undefined,那么不会被默认值赋值。

  1. // 对象解构赋值的同时还可以重命名
  2. let o = {
  3. a: 'foo',
  4. b: 'bar',
  5. c: 12,
  6. d: false
  7. }
  8. let { a, b: banana, ...passthrough } = o
  9. console.log(a) // 'foo'
  10. console.log(b) // 报错 Uncaught ReferenceError: b is not defined
  11. console.log(banana) // 'bar'
  12. console.log(passthrough) // {c:12, d: false}
  13. // 传入的对象中,b 属性有可能不存在
  14. // type C = { a: string, b?: number }
  15. function keepWholeObject(wholeObject: { a: string, b?: number }) {
  16. // 所以解构时要为 b 属性提供默认值
  17. let { a, b = 1000 } = wholeObject
  18. console.log(a, b)
  19. }
  20. // 对象解构时,可以为属性可以提供默认值
  21. // 对函数的对象参数进行时提供默认的解构来源
  22. function foo({ a, b = 0 } = { a: '' }): void {
  23. console.log(a, b)
  24. }
  25. foo({ a: 'yes' }) // 传入的对象,没有 b 属性,但好在 b 在解构时提供了默认值 0
  26. foo() //不传参数时,默认从 { a: '' } 解构
  27. foo({}) // 报错!因为类型不匹配。传入空对象,虽然 b 属性有默认值 0,但是 a 属性无从得知

解构表达式有时候会不好理解,尤其和 TS 的类型系统混合在一起时,因此使用时尽量保持小而简单。

5. 展开

5.1 数组的展开

  1. let first = [1, 2]
  2. let second = [3, 4]
  3. let bothPlus = [0, ...first, ...second, 5]
  4. console.log(bothPlus) // [0, 1, 2, 3, 4, 5]

5.2 对象的展开

  1. let defaults = {
  2. food: 'apple',
  3. price: '$10',
  4. total: 50
  5. }
  6. let search = { ...defaults, food: 'rich' }
  7. console.log(search)
  8. // {food: "rich", price: "$10", total: 50} 右边的同名属性 food 覆盖了左边展开后的属性 food

通常来说会把默认值放在前边,用于被一些后来设定的属性所覆盖。