2024年最全TypeScript变量声明_block-scoped variable,2024年最新4年小Golang的心路历程

img
img

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

猜一下下面的代码会返回什么:

for (var i = 0; i < 10; i++) {
  setTimeout(function() {
    console.log(i)
  }, 100 \* i)
}

答案是,setTimeout 会在若干毫秒的延时后执行一个函数(等待其它代码执行完毕):

10
10
10
10
10
10
10
10
10
10

很多 JavaScript 程序员对这种行为已经很熟悉了,但如果你很不解也没有关系,因为你并不是一个人。 大多数人期望输出结果是这样:

0
1
2
3
4
5
6
7
8
9

我们传给 setTimeout 的每一个函数表达式实际上都引用了相同作用域里的同一个 i

让我们花点时间思考一下这是为什么。 setTimeout 在若干毫秒后执行一个函数,并且是在 for 循环结束后。for 循环结束后,i 的值为 10。 所以当函数被调用的时候,它会打印出 10

一个通常的解决方法是使用立即执行的函数表达式(IIFE)来捕获每次迭代时 i 的值:

for (var i = 0; i < 10; i++) {
  (function(i) {
    setTimeout(function() {
      console.log(i)
    }, 100 \* i)
  })(i)
}

这种奇怪的形式我们已经司空见惯了。 参数 i 会覆盖 for 循环里的 i,但是因为我们起了同样的名字,所以我们不用怎么改 for 循环体里的代码。

let 声明

现在你已经知道了 var 存在一些问题,这恰好说明了为什么用 let 语句来声明变量。 除了名字不同外, letvar 的写法一致:

let hello = 'Hello!'

主要的区别不在语法上,而是语义,我们接下来会深入研究。

块作用域

当用 let 声明一个变量,它使用的是块作用域。 不同于使用 var 声明的变量那样可以在包含它们的函数外访问,块作用域变量在包含它们的块或 for 循环之外是不能访问的。

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

  if (input) {
    // OK: 仍然能访问到 a
    let b = a + 1
    return b
  }

  // Error: 'b' 在这里不存在
  return b
}

这里我们定义了 2 个变量 aba 的作用域是 f 函数体内,而 b 的作用域是 if 语句块里。

catch 语句里声明的变量也具有同样的作用域规则。

try {
  throw 'Oh no!';
}
catch (e) {
  console.log('Catch it.')
}

// Error: 'e' 在这里不存在
console.log(e)

拥有块级作用域的变量的另一个特点是,它们不能在被声明之前读或写。 虽然这些变量始终“存在”于它们的作用域里,但在直到声明它的代码之前的区域都属于暂时性死区。 它只是用来说明我们不能在 let 语句之前访问它们,幸运的是 TypeScript 可以告诉我们这些信息。

a++ // TS2448: Block-scoped variable 'a' used before its declaration.
let a

注意一点,我们仍然可以在一个拥有块作用域变量被声明前获取它。 只是我们不能在变量声明前去调用那个函数。 如果生成代码目标为 ES2015,现代的运行时会抛出一个错误;然而,现今 TypeScript 是不会报错的。

function foo() {
  // okay to capture 'a'
  return a
}

// 不能在'a'被声明前调用'foo'
// 运行时应该抛出错误
foo()

let a

关于暂时性死区的更多信息,查看这里 Mozilla Developer Network

重定义及屏蔽

我们提过使用 var 声明时,它不在乎你声明多少次;你只会得到 1 个。

function f(x) {
  var x
  var x

  if (true) {
    var x
  }
}

在上面的例子里,所有 x 的声明实际上都引用一个相同的x,并且这是完全有效的代码,但这经常会成为 bug 的来源。幸运的是 let 的声明就不会这么宽松了。

let x = 10
let x = 20 // 错误,不能在 1 个作用域里多次声明 x

并不是要求两个均是块级作用域的声明 TypeScript 才会给出一个错误的警告。

function f(x) {
  let x = 100 // Error: 干扰参数声明
}

function g() {
  let x = 100
  var x = 100 // Error: 不能同时具有 x 的两个声明
}

并不是说块级作用域变量不能用函数作用域变量来声明。 而是块级作用域变量需要在明显不同的块里声明。

function f(condition, x) {
  if (condition) {
    let x = 100
    return x
  }

  return x
}

f(false, 0) // returns 0
f(true, 0)  // returns 100

在一个嵌套作用域里引入一个新名字的行为称做屏蔽。 它是一把双刃剑,它可能会不小心地引入新问题,同时也可能会解决一些错误。 例如,假设我们现在用 let 重写之前的 sumMatrix 函数。

function sumMatrix(matrix: number[][]) {
  let sum = 0
  for (let i = 0; i < matrix.length; i++) {
    let currentRow = matrix[i]
    for (let i = 0; i < currentRow.length; i++) {
      sum += currentRow[i]
    }
  }

  return sum
}

这个版本的循环能得到正确的结果,因为内层循环的 i 可以屏蔽掉外层循环的 i

通常来讲应该避免使用屏蔽,因为我们需要写出清晰的代码。 同时也有些场景适合利用它,你需要好好权衡一下。

块级作用域变量的获取

每次进入一个作用域时,let 会创建一个变量的环境。就算作用域内代码已经执行完毕,这个环境与其捕获的变量依然存在。

回想一下前面 setTimeout 的例子,我们最后需要使用立即执行的函数表达式来获取每次 for 循环迭代里的状态。 实际上,我们做的是为获取到的变量创建了一个新的变量环境。 这样做挺痛苦的,但是幸运的是,你不必在 TypeScript 里这样做了。

let 声明出现在循环体里时拥有完全不同的行为。不仅是在循环里引入了一个新的变量环境,而且针对每次迭代都会创建这样一个新作用域,这就相当于我们在使用立即执行的函数表达式时做的事。所以在 setTimeout 例子里我们仅使用 let 声明就可以了。

for (let i = 0; i < 10 ; i++) {
  setTimeout(function() {
    console.log(i)
  }, 100 \* i)
}

会输出与预料一致的结果:

0
1
2
3
4
5
6
7
8
9

const 声明

const 声明是声明变量的另一种方式。

const numLivesForCat = 9

它们与 let 声明相似,但是就像它的名字所表达的,它们被赋值后不能再改变。 换句话说,它们拥有与 let 相同的作用域规则,但是不能对它们重新赋值。

这很好理解,它们引用的值是不可变的。

const numLivesForCat = 9
const kitty = {
  name: 'Kitty',
  numLives: numLivesForCat
}

// Error
kitty = {
  name: 'Tommy',
  numLives: numLivesForCat
};

// OK
kitty.name = 'Jerry'
kitty.numLives--

除非你使用特殊的方法去避免,实际上 const 变量的内部状态是可修改的。 幸运的是,TypeScript 允许你将对象的成员设置成只读的。接口一章有详细说明。

let vs. const

现在我们有两种作用域相似的声明方式,我们自然会问到底应该使用哪个。与大多数泛泛的问题一样,答案是:依情况而定。

使用最小特权原则,所有变量除了你计划去修改的都应该使用 const。 基本原则就是如果一个变量不需要对它写入,那么其它使用这些代码的人也不能够写入它们,并且要思考为什么会需要对这些变量重新赋值。使用 const 也可以让我们更容易的推测数据的流动。

解构

解构数组

最简单的解构莫过于数组的解构赋值了:

let input = [1, 2]
let [first, second] = input
console.log(first) // outputs 1
console.log(second) // outputs 2

这创建了 2 个命名变量 firstsecond。 相当于使用了索引,但更为方便:

let first = input[0]
let second = input[1]

作用于函数参数:

let input: [number, number] = [1, 2]

function f([first, second]: [number, number]) {
  console.log(first)
  console.log(second)
}

f(input)

你可以在数组里使用 ... 语法创建剩余变量:

let [first, ...rest] = [1, 2, 3, 4]
console.log(first) // outputs 1
console.log(rest) // outputs [ 2, 3, 4 ]

你也可以忽略你不关心的尾随元素:

let [first] = [1, 2, 3, 4]
console.log(first) // outputs 1

或其它元素:

let [, second, , fourth] = [1, 2, 3, 4]

对象解构

你也可以解构对象:

let o = {
    a: 'foo',
    b: 12,
    c: 'bar'
}
let { a, b } = o


这通过 o.ao.b 创建了 ab 。 注意,如果你不需要 c 你可以忽略它。

img
img

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

a: 'foo',
b: 12,
c: 'bar'

}
let { a, b } = o


这通过 `o.a` 和 `o.b` 创建了 `a` 和 `b` 。 注意,如果你不需要 `c` 你可以忽略它。



[外链图片转存中...(img-N9Zj9ltq-1715720966831)]
[外链图片转存中...(img-Ex8D9HNn-1715720966832)]

**网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。**

**[需要这份系统化的资料的朋友,可以添加戳这里获取](https://bbs.csdn.net/topics/618658159)**


**一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**

  • 26
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值