Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Hello TypeScript #35

Open
naseeihity opened this issue Dec 1, 2017 · 0 comments
Open

Hello TypeScript #35

naseeihity opened this issue Dec 1, 2017 · 0 comments
Assignees
Labels

Comments

@naseeihity
Copy link
Owner

naseeihity commented Dec 1, 2017

TypeScript——类

基于类的面向对象的编码风格。

基础

// Greeter类
class Greeter {
    // greeting属性
    greeting: string;
    // 构造函数
    constructor(message: string) {
        this.greeting = message;
    }
    // 方法
    greet() {
        return "Hello, " + this.greeting;
    }
}
// 创建了一个Greeter实例,并执行构造函数初始化他
let greeter = new Greeter("world");

继承

class Animal {
    name:string;
    constructor(theName: string) { this.name = theName; }
    move(distanceInMeters: number = 0) {
        console.log(`${this.name} moved ${distanceInMeters}m.`);
    }
}

// 使用extends实现继承
class Snake extends Animal {
    //包含构造函数的派生类必须调用super(),他会执行基类的构造方法
    constructor(name: string) { super(name); }
    move(distanceInMeters = 5) {
        console.log("Slithering...");
        super.move(distanceInMeters);
    }
}

class Horse extends Animal {
    constructor(name: string) { super(name); }
    //子类可以重写父类的放方法
    move(distanceInMeters = 45) {
        console.log("Galloping...");
        super.move(distanceInMeters);
    }
}

let sam = new Snake("Sammy the Python");
let tom: Animal = new Horse("Tommy the Palomino");

sam.move();
tom.move(34);

修饰符

TS中,成员默认为public,我们也可以手动将其标记为public

private

被标记为private的成员,不可以从类的外部访问。如果其中一个类型里包含一个private成员,那么只有当另外一个类型中也存在这样一个 private成员, 并且它们都是来自同一处声明时,我们才认为这两个类型是兼容的。

protected

protected成员在派生类中仍然可以访问,构造函数被标记为protected意味着他不能再包含他的类外被实例化,但是能被继承。

class Person {
    protected name: string;
    protected constructor(theName: string) { this.name = theName; }
}

// Employee can extend Person
class Employee extends Person {
    private department: string;

    constructor(name: string, department: string) {
        super(name);
        this.department = department;
    }

    public getElevatorPitch() {
        return `Hello, my name is ${this.name} and I work in ${this.department}.`;
    }
}

let howard = new Employee("Howard", "Sales");
let john = new Person("John"); // Error: The 'Person' constructor is protected

参数属性

在一个地方定义和初始化一个成员。

class Animal {
    // private name:string
    constructor(private name: string) { }
    move(distanceInMeters: number) {
        console.log(`${this.name} moved ${distanceInMeters}m.`);
    }
}

存取器

let passcode = "secret passcode";
// 只带有get不带有set的存取器会自动被判断为readonly
class Employee {
    private _fullName: string;

    get fullName(): string {
        return this._fullName;
    }

    set fullName(newName: string) {
        if (passcode && passcode == "secret passcode") {
            this._fullName = newName;
        }
        else {
            console.log("Error: Unauthorized update of employee!");
        }
    }
}

let employee = new Employee();
employee.fullName = "Bob Smith";
if (employee.fullName) {
    alert(employee.fullName);
}

静态属性

每个实例都要访问这个属性。

class Grid {
    static origin = {x: 0, y: 0};
    calculateDistanceFromOrigin(point: {x: number; y: number;}) {
        // 使用Grid.来访问静态属性
        let xDist = (point.x - Grid.origin.x);
        let yDist = (point.y - Grid.origin.y);
        return Math.sqrt(xDist * xDist + yDist * yDist) / this.scale;
    }
    constructor (public scale: number) { }
}

let grid1 = new Grid(1.0);  // 1x scale
let grid2 = new Grid(5.0);  // 5x scale

console.log(grid1.calculateDistanceFromOrigin({x: 10, y: 10}));
console.log(grid2.calculateDistanceFromOrigin({x: 10, y: 10}));

抽象类

作为其他派生类的基类使用,一般不直接被实例化。抽象类中的抽象方法不包含具体实现且必须在派生类中实现。

abstract class Department {

    constructor(public name: string) {
    }

    printName(): void {
        console.log('Department name: ' + this.name);
    }

    abstract printMeeting(): void; // 必须在派生类中实现
}

class AccountingDepartment extends Department {

    constructor() {
        super('Accounting and Auditing'); // constructors in derived classes must call super()
    }

    printMeeting(): void {
        console.log('The Accounting Department meets each Monday at 10am.');
    }

    generateReports(): void {
        console.log('Generating accounting reports...');
    }
}

let department: Department; // ok to create a reference to an abstract type
department = new Department(); // error: cannot create an instance of an abstract class
department = new AccountingDepartment(); // ok to create and assign a non-abstract subclass
department.printName();
department.printMeeting();
department.generateReports(); // error: method doesn't exist on declared abstract type

高级技巧

构造函数

class Greeter {
    static standardGreeting = "Hello, there";
    greeting: string;
    greet() {
        if (this.greeting) {
            return "Hello, " + this.greeting;
        }
        else {
            return Greeter.standardGreeting;
        }
    }
}

let greeter1: Greeter;
greeter1 = new Greeter();
console.log(greeter1.greet());

// 取Greeter的类型
let greeterMaker: typeof Greeter = Greeter;
greeterMaker.standardGreeting = "Hey there!";

// 创建Greeter实例
let greeter2: Greeter = new greeterMaker();
console.log(greeter2.greet());

把类当做接口使用

class Point {
    x: number;
    y: number;
}

interface Point3d extends Point {
    z: number;
}

let point3d: Point3d = {x: 1, y: 2, z: 3};

TypeScript——泛型

创建支持多种数据类型的可重用的组件。

基本

// T用于捕获用户传入的类型,并用它作为返回值的类型
function identity<T>(arg: T): T {
    return arg;
}

let output = identity<string>("myString");  // type of output will be 'string'
let output = identity("myString");  // type of output will be 'string'

泛型变量

泛型函数要求我们在函数体中正确使用这个传入的通用类型.

function loggingIdentity<T>(arg: T): T {
    console.log(arg.length);  // Error: T doesn't have .length
    return arg;
}

// 下面的写法是正确的
function loggingIdentity<T>(arg: T[]): T[] {
    console.log(arg.length);  // Array has a .length, so no more error
    return arg;
}

泛型接口

interface GenericIdentityFn<T> {
    (arg: T): T;
}

function identity<T>(arg: T): T {
    return arg;
}

//指定泛型类型为number
let myIdentity: GenericIdentityFn<number> = identity;

泛型类

// 泛型类只可用于类的实例部分
class GenericNumber<T> {
    zeroValue: T;
    add: (x: T, y: T) => T;
}

let myGenericNumber = new GenericNumber<number>();
myGenericNumber.zeroValue = 0;
myGenericNumber.add = function(x, y) { return x + y; };

泛型约束

我们不仅可以通过泛型变量中提到的传入正确的通用类型来约束泛型变量,也可以通过接口来描述约束条件。

interface Lengthwise {
    length: number;
}

// 接口约束了传入的值必须有number类型的length属性
function loggingIdentity<T extends Lengthwise>(arg: T): T {
    console.log(arg.length);  // Now we know it has a .length property, so no more error
    return arg;
}

loggingIdentity(3);  // Error, number doesn't have a .length property

loggingIdentity({length: 10, value: 3});
// 用属性名从对象中获取属性值,通过约束来确保属性存在于对象上
function getProperty<T, K extends keyof T>(obj: T, key: K) {
    return obj[key];
}

let x = { a: 1, b: 2, c: 3, d: 4 };

getProperty(x, "a"); // okay
getProperty(x, "m"); // error: Argument of type 'm' isn't assignable to 'a' | 'b' | 'c' | 'd'.

TypeScript——模块

基础

模块在其自身的作用域里执行,不在全局作用域里。

导出语句

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

导入

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

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

默认导出

const numberRegexp = /^[0-9]+$/;

export default function (s: string) {
    return s.length === 5 && numberRegexp.test(s);
}

// 可以自己指定默认导出的变量名
import validate from "./StaticZipCodeValidator";

let strings = ["Hello", "98052", "101"];

// Use function validate
strings.forEach(s => {
  console.log(`"${s}" ${validate(s) ? " matches" : " does not match"}`);
});

使用其他的JavaScript库

TypeScript——枚举,类型推论,类型兼容性,高级类型

枚举

定义一些有名字的数字常量,使用enum关键字。

enum Direction {
    Up = 1,
    Down, // 2
    Left, // 3
    Right, // 4
    Read = 1 << 1, //2
    G = "123".length //3
}

类型推论

//  被推断为联合数组类型,(Rhino | Elephant | Snake)[]
let zoo = [new Rhino(), new Elephant(), new Snake()];

// 被推断为Animal[]类型
let zoo: Animal[] = [new Rhino(), new Elephant(), new Snake()];

类型兼容性

如果x要兼容y,那么y至少具有与x相同的属性。

比较两个函数的类型兼容性

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

y = x; // OK
x = y; // Error
 
// 返回值
let x = () => ({name: 'Alice'});
let y = () => ({name: 'Alice', location: 'Seattle'});

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

高级类型

交叉类型

多在混入(mixins)时使用,将多个类型合并为一个类型。

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();

联合类型

一个值可以是几种类型之一。

/**
 * 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: number | string) {
    if (typeof padding === "number") {
        return Array(padding + 1).join(" ") + value;
    }
    if (typeof padding === "string") {
        return padding + value;
    }
}

padLeft("Hello world", 4); // returns "    Hello world"
let indentedString = padLeft("Hello world", true); // errors during compilation
interface Bird {
    fly();
    layEggs();
}

interface Fish {
    swim();
    layEggs();
}

function getSmallPet(): Fish | Bird {
    // ...
}

// 当一个值是联合类型时,我们只能访问所有类型的共有成员
let pet = getSmallPet();
pet.layEggs(); // okay
pet.swim();    // errors

// 检查成员是否存在,需要增加类型断言
if ((<Fish>pet).swim) {
    (<Fish>pet).swim();
}
else {
    (<Bird>pet).fly();
}

// 用户自定义类型保护
function isFish(pet: Fish | Bird): pet is Fish {
    return (<Fish>pet).swim !== undefined;
}

if (isFish(pet)) {
    pet.swim();
}
else {
    pet.fly();
}

typeof作为类型保护

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}'.`);
}

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'
}

字符串字面量类型

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

TypeScript——接口

为对象类型命名,定义相关契约。

基本用法

// 接口声明
interface LabelledValue {
  label: string;
}

//只会检查接口设置的属性参数,不会检查其余属性
function printLabel(labelledObj: LabelledValue) {
  console.log(labelledObj.label);
}

let myObj = {size: 10, label: "Size 10 Object"};
printLabel(myObj);

可选属性

一方面对可能存在的属性进行预定义,另一方面可以捕获引用了不存在的属性的错误。

// 可选属性用?表示
interface SquareConfig {
    color?: string;
    width?: number;
}

function createSquare(config: SquareConfig): {color: string; area: number} {
    let newSquare = {color: "white", area: 100};
    if (config.color) {
        newSquare.color = config.color;
    }
    if (config.width) {
        newSquare.area = config.width * config.width;
    }
    return newSquare;
}

let mySquare = createSquare({color: "black"});

只读属性

// 变量用const,属性用readonly
interface Point {
    readonly x: number;
    readonly y: number;
}

额外的属性检查

如果使用可选属性,但却传入了可选属性外的属性,依然会报错(将对象向字面量赋值给另一个变量,可以绕过这一检查)。如果确认除了定义好的属性还可能传入任意数量的属性时,可以这样定义:

interface SquareConfig {
    color?: string;
    width?: number;
    [propName: string]: any;
}

描述函数类型

// 入参类型和返回值类型
interface SearchFunc {
  (source: string, subString: string): boolean;
}

let mySearch: SearchFunc;
// 参数只需按顺序类型匹配,不需要名称匹配
mySearch = function(src, sub) {
    let result = src.search(sub);
    return result > -1;
}

可索引的类型

class Animal {
    name: string;
}
class Dog extends Animal {
    breed: string;
}

// 表示用number去索引Okay时会得到Dog类型的返回值
// 可以使用readonly限制索引为只读
interface Okay {
    readonly [x: number]: Dog;
    [x: string]: Animal;
}
// 错误:使用'string'索引,有时会得到Animal!
// 索引支持number和string,但number的返回值类型必须是string返回值类型的子集
interface NotOkay {
    [x: number]: Animal;
    [x: string]: Dog;
}

类类型——实现接口

// 强制一个类符合某种契约
interface ClockInterface {
    currentTime: Date;
    setTime(d: Date);
}

class Clock implements ClockInterface {
    currentTime: Date;
    setTime(d: Date) {
        this.currentTime = d;
    }
    constructor(h: number, m: number) { }
}

继承接口

更灵活地将接口分割到可重用的模块里。

interface Shape {
    color: string;
}

interface PenStroke {
    penWidth: number;
}

// 一个接口可以继承多个接口
interface Square extends Shape, PenStroke {
    sideLength: number;
}

let square = <Square>{};
square.color = "blue";
square.sideLength = 10;
square.penWidth = 5.0;

混合类型

一个对象同时作为函数和对象使用。

interface Counter {
    (start: number): string;
    interval: number;
    reset(): void;
}

function getCounter(): Counter {
    let counter = <Counter>function (start: number) { };
    counter.interval = 123;
    counter.reset = function () { };
    return counter;
}

let c = getCounter();
c(10);
c.reset();
c.interval = 5.0;

TypeScript——Symbols

基本类型,通过Symbol构造函数创建。

// 不可变且唯一
let sym2 = Symbol("key");
let sym3 = Symbol("key");

sym2 === sym3; // false, symbols是唯一的

// 可以用作对象属性的键
let sym = Symbol();

let obj = {
    [sym]: "value"
};

console.log(obj[sym]); // "value"

可迭代性

实现了Symbol.iterator属性的对象时可迭代的,该函数负责返回供迭代的值。

for..of & for..in

for..of迭代对象的键值(value)并且可以用来迭代set,map,for..in迭代键(key)。

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"
}
@naseeihity naseeihity added the JS label Dec 1, 2017
@naseeihity naseeihity self-assigned this Mar 23, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

1 participant