2.5 变量
变量是一种占位符,用于引用计算机内存地址。变量是方便人来存取数据的,而内存地址是方便计算机来存取数据的。可以把变量看作存储数据的容器。类型分为值类型和引用类型,所以变量可分为值类型变量和引用类型变量。
JavaScript中的数据类型主要包括两种,一种是基本类型(值类型),另一种是引用类型(引用类型)。内存分为两个部分,栈内存和堆内存。基本类型值保存在栈内存中,引用类型值在堆内存中保存着对象、在栈内存中保存着指向堆内存的指针。
JavaScript中,基本类型值包括undefined、null、number、string和boolean,在内存中分别占有固定大小的空间。引用类型值只有object,这种值的大小不固定,可以动态添加属性和方法,而基本类型则不可以。
TypeScript和JavaScript类似。对基本类型值进行复制,复制的是值本身,相当于复制了一个副本,修改一个变量的值不会影响另外一个变量的值。而对引用类型值进行复制,复制的是对象所在的内存地址。所以两者指向的都是栈内存中的同一个数据,修改一个变量会导致另外一个变量的值也进行修改。
理解变量的值类型和引用类型是非常重要的。为了让读者更加直观地了解值类型和引用类型变量的核心区别,下面用一张C#语言的示例图来说明值类型和引用类型的差异,如图2.1所示。
图2.1 C#中的值类型和引用类型示意图
在图2.1中,由于int j=i中是int类型,为值类型变量,因此j变量的值是i变量值的副本,值都在Stack内存中,都是3,但是二者不是同一个对象。因此,修改i不会修改j,修改j也不会修改i。二者是独立的。
在Person p2=p语句中,Person为类,是引用类型,p2和p指向同一个Heap地址块,因此修改p2的值会影响p的值。
提示
在JavaScript中,函数的参数传递是按值传递,而且不能按引用传递。
在TypeScript中,字符串、布尔型和数值型都是值类型,而类、数组和元组等都是引用类型。从下面的代码2-50可以看出,数组是引用类型,变量a和b指向同一个内存地址,修改了b的值,同时也修改了a的值。
【代码2-50】引用类型示例:ref_var.ts
01 let a = [1,2]; 02 let b = a; //数组是引用类型 03 b[2] = 3; 04 console.log(a); //[1,2,3] 05 console.log(b); //[1,2,3]
值类型就不是这样的,如字符串和数值类型。从下面的代码2-51可以看出,字符类型是值类型变量,变量a和b指向的是不同的内存地址,只是值一开始一致而已,修改了b的值,不会修改a的值。
【代码2-51】值类型示例:value_var.ts
01 let a = "1,2"; 02 let b = a; 03 b= b+",3"; 04 console.log(a); //1,2 05 console.log(b); //1,2,3
2.5.1 声明变量
在ES5中声明变量的方法最常用的就是var。在ES6中,添加了let和const进行变量声明。在ES6环境下,一般的变量声明都采用let,而不建议使用var。
变量的命名一般都是有约定的。在TypeScript中,变量命名必须满足如下规则:
- 变量名称可以包含数字和字母,如stuName01。
- 除了下画线_和美元$符号外,不能包含其他特殊字符,包括空格,如_stuName和$tmp都是合法的变量名。
- 变量名不能以数字开头,如9Num是错误的。
提示
TypeScript是区分大小写的,比如numA和NumA不同。
在TypeScript编码规范中,建议在使用前变量一定要先声明。变量声明可以使用以下4种方式:
(1)[var或let或const ] [变量名]:[类型]=值;
此范式进行变量声明,同时指定了声明变量的类型及初始值。代码2-52分别给出了用var、let和const声明变量的方式。
【代码2-52】变量声明并初始化的示例:declare_var1.ts
01 var uname:string = "JackWang"; 02 let uname2:string = "JackWang"; 03 const version:string = "1.0";
提示
const声明的常量变量一定要初始化,否则会报错。
(2)[var或let ] [变量名]:[类型] ;
此范式进行变量声明,只指定了声明变量的类型,初始值默认为undefined。代码2-53分别给出了用var和let声明变量的方式。
【代码2-53】变量声明不初始化的示例:declare_var2.ts
01 var uname:string ; 02 let uname2:string;
(3)[var或let ] [变量名] ;
此范式进行变量声明,只提供了变量名,声明变量的类型和初始值都未提供。变量类型默认为any,变量值默认为undefined,如代码2-54所示。
【代码2-54】变量声明未提供类型和初始值示例:declare_var3.ts
01 var uname; 02 let uname2;
(4)[var或let或const] [变量名] =值;
此范式进行变量声明,未指定声明变量的类型,但是给出了初始值,这时会用类型推断来确定变量的类型,这种写法更加简洁,如代码2-55所示。
【代码2-55】变量声明只给出初始值的示例:declare_var4.ts
01 var uname = "JackWang"; //string 02 let uname2 = 100 ; //number 03 const version = "1.0"; //string
2.5.2 变量的作用域
变量的作用域就是定义的变量可以使用的代码范围。变量可以分为全局变量和局部变量。在日常的程序开发中,尽量少用全局变量,防止变量冲突。局部变量只在块作用域和函数体内有效,从而保证变量的安全访问。
程序中变量的可用性由变量作用域决定。TypeScript有以下几种作用域:
- 全局作用域:全局变量定义在程序结构的外部,可以在代码的任何位置使用。
- 类作用域:这个变量也可以称为字段。类变量声明在一个类里,但在类的方法外面无法访问。该变量可以通过类的实例对象来访问。类变量也可以是静态的,可以通过类名直接访问。
- 局部作用域:局部变量,只能在声明它的一个代码块(如方法)中使用。
代码2-56说明了3种作用域的使用。此示例涉及类的相关知识点,但读者此时不要过多在意类的语法,这些会在后面的章节详细进行说明。此时只需理解变量作用域的相关知识点即可。
【代码2-56】变量作用域示例:scope_var.ts
01 var global_var = 12 // 全局变量 02 class MyClazz { 03 clazz_val = 13; // 类变量 04 static sval = 10; // 静态变量 05 storeNum(): void { 06 var local_var = 14; // 局部变量 07 } 08 } 09 console.log("全局变量为: " + global_var); 10 console.log(MyClazz.sval); // 静态变量 11 var obj = new MyClazz(); 12 console.log("类变量: " + obj.clazz_val);
提示
var在函数或方法中声明变量,作用域限于此函数或方法中。
2.5.3 const声明变量
const和let在声明变量的用法上基本一致,只是const声明的变量被赋值后不能再改变,是只读的常量。因此,对于const声明的变量来说,只声明不赋值就会报错。
const声明的变量作用域和let一致,但是在某些情况下用const更加安全,可以防止在其他地方被修改从而影响程序的正常运行,如代码2-57所示。
【代码2-57】 const声明变量示例:const_var.ts
01 const cVar = "hello"; 02 cVar = "change"; //错误,不能赋值 03 console.log(cVar);
const实际上保证的并不是变量的值不能改动,而是变量指向的那个内存地址所保存的数据不能改动。对于简单类型的数据(如数值、字符串和布尔值),值就保存在变量指向的那个内存地址,因此等同于常量。
对于复合类型的数据(主要是对象和数组),变量指向的是内存地址,保存的只是一个指向实际数据的指针,const只能保证这个指针是固定的(总是指向一个固定的地址),至于它指向的数据结构是不是可变的,则完全不能控制。因此,将一个对象声明为常量类型,其对象的值也是可以修改的,如代码2-58所示。
【代码2-58】 const声明复合类型变量示例:const_var.ts
01 interface Object{ 02 prop: string; 03 func: () => string; 04 } 05 const foo: Object = {}; 06 // 为 foo 添加一个属性可以成功 07 foo.prop = "123"; //说明值可以修改 08 foo.func = function (): string { 09 return "hello"; 10 } 11 // 将 foo 指向另一个对象就会报错 12 foo = {}; //报错 13 const a = []; 14 a.push('Hello'); // 可执行 15 a.length = 0; // 可执行 16 a = ['Dave']; // 报错
代码2-58涉及接口的相关知识点,此示例为了说明const声明的常量对象本身的值是可以修改的,必须借助接口来扩展Object的属性或方法。
在代码2-58中,常量foo储存的是一个地址,这个地址指向一个对象。不可变的只是这个地址,即不能把foo指向另一个地址,但对象本身是可变的,所以依然可以为其添加新属性。
提示
在TypeScript中,Object类型的变量不能动态添加属性和方法,而只能通过接口扩展属性或者方法。
在代码2-58中,13行声明了一个数组常量a,这个数组中的相关属性是可以修改的,但是如果将另一个数组赋值给a就会报错。如果真的想将对象冻结,应该使用Object.freeze方法,如代码2-59所示。
【代码2-59】 const声明数值变量冻结示例:const_freeze.ts
01 const foo = Object.freeze([]); 02 foo.length = 1; //报错