Go最全TypeScript变量声明_block-scoped variable(1),阿里牛逼

img
img

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

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

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

return b
}


这里我们定义了 2 个变量 `a` 和 `b`。 `a` 的作用域是 `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 个命名变量 `first` 和 `second`。 相当于使用了索引,但更为方便:



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.a` 和 `o.b` 创建了 `a` 和 `b` 。 注意,如果你不需要 `c` 你可以忽略它。


你可以在对象里使用 `...` 语法创建剩余变量:



let { a, …passthrough } = o
let total = passthrough.b + passthrough.c.length


#### 属性重命名


你也可以给属性以不同的名字:



let { a: newName1, b: newName2 } = o


这里的语法开始变得混乱。 你可以将 `a: newName1` 读做 `"a 作为 newName1"`。 方向是从左到右,好像你写成了以下样子:



let newName1 = o.a
let newName2 = o.b


令人困惑的是,这里的冒号不是指示类型的。 如果你想指定它的类型,仍然需要在其后写上完整的模式。



let {a, b}: {a: string, b: number} = o


#### 默认值


默认值可以让你在属性为 `undefined` 时使用缺省值:



function keepWholeObject(wholeObject: { a: string, b?: number }) {
let { a, b = 1001 } = wholeObject
}


现在,即使 `b` 为 `undefined` , `keepWholeObject` 函数的变量 `wholeObject` 的属性 `a` 和 `b` 都会有值。


#### 函数声明


解构也能用于函数声明。 看以下简单的情况:



type C = { a: string, b?: number }
function f({ a, b }: C): void {
// …
}


但是,通常情况下更多的是指定默认值,解构默认值有些棘手。 首先,你需要在默认值之前设置其格式。



function f({ a = ‘’, b = 0 } = {}): void {
// …
}
f()



> 
> 上面的代码是一个类型推断的例子,将在后续章节介绍。
> 
> 
> 


其次,你需要知道在解构属性上给予一个默认或可选的属性用来替换主初始化列表。 要知道 C 的定义有一个 b 可选属性:



function f({ a, b = 0 } = { a: ‘’ }): void {
// …
}
f({ a: ‘yes’ }) // OK, 默认 b = 0
f() // OK, 默认 a: ‘’, b = 0
f({}) // Error, 一旦传入参数则 a 是必须的


要小心使用解构。 从前面的例子可以看出,就算是最简单的解构表达式也是难以理解的。 尤其当存在深层嵌套解构的时候,就算这时没有堆叠在一起的重命名,默认值和类型注解,也是令人难以理解的。 解构表达式要尽量保持小而简单。


### 展开


![img](https://img-blog.csdnimg.cn/img_convert/471a55498b7cef224be6b322fd1499df.png)
![img](https://img-blog.csdnimg.cn/img_convert/6d983033029c9fb3fa665b26b1c19f1e.png)

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

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


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

的

要小心使用解构。 从前面的例子可以看出,就算是最简单的解构表达式也是难以理解的。 尤其当存在深层嵌套解构的时候,就算这时没有堆叠在一起的重命名,默认值和类型注解,也是令人难以理解的。 解构表达式要尽量保持小而简单。

展开

[外链图片转存中…(img-BFVNTOmn-1715819847573)]
[外链图片转存中…(img-PSCRks6l-1715819847573)]

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

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

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值