TypeScript学习二(变量声明)

在前一篇文章,我们了解了TS的安装使用以及基础类型,本文接着学习类型推断以及变量声明的内容。

类型推断

TS的核心功能就是类型检查,我们需要给变量或方法添加类型注解来表明数据类型。当没有提供类型注解时,TS编译器会利用类型推断来推断类型。

var num = 2; // 类型推断为 number
num = "12"; // 编译错误,不能将类型“string”分配给类型“number”

如果由于缺乏声明而不能推断出类型,那么它的类型被视作默认的动态 any 类型。

类型断言 Type Assertion

有些情况下,我们会比TS清楚更确切的类型,可以使用类型断言手动指定一个值的类型,即允许变量从一种类型更改为另一种类型。它没有运行时的影响,只是在编译阶段起作用。

类型断言有两种形式,第一种是尖括号语法:

<类型>值

第二种是 as 语法:

值 as 类型

举个例子:

// typescript
// 类型推断为 string
var str = '1'
// 成功,类型断言先转成any,再转成number
var str2: number = <number><any>str
// 失败,string直接转成number会报错
var str3: number = <number>str
// 编译为
// javascript
var str = '1';
var str2 = str;

通过上面的例子可以知道,使用类型断言转换需要两个类型间有关联,比如stringany的子集,那么它们就能互相转换,而number也是any的子集,那么它们也是可以互相转换,因此str2可以成功被赋值;但是stringnumber两个类型不能充分重叠,因此是不能转换的。只有在明确类型的前提下才能使用类型断言,毫无根据的断言是很危险的。

变量声明

TS和JS的变量声明方式是一样的,这里简单讲一下var、let和const的关系与区别。

var

一直以来,我们使用var语句来声明一个函数范围全局范围的变量。

// 全局范围
var a = 1;
function fn() {
    
    
  // 函数范围
  var message = "Hello, world!";
  return message;
}

var有一些奇怪的特性,有一个特性叫变量提升,无论你在哪里声明变量,都会在执行任何代码之前进行处理:

function fn(flag) {
    
    
  if (flag) {
    
    
    var x = 10;
  }

  return x;
}

fn(true); // 10
fn(false); // undefined

上面的函数fn相当于

function fn(flag) {
    
    
  // 变量x的初始值为 undefined
  var x;
  if (flag) {
    
    
    x = 10;
  }

  return x;
}

在TS中,上面的代码增加了校验,虽然依旧能够运行,但是会给出提示

// typescript
function fn(flag: boolean) {
    
    
  if (flag) {
    
    
      var x = 10;
  }

  return x; // 报错:在赋值前使用了变量“x”
}

另一个例子:

var a = [];
for (var i = 0; i < 10; i++) {
    
    
  a[i] = function () {
    
    
    console.log(i);
  };
}
a[1](); // 输出10

这是因为在循环时传入函数的变量i全局变量,在for循环结束后,i的值为10,调用函数输出的就是10。

另外,使用var可以重复声明相同变量而不报错:

var a;
var a;
var a;

let

正因为 var 存在一些问题,在 ES6 新增了 let 语句。除了名字不同外, letvar 的写法一致。

let hello = "Hello!";

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

function fn(flag) {
    
    
    if (flag) {
    
    
        let x = 10;
    }

    return x;
}

fn(true); // 10
fn(false); // 报错:x is not defined

另一个例子:

let a = [];
for (let i = 0; i < 10; i++) {
    
    
  a[i] = function () {
    
    
    console.log(i);
  };
}
a[1](); // 输出1

拥有块级作用域的变量的另一个特点是,它们不能在被声明之前读或写。

// 报错:Cannot access 'a' before initialization
a++;
let a;

上面并没有报错说 a 未定义,而是说不能在初始化之前访问它。意思是变量a存在于作用域中,但是直到声明它的代码之前的区域都属于暂时性死区,是无法访问它的。在TS中,上述代码会收到错误提示声明之前已使用的块范围变量“a”

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

function foo() {
    
    
    // 可以成功捕获到a
    // 运行时会报错:Cannot access 'a' before initialization
    return a;
}

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

let a;

上面代码在TS中是不会提示报错的,需要注意。

在上面我们提到过,使用var可以重复声明,但是let就没那么宽松了:

let a;
let a;

上述代码在运行时报错:Identifier 'a' has already been declared ,而在TS中也会提示无法重新声明块范围变量“a”,同一个块作用域中只能声明一次。

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

// case1
function f(x) {
    
    
  // TS警告:标识符“x”重复
  let x = 100; // 运行时报错:Identifier 'x' has already been declared
}
// case2
function g() {
    
    
  // TS警告:无法重新声明块范围变量“x”
  let x = 100;
  var x = 100; // 运行时报错:Identifier 'x' has already been declared
}

如果要在函数作用域中声明相同名称的块作用域变量,需要放在不同的块中:

function fn(flag, x) {
    
    
  if (flag) {
    
    
    let x = 100;
    return x;
  }

  return x;
}

fn(false, 0); // 0
fn(true, 0);  // 100

在一个嵌套作用域里引入一个新名字的行为称做屏蔽

const

const 语句是声明变量的另一种方式,与 let 声明方式相似,区别是声明赋值后不能重新赋值

const role = 'Tom'
// TS警告:无法分配到 "role" ,因为它是常数
role = 'Jerry'

上述代码运行时报错:Assignment to constant variable

const 引用的值不可改变,但是内部状态可以改变:

const person = {
    
    
  name: 'Tom'
}
// 失败
person = {
    
    
  name: 'Jerry'
}
// 成功
person.name = 'Jerry'

总的来说,letconst 使用方式接近,基本原则就是如果一个变量不需要重新赋值就使用 const ,其他情况则使用 let

猜你喜欢

转载自blog.csdn.net/sunddy_x/article/details/125446901