TypeScript(二)

作者: madisn | 来源:发表于2017-09-16 17:40 被阅读116次

    变量声明

    letconst是JavaScript里相对较新的变量声明方式,而let在很多方面与var是相似的,但是可以避免在JavaScript里常见的一些问题.const是对let的一个增强,它能阻止对一个变量再次赋值.
    因为TypeScript是JavaScript的超集,所以它本身支持letconst.

    var声明

    一直以来我们都是通过var关键字定义JavaScript变量:

    var 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();//return 11
    
    function f() {
        var a = 1;
        a = 2;
        var b = g();//这里调用了函数,传入了a=2
        a = 3;//这里的赋值a=3并没有传入函数中
        return b;
        //定义函数
        function g() {
            return a;
        }
    }
    f(); // returns 2
    
    作用域规则

    var声明有些奇怪的作用域规则

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

    变量虽然定义在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];
            }
        }
    
        return sum;
    }//错误代码
    

    上面的代码里,里层的for循环会覆盖变量i,因为所有i都引用相同的函数作用域内的变量.

    变量获取怪异之处
    for (var i = 0; i < 10; i++) {
        setTimeout(function() { console.log(i); }, 100 * i);
    }//10个10
    

    在这个for循环中,setTimeout在若干秒后执行一个函数,并且是在for循环结束后.for循环结束后,i的值为10.所以函数被调用的时候,它会打印出10.
    一个通常的解决方法是使用立即执行的函数表达式(IIFE)来捕获每次迭代时i的值

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

    参数i会覆盖for循环里的i,但是因为我们起了同样的名字,所以我们不用怎么改for循环里的代码

    let声明

    除了名字不同外,letvar的写法一致

    let hello = "Hello";
    

    主要的区别不在语法上,而是语义.

    块作用域

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

    function f(input: boolean) {
        let a = 100;
        if (input){
            let b = a + 1;
            return b;
        }
        return b;//报错
    }
    

    a 的作用域在f函数体内,而b的作用域是if语句块里.
    catch语句里声明的变量也具有同样的作用域规则.

    try {
        throw "oh no!";
    }
    catch (e) {
        console.log("Oh well.")
    }
    console.log(e);//报错,该变量未定义
    

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

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

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

    function foo(){
        return a;//这里可以使用a变量
    }
    foo();//这里调用在变量声明前应该报错(但是TypeScript不报错)
    let a;
    
    重定义及屏蔽

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

    function (){
        var x;
        var x;
        if (true) {
            var x;
        }
    }
    

    在上面的代码里所有x的声明实际上都引用一个相同的'x',并且这是完全有效的代码,这经常会导致一些bug的出现.而现在,let声明就不会那么宽松了.

    let x = 10;
    let x = 20;//错误,不能在1个作用域里多次声明'x'
    

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

    fun f(x){
        let x = 100;//因为其实在函数的传参过程中,x已经被声明,所以会报错;
    }
    function g(){
        let x = 100;
        var x = 100;//这里的意思就是说同一个块级作用域下不能声明两个变量(只要有一个使用了let)
    }
    

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

    function f(condition, x){
        if (condition){
            let x = 100;
            return x;//这里面的x是if块级作用域里声明的
        }
        return x;//这个x是函数传参时声明的其实省略了else
    }
    f(false, 0);//0
    f(true, 0);//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();
    } // Seattle
    

    因为我们已经在city的环境里获取到了city,所以就算if语句执行结束后我们仍然可以访问它.
    let声明出现在循环体里拥有完全不同于var的行为,不仅是在循环里引入了一个新的变量环境,而且针对每次迭代都会创建一个新的作用域,这就是我们在使用立即执行的函数表达式时做的事

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

    这样就能得到我们想要的结果.

    const声明

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

    const numberLivesForCat = 9;
    

    let声明类似,但是const被赋值后不能再改变.也就是说,const拥有与let相同的作用域规则,但是不能对它们重新赋值.

    const num= 1;
    const kitty = {
        name: 'xiaoji',
        numLives: num;
    }
    kitty = {
        name: "jiji",
        numLives: num
    }//报错,因为const声明的变量的值是不可变的
    //但是可以像下面一样进行变量内部的改变
    kitty.name = 'xiaogang';//ok
    kitty.numLives--;//ok
    

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

    解构

    解构数组

    最简单的解构:数组的解构赋值

    let input = [1, 2];
    let [first, second] = input;
    console.log(first);//1
    console.log(second);//2
    

    上面的代码相当于:

    first = input[0];
    second = input[1];
    

    解构作用于已声明的变量:

    [first, second] = [second, first];//将两者的值交换一下
    

    作用于函数参数:

    function f([first, second]: [number, number]) {
        console.log(first);
        console.log(second);
    }
    f(input);
    

    可以在数组里使用...语法创建剩余变量:

    let  [first, ...rest] = [1 ,2 , 3, 4];
    console.log(first);//1
    console.log(rest);//[2, 3, 4]
    

    当然也可以忽略尾随元素:

    let [first] = [1, 2, 3, 4];
    console.log(first);//1
    

    或其他元素:

    let [, second, , fourth] = [1, 2, 3, 4];
    console.log(second);//2
    consol.log(fourth);//4
    
    对象解构
    let o = {
        a: 'foo',
        b: 12,
        c: 'bar'
    };
    let {a, b} = o;
    console.log(a);//'foo'
    console.log(b);//12
    

    就像数组解构,可以使用没有声明的赋值:

    ({a, b} = {a: 'baz', b: 101});
    

    注意我们需要用括号将它包起来,因为JavaScript通常会将以{起始的语句解析为一个块,上面这行代码经测试直接运行会报错,还是需要在前面加上let a, b;
    可以在对象里使用...语法创建剩余变量

    let {a, ..passthrough} = o;
    let total = passthrough.b + passthrough.c.length;
    
    属性重命名

    可以给属性以不同的名字:

    let { a: newName1, b: newName2} = o;
    

    这里可以读作"a 作为newName1,意思是:

    let newName1 = o.a;
    let newName2 = o.b;
    

    这里的冒号不是指定类型的,如果想指定它的类型,仍然需要在其后写上完整的模式

    let {a, b} : {a: string, b: number} = o;
    
    默认值

    默认值可以让你在属性为undefined时使用缺省值:

    function keepWholeObject (wholeObject: {a: string, b?: number}) {
        let {a, b = 1001} = wholeObject;
    }
    

    现在即使bundefined,keepWholeObject函数的变量wholeObject的属性a,b都会有值.

    函数声明

    解构也能用于函数声明:

    type C = { a: string, b?: number }
    function f({a, b}: C):void {
        //...
    }
    

    通常更多情况下是指定默认值:

    function f({a, b} = {a: "", b: 0}): void {
        //...
    }
    f();//ok default to {a: "", b: 0}
    
    function f({ a, b = 0 } = { a: "" }): void {
        // ...
    }
    f({ a: "yes" }); // ok, default b = 0
    f(); // ok, default to {a: ""}, which then defaults b = 0
    f({}); // error, 'a' is required if you supply an argument
    

    展开

    展开操作符与解构相反,它允许你将一个数组展开为另一个数组,或将一个对象展开为另一个对象:

    let first = [1, 2];
    let second = [3, 4];
    let bothPlus = [0, ...first, ...second, 5];//[0, 1, 2, 3, 4, 5]
    

    展开对象

    let defaults = {food: "spicy", price: "$$", amibiance: "noisy"};
    let search = {...defaults, food: "rich"};//{food: "rich", price: "$$", amibiance: "noisy"} 
    

    如果将defaults放在了search后面,则展开为:

    { food: 'spicy', price: '$$', ambiance: 'no
    isy' }
    

    所以这点要注意,默认值的覆盖问题
    对象展开仅包含对象自身的可枚举属性,意思是说当你展开一个对象实例时,你会丢失其方法:

    class C {
      p = 12;
      m() {
      }
    }
    let c = new C();
    let clone = { ...c };
    clone.p; // ok
    clone.m(); // error!
    

    相关文章

      网友评论

        本文标题:TypeScript(二)

        本文链接:https://www.haomeiwen.com/subject/jmiksxtx.html