typescript——BASE64 TypeScripts

/**
 *
 * @author 
 *
 */
class Base64Util {
    public constructor() {
    }

    public static BASE64_CHARS:string = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";

        public static version:string = "1.0.0";

        public static encode(data:string):string {
            // Convert string to ByteArray
            var bytes: egret.ByteArray = new egret.ByteArray();
            bytes.writeUTFBytes(data);

            // Return encoded ByteArray
            return this.encodeByteArray(bytes);
        }

        private static encodeByteArray(data:egret.ByteArray):string {
            // Initialise output
            var output:string = "";

            // Create data and output buffers
            var dataBuffer:Array<any>;
            var outputBuffer:Array<any> = new Array(4);

            // Rewind ByteArray
            data.position = 0;

            // while there are still bytes to be processed
            while (data.bytesAvailable > 0) {
                // Create new data buffer and populate next 3 bytes from data
                dataBuffer = new Array();
                for (var i:number = 0; i < 3 && data.bytesAvailable > 0; i++) {
                    dataBuffer[i] = data.readUnsignedByte();
                }

                // Convert to data buffer Base64 character positions and 
                // store in output buffer
                outputBuffer[0] = (dataBuffer[0] & 0xfc) >> 2;
                outputBuffer[1] = ((dataBuffer[0] & 0x03) << 4) | ((dataBuffer[1]) >> 4);
                outputBuffer[2] = ((dataBuffer[1] & 0x0f) << 2) | ((dataBuffer[2]) >> 6);
                outputBuffer[3] = dataBuffer[2] & 0x3f;

                // If data buffer was short (i.e not 3 characters) then set
                // end character indexes in data buffer to index of '=' symbol.
                // This is necessary because Base64 data is always a multiple of
                // 4 bytes and is basses with '=' symbols.
                for (var j:number = dataBuffer.length; j < 3; j++) {
                    outputBuffer[j + 1] = 64;
                }

                // Loop through output buffer and add Base64 characters to 
                // encoded data string for each character.
                for (var k:number = 0; k < outputBuffer.length; k++) {
                    output += this.BASE64_CHARS.charAt(outputBuffer[k]);
                }
            }

            // Return encoded data
            return output;
        }

        public static decode(data:string):string {
            // Decode data to ByteArray
            var bytes:egret.ByteArray = this.decodeToByteArray(data);

            // Convert to string and return
            return bytes.readUTFBytes(bytes.length);
        }

        private static decodeToByteArray(data:String):egret.ByteArray {
            // Initialise output ByteArray for decoded data
            var output:egret.ByteArray = new egret.ByteArray();

            // Create data and output buffers
            var dataBuffer:Array<any> = new Array(4);
            var outputBuffer: Array<any> = new Array(3);

            // While there are data bytes left to be processed
            for (var i:number = 0; i < data.length; i += 4) {
                // Populate data buffer with position of Base64 characters for
                // next 4 bytes from encoded data
                for(var j: number = 0; j < 4 && i + j < data.length; j++) {
                    dataBuffer[j] = this.BASE64_CHARS.indexOf(data.charAt(i + j));
                }

                // Decode data buffer back into bytes
                outputBuffer[0] = (dataBuffer[0] << 2) + ((dataBuffer[1] & 0x30) >> 4);
                outputBuffer[1] = ((dataBuffer[1] & 0x0f) << 4) + ((dataBuffer[2] & 0x3c) >> 2);        
                outputBuffer[2] = ((dataBuffer[2] & 0x03) << 6) + dataBuffer[3];

                // Add all non-padded bytes in output buffer to decoded data
                for(var k: number = 0; k < outputBuffer.length; k++) {
                    if (dataBuffer[k+1] == 64) break;
                    output.writeByte(outputBuffer[k]);
                }
            }

            // Rewind decoded data ByteArray
            output.position = 0;

            // Return decoded data
            return output;
        }
}

new 发布于 2017-2-23 06:30

[转]TypeScript中文文档——混入 TypeScripts

转自:https://zhongsp.gitbooks.io/typescript-handbook/content/

介绍

除了传统的面向对象继承方式,还流行一种通过可重用组件创建类的方式,就是联合另一个简单类的代码。 你可能在Scala等语言里对mixins及其特性已经很熟悉了,但它在JavaScript中也是很流行的。

混入示例

下面的代码演示了如何在TypeScript里使用混入。 后面我们还会解释这段代码是怎么工作的。

// Disposable Mixin class Disposable {
    isDisposed: boolean;
    dispose() { this.isDisposed = true;
    }

} // Activatable Mixin class Activatable {
    isActive: boolean;
    activate() { this.isActive = true;
    }
    deactivate() { this.isActive = false;
    }
} class SmartObject implements Disposable, Activatable { constructor() {
        setInterval(() => console.log(this.isActive + " : " + this.isDisposed), 500);
    }

    interact() { this.activate();
    } // Disposable isDisposed: boolean = false;
    dispose: () => void; // Activatable isActive: boolean = false;
    activate: () => void;
    deactivate: () => void;
}
applyMixins(SmartObject, [Disposable, Activatable]); let smartObj = new SmartObject();
setTimeout(() => smartObj.interact(), 1000); //////////////////////////////////////// // In your runtime library somewhere //////////////////////////////////////// function applyMixins(derivedCtor: any, baseCtors: any[]) {
    baseCtors.forEach(baseCtor => { Object.getOwnPropertyNames(baseCtor.prototype).forEach(name => {
            derivedCtor.prototype[name] = baseCtor.prototype[name];
        });
    });
} 

理解这个例子

代码里首先定义了两个类,它们将做为mixins。 可以看到每个类都只定义了一个特定的行为或功能。 稍后我们使用它们来创建一个新类,同时具有这两种功能。

// Disposable Mixin class Disposable {
    isDisposed: boolean;
    dispose() { this.isDisposed = true;
    }

} // Activatable Mixin class Activatable {
    isActive: boolean;
    activate() { this.isActive = true;
    }
    deactivate() { this.isActive = false;
    }
} 

下面创建一个类,结合了这两个mixins。 下面来看一下具体是怎么操作的:

class SmartObject implements Disposable, Activatable { 

首先应该注意到的是,没使用extends而是使用implements。 把类当成了接口,仅使用Disposable和Activatable的类型而非其实现。 这意味着我们需要在类里面实现接口。 但是这是我们在用mixin时想避免的。

我们可以这么做来达到目的,为将要mixin进来的属性方法创建出占位属性。 这告诉编译器这些成员在运行时是可用的。 这样就能使用mixin带来的便利,虽说需要提前定义一些占位属性。

// Disposable isDisposed: boolean = false;
dispose: () => void; // Activatable isActive: boolean = false;
activate: () => void;
deactivate: () => void; 

最后,把mixins混入定义的类,完成全部实现部分。

applyMixins(SmartObject, [Disposable, Activatable]); 

最后,创建这个帮助函数,帮我们做混入操作。 它会遍历mixins上的所有属性,并复制到目标上去,把之前的占位属性替换成真正的实现代码。

function applyMixins(derivedCtor: any, baseCtors: any[]) {
    baseCtors.forEach(baseCtor => { Object.getOwnPropertyNames(baseCtor.prototype).forEach(name => {
            derivedCtor.prototype[name] = baseCtor.prototype[name];
        })
    });
}




new 发布于 2016-10-18 01:06

[转]TypeScript中文文档——三斜线命令 TypeScripts

转自:https://zhongsp.gitbooks.io/typescript-handbook/content/

三斜线指令是包含单个XML标签的单行注释。 注释的内容会做为编译器指令使用。

三斜线指令可放在包含它的文件的最顶端。 一个三斜线指令的前面只能出现单行或多行注释,这包括其它的三斜线指令。 如果它们出现在一个语句或声明之后,那么它们会被当做普通的单行注释,并且不具有特殊的涵义。

/// <reference path="..." />

/// <reference path="..." />指令是三斜线指令中最常见的一种。 它用于声明文件间的依赖

三斜线引用告诉编译器在编译过程中要引入的额外的文件。

当使用--out--outFile时,它也可以做为调整输出内容顺序的一种方法。 文件在输出文件内容中的位置与经过预处理后的输入顺序一致。

预处理输入文件

编译器会对输入文件进行预处理来解析所有三斜线引用指令。 在这个过程中,额外的文件会加到编译过程中。

这个过程会以一些根文件开始; 它们是在命令行中指定的文件或是在tsconfig.json中的"files"列表里的文件。 这些根文件按指定的顺序进行预处理。 在一个文件被加入列表前,它包含的所有三斜线引用都要被处理,还有它们包含的目标。 三斜线引用以它们在文件里出现的顺序,使用深度优先的方式解析。

一个三斜线引用路径是相对于包含它的文件的,如果不是根文件。

错误

引用不存在的文件会报错。 一个文件用三斜线指令引用自己会报错。

使用 --noResolve

如果指定了--noResolve编译选项,三斜线引用会被忽略;它们不会增加新文件,也不会改变给定文件的顺序。

/// <reference no-default-lib="true"/>

这个指令把一个文件标记成默认库。 你会在lib.d.ts文件和它不同的变体的顶端看到这个注释。

这个指令告诉编译器在编译过程中不要包含这个默认库(比如,lib.d.ts)。 这与在命令行上使用--noLib相似。

还要注意,当传递了--skipDefaultLibCheck时,编译器只会忽略检查带有/// <reference no-default-lib="true"/>的文件。

/// <amd-module />

默认情况下生成的AMD模块都是匿名的。 但是,当一些工具需要处理生成的模块时会产生问题,比如r.js

amd-module指令允许给编译器传入一个可选的模块名:

amdModule.ts
///<amd-module name='NamedModule'/> export class C {
} 

这会将NamedModule传入到AMD define函数里:

amdModule.js
define("NamedModule", ["require", "exports"], function (require, exports) { var C = (function () { function C() {
        } return C;
    })();
    exports.C = C;
}); 

/// <amd-dependency />

注意:这个指令被废弃了。使用import "moduleName";语句代替。

/// <amd-dependency path="x" />告诉编译器有一个非TypeScript模块依赖需要被注入,做为目标模块require调用的一部分。

amd-dependency指令也可以带一个可选的name属性;它允许我们为amd-dependency传入一个可选名字:

/// <amd-dependency path="legacy/moduleA" name="moduleA"/> declare var moduleA:MyType
moduleA.callStuff() 

生成的JavaScript代码:

define(["require", "exports", "legacy/moduleA"], function (require, exports, moduleA) {
    moduleA.callStuff()
});




new 发布于 2016-10-18 01:06

[转]TypeScript中文文档——JSX TypeScripts

转自:https://zhongsp.gitbooks.io/typescript-handbook/content/



介绍

JSX是一种嵌入式的类似XML的语法。 它可以被转换成合法的JavaScript,尽管转换的语义是依据不同的实现而定的。 JSX因React框架而流行,但是也被其它应用所使用。 TypeScript支持内嵌,类型检查和将JSX直接编译为JavaScript。

基本用法

想要使用JSX必须做两件事:

  1. 给文件一个.tsx扩展名
  2. 启用jsx选项

TypeScript具有两种JSX模式:preservereact。 这些模式只在代码生成阶段起作用 - 类型检查并不受影响。 在preserve模式下生成代码中会保留JSX以供后续的转换操作使用(比如:Babel)。 另外,输出文件会带有.jsx扩展名。 react模式会生成React.createElement,在使用前不需要再进行转换操作了,输出文件的扩展名为.js

模式 输入 输出 输出文件扩展名
preserve <div /> <div /> .jsx
react <div /> React.createElement("div") .js

你可以通过在命令行里使用--jsx标记或tsconfig.json里的选项来指定模式。

注意:React标识符是写死的硬代码,所以你必须保证React(大写的R)是可用的。 Note: The identifier React is hard-coded, so you must make React available with an uppercase R.

as操作符

回想一下怎么写类型断言:

var foo = <foo>bar; 

这里我们断言bar变量是foo类型的。 因为TypeScript也使用尖括号来表示类型断言,JSX的语法带来了解析的困难。因此,TypeScript在.tsx文件里禁用了使用尖括号的类型断言。

为了弥补.tsx里的这个功能,新加入了一个类型断言符号:as。 上面的例子可以很容易地使用as操作符改写:

var foo = bar as foo; 

as操作符在.ts.tsx里都可用,并且与其它类型断言行为是等价的。

类型检查

为了理解JSX的类型检查,你必须首先理解固有元素与基于值的元素之间的区别。 假设有这样一个JSX表达式<expr />expr可能引用环境自带的某些东西(比如,在DOM环境里的divspan)或者是你自定义的组件。 这是非常重要的,原因有如下两点:

  1. 对于React,固有元素会生成字符串(React.createElement("div")),然而由你自定义的组件却不会生成(React.createElement(MyComponent))。
  2. 传入JSX元素里的属性类型的查找方式不同。 固有元素属性本身就支持,然而自定义的组件会自己去指定它们具有哪个属性。

TypeScript使用与React相同的规范 来区别它们。 固有元素总是以一个小写字母开头,基于值的元素总是以一个大写字母开头。

固有元素

固有元素使用特殊的接口JSX.IntrinsicElements来查找。 默认地,如果这个接口没有指定,会全部通过,不对固有元素进行类型检查。 然而,如果接口存在,那么固有元素的名字需要在JSX.IntrinsicElements接口的属性里查找。 例如:

declare namespace JSX { interface IntrinsicElements {
        foo: any }
}

<foo />; // 正确 <bar />; // 错误 

在上例中,<foo />没有问题,但是<bar />会报错,因为它没在JSX.IntrinsicElements里指定。

注意:你也可以在JSX.IntrinsicElements上指定一个用来捕获所有字符串索引:

declare namespace JSX { interface IntrinsicElements {
       [elemName: string]: any;
   }
} 

基于值的元素

基于值的元素会简单的在它所在的作用域里按标识符查找。

import MyComponent from "./myComponent";

<MyComponent />; // 正确 <SomeOtherComponent />; // 错误 

可以限制基于值的元素的类型。 然而,为了这么做我们需要引入两个新的术语:元素类的类型元素实例的类型

现在有<Expr />元素类的类型Expr的类型。 所以在上面的例子里,如果MyComponent是ES6的类,那么它的类类型就是这个类。 如果MyComponent是个工厂函数,类类型为这个函数。

一旦建立起了类类型,实例类型就确定了,为类类型调用签名的返回值与构造签名的联合类型。 再次说明,在ES6类的情况下,实例类型为这个类的实例的类型,并且如果是工厂函数,实例类型为这个函数返回值类型。

class MyComponent {
  render() {}
} // 使用构造签名 var myComponent = new MyComponent(); // 元素类的类型 => MyComponent // 元素实例的类型 => { render: () => void } function MyFactoryFunction() { return {
    render: () => {
    }
  }
} // 使用调用签名 var myComponent = MyFactoryFunction(); // 元素类的类型 => FactoryFunction // 元素实例的类型 => { render: () => void } 

元素的实例类型很有趣,因为它必须赋值给JSX.ElementClass或抛出一个错误。 默认的JSX.ElementClass{},但是它可以被扩展用来限制JSX的类型以符合相应的接口。

declare namespace JSX { interface ElementClass {
    render: any;
  }
} class MyComponent {
  render() {}
} function MyFactoryFunction() { return { render: () => {} }
}

<MyComponent />; // 正确 <MyFactoryFunction />; // 正确 class NotAValidComponent {} function NotAValidFactoryFunction() { return {};
}

<NotAValidComponent />; // 错误 <NotAValidFactoryFunction />; // 错误 

属性类型检查

属性类型检查的第一步是确定元素属性类型。 这在固有元素和基于值的元素之间稍有不同。

对于固有元素,这是JSX.IntrinsicElements属性的类型。

declare namespace JSX { interface IntrinsicElements {
    foo: { bar?: boolean }
  }
} // `foo`的元素属性类型为`{bar?: boolean}` <foo bar />; 

对于基于值的元素,就稍微复杂些。 它取决于先前确定的在元素实例类型上的某个属性的类型。 至于该使用哪个属性来确定类型取决于JSX.ElementAttributesProperty。 它应该使用单一的属性来定义。 这个属性名之后会被使用。

declare namespace JSX { interface ElementAttributesProperty {
    props; // 指定用来使用的属性名 }
} class MyComponent { // 在元素实例类型上指定属性 props: {
    foo?: string;
  }
} // `MyComponent`的元素属性类型为`{foo?: string}` <MyComponent foo="bar" /> 

元素属性类型用于的JSX里进行属性的类型检查。 支持可选属性和必须属性。

declare namespace JSX { interface IntrinsicElements {
    foo: { requiredProp: string; optionalProp?: number }
  }
}

<foo requiredProp="bar" />; // 正确 <foo requiredProp="bar" optionalProp={0} />; // 正确 <foo />; // 错误, 缺少 requiredProp <foo requiredProp={0} />; // 错误, requiredProp 应该是字符串 <foo requiredProp="bar" unknownProp />; // 错误, unknownProp 不存在 <foo requiredProp="bar" some-unknown-prop />; // 正确, `some-unknown-prop`不是个合法的标识符 

注意:如果一个属性名不是个合法的JS标识符(像data-*属性),并且它没出现在元素属性类型里时不会当做一个错误。

延展操作符也可以使用:

var props = { requiredProp: 'bar' };
; // 正确

var badProps = {};
; // 错误

JSX结果类型

默认地JSX表达式结果的类型为any。 你可以自定义这个类型,通过指定JSX.Element`接口。 然而,不能够从接口里检索元素,属性或JSX的子元素的类型信息。 它是一个黑盒。

嵌入的表达式

JSX允许你使用{ }标签来内嵌表达式。

var a =
{['foo', 'bar'].map(i => {i / 2})}

上面的代码产生一个错误,因为你不能用数字来除以一个字符串。 输出如下,若你使用了preserve选项:

var a =
{['foo', 'bar'].map(function (i) { return {i / 2}; })}

React整合

要想一起使用JSX和React,你应该使用React类型定义。 这些类型声明定义了JSX合适命名空间来使用React。

/// <reference path="react.d.ts" />

interface Props {
  foo: string;
}

class MyComponent extends React.Component<Props, {}> {
  render() {
    return <span>{this.props.foo}</span>
  }
}

<MyComponent foo="bar" />; // 正确
<MyComponent foo={0} />; // 错误



new 发布于 2016-10-18 00:54

[转]TypeScript中文文档——装饰器(Decorators) TypeScripts

转自:https://zhongsp.gitbooks.io/typescript-handbook/content/

介绍

随着TypeScript和ES6里引入了类,在一些场景下我们需要额外的特性来支持标注或修改类及其成员。 装饰器(Decorators)为我们在类的声明及成员上通过元编程语法添加标注提供了一种方式。 Javascript里的装饰器目前处在建议征集的第一阶段,但在TypeScript里已做为一项实验性特性予以支持。

注意  装饰器是一项实验性特性,在未来的版本中可能会发生改变。

若要启用实验性的装饰器特性,你必须在命令行或tsconfig.json里启用experimentalDecorators编译器选项:

命令行:

tsc --target ES5 --experimentalDecorators 

tsconfig.json:

{ "compilerOptions": { "target": "ES5", "experimentalDecorators": true }
} 

装饰器

装饰器是一种特殊类型的声明,它能够被附加到类声明方法访问符属性参数上。 装饰器使用@expression这种形式,expression求值后必须为一个函数,它会在运行时被调用,被装饰的声明信息做为参数传入。

例如,有一个@sealed装饰器,我们会这样定义sealed函数:

function sealed(target) { // do something with "target" ... } 

注意  后面类装饰器小节里有一个更加详细的例子。

装饰器工厂

如果我们要定制一个修饰器如何应用到一个声明上,我们得写一个装饰器工厂函数。 装饰器工厂就是一个简单的函数,它返回一个表达式,以供装饰器在运行时调用。

我们可以通过下面的方式来写一个装饰器工厂函数:

function color(value: string) { // 这是一个装饰器工厂 return function (target) { //  这是装饰器 // do something with "target" and "value"... }
} 

注意  下面方法装饰器小节里有一个更加详细的例子。

装饰器组合

多个装饰器可以同时应用到一个声明上,就像下面的示例:

  • 书写在同一行上:
@f @g x 
  • 书写在多行上:
@f
@g
x 

当多个装饰器应用于一个声明上,它们求值方式与复合函数相似。在这个模型下,当复合fg时,复合的结果(f ∘ g)(x)等同于f(g(x))。

同样的,在TypeScript里,当多个装饰器应用在一个声明上时会进行如下步骤的操作:

  1. 由上至下依次对装饰器表达式求值。
  2. 求值的结果会被当作函数,由下至上依次调用。

如果我们使用装饰器工厂的话,可以通过下面的例子来观察它们求值的顺序:

function f() { console.log("f(): evaluated"); return function (target, propertyKey: string, descriptor: PropertyDescriptor) { console.log("f(): called");
    }
} function g() { console.log("g(): evaluated"); return function (target, propertyKey: string, descriptor: PropertyDescriptor) { console.log("g(): called");
    }
} class C {
    @f()
    @g()
    method() {}
} 

在控制台里会打印出如下结果:

f(): evaluated
g(): evaluated
g(): called
f(): called 

装饰器求值

类中不同声明上的装饰器将按以下规定的顺序应用:

1
  1. 参数装饰器,其次是方法访问符,或属性装饰器应用到每个实例成员。
  2. 参数装饰器,其次是方法访问符,或属性装饰器应用到每个静态成员。
  3. 参数装饰器应用到构造函数。
  4. 类装饰器应用到类。

类装饰器

类装饰器在类声明之前被声明(紧靠着类声明)。 类装饰器应用于类构造函数,可以用来监视,修改或替换类定义。 类装饰器不能用在声明文件中(.d.ts),也不能用在任何外部上下文中(比如declare的类)。

类装饰器表达式会在运行时当作函数被调用,类的构造函数作为其唯一的参数。

如果类装饰器返回一个值,它会使用提供的构造函数来替换类的声明。

注意  如果你要返回一个新的构造函数,你必须注意处理好原来的原型链。 在运行时的装饰器调用逻辑中不会为你做这些。

下面是使用类装饰器(@sealed)的例子,应用在Greeter类:

@sealed class Greeter {
    greeting: string; constructor(message: string) { this.greeting = message;
    }
    greet() { return "Hello, " + this.greeting;
    }
} 

我们可以这样定义@sealed装饰器:

function sealed(constructor: Function) { Object.seal(constructor);
    Object.seal(constructor.prototype);
} 

@sealed被执行的时候,它将密封此类的构造函数和原型。(注:参见Object.seal)

方法装饰器

方法装饰器声明在一个方法的声明之前(紧靠着方法声明)。 它会被应用到方法的属性描述符上,可以用来监视,修改或者替换方法定义。 方法装饰器不能用在声明文件(.d.ts),重载或者任何外部上下文(比如declare的类)中。

方法装饰器表达式会在运行时当作函数被调用,传入下列3个参数:

  1. 对于静态成员来说是类的构造函数,对于实例成员是类的原型对象。
  2. 成员的名字。
  3. 成员的属性描述符

注意  如果代码输出目标版本小于ES5Property Descriptor将会是undefined

如果方法装饰器返回一个值,它会被用作方法的属性描述符

注意  如果代码输出目标版本小于ES5返回值会被忽略。

下面是一个方法装饰器(@enumerable)的例子,应用于Greeter类的方法上:

class Greeter {
    greeting: string; constructor(message: string) { this.greeting = message;
    }

    @enumerable(false)
    greet() { return "Hello, " + this.greeting;
    }
} 

我们可以用下面的函数声明来定义@enumerable装饰器:

function enumerable(value: boolean) { return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
        descriptor.enumerable = value;
    };
} 

这里的@enumerable(false)是一个装饰器工厂。 当装饰器@enumerable(false)被调用时,它会修改属性描述符的enumerable属性。

访问器装饰器

访问器装饰器声明在一个访问器的声明之前(紧靠着访问器声明)。 访问器装饰器应用于访问器的属性描述符并且可以用来监视,修改或替换一个访问器的定义。 访问器装饰器不能用在声明文件中(.d.ts),或者任何外部上下文(比如declare的类)里。

注意  TypeScript不允许同时装饰一个成员的getset访问器。取而代之的是,一个成员的所有装饰的必须应用在文档顺序的第一个访问器上。这是因为,在装饰器应用于一个属性描述符时,它联合了getset访问器,而不是分开声明的。

访问器装饰器表达式会在运行时当作函数被调用,传入下列3个参数:

  1. 对于静态成员来说是类的构造函数,对于实例成员是类的原型对象。
  2. 成员的名字。
  3. 成员的属性描述符

注意  如果代码输出目标版本小于ES5Property Descriptor将会是undefined

如果访问器装饰器返回一个值,它会被用作方法的属性描述符

注意  如果代码输出目标版本小于ES5返回值会被忽略。

下面是使用了访问器装饰器(@configurable)的例子,应用于Point类的成员上:

class Point { private _x: number; private _y: number; constructor(x: number, y: number) { this._x = x; this._y = y;
    }

    @configurable(false) get x() { return this._x; }

    @configurable(false) get y() { return this._y; }
} 

我们可以通过如下函数声明来定义@configurable装饰器:

function configurable(value: boolean) { return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
        descriptor.configurable = value;
    };
} 

属性装饰器

属性装饰器声明在一个属性声明之前(紧靠着属性声明)。 属性装饰器不能用在声明文件中(.d.ts),或者任何外部上下文(比如declare的类)里。

属性装饰器表达式会在运行时当作函数被调用,传入下列2个参数:

  1. 对于静态成员来说是类的构造函数,对于实例成员是类的原型对象。
  2. 成员的名字。

注意  属性描述符不会做为参数传入属性装饰器,这与TypeScript是如何初始化属性装饰器的有关。 因为目前没有办法在定义一个原型对象的成员时描述一个实例属性,并且没办法监视或修改一个属性的初始化方法。 因此,属性描述符只能用来监视类中是否声明了某个名字的属性。

如果属性装饰器返回一个值,它会被用作方法的属性描述符

注意  如果代码输出目标版本小于ES5,返回值会被忽略。

如果访问符装饰器返回一个值,它会被用作方法的属性描述符

我们可以用它来记录这个属性的元数据,如下例所示:

class Greeter {
    @format("Hello, %s")
    greeting: string; constructor(message: string) { this.greeting = message;
    }
    greet() { let formatString = getFormat(this, "greeting"); return formatString.replace("%s", this.greeting);
    }
} 

然后定义@format装饰器和getFormat函数:

import "reflect-metadata"; const formatMetadataKey = Symbol("format"); function format(formatString: string) { return Reflect.metadata(formatMetadataKey, formatString);
} function getFormat(target: any, propertyKey: string) { return Reflect.getMetadata(formatMetadataKey, target, propertyKey);
} 

这个@format("Hello, %s")装饰器是个 装饰器工厂。 当@format("Hello, %s")被调用时,它添加一条这个属性的元数据,通过reflect-metadata库里的Reflect.metadata函数。 当getFormat被调用时,它读取格式的元数据。

注意  这个例子需要使用reflect-metadata库。 查看元数据了解reflect-metadata库更详细的信息。

参数装饰器

参数装饰器声明在一个参数声明之前(紧靠着参数声明)。 参数装饰器应用于类构造函数或方法声明。 参数装饰器不能用在声明文件(.d.ts),重载或其它外部上下文(比如declare的类)里。

参数装饰器表达式会在运行时当作函数被调用,传入下列3个参数:

  1. 对于静态成员来说是类的构造函数,对于实例成员是类的原型对象。
  2. 成员的名字。
  3. 参数在函数参数列表中的索引。

注意  参数装饰器只能用来监视一个方法的参数是否被传入。

参数装饰器的返回值会被忽略。

下例定义了参数装饰器(@required)并应用于Greeter类方法的一个参数:

class Greeter {
    greeting: string; constructor(message: string) { this.greeting = message;
    }

    @validate
    greet(@required name: string) { return "Hello " + name + ", " + this.greeting;
    }
} 

然后我们使用下面的函数定义 @required 和 @validate 装饰器:

import "reflect-metadata"; const requiredMetadataKey = Symbol("required"); function required(target: Object, propertyKey: string | symbol, parameterIndex: number) { let existingRequiredParameters: number[] = Reflect.getOwnMetadata(requiredMetadataKey, target, propertyKey) || [];
    existingRequiredParameters.push(parameterIndex);
    Reflect.defineMetadata(requiredMetadataKey, existingRequiredParameters, target, propertyKey);
} function validate(target: any, propertyName: string, descriptor: TypedPropertyDescriptor<Function>) { let method = descriptor.value;
    descriptor.value = function () { let requiredParameters: number[] = Reflect.getOwnMetadata(requiredMetadataKey, target, propertyName); if (requiredParameters) { for (let parameterIndex of requiredParameters) { if (parameterIndex >= arguments.length || arguments[parameterIndex] === undefined) { throw new Error("Missing required argument.");
                }
            }
        } return method.apply(this, arguments);
    }
} 

@required装饰器添加了元数据实体把参数标记为必需的。 @validate装饰器把greet方法包裹在一个函数里在调用原先的函数前验证函数参数。

注意  这个例子使用了reflect-metadata库。 查看元数据了解reflect-metadata库的更多信息。

元数据

一些例子使用了reflect-metadata库来支持实验性的metadata API。 这个库还不是ECMAScript (JavaScript)标准的一部分。 然而,当装饰器被ECMAScript官方标准采纳后,这些扩展也将被推荐给ECMAScript以采纳。

你可以通过npm安装这个库:

npm i reflect-metadata --save 

TypeScript支持为带有装饰器的声明生成元数据。 你需要在命令行或tsconfig.json里启用emitDecoratorMetadata编译器选项。

Command Line:

tsc --target ES5 --experimentalDecorators --emitDecoratorMetadata 

tsconfig.json:

{ "compilerOptions": { "target": "ES5", "experimentalDecorators": true, "emitDecoratorMetadata": true }
} 

当启用后,只要reflect-metadata库被引入了,设计阶段添加的类型信息可以在运行时使用。

如下例所示:

import "reflect-metadata"; class Point {
    x: number;
    y: number;
} class Line { private _p0: Point; private _p1: Point;

    @validate set p0(value: Point) { this._p0 = value; } get p0() { return this._p0; }

    @validate set p1(value: Point) { this._p1 = value; } get p1() { return this._p1; }
} function validate<T>(target: any, propertyKey: string, descriptor: TypedPropertyDescriptor<T>) { let set = descriptor.set;
    descriptor.set = function (value: T) { let type = Reflect.getMetadata("design:type", target, propertyKey); if (!(value instanceof type)) { throw new TypeError("Invalid type.");
        }
    }
} 

TypeScript编译器可以通过@Reflect.metadata装饰器注入设计阶段的类型信息。 你可以认为它相当于下面的TypeScript:

class Line { private _p0: Point; private _p1: Point;

    @validate
    @Reflect.metadata("design:type", Point) set p0(value: Point) { this._p0 = value; } get p0() { return this._p0; }

    @validate
    @Reflect.metadata("design:type", Point) set p1(value: Point) { this._p1 = value; } get p1() { return this._p1; }
} 

注意  装饰器元数据是个实验性的特性并且可能在以后的版本中发生破坏性的改变(breaking changes)。




new 发布于 2016-10-18 00:54

[转]TypeScript中文文档——通用类型 TypeScripts

转自:https://zhongsp.gitbooks.io/typescript-handbook/content/

介绍

这节介绍TypeScript里的类型推论。即,类型是在哪里如何被推断的。

基础

TypeScript里,在有些没有明确指出类型的地方,类型推论会帮助提供类型。如下面的例子

let x = 3; 

变量x的类型被推断为数字。 这种推断发生在初始化变量和成员,设置默认参数值和决定函数返回值时。

大多数情况下,类型推论是直截了当地。 后面的小节,我们会浏览类型推论时的细微差别。

最佳通用类型

当需要从几个表达式中推断类型时候,会使用这些表达式的类型来推断出一个最合适的通用类型。例如,

let x = [0, 1, null]; 

为了推断x的类型,我们必须考虑所有元素的类型。 这里有两种选择:numbernull。 计算通用类型算法会考虑所有的候选类型,并给出一个兼容所有候选类型的类型。

由于最终的通用类型取自候选类型,有些时候候选类型共享相同的通用类型,但是却没有一个类型能做为所有候选类型的类型。例如:

let zoo = [new Rhino(), new Elephant(), new Snake()]; 

这里,我们想让zoo被推断为Animal[]类型,但是这个数组里没有对象是Animal类型的,因此不能推断出这个结果。 为了更正,当候选类型不能使用的时候我们需要明确的指出类型:

let zoo: Animal[] = [new Rhino(), new Elephant(), new Snake()]; 

如果没有找到最佳通用类型的话,类型推论的结果是空对象类型,{}。 因为这个类型没有任何成员,所以访问其成员的时候会报错。

上下文类型

TypeScript类型推论也可能按照相反的方向进行。 这被叫做“按上下文归类”。按上下文归类会发生在表达式的类型与所处的位置相关时。比如:

window.onmousedown = function(mouseEvent) { console.log(mouseEvent.buton); //<- Error }; 

这个例子会得到一个类型错误,TypeScript类型检查器使用Window.onmousedown函数的类型来推断右边函数表达式的类型。 因此,就能推断出mouseEvent参数的类型了。 如果函数表达式不是在上下文类型的位置,mouseEvent参数的类型需要指定为any,这样也不会报错了。

如果上下文类型表达式包含了明确的类型信息,上下文的类型被忽略。 重写上面的例子:

window.onmousedown = function(mouseEvent: any) { console.log(mouseEvent.buton); //<- Now, no error is given }; 

这个函数表达式有明确的参数类型注解,上下文类型被忽略。 这样的话就不报错了,因为这里不会使用到上下文类型。

上下文归类会在很多情况下使用到。 通常包含函数的参数,赋值表达式的右边,类型断言,对象成员和数组字面量和返回值语句。 上下文类型也会做为最佳通用类型的候选类型。比如:

function createZoo(): Animal[] {
    return [new Rhino(), new Elephant(), new Snake()];
} 

这个例子里,最佳通用类型有4个候选者:AnimalRhinoElephantSnake。 当然,Animal会被做为最佳通用类型。


new 发布于 2016-10-17 09:52

[转]TypeScript中文文档——类型兼容性 TypeScripts

转自:https://zhongsp.gitbooks.io/typescript-handbook/content/

介绍

TypeScript里的类型兼容性是基于结构子类型的。 结构类型是一种只使用其成员来描述类型的方式。 它正好与名义(nominal)类型形成对比。(译者注:在基于名义类型的类型系统中,数据类型的兼容性或等价性是通过明确的声明和/或类型的名称来决定的。这与结构性类型系统不同,它是基于类型的组成结构,且不要求明确地声明。) 看下面的例子:

interface Named {
    name: string;
} class Person {
    name: string;
} let p: Named; // OK, because of structural typing p = new Person(); 

在使用基于名义类型的语言,比如C#或Java中,这段代码会报错,因为Person类没有明确说明其实现了Named接口。

TypeScript的结构性子类型是根据JavaScript代码的典型写法来设计的。 因为JavaScript里广泛地使用匿名对象,例如函数表达式和对象字面量,所以使用结构类型系统来描述这些类型比使用名义类型系统更好。

关于可靠性的注意事项

TypeScript的类型系统允许某些在编译阶段无法确认其安全性的操作。当一个类型系统具此属性时,被当做是“不可靠”的。TypeScript允许这种不可靠行为的发生是经过仔细考虑的。通过这篇文章,我们会解释什么时候会发生这种情况和其有利的一面。

开始

TypeScript结构化类型系统的基本规则是,如果x要兼容y,那么y至少具有与x相同的属性。比如:

interface Named {
    name: string;
} let x: Named; // y's inferred type is { name: string; location: string; } let y = { name: 'Alice', location: 'Seattle' };
x = y; 

这里要检查y是否能赋值给x,编译器检查x中的每个属性,看是否能在y中也找到对应属性。 在这个例子中,y必须包含名字是namestring类型成员。y满足条件,因此赋值正确。

检查函数参数时使用相同的规则:

function greet(n: Named) {
    alert('Hello, ' + n.name);
}
greet(y); // OK 

注意,y有个额外的location属性,但这不会引发错误。 只有目标类型(这里是Named)的成员会被一一检查是否兼容。

这个比较过程是递归进行的,检查每个成员及子成员。

比较两个函数

相对来讲,在比较原始类型和对象类型的时候是比较容易理解的,问题是如何判断两个函数是兼容的。 下面我们从两个简单的函数入手,它们仅是参数列表略有不同:

let x = (a: number) => 0; let y = (b: number, s: string) => 0;

y = x; // OK x = y; // Error 

要查看x是否能赋值给y,首先看它们的参数列表。 x的每个参数必须能在y里找到对应类型的参数。 注意的是参数的名字相同与否无所谓,只看它们的类型。 这里,x的每个参数在y中都能找到对应的参数,所以允许赋值。

第二个赋值错误,因为y有个必需的第二个参数,但是x并没有,所以不允许赋值。

你可能会疑惑为什么允许忽略参数,像例子y = x中那样。 原因是忽略额外的参数在JavaScript里是很常见的。 例如,Array#forEach给回调函数传3个参数:数组元素,索引和整个数组。 尽管如此,传入一个只使用第一个参数的回调函数也是很有用的:

let items = [1, 2, 3]; // Don't force these extra arguments items.forEach((item, index, array) => console.log(item)); // Should be OK! items.forEach((item) => console.log(item)); 

下面来看看如何处理返回值类型,创建两个仅是返回值类型不同的函数:

let x = () => ({name: 'Alice'}); let y = () => ({name: 'Alice', location: 'Seattle'});

x = y; // OK y = x; // Error because x() lacks a location property 

类型系统强制源函数的返回值类型必须是目标函数返回值类型的子类型。

函数参数双向协变

当比较函数参数类型时,只有当源函数参数能够赋值给目标函数或者反过来时才能赋值成功。 这是不稳定的,因为调用者可能传入了一个具有更精确类型信息的函数,但是调用这个传入的函数的时候却使用了不是那么精确的类型信息。 实际上,这极少会发生错误,并且能够实现很多JavaScript里的常见模式。例如:

enum EventType { Mouse, Keyboard }

interface Event { timestamp: number; }
interface MouseEvent extends Event { x: number; y: number }
interface KeyEvent extends Event { keyCode: number }

function listenEvent(eventType: EventType, handler: (n: Event) => void) {
    /* ... */
}

// Unsound, but useful and common
listenEvent(EventType.Mouse, (e: MouseEvent) => console.log(e.x + ',' + e.y));

// Undesirable alternatives in presence of soundness
listenEvent(EventType.Mouse, (e: Event) => console.log((<MouseEvent>e).x + ',' + (<MouseEvent>e).y));
listenEvent(EventType.Mouse, <(e: Event) => void>((e: MouseEvent) => console.log(e.x + ',' + e.y)));

// Still disallowed (clear error). Type safety enforced for wholly incompatible types
listenEvent(EventType.Mouse, (e: number) => console.log(e)); 

可选参数及剩余参数

比较函数兼容性的时候,可选参数与必须参数是可交换的。 原类型上额外的可选参数并不会造成错误,目标类型的可选参数没有对应的参数也不是错误。

当一个函数有剩余参数时,它被当做无限个可选参数。

这对于类型系统来说是不稳定的,但从运行时的角度来看,可选参数一般来说是不强制的,因为对于大多数函数来说相当于传递了一些undefinded

有一个好的例子,常见的函数接收一个回调函数并用对于程序员来说是可预知的参数但对类型系统来说是不确定的参数来调用:

function invokeLater(args: any[], callback: (...args: any[]) => void) {
    /* ... Invoke callback with 'args' ... */
}

// Unsound - invokeLater "might" provide any number of arguments
invokeLater([1, 2], (x, y) => console.log(x + ', ' + y));

// Confusing (x and y are actually required) and undiscoverable
invokeLater([1, 2], (x?, y?) => console.log(x + ', ' + y)); 

函数重载

对于有重载的函数,源函数的每个重载都要在目标函数上找到对应的函数签名。 这确保了目标函数可以在所有源函数可调用的地方调用。

枚举

枚举类型与数字类型兼容,并且数字类型与枚举类型兼容。不同枚举类型之间是不兼容的。比如,

enum Status { Ready, Waiting }; enum Color { Red, Blue, Green }; let status = Status.Ready;
status = Color.Green; //error 

类与对象字面量和接口差不多,但有一点不同:类有静态部分和实例部分的类型。 比较两个类类型的对象时,只有实例的成员会被比较。 静态成员和构造函数不在比较的范围内。

class Animal {
    feet: number; constructor(name: string, numFeet: number) { }
} class Size {
    feet: number; constructor(numFeet: number) { }
} let a: Animal; let s: Size;

a = s; //OK s = a; //OK 

类的私有成员

私有成员会影响兼容性判断。 当类的实例用来检查兼容时,如果它包含一个私有成员,那么目标类型必须包含来自同一个类的这个私有成员。 这允许子类赋值给父类,但是不能赋值给其它有同样类型的类。

泛型

因为TypeScript是结构性的类型系统,类型参数只影响使用其做为类型一部分的结果类型。比如,

interface Empty<T> {
} let x: Empty<number>; let y: Empty<string>;

x = y; // okay, y matches structure of x 

上面代码里,xy是兼容的,因为它们的结构使用类型参数时并没有什么不同。 把这个例子改变一下,增加一个成员,就能看出是如何工作的了:

interface NotEmpty<T> {
    data: T;
} let x: NotEmpty<number>; let y: NotEmpty<string>;

x = y; // error, x and y are not compatible 

在这里,泛型类型在使用时就好比不是一个泛型类型。

对于没指定泛型类型的泛型参数时,会把所有泛型参数当成any比较。 然后用结果类型进行比较,就像上面第一个例子。

比如,

let identity = function<T>(x: T): T { // ... } let reverse = function<U>(y: U): U { // ... }

identity = reverse; // Okay because (x: any)=>any matches (y: any)=>any 

高级主题

子类型与赋值

目前为止,我们使用了兼容性,它在语言规范里没有定义。 在TypeScript里,有两种类型的兼容性:子类型与赋值。 它们的不同点在于,赋值扩展了子类型兼容,允许给any赋值或从any取值和允许数字赋值给枚举类型或枚举类型赋值给数字。

语言里的不同地方分别使用了它们之中的机制。 实际上,类型兼容性是由赋值兼容性来控制的甚至在implementsextends语句里。 更多信息,请参阅TypeScript语言规范.



new 发布于 2016-10-17 09:52

[转]TypeScript中文文档——高级类型 TypeScripts

转自:https://zhongsp.gitbooks.io/typescript-handbook/content/

联合类型

偶尔你会遇到这种情况,一个代码库希望传入numberstring类型的参数。 例如下面的函数:

/**
 * Takes a string and adds "padding" to the left.
 * If 'padding' is a string, then 'padding' is appended to the left side.
 * If 'padding' is a number, then that number of spaces is added to the left side.
 */ function padLeft(value: string, padding: any) { if (typeof padding === "number") { return Array(padding + 1).join(" ") + value;
    } if (typeof padding === "string") { return padding + value;
    } throw new Error(`Expected string or number, got '${padding}'.`);
}

padLeft("Hello world", 4); // returns "    Hello world" 

padLeft存在一个问题,padding参数的类型指定成了any。 这就是说我们可以传入一个既不是number也不是string类型的参数,但是TypeScript却不报错。

let indentedString = padLeft("Hello world", true); // 编译阶段通过,运行时报错 

在传统的面向对象语言里,我们可能会将这两种类型抽象成有层级的类型。 这么做显然是非常清晰的,但同时也存在了过度设计。 padLeft原始版本的好处之一是允许我们传入原始类型。 这做的话使用起来既方便又不过于繁锁。 如果我们就是想使用已经存在的函数的话,这种新的方式就不适用了。

代替any, 我们可以使用联合类型做为padding的参数:

/**
 * Takes a string and adds "padding" to the left.
 * If 'padding' is a string, then 'padding' is appended to the left side.
 * If 'padding' is a number, then that number of spaces is added to the left side.
 */ function padLeft(value: string, padding: string | number) { // ... } let indentedString = padLeft("Hello world", true); // errors during compilation 

联合类型表示一个值可以是几种类型之一。 我们用竖线(|)分隔每个类型,所以number | string | boolean表示一个值可以是numberstring,或boolean

如果一个值是联合类型,我们只能访问此联合类型的所有类型里共有的成员。

interface Bird {
    fly();
    layEggs();
} interface Fish {
    swim();
    layEggs();
} function getSmallPet(): Fish | Bird { // ... } let pet = getSmallPet();
pet.layEggs(); // okay pet.swim(); // errors 

这里的联合类型可能有点复杂,但是你很容易就习惯了。 如果一个值类型是A | B,我们只能确定它具有成员同时存在于AB里。 这个例子里,Bird具有一个fly成员。 我们不能确定一个Bird | Fish类型的变量是否有fly方法。 如果变量在运行时是Fish类型,那么调用pet.fly()就出错了。

类型保护与区分类型

联合类型非常适合这样的情形,可接收的值有不同的类型。 当我们想明确地知道是否拿到Fish时会怎么做? JavaScript里常用来区分2个可能值的方法是检查它们是否存在。 像之前提到的,我们只能访问联合类型的所有类型中共有的成员。

let pet = getSmallPet(); // 每一个成员访问都会报错 if (pet.swim) {
    pet.swim();
} else if (pet.fly) {
    pet.fly();
} 

为了让这段代码工作,我们要使用类型断言:

let pet = getSmallPet(); if ((<Fish>pet).swim) {
    (<Fish>pet).swim();
} else {
    (<Bird>pet).fly();
} 

用户自定义的类型保护

可以注意到我们使用了多次类型断言。 如果我们只要检查过一次类型,就能够在后面的每个分支里清楚pet的类型的话就好了。

TypeScript里的类型保护机制让它成为了现实。 类型保护就是一些表达式,它们会在运行时检查以确保在某个作用域里的类型。 要定义一个类型保护,我们只要简单地定义一个函数,它的返回值是一个类型断言

function isFish(pet: Fish | Bird): pet is Fish { return (<Fish>pet).swim !== undefined;
} 

在这个例子里,pet is Fish就是类型断言。 一个断言是parameterName is Type这种形式,parameterName必须是来自于当前函数签名里的一个参数名。

每当使用一些变量调用isFish时,TypeScript会将变量缩减为那个具体的类型,只要这个类型与变量的原始类型是兼容的。

// 'swim' 和 'fly' 调用都没有问题了 if (isFish(pet)) {
    pet.swim();
} else {
    pet.fly();
} 

注意TypeScript不仅知道在if分支里petFish类型; 它还清楚在else分支里,一定不是Fish类型,一定是Bird类型。

typeof类型保护

我们还没有真正的讨论过如何使用联合类型来实现padLeft。 我们可以像下面这样利用类型断言来写:

function isNumber(x: any): x is number { return typeof x === "number";
} function isString(x: any): x is string { return typeof x === "string";
} function padLeft(value: string, padding: string | number) { if (isNumber(padding)) { return Array(padding + 1).join(" ") + value;
    } if (isString(padding)) { return padding + value;
    } throw new Error(`Expected string or number, got '${padding}'.`);
} 

然而,必须要定义一个函数来判断类型是否是原始类型,这太痛苦了。 幸运的是,现在我们不必将typeof x === "number"抽象成一个函数,因为TypeScript可以将它识别为一个类型保护。 也就是说我们可以直接在代码里检查类型了。

function padLeft(value: string, padding: string | number) { if (typeof padding === "number") { return Array(padding + 1).join(" ") + value;
    } if (typeof padding === "string") { return padding + value;
    } throw new Error(`Expected string or number, got '${padding}'.`);
} 

这些typeof类型保护只有2个形式能被识别:typeof v === "typename"typeof v !== "typename""typename"必须是"number""string""boolean""symbol"。 但是TypeScript并不会阻止你与其它字符串比较,或者将它们位置对换,且语言不会把它们识别为类型保护。

instanceof类型保护

如果你已经阅读了typeof类型保护并且对JavaScript里的instanceof操作符熟悉的话,你可能已经猜到了这节要讲的内容。

instanceof类型保护是通过其构造函数来细化其类型。 比如,我们借鉴一下之前字符串填充的例子:

interface Padder {
    getPaddingString(): string } class SpaceRepeatingPadder implements Padder { constructor(private numSpaces: number) { }
    getPaddingString() { return Array(this.numSpaces + 1).join(" ");
    }
} class StringPadder implements Padder { constructor(private value: string) { }
    getPaddingString() { return this.value;
    }
} function getRandomPadder() { return Math.random() < 0.5 ? new SpaceRepeatingPadder(4) : new StringPadder("  ");
} // 类型为SpaceRepeatingPadder | StringPadder let padder: Padder = getRandomPadder(); if (padder instanceof SpaceRepeatingPadder) {
    padder; // 类型细化为'SpaceRepeatingPadder' } if (padder instanceof StringPadder) {
    padder; // 类型细化为'StringPadder' } 

instanceof的右侧要求为一个构造函数,TypeScript将细化为:

  1. 这个函数的prototype属性,如果它的类型不为any的话
  2. 类型中构造签名所返回的类型的联合,顺序保持一至。

交叉类型

交叉类型与联合类型密切相关,但是用法却完全不同。 一个交叉类型,例如Person & Serializable & Loggable,同时是PersonSerializableLoggable。 就是说这个类型的对象同时拥有这三种类型的成员。 实际应用中,你大多会在混入中见到交叉类型。 下面是一个混入的例子:

function extend<T, U>(first: T, second: U): T & U { let result = <T & U>{}; for (let id in first) {
        (<any>result)[id] = (<any>first)[id];
    } for (let id in second) { if (!result.hasOwnProperty(id)) {
            (<any>result)[id] = (<any>second)[id];
        }
    } return result;
} class Person { constructor(public name: string) { }
} interface Loggable {
    log(): void;
} class ConsoleLogger implements Loggable {
    log() { // ... }
} var jim = extend(new Person("Jim"), new ConsoleLogger()); var n = jim.name;
jim.log(); 

类型别名

类型别名会给一个类型起个新名字。 类型别名有时和接口很像,但是可以作用于原始值,联合类型,元组以及其它任何你需要手写的类型。

type Name = string; type NameResolver = () => string; type NameOrResolver = Name | NameResolver; function getName(n: NameOrResolver): Name { if (typeof n === 'string') { return n;
    } else { return n();
    }
} 

起别名不会新建一个类型 - 它创建了一个新名字来引用那个类型。 给原始类型起别名通常没什么用,尽管可以做为文档的一种形式使用。

同接口一样,类型别名也可以是泛型 - 我们可以添加类型参数并且在别名声明的右侧传入:

type Container<T> = { value: T }; 

我们也可以使用类型别名来在属性里引用自己:

type Tree<T> = {
    value: T;
    left: Tree<T>;
    right: Tree<T>;
} 

然而,类型别名不能够出现在声名语句的右侧:

type Yikes = Array<Yikes>; // 错误 

接口 vs. 类型别名

像我们提到的,类型别名可以像接口一样;然而,仍有一些细微差别。

一个重要区别是类型别名不能被extendsimplements也不能去extendsimplements其它类型。 因为软件中的对象应该对于扩展是开放的,但是对于修改是封闭的,你应该尽量去使用接口代替类型别名。

另一方面,如果你无法通过接口来描述一个类型并且需要使用联合类型或元组类型,这时通常会使用类型别名。

字符串字面量类型

字符串字面量类型允许你指定字符串必须的固定值。 在实际应用中,字符串字面量类型可以与联合类型,类型保护和类型别名很好的配合。 通过结合使用这些特性,你可以实现类似枚举类型的字符串。

type Easing = "ease-in" | "ease-out" | "ease-in-out"; class UIElement {
    animate(dx: number, dy: number, easing: Easing) { if (easing === "ease-in") { // ... } else if (easing === "ease-out") {
        } else if (easing === "ease-in-out") {
        } else { // error! should not pass null or undefined. }
    }
} let button = new UIElement();
button.animate(0, 0, "ease-in");
button.animate(0, 0, "uneasy"); // error: "uneasy" is not allowed here 

你只能从三种允许的字符中选择其一来做为参数传递,传入其它值则会产生错误。

Argument of type '"uneasy"' is not assignable to parameter of type '"ease-in" | "ease-out" | "ease-in-out"' 

字符串字面量类型还可以用于区分函数重载:

function createElement(tagName: "img"): HTMLImageElement;
function createElement(tagName: "input"): HTMLInputElement;
// ... more overloads ...
function createElement(tagName: string): Element {
    // ... code goes here ...
} 

多态的this类型

多态的this类型表示的是某个包含类或接口的子类型。 这被称做F-bounded多态性。 它能很容易的表现连贯接口间的继承,比如。 在计算器的例子里,在每个操作之后都返回this类型:

class BasicCalculator { public constructor(protected value: number = 0) { } public currentValue(): number { return this.value;
    } public add(operand: number): this { this.value += operand; return this;
    } public multiply(operand: number): this { this.value *= operand; return this;
    } // ... other operations go here ... } let v = new BasicCalculator(2)
            .multiply(5)
            .add(1)
            .currentValue(); 

由于这个类使用了this类型,你可以继承它,新的类可以直接使用之前的方法,不需要做任何的改变。

class ScientificCalculator extends BasicCalculator { public constructor(value = 0) { super(value);
    } public sin() { this.value = Math.sin(this.value); return this;
    } // ... other operations go here ... } let v = new ScientificCalculator(2)
        .multiply(5)
        .sin()
        .add(1)
        .currentValue(); 

如果没有this类型,ScientificCalculator就不能够在继承BasicCalculator的同时还保持接口的连贯性。 multiply将会返回BasicCalculator,它并没有sin方法。 然而,使用this类型,multiply会返回this,在这里就是ScientificCalculator



new 发布于 2016-10-17 09:51

[转]TypeScript中文文档——迭代 TypeScripts

转自:https://zhongsp.gitbooks.io/typescript-handbook/content/

可迭代性

当一个对象实现了Symbol.iterator属性时,我们认为它是可迭代的。 一些内置的类型如ArrayMapSetStringInt32ArrayUint32Array等都已经实现了各自的Symbol.iterator。 对象上的Symbol.iterator函数负责返回供迭代的值。

for..of 语句

for..of会遍历可迭代的对象,调用对象上的Symbol.iterator方法。 下面是在数组上使用for..of的简单例子:

let someArray = [1, "string", false]; for (let entry of someArray) { console.log(entry); // 1, "string", false } 

for..of vs. for..in 语句

for..offor..in均可迭代一个列表;但是用于迭代的值却不同,for..in迭代的是对象的  的列表,而for..of则迭代对象的键对应的值。

下面的例子展示了两者之间的区别:

let list = [4, 5, 6]; for (let i in list) { console.log(i); // "0", "1", "2", } for (let i of list) { console.log(i); // "4", "5", "6" } 

另一个区别是for..in可以操作任何对象;它提供了查看对象属性的一种方法。 但是for..of关注于迭代对象的值。内置对象MapSet已经实现了Symbol.iterator方法,让我们可以访问它们保存的值。

let pets = new Set(["Cat", "Dog", "Hamster"]);
pets["species"] = "mammals"; for (let pet in pets) { console.log(pet); // "species" } for (let pet of pets) { console.log(pet); // "Cat", "Dog", "Hamster" } 

代码生成

目标为 ES5 和 ES3

当生成目标为ES5或ES3,迭代器只允许在Array类型上使用。 在非数组值上使用for..of语句会得到一个错误,就算这些非数组值已经实现了Symbol.iterator属性。

编译器会生成一个简单的for循环做为for..of循环,比如:

let numbers = [1, 2, 3]; for (let num of numbers) { console.log(num);
} 

生成的代码为:

var numbers = [1, 2, 3]; for (var _i = 0; _i < numbers.length; _i++) { var num = numbers[_i]; console.log(num);
} 

目标为 ECMAScript 2015 或更高

当目标为兼容ECMAScipt 2015的引擎时,编译器会生成相应引擎的for..of内置迭代器实现方式。



new 发布于 2016-10-17 09:51

[转]TypeScript中文文档——模块 TypeScripts

转自:https://zhongsp.gitbooks.io/typescript-handbook/content/

关于术语的一点说明: 请务必注意一点,TypeScript 1.5里术语名已经发生了变化。 “内部模块”现在称做“命名空间”。 “外部模块”现在则简称为“模块”,这是为了与ECMAScript 2015里的术语保持一致,(也就是说 module X { 相当于现在推荐的写法 namespace X {)。

介绍

从ECMAScript 2015开始,JavaScript引入了模块的概念。TypeScript也沿用这个概念。

模块在其自身的作用域里执行,而不是在全局作用域里;这意味着定义在一个模块里的变量,函数,类等等在模块外部是不可见的,除非你明确地使用export形式之一导出它们。 相反,如果想使用其它模块导出的变量,函数,类,接口等的时候,你必须要导入它们,可以使用import形式之一。

模块是自声明的;两个模块之间的关系是通过在文件级别上使用imports和exports建立的。

模块使用模块加载器去导入其它的模块。 在运行时,模块加载器的作用是在执行此模块代码前去查找并执行这个模块的所有依赖。 大家最熟知的JavaScript模块加载器是服务于Node.js的CommonJS和服务于Web应用的Require.js

TypeScript与ECMAScript 2015一样,任何包含顶级import或者export的文件都被当成一个模块。

导出

导出声明

任何声明(比如变量,函数,类,类型别名或接口)都能够通过添加export关键字来导出。

Validation.ts
export interface StringValidator {
    isAcceptable(s: string): boolean;
} 
ZipCodeValidator.ts
export const numberRegexp = /^[0-9]+$/; export class ZipCodeValidator implements StringValidator {
    isAcceptable(s: string) { return s.length === 5 && numberRegexp.test(s);
    }
} 

导出语句

导出语句很便利,因为我们可能需要对导出的部分重命名,所以上面的例子可以这样改写:

class ZipCodeValidator implements StringValidator {
    isAcceptable(s: string) { return s.length === 5 && numberRegexp.test(s);
    }
} export { ZipCodeValidator }; export { ZipCodeValidator as mainValidator }; 

重新导出

我们经常会去扩展其它模块,并且只导出那个模块的部分内容。 重新导出功能并不会在当前模块导入那个模块或定义一个新的局部变量。

ParseIntBasedZipCodeValidator.ts
export class ParseIntBasedZipCodeValidator {
    isAcceptable(s: string) { return s.length === 5 && parseInt(s).toString() === s;
    }
} // 导出原先的验证器但做了重命名 export {ZipCodeValidator as RegExpBasedZipCodeValidator} from "./ZipCodeValidator"; 

或者一个模块可以包裹多个模块,并把他们导出的内容联合在一起通过语法:export * from "module"

AllValidators.ts
export * from "./StringValidator"; // exports interface StringValidator export * from "./LettersOnlyValidator"; // exports class LettersOnlyValidator export * from "./ZipCodeValidator"; // exports class ZipCodeValidator 

导入

模块的导入操作与导出一样简单。 可以使用以下import形式之一来导入其它模块中的导出内容。

导入一个模块中的某个导出内容

import { ZipCodeValidator } from "./ZipCodeValidator"; let myValidator = new ZipCodeValidator(); 

可以对导入内容重命名

import { ZipCodeValidator as ZCV } from "./ZipCodeValidator"; let myValidator = new ZCV(); 

将整个模块导入到一个变量,并通过它来访问模块的导出部分

import * as validator from "./ZipCodeValidator"; let myValidator = new validator.ZipCodeValidator(); 

具有副作用的导入模块

尽管不推荐这么做,一些模块会设置一些全局状态供其它模块使用。 这些模块可能没有任何的导出或用户根本就不关注它的导出。 使用下面的方法来导入这类模块:

import "./my-module.js"; 

默认导出

每个模块都可以有一个default导出。 默认导出使用default关键字标记;并且一个模块只能够有一个default导出。 需要使用一种特殊的导入形式来导入default导出。

default导出十分便利。 比如,像JQuery这样的类库可能有一个默认导出jQuery$,并且我们基本上也会使用同样的名字jQuery$导出JQuery。

JQuery.d.ts
declare let $: JQuery; export default $; 
App.ts
import $ from "JQuery";

$("button.continue").html( "Next Step..." ); 

类和函数声明可以直接被标记为默认导出。 标记为默认导出的类和函数的名字是可以省略的。

ZipCodeValidator.ts
export default class ZipCodeValidator { static numberRegexp = /^[0-9]+$/;
    isAcceptable(s: string) { return s.length === 5 && ZipCodeValidator.numberRegexp.test(s);
    }
} 
Test.ts
import validator from "./ZipCodeValidator"; let myValidator = new validator(); 

或者

StaticZipCodeValidator.ts
const numberRegexp = /^[0-9]+$/; export default function (s: string) { return s.length === 5 && numberRegexp.test(s);
} 
Test.ts
import validate from "./StaticZipCodeValidator"; let strings = ["Hello", "98052", "101"]; // Use function validate strings.forEach(s => { console.log(`"${s}" ${validate(s) ? " matches" : " does not match"}`);
}); 

default导出也可以是一个值

OneTwoThree.ts
export default "123"; 
Log.ts
import num from "./OneTwoThree"; console.log(num); // "123" 

export = 和 import = require()

CommonJS和AMD都有一个exports对象的概念,它包含了一个模块的所有导出内容。

它们也支持把exports替换为一个自定义对象。 默认导出就好比这样一个功能;然而,它们却并不相互兼容。 TypeScript模块支持export =语法以支持传统的CommonJS和AMD的工作流模型。

export =语法定义一个模块的导出对象。 它可以是类,接口,命名空间,函数或枚举。

若要导入一个使用了export =的模块时,必须使用TypeScript提供的特定语法import let = require("module")

ZipCodeValidator.ts
let numberRegexp = /^[0-9]+$/; class ZipCodeValidator {
    isAcceptable(s: string) { return s.length === 5 && numberRegexp.test(s);
    }
} export = ZipCodeValidator; 
Test.ts
import zip = require("./ZipCodeValidator"); // Some samples to try let strings = ["Hello", "98052", "101"]; // Validators to use let validator = new zip(); // Show whether each string passed each validator strings.forEach(s => { console.log(`"${ s }" - ${ validator.isAcceptable(s) ? "matches" : "does not match" }`);
}); 

生成模块代码

根据编译时指定的模块目标参数,编译器会生成相应的供Node.js (CommonJS),Require.js (AMD),isomorphic (UMD), SystemJSECMAScript 2015 native modules (ES6)模块加载系统使用的代码。 想要了解生成代码中definerequire 和 register的意义,请参考相应模块加载器的文档。

下面的例子说明了导入导出语句里使用的名字是怎么转换为相应的模块加载器代码的。

SimpleModule.ts
import m = require("mod"); export let t = m.something + 1; 
AMD / RequireJS SimpleModule.js
define(["require", "exports", "./mod"], function (require, exports, mod_1) {
    exports.t = mod_1.something + 1;
}); 
CommonJS / Node SimpleModule.js
let mod_1 = require("./mod");
exports.t = mod_1.something + 1; 
UMD SimpleModule.js
(function (factory) { if (typeof module === "object" && typeof module.exports === "object") { let v = factory(require, exports); if (v !== undefined) module.exports = v;
    } else if (typeof define === "function" && define.amd) {
        define(["require", "exports", "./mod"], factory);
    }
})(function (require, exports) { let mod_1 = require("./mod");
    exports.t = mod_1.something + 1;
}); 
System SimpleModule.js
System.register(["./mod"], function(exports_1) { let mod_1; let t; return {
        setters:[ function (mod_1_1) {
                mod_1 = mod_1_1;
            }],
        execute: function() {
            exports_1("t", t = mod_1.something + 1);
        }
    }
}); 
Native ECMAScript 2015 modules SimpleModule.js
import { something } from "./mod"; export let t = something + 1; 

简单示例

下面我们来整理一下前面的验证器实现,每个模块只有一个命名的导出。

为了编译,我们必需要在命令行上指定一个模块目标。对于Node.js来说,使用--module commonjs; 对于Require.js来说,使用`--module amd。比如:

tsc --module commonjs Test.ts 

编译完成后,每个模块会生成一个单独的.js文件。 好比使用了reference标签,编译器会根据import语句编译相应的文件。

Validation.ts
export interface StringValidator {
    isAcceptable(s: string): boolean;
} 
LettersOnlyValidator.ts
import { StringValidator } from "./Validation"; const lettersRegexp = /^[A-Za-z]+$/; export class LettersOnlyValidator implements StringValidator {
    isAcceptable(s: string) { return lettersRegexp.test(s);
    }
} 
ZipCodeValidator.ts
import { StringValidator } from "./Validation"; const numberRegexp = /^[0-9]+$/; export class ZipCodeValidator implements StringValidator {
    isAcceptable(s: string) { return s.length === 5 && numberRegexp.test(s);
    }
} 
Test.ts
import { StringValidator } from "./Validation"; import { ZipCodeValidator } from "./ZipCodeValidator"; import { LettersOnlyValidator } from "./LettersOnlyValidator"; // Some samples to try let strings = ["Hello", "98052", "101"]; // Validators to use let validators: { [s: string]: StringValidator; } = {};
validators["ZIP code"] = new ZipCodeValidator();
validators["Letters only"] = new LettersOnlyValidator(); // Show whether each string passed each validator strings.forEach(s => { for (let name in validators) { console.log(`"${ s }" - ${ validators[name].isAcceptable(s) ? "matches" : "does not match" } ${ name }`);
    }
}); 

可选的模块加载和其它高级加载场景

有时候,你只想在某种条件下才加载某个模块。 在TypeScript里,使用下面的方式来实现它和其它的高级加载场景,我们可以直接调用模块加载器并且可以保证类型完全。

编译器会检测是否每个模块都会在生成的JavaScript中用到。 如果一个模块标识符只在类型注解部分使用,并且完全没有在表达式中使用时,就不会生成require这个模块的代码。 省略掉没有用到的引用对性能提升是很有益的,并同时提供了选择性加载模块的能力。

这种模式的核心是import id = require("...")语句可以让我们访问模块导出的类型。 模块加载器会被动态调用(通过require),就像下面if代码块里那样。 它利用了省略引用的优化,所以模块只在被需要时加载。 为了让这个模块工作,一定要注意import定义的标识符只能在表示类型处使用(不能在会转换成JavaScript的地方)。

为了确保类型安全性,我们可以使用typeof关键字。 typeof关键字,当在表示类型的地方使用时,会得出一个类型值,这里就表示模块的类型。

示例:Node.js里的动态模块加载
declare function require(moduleName: string): any; import { ZipCodeValidator as Zip } from "./ZipCodeValidator"; if (needZipValidation) { let ZipCodeValidator: typeof Zip = require("./ZipCodeValidator"); let validator = new ZipCodeValidator(); if (validator.isAcceptable("...")) { /* ... */ }
} 
示例:require.js里的动态模块加载
declare function require(moduleNames: string[], onLoad: (...args: any[]) => void): void;

import  * as Zip from "./ZipCodeValidator";

if (needZipValidation) {
    require(["./ZipCodeValidator"], (ZipCodeValidator: typeof Zip) => {
        let validator = new ZipCodeValidator.ZipCodeValidator();
        if (validator.isAcceptable("...")) { /* ... */ }
    });
} 
示例:System.js里的动态模块加载
declare const System: any; import { ZipCodeValidator as Zip } from "./ZipCodeValidator"; if (needZipValidation) {
    System.import("./ZipCodeValidator").then((ZipCodeValidator: typeof Zip) => { var x = new ZipCodeValidator(); if (x.isAcceptable("...")) { /* ... */ }
    });
} 

使用其它的JavaScript库

要想描述非TypeScript编写的类库的类型,我们需要声明类库所暴露出的API。

我们叫它声明因为它不是“外部程序”的具体实现。 它们通常是在.d.ts文件里定义的。 如果你熟悉C/C++,你可以把它们当做.h文件。 让我们看一些例子。

外部模块

在Node.js里大部分工作是通过加载一个或多个模块实现的。 我们可以使用顶级的export声明来为每个模块都定义一个.d.ts文件,但最好还是写在一个大的.d.ts文件里。 我们使用与构造一个外部命名空间相似的方法,但是这里使用module关键字并且把名字用引号括起来,方便之后import。 例如:

node.d.ts (simplified excerpt)
declare module "url" { export interface Url {
        protocol?: string;
        hostname?: string;
        pathname?: string;
    } export function parse(urlStr: string, parseQueryString?, slashesDenoteHost?): Url;
} declare module "path" { export function normalize(p: string): string; export function join(...paths: any[]): string; export let sep: string;
} 

现在我们可以/// <reference> node.d.ts并且使用import url = require("url");加载模块。

/// <reference path="node.d.ts"/> import * as URL from "url"; let myUrl = URL.parse("http://www.typescriptlang.org"); 

外部模块简写

假如你不想在使用一个新模块之前花时间去编写声明,你可以采用声明的简写形式以便能够快速使用它。

declarations.d.ts
declare module "hot-new-module"; 

简写模块里所有导出的类型将是any

import x, {y} from "hot-new-module";
x(y); 

模块声明通配符

某些模块加载器如SystemJS 和AMD支持导入非JavaScript内容。 它们通常会使用一个前缀或后缀来表示特殊的加载语法。 模块声明通配符可以用来表示这些情况。

declare module "*!text" { const content: string; export default content;
} // Some do it the other way around. declare module "json!*" { const value: any; export default value;
} 

现在你可以就导入匹配"*!text""json!*"的内容了。

import fileContent from "./xyz.txt!text"; import data from "json!http://example.com/data.json"; console.log(data, fileContent); 

UMD模块

有些模块被设计成兼容多个模块加载器,或者不使用模块加载器(全局变量)。 它们以UMDIsomorphic模块为代表。 这些库可以通过导入的形式或全局变量的形式访问。 例如:

math-lib.d.ts
export const isPrime(x: number): boolean; export as namespace mathLib; 

之后,这个库可以在某个模块里通过导入来使用:

import { isPrime } from "math-lib";
isPrime(2);
mathLib.isPrime(2); // ERROR: can't use the global definition from inside a module 

它同样可以通过全局变量的形式使用,但只能在某个脚本里。 (脚本是指一个不带有导入或导出的文件。)

mathLib.isPrime(2); 

创建模块结构指导

尽可能地在顶层导出

用户应该更容易地使用你模块导出的内容。 嵌套层次过多会变得难以处理,因此仔细考虑一下如何组织你的代码。

从你的模块中导出一个命名空间就是一个增加嵌套的例子。 虽然命名空间有时候有它们的用处,在使用模块的时候它们额外地增加了一层。 这对用户来说是很不便的并且通常是多余的。

导出类的静态方法也有同样的问题 - 这个类本身就增加了一层嵌套。 除非它能方便表述或便于清晰使用,否则请考虑直接导出一个辅助方法。

如果仅导出单个 class 或 function,使用 export default

就像“在顶层上导出”帮助减少用户使用的难度,一个默认的导出也能起到这个效果。 如果一个模块就是为了导出特定的内容,那么你应该考虑使用一个默认导出。 这会令模块的导入和使用变得些许简单。 比如:

MyClass.ts

export default class SomeType { constructor() { ... }
} 

MyFunc.ts

export default function getThing() { return 'thing'; } 

Consumer.ts

import t from "./MyClass"; import f from "./MyFunc"; let x = new t(); console.log(f()); 

对用户来说这是最理想的。他们可以随意命名导入模块的类型(本例为t)并且不需要多余的(.)来找到相关对象。

如果要导出多个对象,把它们放在顶层里导出

MyThings.ts

export class SomeType { /* ... */ } export function someFunc() { /* ... */ } 

相反地,当导入的时候:

明确地列出导入的名字

Consumer.ts

import { SomeType, SomeFunc } from "./MyThings"; let x = new SomeType(); let y = someFunc(); 

使用命名空间导入模式当你要导出大量内容的时候

MyLargeModule.ts

export class Dog { ... } export class Cat { ... } export class Tree { ... } export class Flower { ... } 

Consumer.ts

import * as myLargeModule from "./MyLargeModule.ts"; let x = new myLargeModule.Dog(); 

使用重新导出进行扩展

你可能经常需要去扩展一个模块的功能。 JS里常用的一个模式是JQuery那样去扩展原对象。 如我们之前提到的,模块不会像全局命名空间对象那样去合并。 推荐的方案是不要去改变原来的对象,而是导出一个新的实体来提供新的功能。

假设Calculator.ts模块里定义了一个简单的计算器实现。 这个模块同样提供了一个辅助函数来测试计算器的功能,通过传入一系列输入的字符串并在最后给出结果。

Calculator.ts

export class Calculator { private current = 0; private memory = 0; private operator: string; protected processDigit(digit: string, currentValue: number) { if (digit >= "0" && digit <= "9") { return currentValue * 10 + (digit.charCodeAt(0) - "0".charCodeAt(0));
        }
    } protected processOperator(operator: string) { if (["+", "-", "*", "/"].indexOf(operator) >= 0) { return operator;
        }
    } protected evaluateOperator(operator: string, left: number, right: number): number { switch (this.operator) { case "+": return left + right; case "-": return left - right; case "*": return left * right; case "/": return left / right;
        }
    } private evaluate() { if (this.operator) { this.memory = this.evaluateOperator(this.operator, this.memory, this.current);
        } else { this.memory = this.current;
        } this.current = 0;
    } public handelChar(char: string) { if (char === "=") { this.evaluate(); return;
        } else { let value = this.processDigit(char, this.current); if (value !== undefined) { this.current = value; return;
            } else { let value = this.processOperator(char); if (value !== undefined) { this.evaluate(); this.operator = value; return;
                }
            }
        } throw new Error(`Unsupported input: '${char}'`);
    } public getResult() { return this.memory;
    }
} export function test(c: Calculator, input: string) { for (let i = 0; i < input.length; i++) {
        c.handelChar(input[i]);
    } console.log(`result of '${input}' is '${c.getResult()}'`);
} 

这是使用导出的test函数来测试计算器。

TestCalculator.ts

import { Calculator, test } from "./Calculator"; let c = new Calculator();
test(c, "1+2*33/11="); // prints 9 

现在扩展它,添加支持输入其它进制(十进制以外),让我们来创建ProgrammerCalculator.ts

ProgrammerCalculator.ts

import { Calculator } from "./Calculator"; class ProgrammerCalculator extends Calculator { static digits = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F"]; constructor(public base: number) { super(); if (base <= 0 || base > ProgrammerCalculator.digits.length) { throw new Error("base has to be within 0 to 16 inclusive.");
        }
    } protected processDigit(digit: string, currentValue: number) { if (ProgrammerCalculator.digits.indexOf(digit) >= 0) { return currentValue * this.base + ProgrammerCalculator.digits.indexOf(digit);
        }
    }
} // Export the new extended calculator as Calculator export { ProgrammerCalculator as Calculator }; // Also, export the helper function export { test } from "./Calculator"; 

新的ProgrammerCalculator模块导出的API与原先的Calculator模块很相似,但却没有改变原模块里的对象。 下面是测试ProgrammerCalculator类的代码:

TestProgrammerCalculator.ts

import { Calculator, test } from "./ProgrammerCalculator"; let c = new Calculator(2);
test(c, "001+010="); // prints 3 

模块里不要使用命名空间

当初次进入基于模块的开发模式时,可能总会控制不住要将导出包裹在一个命名空间里。 模块具有其自己的作用域,并且只有导出的声明才会在模块外部可见。 记住这点,命名空间在使用模块时几乎没什么价值。

在组织方面,命名空间对于在全局作用域内对逻辑上相关的对象和类型进行分组是很便利的。 例如,在C#里,你会从System.Collections里找到所有集合的类型。 通过将类型有层次地组织在命名空间里,可以方便用户找到与使用那些类型。 然而,模块本身已经存在于文件系统之中,这是必须的。 我们必须通过路径和文件名找到它们,这已经提供了一种逻辑上的组织形式。 我们可以创建/collections/generic/文件夹,把相应模块放在这里面。

命名空间对解决全局作用域里命名冲突来说是很重要的。 比如,你可以有一个My.Application.Customer.AddFormMy.Application.Order.AddForm -- 两个类型的名字相同,但命名空间不同。 然而,这对于模块来说却不是一个问题。 在一个模块里,没有理由两个对象拥有同一个名字。 从模块的使用角度来说,使用者会挑出他们用来引用模块的名字,所以也没有理由发生重名的情况。

更多关于模块和命名空间的资料查看命名空间和模块

危险信号

以下均为模块结构上的危险信号。重新检查以确保你没有在对模块使用命名空间:

+
  • 文件的顶层声明是export namespace Foo { ... } (删除Foo并把所有内容向上层移动一层)
  • 文件只有一个export classexport function (考虑使用export default
  • 多个文件的顶层具有同样的export namespace Foo { (不要以为这些会合并到一个Foo中!)


new 发布于 2016-10-17 09:51