TypeScript----变量声明

目录

前言

一、var声明

1、var声明

2、作用域规则

3、捕获变量怪异之处

 二、let声明

1、块作用域

2、重定义及屏蔽

3、块级作用域变量的获取

三、const声明

四、let与const


前言

letconst是JavaScript里相对较新的变量声明方式。 像我们之前提到过的, let在很多方面与var是相似的,但是可以帮助大家避免在JavaScript里常见一些问题。 const是对let的一个增强,它能阻止对一个变量再次赋值。

因为TypeScript是JavaScript的超集,所以它本身就支持letconst

一、var声明

1、var声明

在JavaScript中我们一直都是使用var关键字声明变量。

var a = 10;
//大家都能理解,这里定义了一个名为a值为10的变量。

//我们也可以在函数内部定义变量:
function f() {
    var message = "Hello, world!";

    return message;
}

//并且我们也可以在其它函数内部访问相同的变量。
function f() {
    var a = 10;
    return function g() {
        var b = a + 1;
        return b;
    }
}

var g = f();
g(); 
// returns 11;

上面的例子,g可以获取到f函数里定义的a变量,每当g被调用时,他都可以访问到 f 里的a变量。即使g 在 f 已经执行完后才被调用,它仍然可以访问修改a。

function f() {
    var a = 1;

    a = 2;
    var b = g();
    a = 3;

    return b;

    function g() {
        return a;
    }
}

f(); // returns 2

2、作用域规则

对于熟悉其他语言的人来说,var声明有些奇怪的作用域规则,比如:

function f(shouldInitialize:boolean){
    if (shouldInitialize){
        var x = 10;
    }
    return x;
}
f(true); // returns '10'
f(false); //returns 'undefined'

变量 x是定义在*if语句里面*,但是我们却可以在语句的外面访问它。 这是因为 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都引用相同的函数作用域内的变量。 有经验的开发者们很清楚,这些问题可能在代码审查时漏掉,引发无穷的麻烦。

3、捕获变量怪异之处

快速的猜一下下面的代码会返回什么:

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

介绍一下,setTimeout会在若干毫秒的延时后执行一个函数(等待其它代码执行完毕)。

好吧,看一下结果:

10
10
10
10
10
10
10
10
10
10

很多JavaScript程序员对这种行为已经很熟悉了,但如果你很不解,你并不是一个人。 大多数人期望输出结果是这样:

0
1
2
3
4
5
6
7
8
9

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

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

让我们花点时间思考一下这是为什么。 setTimeout在若干毫秒后执行一个函数,并且是在for循环结束后。 for循环结束后,i的值为10。 所以当函数被调用的时候,它会打印出 10

一个通常的解决方法是使用立即执行的函数表达式(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);
}

这种奇怪的形式我们已经司空见惯了。 参数 i会覆盖for循环里的i,但是因为我们起了同样的名字,所以我们不用怎么改for循环体里的代码。

 二、let声明

现在你已经知道了var存在一些问题,这恰好说明了为什么用let语句来声明变量。 除了名字不同外, letvar的写法一致

let hello = "Hello!";

主要的区别不在语法上,而是语义,我们接下来会深入研究。

1、块作用域

当使用 let 声明变量时,它使用所谓的词法作用域或块作用域。与使用 var 声明的变量不同,变量的作用域泄漏到它们的包含函数,块作用域变量在它们最近的包含块或 for循环之外不可见。

function f(input: boolean) {
  let a = 100;
  if (input) {
    // Still okay to reference 'a'
    let b = a + 1;
    return b;
  }
  // Error: 'b' doesn't exist here
  return b;
}

这里我们定义了2个变量ab。 a的作用域是f函数体内,而b的作用域是if语句块里。

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

try {
    throw "oh no!";
}
catch (e) {
    console.log("Oh well.");
}

// Error: 'e' doesn't exist here
console.log(e);

块作用域变量的另一个属性是,在实际声明它们之前,不能读取或写入它们。虽然这些变量在其整个范围内都是”存在的”,但在其声明之前的所有点都是其时间死区的一部分。这只是一种复杂的说法,你不能在 let 语句之前访问它们,幸运的是 TypeScript 会让你知道这一点。

a++; // illegal to use 'a' before it's declared;
let a;

需要注意的是,您仍然可以在声明块范围的变量之前捕获它。唯一的问题是,在声明之前调用该函数是非法的。如果目标是 ES2015,现代运行时将抛出一个错误; 然而,目前 TypeScript 是许可的,不会将其作为错误报告。

function foo() {
  // okay to capture 'a'
  return a;
}
// illegal call 'foo' before 'a' is declared
// runtimes should throw an error here
foo();
let a;

2、重定义及屏蔽

我们用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: interferes with parameter declaration
}

function g() {
    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

在一个嵌套作用域里引入一个新名字的行为称做屏蔽。 它是一把双刃剑,它可能会不小心地引入新问题,同时也可能会解决一些错误。 例如,假设我们现在用 let重写之前的sumMatrix函数。

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

3、块级作用域变量的获取

每次进入一个作用域的时候,它会创建一个变量的环境,就算作用域代码完成,环境与其捕获的代码依然在。

function theCityThatAlwaysSleeps() {
    let getCity;

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

    return getCity();
}

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

于是let就避免了这种事情的发生,它不仅会引入新的变量环境,还会针对每次迭代都会创建这样一个新作用域。所以对于上面的例子,我们使用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声明

它与let声明相似,但是它赋值以后不能改变,它拥有let一样的作用域规则,但是不能对它们重新赋值。

也就是他们引用的值是不可变的:

const numLivesForCat = 9;
const kitty = {
    name: "Aurora",
    numLives: numLivesForCat,
}

// Error
kitty = {
    name: "Danielle",
    numLives: numLivesForCat
};

// all "okay"
kitty.name = "Rory";
kitty.name = "Kitty";
kitty.name = "Cat";
kitty.numLives--;

除非你使用特殊的方法去避免,实际上const变量的内部状态是可修改的。 幸运的是,TypeScript允许你将对象的成员设置成只读的。

四、let与const

在开发中两个到底使用哪一个呢?

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

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

是小先生

知识是无价的,白嫖也可以的。

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值