技术

【TS学习系列】3.变量声明

TypeScript中一共有三种声明变量的方式:varletconstletconst 是JavaScript里相对较新的变量声明方式。在TypeScript官方文档中,推荐使用 let 替代 var 声明变量的方式、

 

var声明变量

 

variable 语句声明了一个变量,可选地将其初始化为一个值。

 

语法

 

var 变量名 [: 变量类型] = 变量初始值 ;

变量名:可以定义为任何合法标识符。

 

变量初始值:该值可以为任何合法表达式。

 

如表示定义一个变量名为 myName 值为 Jane字符串

 

var myName : string = "Jane";

 

我们也可以在函数内部定义变量:

 

(function myFun(){
    var hello : string = "Hello!";
    console.log(hello);//Hello!
})();

 

并且,我们也可以在其他函数内部访问相同的变量:

 

function myFun1(){
    var world : string = "World!";
    return function myFun2(){
        var helloWorld = "Hello " + world;
        console.log(helloWorld);
    };
}
var cFun1 =  myFun1();
cFun1();//Hello World!

 

var 的作用域

 

function showFun(shouldInitialize : boolean){
    if(shouldInitialize){
        var a = 10;
    }
    console.log(a);
}

showFun(true);//0
showFun(false);//undefined

 

此处,变量 a 是定义在 <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];
        }
    }
    console.log(sum);
}
sumMatrix([[1,2],[1,3],[1,5,5]]);//3

 

,这里多次声明同一个变量是指变量名、变量类型完全一样的变量,如果变量名相同,变量类型不同会报错:

 


var i : number = 0;
var i : string= 0;//[ts] Type '0' is not assignable to type 'string'.
//[ts] Subsequent variable declarations must have the same type.  
//Variable 'i' must be of type 'number', but here has type 'string'.

 

捕获变量

 

快速的猜一下下面的代码会输出什么:

for (var i = 0; i < 10; i++) {
    //100 * i 毫秒之后输出i
    setTimeout(function() { 
        console.log(i); 
    }, 100 * i);
}

 

期望结果:

 

0 1 2 3 4 5 6 7 8 9

 

实际结果:

 

10 10 10 10 10 10 10 10 10 10

 

还记得上面提到的捕获变量吗?

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

 

这里,setTimeout i * 100 毫秒之后开始执行一个输出 i 值的函数,当执行到这些函数的时候,for 循环已经结束,此时 i 的值为 10 。所以,当函数被调用的时候,它会打印出 10

 

为了解决这种错误,通常的方法是使用立即执行的函数表达式( IIFE )来捕获每次迭代时 i 的值:

 

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

 

let 声明

 

语法上除了名字不一样,letvar 的写法一致,语法:

 

let 变量名 [: 变量类型] = 变量初始值 ;

 

如:

 

let myName : string = "Jane";

 

它们两者的区别不在语法上,而是语义。

 

块作用域

 

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

 

function myFun3(input : boolean){
    let numA : number = 100;
    if(input){
        let sum = numA + 1;
        return sum;
    }
    return b;//Error, [ts] Cannot find name 'b'
}

 

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

 

try{
    throw "no";
}
catch(e){
    console.log("ok");
}

console.log(e);//Error, [ts] Cannot find name 'e'.

 

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

 


aStr += aStr;//Error [ts] Block-scoped variable 'aStr' used before its declaration.
let aStr : string ;

 

重定义及屏蔽

 

使用 var 声明时,它不在乎声明过多少次,你只会得到一个。

 

function f(x) {
    var x;
    var x;

    if (true) {
        var x;
    }
}

 

在上面的例子中,所有 x  的声明实际上都引用了同一个 x ,并且运行时不会报错,不过这样使用经常会导致bug。而使用 let 声明的时候就不会有这个问题。

 


let x = 10;
let x = 20;//Error, 不能在一个作用域里多次声明x

 

除此之外,在以下情况,TypeScript也会给出错误警告,如:

 


function funA(x)
{
   let x =100;//Error: interferes with parameter declaration
}

function funB()
{
   let x =100;
   var x =100;// error: can't have both declarations of 'x'
}

 

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

 


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

    return x;
}

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

 

在一个嵌套作用域里引入一个新名字的行为称作屏蔽。它可能在解决错误的时候产生一些新问题。

 


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

    return sum;
}

 

此处代码可以得到正确结果,因为内层循环的 i 可以屏蔽外层循环的 i。为了可读性,我们应该尽量少的使用屏蔽。

 

块级作用域变量的获取

 

在我们最初谈及获取用 var 声明的变量时,我们简略的探究了一下在获取到变量之后它的行为是怎样的。直观的讲,每次进入一个作用域时,它创建了一个变量的环境。就算作用域内代码执行完毕,这个环境与其捕获的变量依然存在。

 


function theCityThatAlwaysSleeps()
{
	let getCity;
	if(true)
	{
		let city = "Seattle";
		getCity = function()
		{
			return city;
		}
	}
	return getCity();
}

 

因为我们已经在 city 的环境里获取到了city,所以就算 if 语句执行结束后,我们依然可以用。

 

回想前面 setTimeOut 的例子,可以直接使用 let 实现相同的效果。如:

 


for (let i =0;i < 10;i++
{
	setTimeout(function(){
		console.log(i);//打印0~9
	},100 * i);
}

 

const声明

 

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

 


const myName = "BillBill";

 

使用 const 声明的变量与 let 声明的变量类似,它们拥有相同的作用域规则,但是 const 声明的变量不能对它进行重新赋值,它们引用的值是不可变的。如:

 


const numLivesForCat = 5;
numLivesForCat = 2;//Error: Cannot assign to 'numLivesForCat' because it is a constant or a read-only property.
const kitty = {
    name : "Aurora",
    numLives : numLivesForCat
};
kitty = { //Error : Cannot assign to 'kitty' because it is a constant or a read-only property.
    name : "Jane",
    numLives : numLivesForCat
};
//All okey!
kitty.name ="A";
kitty.name ="B";
kitty.name ="C";
kitty.numLives = 10;

 

除非使用一些特殊的方法避免,而实际上,const 声明的变量的内部状态是可以改变的。为了防止内部状态被改变,我们可以设置属性为只读,后面会详细说明具体的用法。

 

let VS. const

 

在具体使用哪种变量声明方式取决于应用场景。

 

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

我们都在黑暗中寻找光明。

留言

您的电子邮箱地址不会被公开。 必填项已用*标注