TypeScript 自学笔记2 变量声明

前言

  • 个人学习笔记,仅供参考

Var 声明

  • 可以在函数内部定义变量
  • 可以在其它函数内部访问相同的变量
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    function f() {
    var a = 1;

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

    return b;

    function g() {
    return a;
    }
    }

    f(); // returns 2

作用域规则

  • 对于var声明奇怪的作用域规则
  • 在这里以为程序在执行时会先编译一遍所以x就已经被声明了
  1. 可以在包含它的函数,模块,命名空间或全局作用域内部任何位置被访问

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    function f(shouldInitialize: boolean) {
    if (shouldInitialize) {
    var x = 10;
    }

    return x;
    }

    f(true); // returns '10'
    f(false); // returns 'undefined'
  2. var多次声明并不会报错

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    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;
    }

捕获变量怪异之处

  • setTimeout 定时器,而且每一次的时间就加长了
  • 执行的速度是非常快的,很快就循环完毕开启了所有的定时器
  • 但是定时器的执行会延时,在最后执行时i已经是10了
  • 所以所有的输出都是10,并不是想象的1……9

    1
    2
    3
    for (var i = 0; i < 10; i++) {
    setTimeout(function() { console.log(i); }, 100 * i);
    }
  • 解决方案,使用闭包,每一次的值都是传进去的就不会被改变

    1
    2
    3
    4
    5
    6
    7
    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);
    }

Let声明

  • 在let中和var还是有一定的区别的
  • let中使用的是作用域或块作用域
  • 所有在区块内没有定义是不可以使用的
  • let并没有作用域提示的概念

块作用域

  • 这里我们定义了2个变量a和b。 a的作用域是f函数体内,而b的作用域是if语句块
  • 这就是我们提到的变量不会提升,所以不能使用
  • 如果是用var这个就可以是用了
  • 而且我们使用ts 在编译时就会报错的
  • 直接会 error TS2304: Cannot find name ‘b’ 提示没有定义这个名称的变量
  • 但是a为什么可以获取呢?因为a所在的作用域块大于if,而在外面的作用域是不能访问内部定义的变量
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    function f(input: boolean) {
    let a = 100;

    // if内部定义的b
    if (input) {
    // 但是在这里面是可以获取a的因为a在fn的作用域块比if大
    // Still okay to reference 'a'
    let b = a + 1;
    return b;
    }

    // 外面是无法访问的,就会报错
    // Error: 'b' doesn't exist here
    return b;
    }

暂时性死区

  • 未声明前无法读/写
  • 很多人对这个let不了解就想当然的像var一样去用
  • 但是虽然变量存在,在let声明前所定义的所有代码都是无效的,因为let有着暂时性死区(声明前的所有区域都是)
  • ts中编译也是会爆出来的
    1
    2
    a++; // illegal to use 'a' before it's declared;
    let a;

  • 注意一点,我们仍然可以在一个拥有块作用域变量被声明前获取它。 只是我们不能在变量声明前去调用那个函数。 如果生成代码目标为ES2015,现代的运行时会抛出一个错误;然而,现今TypeScript是不会报错的。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    function foo() {
    // okay to capture 'a'
    return a;
    }

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

    let a;

重定义及屏蔽

重定义

  • 我们在使用var 时可以无限的重复声明最后只会得到一个(会出现bug)
  • let在同一作用域下是不可以重复声明的
  • 并不是要求两个均是块级作用域的声明TypeScript才会给出一个错误的警告。
  • 并不是说块级作用域变量不能用函数作用域变量来声明。 而是块级作用域变量需要在明显不同的块里声明。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    // 例1
    let x = 10;
    let x = 20; // 错误,不能在1个作用域里多次声明`x`

    // 例2
    // 变量和声明明显在同一个作用域下
    function f(x) {
    let x = 100; // error: interferes with parameter declaration
    }

    // 例3
    // 无论let前后都不能重新定义变量
    function g() {
    let x = 100;
    var x = 100; // error: can't have both declarations of 'x'
    }

    // 例4
    // 正确的重定义
    // 这样就分开了两个完全不同的作用域
    // if内部只可以使用x = 5的这个和x=100一点关系都没有
    function g() {
    let x = 100;
    console.log('fn g x',x) //fn g x 100
    if (true) {
    let x = 5;
    console.log('fn if x',x)//fn if x 5
    }
    console.log('fn g x',x)//fn g x 100
    }

    // 例5
    // 正确的重定义
    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上面var写过的
  • 在这里 在重新定义的数据,会出现一个数据屏蔽把前面定义的数据屏蔽掉
  • 这样解决了数据不会相互影响的问题
1
2
3
4
5
6
7
8
9
10
11
12
- 
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;
}

块级作用域变量的获取

  • 在我们最初谈及获取用var声明的变量时,我们简略地探究了一下在获取到了变量之后它的行为是怎样的。 直观地讲,每次进入一个作用域时,它创建了一个变量的 环境。 就算作用域内代码已经执行完毕,这个环境与其捕获的变量依然存在。
  • 因为我们已经在city的环境里获取到了city,所以就算if语句执行结束后我们仍然可以访问它。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function theCityThatAlwaysSleeps() {
// fn作用域定义了
let getCity;

if (true) {
let city = "Seattle";
// if内部赋值
getCity = function() {
// 在if作用域已经执行获取到了
return city;
}
}

// 外侧依然是能访问
return getCity();
}

  • 当let声明出现在循环体里时拥有完全不同的行为。 不仅是在循环里引入了一个新的变量环境,而是针对 每次迭代都会创建这样一个新作用域。 这就是我们在使用立即执行的函数表达式时做的事,所以在 setTimeout例子里我们仅使用let声明就可以了。
  • 这样就可以理想的输出1-9了
    1
    2
    3
    for (let i = 0; i < 10 ; i++) {
    setTimeout(function() {console.log(i); }, 100 * i);
    }

const 声明

  • 它们与let声明相似,但是就像它的名字所表达的,它们被赋值后不能再改变。 换句话说,它们拥有与 let相同的作用域规则,但是不能对它们重新赋值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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--;

解构【es6特性】

数组解构

  • 相当于使用了索引,但更为方便
  • 你可以在数组里使用…语法创建剩余变量

  • 可以解开单个
  • 可以获取多个
  • 可以跳过,隔开就可以
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    // 1.数组使用
    let input = [1, 2];
    let [first, second] = input;
    console.log(first); // outputs 1
    console.log(second); // outputs 2

    // 相当于
    first = input[0];
    second = input[1];


    // 2.函数使用结构数组,传递一个数组解开两个变量
    function f([first, second]: [number, number]) {
    console.log(first);
    console.log(second);
    }
    f(input);


    // 3. 剩余变量
    let [first, ...rest] = [1, 2, 3, 4];
    console.log(first); // outputs 1
    console.log(rest); // outputs [ 2, 3, 4 ]


    // 4. 单独提取也可以
    let [first] = [1, 2, 3, 4];
    console.log(first); // outputs 1

    // 5. 获取其他元素,隔开
    let [, second, , fourth] = [1, 2, 3, 4];

对象解构

  • 和数组的没什么区别
  • 同样不需要的可以直接忽略
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    let o = {
    a: "foo",
    b: 12,
    c: "bar"
    };
    let { a, b } = o;

    // 剩余变量
    let { a, ...passthrough } = o;
    let total = passthrough.b + passthrough.c.length;

属性重命名

  • 在对象中结构我们可以赋于其他于属性不同的名字
  • 这里就会有疑惑ts里面‘:’后面不是加的是指示类型么
  • 如果你想指定它的类型, 仍然需要在其后写上完整的模式。
1
2
3
4
5
6
7
8
9
10
// 前面为属性名:后面为更改的属性名
let { a: newName1, b: newName2 } = o;

// 相等于
let newName1 = o.a;
let newName2 = o.b;


//添加指示类型
let {a, b}: {a: string, b: number} = o;

默认值

  • ? 可选属性
  • 在指定类型前面加?号代表可以不传
  • 在结构时用=给定默认值
  • 默认值可以让你在属性为 undefined 时使用缺省值
1
2
3
4
5
6
7
8
function keepWholeObject(wholeObject: { a: string, b?: number }) {
let { a, b = 1001 } = wholeObject;
}

let wholeObject = {a:'abc'}

console.log(keepWholeObject(wholeObject))
// a=abc b=1001

函数声明

  • 解构也能用于函数声明。 看以下简单的情况
    1
    2
    3
    4
    type C = { a: string, b?: number }
    function f({ a, b }: C): void {

    }

  • 可以使用结构的同时使用默认值
  • 通常情况下更多的是指定默认值,解构默认值有些棘手。 首先,你需要在默认值之前设置其格式
  • 官网的例子并不能实现
    1
    2
    3
    4
    5
    6
    function f({ a="", b=0 } : {a?:String,b?:Number}): void {
    // ...
    console.log(a);
    console.log(b);
    }
    f({});

展开

  • 展开操作符正与解构相反。 它允许你将一个数组展开为另一个数组,或将一个对象展开为另一个对象
  • 这样我们合并数组/对象就方便了很多
  • 但是展开对,对象的缺陷还是有的
  • 两个对象展开相同还是对象,相同的属性就会覆盖
    1
    2
    3
    4
    5
    6
    7
    8
    // 合并数组
    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" };

  • 对象展开还有其它一些意想不到的限制。 首先,它仅包含对象 自身的可枚举属性。 大体上是说当你展开一个对象实例时,你会丢失其方法:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    class C {
    p = 12;
    m() {
    }
    }
    let c = new C();
    let clone = { ...c };
    clone.p; // ok
    clone.m(); // error!

后记

  • 这个就是我学习Ts的第二天的笔记,欢迎更多的同行大哥指导交流
  • 欢迎进入我的博客https://yhf7.github.io/
  • 如果有什么侵权的话,请及时添加小编微信以及qq也可以来告诉小编(905477376微信qq通用),谢谢!
-------------本文结束感谢您的阅读-------------
0%