美文网首页JavaScript让前端飞Web前端之路
为什么要在javascript中进行静态类型检查.Part1[译

为什么要在javascript中进行静态类型检查.Part1[译

作者: AlienZHOU | 来源:发表于2017-07-05 13:55 被阅读107次

    本文为翻译文章,原文链接见文末

    作为一个JavaScript开发者,你可以编写一整天编写也不会遇到任何静态类型检查得问题。那么为什么要自找麻烦得去学习它呢?
    然而学习静态类型并不仅仅是一个思维拓展的训练。如果你愿意花点时间来学习一些静态类型的优势、劣势以及使用的案例,那将会极大的帮助你进行编码。
    怎么样,有意思吧?要是你感兴趣的话,那接下来四个部分将会向你详细解释。

    一、定义

    理解静态类型最快捷的方式就是它和动态类型进行对比。

    A language with static types is referred to as a statically-typed language. On the other hand, a language with dynamic types is referred to as a dynamically-typed language.

    静态类型语言和动态类型语言得核心区别在于,静态类型语言(statically-typed languages)会在编译时(compile time)进行类型检查,而动态语言(dynamically-typed)则是在运行时进行类型检查(runtime)。

    这里又多了一个概念:什么是“类型检查”(type-checking)?

    为了解释这个概念,我们可以将Java于JavaScript的类型对比看一下。

    这里的类型(Types)指的是一个数据被定义的类型。

    举个例子,在Java中如果你定义一个boolean值:

    boolean result = true;
    

    这个变量就有了一个正确的类型,因为boolean类型的声明和这个变量给定的值是相符的。

    在另一方面,如果你尝试这么来声明这个变量:

    boolean result = 123;
    

    由于变量result有一个错误的类型,因此会编译失败。这里我们明确地声明了result是一个boolean类型,但是它却用整型值123赋给了它。

    JavaScript和其他一些动态类型语言有着不同的处理方法,他们允许上下文环境来确定数据需要被定义为什么类型:

    var result = true;
    

    长话短说,静态类型语言需要你在能够使用这个变量之前定义它的类型。而动态类型语言则不同。JavaScript中变量类型是被“隐去”的,而Java中则是显式声明的。

    类型检查将会确保并且强制使你的变量类型(constant, boolean, number, variable, array, object)和你已经定义和预期的内容相符。例如:你已经确定“这个方法总是会返回一个string型”。当程序运行的时候,你可以很安全地假设它会返回一个string型。

    当出现一个类型错误时,静态类型检查和动态类型检查的差异就凸显出来了。在静态类型语言中,类型检查发生在编译阶段。在动态类型语言中,只有在程序运行了一次的时候错误才会被发现,也就是在运行时。

    这就意味着,对于写动态类型语言的程序员而言,即使代码中包含了会在运行时阻止脚本正常运行的错误类型,这段代码也可以通过编译。

    在另一方面,如果一个写静态语言的程序员写了一段包含了类型错误的代码,那么除非修复这个错误,否则会一直编译失败。

    A new era of JavaScript

    因为JavaScript是一种动态类型语言,因此你可以定义各种变量、方法、对象而不需要声明它的类型。

    var myString = "my string";
    
    var myNumber = 777;
    
    var myObject = {
      name: "Preethi",
      age: 26,
    };
    
    function add(x, y) {
      return x + y;
    }
    

    这非常方便,但有时确不是那么理想。这也就是为什么想FlowTypeScript这样的工具最近开始走入人们的视野,带给JavaScript开发者使用静态类型的选择。

    Flow时Facebook开发和发布的一个开源的静态类型检查库,它允许你逐渐地向你的JavaScript代码中添加类型。

    TypeScript是一个会编译为JavaScript的超集(尽管它看起来几乎像一种新的静态类型语言),这意味着,它使用起来会感觉和JavaScript很像,并不难上手。

    不论是使用上面哪种工具,当你想要使用类型时,你会明确告诉工具哪个(些)文件需要类型检查。对于TypeScript,你需要将.js文件拓展名改为.ts。对于Flow,你需要在文件的顶部引入一段标注@flow

    一旦你声明了想要对某一个文件进行类型检查,你需要使用他们各自的语法去定义类型。这两个工具的一个区别在于,Flow是一个类型“检查器”而不是一个编译器。TypeScript则是一个编译器。

    我相信类似Flow和TypeScript这样的工具为JavaScript带来了一个跨世代的转变与提高。

    下面我会从这四个部分来谈一谈“在javascript中进行静态类型检查”:

    注意我选择在例子中使用Flow而不是TypeScript是因为我比较熟悉它。你可以根据你自己的目的与场景选择一个对你来说合适的工具。TypeScript同样非常不错!

    话不多说,开搞!

    第一部分、Flow语法快速人们

    为了要理解静态类型的优势和劣势,你首先需要通过使用Flow来对静态类型的语法有个基础的认识。如果你以前从来没用过静态类型,那么你可以需要花点时间来熟悉一下这种语法。

    让我们先来看看如何在JavaScript基本类型(以及像数组、对象、函数等)上应用。

    boolean

    下面这段代码描述了JavaScript中的boolean

    var isFetching: boolean = false;
    

    注意,当你想要定义一个类型时,你需要用下面这种语法:


    imageimage

    number

    这里指的是IEEE 754下的浮点型。不像许多其他的程序语言,JavaScript并不会定义不同的数值类型(例如interger、short、long和float points)。而是所有的数值类型都会被存储为双精度浮点数。因此,你只需要一种数据类型来定义所有的数值变量。

    备注:数值型number包含了InfinityNaN

    var luckyNumber: number = 10;
    var notSoLuckyNumber: number = NaN;
    

    string

    下面是一个string类型

    var myName: string = 'Preethi';
    

    null

    下面是一个null数据类型

    var data: null = null;
    

    void

    在这里void描述的是JavaScript中undefined类型

    var data: void = undefined;
    

    注意要将nullundefined区别对待。如果你像下面这么写:

    var data: void = null;
    
    /*------------------------FLOW ERROR------------------------*/
    20: var data: void = null                     
                         ^ null. This type is incompatible with
    20: var data: void = null
                  ^ undefined
    

    由于undefinednull是不同的类型,而void类型应该属于undefined类型,因此Flow会抛出一个错误。

    Array

    JavaScript中的数组类型。使用Array<T>这样的语法来定义一个数组,其中数组的元素类型为T

    var messages: Array<string> = ['hello', 'world', '!'];
    

    注意上面的代码,用string替换了T,表示messages是一个字符串数组。

    Object

    JavaScript中的对象类型。有几种不同的方式来为对象添加类型限制。

    你可以添加类型来描述对象的格式:

    var aboutMe: { name: string, age: number } = {
      name: 'Preethi',
      age: 26,
    };
    

    你可以用对象来作为Map,并给键和值都设置类型:

    var namesAndCities: { [name: string]: string } = {
      Preethi: 'San Francisco',
      Vivian: 'Palo Alto',
    };
    

    你也可以仅仅定义一个对象为Object类型:

    var someObject: Object = {};
    
    someObject.name = {};
    someObject.name.first = 'Preethi';
    someObject.age = 26;
    

    上面这段代码使你可以不受限制得设置对象的键和值,因此就类型检查而言,这种方式并没有增加太多的价值。

    any

    正如字面意思,它可以代表任何类型。any类型一定程度上避免了类型检查,因此如非必要,尽量扁面使用这个类型。

    var iCanBeAnything:any = 'LALA' + 2; // 'LALA2'
    

    有一个场景下比较适用:当你使用了一个扩展了系统原型的外部类库(类似Object.prototype)。
    例如,你使用的类库为Object.prototype扩展了一个叫doSomething的属性。

    Object.prototype.someProperty('something');
    

    你的代码很可能会报如下错误:

    41:   Object.prototype.someProperty('something')
                           ^^^^^^ property `someProperty`. Property not found in
    41:   Object.prototype.someProperty('something')
          ^^^^^^^^^^^^ Object
    

    为了避免着各种情况,你可以使用any类型:

    (Object.prototype: any).someProperty('something'); // No errors!
    

    Functions

    给方法添加类型的最常见的用法是,为该方法的参数和返回值添加类型检查:

    var calculateArea = (radius: number): number => {
      return 3.14 * radius * radius
    };
    

    你甚至可以给async方法和生成器(generator)添加类型:

    async function amountExceedsPurchaseLimit(
      amount: number,
      getPurchaseLimit: () => Promise<number>
    ): Promise<boolean> {
      var limit = await getPurchaseLimit();
    
      return limit > amount;
    }
    

    这里可以关注一下,这段代码是如何将第二个参数getPurchaseLimit声明为一个返回Promise对象的函数的。同时,
    amountExceedsPurchaseLimit方法本身被声明会返回一个Promise对象。

    类型别名(Type alias)

    类型别名(Type alias)是我最喜欢的一种用法。它允许你使用已有的类型(number,、string等)来组合成一个新的类型:

    type PaymentMethod = {
      id: number,
      name: string,
      limit: number,
    };
    

    在上面这段代码中,我创建了一个叫作PaymentMethod的新类型,包含了numberstring两种类型。

    可以这样来使用PaymentMethod类型:

    var myPaypal: PaymentMethod = {
      id: 123456,
      name: 'Preethi Paypal',
      limit: 10000,
    };
    

    通过给原始类型包裹一层新类型,你可以为它们创建一个新的类型别名。例如,创建NameEmail这两个类型别名:

    type Name = string;
    type Email = string;
    
    var myName: Name = 'Preethi';
    var myEmail: Email = 'iam.preethi.k@gmail.com';
    

    这么做的话,可以清楚的表明,NameEmail是指代不同的事物,而不仅仅是一个字符串。由于NameEmail是不可互换的,这么做可以帮助你避免混淆它们。

    泛型(Generics)

    泛型是一种对类型本身进行抽象的方法。什么意思呢?看看下面的代码:

    type GenericObject<T> = { key: T };
    
    var numberT: GenericObject<number> = { key: 123 };
    var stringT: GenericObject<string> = { key: "Preethi" };
    var arrayT: GenericObject<Array<number>> = { key: [1, 2, 3] }
    

    我为类型T创建了一个抽象的概念,你可以使用任何类型来代替T。对于numberT来说,Tnumber类型的;而对于arrayT来说,TArray<number>类型的。

    如果你是第一次接触这些类型,确实可能会有些晕。不过相关的入门介绍马上就要结束了。

    Maybe

    Maybe类型允许我们声明一个包含nullundefined两个潜在类型的值。对于类型TTnullundefined三种类型,意味着一个变量可能是Tnullundefined三者之一。在类型定义前加上一个“?”就可以定义一个Maybe类型:

    var message: ?string = null;
    

    这段代码表示message是string类型、nullundefined

    你也可以用Maybe类型来表示一个对象属性可能是某种类型T或者undefined

    type Person = {
      firstName: string,
      middleInitial?: string,
      lastName: string,
    };
    

    通过将“?”放在属性名middleInitial之后,你可以表明这个对象时可选的。

    Disjoint unions(或操作)

    这是创建你的数据模型的另一个强大的方法。当你的程序需要同时处理不同的数据类型,Disjoint unions会是一个很有用的方法。换句话说,根据环境的不同,数据的结构也会不同。

    我们基于之前的泛型示例来拓展PaymentMethod类型。想象一种场景,在我们的一个应用中,包含了三类不同的支付方法。在这种情况下,你可以这么做:

    type Paypal = { id: number, type: 'Paypal' };
    type CreditCard = { id: number, type: 'CreditCard' };
    type Bank = { id: number, type: 'Bank' };
    

    你可以用disjoint union来定义PaymentMethod类型:

    type PaymentMethod = Paypal | CreditCard | Bank;
    

    现在支付方法将会是这三种类型中的一种。关于disjoint union,在第二部分将会有更多的例子。

    除了上面所述之外,Flow还有一些其他特性有必要在这篇简介中提一下:

    1)类型推断(Type inference):可能的话Flow会使用类型推断的功能。当类型检查可以自动推断出一个表达式的数据类型时,类型推断就会介入进来。这个特性可以帮助避免过多的类型声明。

    举个例子,你可以这么写:

    /* @flow */
    
    class Rectangle {
      width: number;
      height: number;
    
      constructor(width, height) {
        this.width = width;
        this.height = height;
      }
    
      circumference() {
        return (this.width * 2) + (this.height * 2)
      }
    
      area() {
        return this.width * this.height;
      }
    }
    

    即使这个类并没有类型,Flow依然可以进行一定的类型检查:

    var rectangle = new Rectangle(10, 4);
    
    var area: string = rectangle.area();
    
    // Flow errors
    100: var area: string = rectangle.area();
                            ^^^^^^^^^^^^^^^^ number. This type is incompatible with
    100: var area: string = rectangle.area();
                   ^^^^^^ string
    

    在这里我尝试把area定义为string类型,但是在Rectangle类中,我们给widthheight定义的类型时number类型。因此,基于area方法的定义,它只能返回一个number类型。即使我们有显式地给area方法定义类型,Flow还是可以捕获到这个错误。

    需要注意的一点是,Flow的维护人员建议,如果你需要导出(export)类定义,为了在非本地上下文环境(context)下更容易找出错误原因,你最好还是要添加明确的类型定义。

    2)动态类型检查(Dynamic type tests):这个特性意味着,Flow有能力确定一个变量在运行时的类型,因此当进行静态类型检查使Flow也可以使用这个能力。当Flow抛出一个错误但是你需要让Flow相信你的代码是没有问题的时候,这个特性就显得很有用处。

    在这里我不会更深入得讲解更多细节,因为这更多的是一个进阶的特性,针对这一点我希望能够单开一篇来介绍。但如果你想要了解更多,可以看看这里

    语法介绍结束

    这一部分我们了解了非常多的内容。我希望这个总览性质的简介可以对你有帮助。如果你想要进一步探索,我推荐你可以深入地读一下这些文档

    结束了语法的学习,我们可以进入下一个更有趣的部分 ==> 使用静态类型的优势与劣势

    原文:Why use static types in JavaScript? (A 4-part primer on static typing with Flow)

    相关文章

      网友评论

        本文标题:为什么要在javascript中进行静态类型检查.Part1[译

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