未分类

    TypeScript 初体验

    TypeScript 是 JavaScript 的类型的超集,它可以编译成纯 JavaScript。编译出来的 JavaScript 可以运行在任何浏览器上。TypeScript 编译工具可以运行在任何服务器和任何系统上。TypeScript 是开源的。

    今天接触了下 TypeScript, 确实有很多优点。日后的重点应该在如何正确合理的使用上, 或者也许说组里以后不会使用,但有一点技术储备知道技术的也是好的。 学习一门语言不神秘,都是一个星期熟悉的过程,我觉得更重要的是要知道学习了它如何更好的帮我解决问题,或者说学习它会有哪些新的思路,对于团队又有什么价值。或者又说现在你可能只是知道它能干什么,大概怎么做,有哪些价值也是可以的。

    下文总结了下 TypeScript 的一些语法,记录了下我觉得有意义的几个点。


    TypeScript 的优势

    由于模块化了(ES6),命名空间和强大的面向对象编程支持,使构建大型复杂应用程序的代码库更加容易。TypeScript在编译为JavaScript的过程中,在它到达运行时间前可以捕获所有类型的错误,并中断它们的执行。使用 Webstorm 等 IDE,极大的方便了我们的使用,webStorm 都有直接的编译提示或者语法提示

    总结了下,我认为有以下几点:

    1. 支持 ES6 规范
    2. 有强大的 IDE 支持
    3. 静态类型检查
    4. 是 Angular2 的开发语言
    5. TypeScript 是开源的
    6. 更好的发现问题,可读性好

    一般我们可以这样就完成最基本的入门:

    1
    2
    3
    4
    npm install -g typescript
    tsc -v
    tsc test.ts(命名为 ts)


    先举个例子

    由下面的这个例子,来简单的看下 TypeScript 的类型检测。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    interface Shape {
    name: String;
    width: number;
    height: number;
    color?: string
    }
    function area(shape: Shape) {
    var area = shape.width * shape.height;
    return `I'm a ${shape.name} with an area of ${area} cm square`;
    }
    console.log(area({name: 'rectangle', width: 10, height:20, color: 'red'}));

    typescript数据有五种类型:number、string、any、void、boolean

    关于类型,还有其他的内容,比如联合类型等。这里就不展开讲了。

    对于参数的新特性有下面几个:

    1. 参数类型: 在参数名称后面使用冒号来指定参数类型
    2. 默认参数: 在参数生命后面用等号来指定参数的默认值
    3. 可选参数: 在方法的参数生命后面用 来标明此参数为可选参数。

    默认参数和可选参数放在最后,其中可选参数要在方法体里处理。


    TypeScript 语法

    TypeScript 的语法还是很多的,这里我只记录整理我自己感兴趣的几点。

    对象的类型-接口

    在面向对象语言中,接口(Interfaces)是一个很重要的概念,它是对行为的抽象,而具体如何行动需要由类(classes)去实现(implements)。

    TypeScript 中的接口是一个非常灵活的概念,除了可用于对类的一部分行为进行抽象以外,也常用于对「对象的形状(Shape)」进行描述。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    interface Person {
    name: string;
    age: number;
    }
    let seven: Person = {
    name: 'seven',
    age: 2,
    };

    这里注意下赋值的时候,变量的形状必须和接口的形状保持一致。多了少了都不行。 如果不希望完全匹配一个类型,就声明可选属性。

    1
    2
    3
    4
    5
    6
    7
    8
    interface Person {
    name: string;
    age?: number;
    }
    let seven: Person = {
    name: 'seven',
    };

    如果希望一个接口有任意的属性,可以使用 any:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    // 使用 [propName: string] 定义了任意属性取 string 类型的值。
    interface Person {
    name: string;
    age?: number;
    [propName: string]: any;
    }
    let seven: Person = {
    name: 'seven',
    website: 'http://cailidan.cn',
    };

    接口里面还可以有只读属性。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    interface Person {
    readonly id: number;
    name: string;
    age?: number;
    [propName: string]: any;
    }
    let seven: Person = {
    id: 1,
    name: 'seven',
    website: 'http://cailidan.com',
    };
    // 报错
    seven.id = 2;

    注意下:只读的约束存在于第一次给对象赋值的时候,而不是第一次给只读属性赋值的时候, 所以一开始初始化对象,用 id:1 的时候是没问题的, 后面的 id=2 才是出错的地方。


    函数的类型

    函数是 JavaScript 中的一等公民。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    // 函数声明
    function sum(x: number, y: number): number {
    return x + y;
    }
    // 函数表达式
    let mySum: (x: number, y: number) => number = function (x: number, y: number): number {
    return x + y;
    };


    类型断言 Type Assertion

    断言表示为一些布尔表达式,程序员相信在程序中的某个特定点该表达式值为真,可以在任何时候启用和禁用断言验证,因此可以在测试时启用断言而在部署时禁用断言。同样,程序投入运行后,最终用户在遇到问题时可以重新启用断言。

    类型断言(Type Assertion)可以用来绕过编译器的类型推断,手动指定一个值的类型(即程序员对编译器断言)。

    先来对比下 c 语言里面的断言:

    1
    2
    3
    4
    5
    6
    7
    assert宏的原型定义在<assert.h>中,其作用是如果它的条件返回错误,则终止程序执行,原型定义:
    #include <assert.h>
    void assert( int expression );
    assert的作用是现计算表达式 expression ,如果其值为假(即为0),那么它先向stderr打印一条出错信息,然后通过调用 abort 来终止程序运行。
    缺点是频繁的调用会极大的影响程序的性能,增加额外的开销。

    在 nodejs 中,我们也用断言来测试:

    1
    2
    var assert= require('assert');
    assert.equal('1', '2', ['1和2是不相等的']);

    这里如果 1 和 2 相等,则没有问题,但是因为这里是不等的,所以会输出1和2是不相等的。

    对于 TypeScript 这里比如将一个联合类型的变量指定为一个更加具体的类型:

    1
    2
    3
    4
    5
    6
    7
    function getLength(something: string | number): number {
    if (something.length) {
    return something.length;
    } else {
    return something.toString().length;
    }
    }

    上面的代码会报错,因为如果传过来的是一个 number, number 里面没有 length 属性,所以会报错。所以按道理我们本来是:只能访问此联合类型的所有类型里共有的属性或方法,但是如果这个时候我们使用 类型断言, 可以将 something 断言成 string, 这个时候编译器就不会报错了。

    1
    2
    3
    4
    5
    6
    7
    8
    // 类型断言的用法如下,在需要断言的变量前加上 <Type> 即可。
    function getLength(something: string | number): number {
    if ((<string>something).length) {
    return (<string>something).length;
    } else {
    return something.toString().length;
    }
    }

    类型断言不是类型转换。


    泛型

    泛型(Generics)是指在定义函数、接口或类的时候,不预先指定具体的类型,而在使用的时候再指定类型的一种特性。

    在 c++ 里面我们也这么用,比如一个人想要编写一个通用的加法函数,使用函数重载的话,针对每个相同的行为不同的类型都要实现它。这样就很麻烦,代码服复用率低。并且不好维护。例如如下一个例子:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    template <typename T> //T可是自己起的名字,满足命名规范即可
    T Add(T left, T right)
    {
    return left + right;
    }
    int main()
    {
    Add(1, 2); // ok
    Add(1, 2.0); // error 只有一个类型T,传递两个不同类型参数则无法确定模板参数T的类型,编译报错
    Add(1.0, 2.0); // ok
    return 0;
    }

    那么在 TypeScript 中,或者可以这么理解:泛型(Generics)是允许同一个函数接受不同类型参数的一种模板。相比于使用 any 类型,使用泛型来创建可复用的组件要更好,因为泛型会保留参数类型。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    function genericFunc<T>(argument: T): T[] {
    var arrayOfT: T[] = []; // Create empty array of type T.
    arrayOfT.push(argument); // Push, now arrayOfT = [argument].
    return arrayOfT;
    }
    var arrayFromString = genericFunc<string>("beep");
    console.log(arrayFromString[0]); // "beep"
    console.log(typeof arrayFromString[0]) // String
    var arrayFromNumber = genericFunc(42);
    console.log(arrayFromNumber[0]); // 42
    console.log(typeof arrayFromNumber[0]) // number

    再看个例子 createSameArray , 可以指定长度数组,并且赋值初始值。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    function createSameArray(length: number, value: any): Array<any> {
    let result = [];
    for (let i = 0; i < length; i++) {
    result[i] = value;
    }
    return result;
    }
    createSameArray(3, 'x'); // ['x', 'x', 'x']

    上面的 Array<any> 就是 数组泛型,来定义任意值。但这会有一个问题,就是它没有准确的定义返回值的类型。Array<any> 允许数组的每一项都为任意类型。但是我们预期的是,数组中每一项都应该是输入的 value 的类型。这个时候就可以用泛型:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    function createSameArray<T>(length: number, value: T): Array<T> {
    let result = [];
    for (let i = 0; i < length; i++) {
    result[i] = value;
    }
    return result;
    }
    createSameArray<string>(3, 'x'); // ['x', 'x', 'x']

    上面我们用到了 <T>, T 来指代任意输入的类型,在后面的输入 value: T 和输出 Array<T> 中即可使用了。


    知道了前面的语法,就是最重要的类了,类的话就是各种语法的集合,比如函数。基本上就和ES6很像很像了,当然 TypeScript 也加入了一些 ES7 的特性,比如 public, private, protected 的成员变量。这里就简单的列出来一个例子。例子来源于:learn-typescript-in-30-minutes,我觉得比我自己写的 demo 的例子好,所以这里就列举这个例子的代码:

    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
    class Menu {
    // Our properties:
    // By default they are public, but can also be private or protected.
    items: Array<string>; // The items in the menu, an array of strings.
    pages: number; // How many pages will the menu be, a number.
    // A straightforward constructor.
    constructor(item_list: Array<string>, total_pages: number) {
    // The this keyword is mandatory.
    this.items = item_list;
    this.pages = total_pages;
    }
    // Methods
    list(): void {
    console.log("Our menu for today:");
    for(let i=0; i<this.items.length; i++) {
    console.log(this.items[i]);
    }
    }
    }
    // Create a new instance of the Menu class.
    let sundayMenu = new Menu(["pancakes","waffles","orange juice"], 1);
    // Call the list method.
    sundayMenu.list();

    然后我们使用下继承:

    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
    class HappyMeal extends Menu {
    // Properties are inherited
    // A new constructor has to be defined.
    constructor(item_list: Array<string>, total_pages: number) {
    // In this case we want the exact same constructor as the parent class (Menu),
    // To automatically copy it we can call super() - a reference to the parent's constructor.
    super(item_list, total_pages);
    }
    // Just like the properties, methods are inherited from the parent.
    // However, we want to override the list() function so we redefine it.
    list(): void{
    console.log("Our special menu for children:");
    for(var i=0; i<this.items.length; i++) {
    console.log(this.items[i]);
    }
    }
    }
    // Create a new instance of the HappyMeal class.
    var menu_for_children = new HappyMeal(["candy","drink","toy"], 1);
    // This time the log message will begin with the special introduction.
    menu_for_children.list();

    总结

    写完这篇文章,想要分享最近看到的一篇08 年文章:

    技术路线的选择重要但不具有决定性。文章的内容大概讲的是: 技术都在不断的更新,随着发展,总有进步或者淘汰的那天, 一个技术人的价值不在于他学了多少最新的技术,而在于这个技术人本身的个性知识经验组合。

    技术方案的选择很重要,自身努力的学习新知识也很重要,但是不要以这个作为自己的负担或者是任务,又或者把自己学到的新技术当成自己的炫耀或者资本。 一个技术人真正的价值在于 他学到的新知识 是否能够带来价值,比如是否真的让 团队更加规范,让彼此之间的合作更好, 让线上的效率质量更高。这是看了这篇文章的一些小小的体会。好像有点扯远了,就是觉得这篇文章写得好,推荐给大家。

    这边文章主要就是简单的 讲了下今天学到的 TypeScript, 没有细节,没有很多的技术点。纯粹记录了下我自己感兴趣的几个地方。 希望以后能够找到机会实践。不实践知道的就永远是皮毛。也不会有理解。也很容易忘记。今天是给自己相当于科普了下。最起码以后 别人说 TypeScript 不会觉得很神秘了。嘻嘻。

    参考并阅读了以下文章,感谢:

    1. hello-typescript
    2. 三十分钟学会TypeScript
    3. getting-started-with-typescript