var 的声明
在let和const没出现的时候,我们都是通过var关键字定义JavaScript变量。
var a = 10;
在现实使用中,使用var声明变量会产生一些问题;
1. 作用域问题
使用 var 声明的变量存在函数作用域而非块级作用域。这意味着在使用 var 声明的变量在函数内部的任何位置都是可见的,而不仅仅是在声明的块级范围内。
function f(shouldInitialize: boolean) {
if (shouldInitialize) {
var x = 10;
}
return x;
}
f(true); // returns '10'
f(false); // returns 'undefined'
这个例子中,变量 x是定义在if语句里面,但是我们却可以在语句的外面访问它。 这是因为 var声明可以在包含它的函数,模块,命名空间或全局作用域内部任何位置被访问,包含它的代码块对此没有什么影响。 有些人称此为* var作用域或函数作用域*。 函数参数也使用函数作用域。
这会引发一些错误:
function sumMatrix(matrix: number[][]) {
var sum = 0;
for (var i = 0; i < matrix.length; i++) {
var currentRow = matrix[i];
for (var i = 0; i < currentRow.length; i++) {
sum += currentRow[i];
}
}
return sum;
}
这里很容易看出一些问题,里层的for循环会覆盖变量i,因为所有i都引用相同的函数作用域内的变量。 有经验的开发者们很清楚,这些问题可能在代码审查时漏掉,引发无穷的麻烦。
for (var i = 0; i < 10; i++) {
setTimeout(function() { console.log(i); }, 100 * i);
}
结果:
10、10、10、10、10、10、10、10、10、10
解决方法:
1: 将变量i改为let声明的变量而不是全局变量,此时的for循环就会变成是一个单独的作用域,作用与闭包形成的块级作用将相同,因此能够输出想要的结果
for(let i=0;i<10;i++){
setTimeout(function(){
console.log(i);
},i*1000);
}
2: 使用立即执行的函数表达式(IIFE)来捕获每次迭代时i的值
for (var i = 0; i < 10; i++) {
// capture the current state of 'i'
// by invoking a function with its current value
(function(i) {
setTimeout(function() { console.log(i); }, 100 * i);
})(i);
}
结果:
0、1、2、3、4、5、6、7、8、9
2. 变量提升问题
使用 var 声明的变量存在变量提升(hoisting)现象。这意味着变量的声明会被提升到作用域的顶部,在变量声明之前使用变量也不会报错。
function example() {
console.log(x); // 输出:undefined
var x = 10;
}
在这个例子中,尽管在打印 x 的语句之前才实际声明变量 x,但由于变量提升的原因,x 的值被视为 undefined。
3. 全局污染问题
使用 var 声明的变量会成为全局变量,会污染全局命名空间,容易造成命名冲突和意外的行为。
var x: number = 10;
function example() {
var x: number = 20;
console.log(x); // 输出:20
}
example();
console.log(x); // 输出:10
在这个例子中,var 声明的变量 x 在函数内部重新声明并赋值为 20,但它不会影响全局变量 x 的值。
为了避免以上var引起的缺陷,TypeScript 推荐使用 let 或 const 来声明变量;
let 和 const 声明
let 用于声明可变(可重新赋值)的变量,而 const 用于声明不可变(不可重新赋值)的变量(常量)。变量声明的一般语法如下
let variableName: string = 'hello';
const constantName: string = 'hello';
variableName = '改变的值'; // 可以
constantName= '改变的值'; // 报错,常量不能重新赋值
let 变量声明
let 关键字用于声明可变的变量。它的作用范围被限制在块级作用域内。块级作用域是由花括号 {} 包围的一段代码。例如:
{
let num: number = 10;
console.log(num); // 输出 10
}
console.log(num); // 报错,num 在这里不可访问, 因为let有块级作用域
const 声明
const关键字用于声明不可变的变量,但是可以对引用类型里的属性,可以进行改变!
const num = 1;
num = 2; // 报错,不能重新赋值
const obj = {name: 'Alice', age: 18}
obj.age = 20 // 可以修改属性
obj = {name: 'tom', age: 19} // 报错,不能重新赋值
const arr = [1,2,3]
arr[0] = 4;
console.log(arr); // [4,2,3]
arr = [4,2,3] // 报错,不能重新赋值
解构赋值
TypeScript 支持解构赋值,可以从数组或对象中提取值并赋给变量。
1. 解构数组:
// 普通结构
let input = [1, 2];
let [first, second] = input;
console.log(first); // outputs 1
console.log(second); // outputs 2
// 作用于函数参数
function f([first, second]: [number, number]) {
console.log(first);
console.log(second);
}
f(input);
2. 解构对象:
// 普通结构
let o = {
a: "foo",
b: 12,
c: "bar"
};
let { a, b } = o;
// 可以在对象里使用...语法创建剩余变量
let { a, ...passthrough } = o;
let total = passthrough.b + passthrough.c.length;
// 给属性取别名
let { a: newName1, b: newName2 } = o;
// 额外进行设置指定类型
let {a, b}: {a: string, b: number} = o;
3. 函数声明:
解构也能用于函数声明
type C = { a: string, b?: number }
function f({ a, b }: C): void {
// ...
}
但是,通常情况下更多的是指定默认值,解构默认值有些棘手。 首先,你需要在默认值之前设置其格式。
function f({ a="", b=0 } = {}): void {
// ...
}
f();
使用结构,可以将一个数组展开为另一个数组,或将一个对象展开为另一个对象
// 数组
let first = [1, 2];
let second = [3, 4];
let bothPlus = [0, ...first, ...second, 5];
// 对象
let defaults = { food: "spicy", price: "$$", ambiance: "noisy" };
let search = { ...defaults, food: "rich" };