TypeScript

内容纲要

  • 了解什么是 TypeScript
  • TypeScript 基本语法
TS.png

1. TypeScript 概述

a. TypeScript 是什么

TypeScript 是 JavaScript 的强类型版本。然后在编译期去掉类型和特有语法,生成纯粹的 JavaScript 代码。由于最终在浏览器中运行的仍然是 JavaScript,所以 TypeScript 并不依赖于浏览器的支持,也并不会带来兼容性问题。

TypeScript 是 JavaScript 的超集,这意味着他支持所有的 JavaScript 语法。并在此之上对 JavaScript 添加了一些扩展,如 class / interface / module 等。这样会大大提升代码的可阅读性。

和 JavaScript 若类型不同,TypeScript 这种强类型语言最大的优势在于静态类型检查,可以在代码开发阶段就预知一些低级错误的发生。

  • 一种类似于 JavaScript 的语言,在 JavaScript 的基础之上增加了类型,同时增强了 JavaScript 部分语法功能
  • 遵循 EcmaScript 6 标准规范
  • 由微软开发
  • Angular 8 框架采用 TypeScript 编写
  • 背后有微软和谷歌两大公司的支持
  • TypeScript 可以编译成 JavaScript 从而在支持 JavaScript 的环境中运行
  • TypeScript 和 JavaScript 的关系就好比 less 和 css 的关系
b. Why TypeScript
  • 从 Angular 2 之后,官方推荐使用 TypeScript 作为开发 Angular 应用的首选语言
  • 遵循 EcmaScript 6
  • 强大的 IDE 支持
    • 类型检查
    • 严谨的语法提示
  • 代码重构
  • 可读性良好
c. TypeScript 使用场景
  • 大型团队开发
  • Angular 官推语言
  • 其它…
d. TypeScript 不仅仅用于开发 Angular 应用

https://www.typescriptlang.org/samples/index.html

  • React
  • Angular
  • Node.js
  • Vue.js
  • WeChat

凡是可以写 JavaScript 的都可以使用 TypeScript。

前置知识基础
  • EcmaScript 6
  • TypeScript 概念及关系
  • 具有一定的 JavaScript 开发经验
  • 有 Java、C#、C++、C 等静态类型语言使用经验更佳
如何学习 TypeScript
  • 官方文档为准
  • 阅读别人的代码
  • 由于 TypeScript 是兼容 EcmaScript 6 的,所以在开发的时候不需要完全学会 TypeScript 再使用
  • 一个建议是有空就学,会了就用
  • 虽然兼容 EcmaScript 6,但建议既然使用了 TypeScript 就让你的 TypeScript 代码更加 TypeScript,这样才能发挥出 TypeScript 的威力。

相关链接

起步
  • 什么是 compiler?
  • less 编译器:less
  • ECMAScript 6 编译器:babel
  • TypeScript 编译器:typescript
  • 一句话:把 TypeScript 转换为 JavaScript ,浏览器就可以运行了
  • 在线测试编译环境 compiler
  • 本地开发编译环境
================================================

2. 搭建 TypeScript 开发环境

a. 安装配置

cd /myts 进入项目
npm install typescript -g 安装编译器
tsc --version 查看版本号(有版本号为安装成功)
tsc --help 查看使用帮助

生成tsc目录下的配置文件:tsconfig.json

tsc --init

目录配置

修改tsc编译生成文件的输出目录和ts文件所在根目录的地址(打开tsconfig.json修改outDir 和 rootDir)

{
    "outDir": "./dist",         //这是自己新建的俩目录            
    "rootDir": "./src",         //greeter在 src 目录下      
}

b. Hello World

新建 greeter.ts 并写入以下内容:

function greeter(person: string) {
    return "Hello, " + person;
}
let user = "Jane User";
document.body.innerHTML = greeter(user);

tsc ./src/greeter.ts 编译:编译greeter.ts文件
tsc -w 检测变化并编译

重新编译执行。

让我们继续修改:

function greeter(person: string) {
    return "Hello, " + person;
}
let user = [0, 1, 2];
document.body.innerHTML = greeter(user);
//user未声明类型

重新编译,你将看到如下错误:

error TS2345: Argument of type 'number[]' is not assignable to parameter of type 'string'.

接口(Interface)

interface Person {
      firstName: string;
      lastName: string;
  }
  function greeter(person: Person) {
      return "Hello, " + person.firstName + " " + person.lastName;
  }
  let user = { firstName: "Jane", lastName: "User" };
  document.body.innerHTML = greeter(user);

类(Class)

class Student {
      fullName: string;
      constructor(public firstName: string, public middleInitial: string, public lastName: string) {
          this.fullName = firstName + " " + middleInitial + " " + lastName;
      }
  }
 interface Person {
      firstName: string;
      lastName: string;
  }
 function greeter(person : Person) {
      return "Hello, " + person.firstName + " " + person.lastName;
  }
  let user = new Student("Jane", "M.", "User");
  document.body.innerHTML = greeter(user);

3. 变量声明

  • var
  • let
  • const
  • var
    作用域
    重复声明

  • let
    块级作用域
    在同一个块中不能重复声明

  • const
    声明同时必须赋值
    一经声明不可改变
    对象可以修改
    块级作用域

let vs const

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

4. 基本数据类型

  • 布尔值 boolean
  • 数字 number
  • 字符串 string
  • 数组 number[] 或者 Array<number>
  • 元祖 [number, string]
  • 对象 object,了解即可
  • 任意类型 any
  • 函数空返回值 void
  • null 和 undefined

1. boolean(布尔值)

let b: boolean = false;

2. number(数字)

let amount: number = 6;

3. string(字符串)

let name: string = '张三';
(和 JavaScript 一样,可以使用双引号,也可以使用单引号,推荐单引号,
还可以使用模板字符串(换行 + 嵌入表达式))

let name: string = Gene;
let age: number = 37;
let sentence: string = `Hello, my name is ${ name }.
I'll be ${ age + 1 } years old next month.`;

4. 数组

TypeScript像JavaScript一样可以操作数组元素。 有两种方式可以定义数组。 第一种,可以在元素类型后面接上[],表示由此类型元素组成的一个数组:

let list: number[] = [1, 2, 3];

第二种方式是使用数组泛型,Array<元素类型>

let list: Array<number> = [1, 2, 3];

5. 元组

元组类型允许表示一个已知元素数量和类型的数组,各元素的类型不必相同。 比如,你可以定义一对值分别为stringnumber类型的元组。

  // Declare a tuple type
  let x: [string, number];
  // Initialize it
  x = ['hello', 10]; // OK
  // Initialize it incorrectly
  x = [10, 'hello']; // Error

6. Object

  • 允许赋任意值
  • 但是不能调用任意方法,即便它真的有
let foo: Object = {
    name: 'Jack',
    age: 18
  }

知道即可,用的很少,没有类型校验和语法提示

7. Any

有时候,我们会想要为那些在编程阶段还不清楚类型的变量指定一个类型。 这些值可能来自于动态的内容,比如来自用户输入或第三方代码库。 这种情况下,我们不希望类型检查器对这些值进行检查而是直接让它们通过编译阶段的检查。 那么我们可以使用 any类型来标记这些变量:

let notSure: any = 4;
notSure = "maybe a string instead";
notSure = false; // okay, definitely a boolean

8. Void

void类型像是与any类型相反,它表示没有任何类型。 当一个函数没有返回值时,你通常会见到其返回值类型是 void

function warnUser(): void {
    alert("This is my warning message");
  }

声明一个void类型的变量没有什么大用,因为你只能为它赋予undefinednull

let unusable: void = undefined;

9. Null 和 Undefined

void相似,它们的本身的类型用处不是很大:

// Not much else we can assign to these variables!
  let u: undefined = undefined;
  let n: null = null;

默认情况下nullundefined是所有类型的子类型。 就是说你可以把 nullundefined赋值给number类型的变量。

然而,当你指定了--strictNullChecks 标记,nullundefined 只能赋值给 void 和它们各自。 这能避免 很多 常见的问题。许在某处你想传入一个 stringnullundefined,你可以使用联合类型string | null | undefined

注意:我们推荐尽可能地使用--strictNullChecks ,因为它使你的代码更严谨,可以极大的减少出错的几率。

类型推断

有时候你会遇到这样的情况,你会比TypeScript更了解某个值的详细信息。 通常这会发生在你清楚地知道一个实体具有比它现有类型更确切的类型。

通过 类型断言 这种方式可以告诉编译器,“相信我,我知道自己在干什么”。 类型断言好比其它语言里的类型转换,但是不进行特殊的数据检查和解构。 它没有运行时的影响,只是在编译阶段起作用。 TypeScript会假设你,程序员,已经进行了必须的检查。

类型断言有两种形式。 其一是“尖括号”语法:

let someValue: any = "this is a string";
  let strLength: number = (<string>someValue).length;

另一个为as语法:

let someValue: any = "this is a string";
  let strLength: number = (someValue as string).length;

两种形式是等价的。 至于使用哪个大多数情况下是凭个人喜好;然而,当你在TypeScript里使用JSX时,只有 as语法断言是被允许的。

其它

  • ReadonlyArray<T> 去除了数组的所有可变方法,确保数组创建后再也不能被修改

5 . 函数

函数参数

  • 参数及返回值类型
function add(x: number, y: number): number {
      return x + y ;
  }
console.log(add(4,5)) ;  // 9
  • 可选参数
function add(x: number, y?: number): number {
    let _y = y||10;
   // return x + 10;
    return x + _y;
  }
console.log(add(5))       //15
console.log(add(5,20))    //25
  • 默认参数
function add(x: number, y: number = 20): number {
      return x + y
  }
console.log(add(7));   // 27
console.log(add(3,4));  // 7
  • 剩余参数
function sum(...args: number[]): number {
      let ret: number = 0
      args.forEach((item: number): void => {
          ret += item
      })
      return ret
  }
    sum(1, 2, 3)
  • 箭头函数
let add = (x: number, y: number): number => x + y
console.log(add(5,7));

6. 接口(interface)

TypeScript的核心原则之一是对值所具有的 结构 进行类型检查。 它有时被称做“鸭式辨型法”或“结构性子类型化”。 在TypeScript里,接口的作用就是为这些类型命名和为你的代码或第三方代码定义契约。

基本示例

function printLabel(labelledObj: { label: string }) {
    console.log(labelledObj.label);
  }
  let myObj = { size: 10, label: "Size 10 Object" };
  printLabel(myObj);

类型检查器会查看printLabel的调用。 printLabel有一个参数,并要求这个对象参数有一个名为label类型为string的属性。 需要注意的是,我们传入的对象参数实际上会包含很多属性,但是编译器只会检查那些必需的属性是否存在,并且其类型是否匹配。 然而,有些时候TypeScript却并不会这么宽松,我们下面会稍做讲解。

下面我们重写上面的例子,这次使用接口来描述:必须包含一个label属性且类型为string

interface LabelledValue {
    label: string;
  }
  function printLabel(labelledObj: LabelledValue) {
    console.log(labelledObj.label);
  }
  let myObj = {size: 10, label: "Size 10 Object"};
  printLabel(myObj);

//定义
interface Persion {
    name:string,
    age:number,
  }
// 实现接口
let user:persion{
name:"张三",
age:18
}
//使用接口
function func(p:Persion){
console.log(p);
}
func{user} ;

可选属性

接口里的属性不全都是必需的。 有些是只在某些条件下存在,或者根本不存在。 可选属性在应用“option bags”模式时很常用,即给函数传入的参数对象中只有部分属性赋值了。

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"});

只读属性

一些对象属性只能在对象刚刚创建的时候修改其值。 你可以在属性名前用 readonly来指定只读属性:

interface Point {
      readonly x: number;
      readonly y: number;
  }

你可以通过赋值一个对象字面量来构造一个Point。 赋值后, xy再也不能被改变了。

let p1: Point = { x: 10, y: 20 };
  p1.x = 5; // error!

readonly vs const

  • 常量使用 const
  • 对象属性使用 readonly

7. 解构赋值

1. 数组解构

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

上面的写法等价于:

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

利用解构赋值交换变量:

[first, second] = [second, first];

函数参数解构:

function f ([first, second]: [number, number]) [
    console.log(first)
    console.log(second)
  ]
    f(1, 2)

解构剩余参数:

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); // outputs 1

或者跳过解构:

let [, second, , fourth] = [1, 2, 3, 4]

2. 对象解构

示例一:

let o = {
      a: "foo",
      b: 12,
      c: "bar"
  };
  let { a, b } = o;

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

let a: number,
    b: number;
    ({a, b} = {a: 123, b: 456})
    console.log(a, b) // 123 456

你可以在对象里使用 ... 语法创建剩余变量:

let { a, ...passthrough } = o;
  let total = passthrough.b + passthrough.c.length;

属性解构重命名

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

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

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

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

默认值

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

8. 展开操作符

  • 展开数组
  • 展开对象
    //不会展开方法

解构赋值用于函数声明

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

解构赋值用于加载指定模块成员

9. 类

基本示例

class Person {
      name: string;
      age: number;
        constructor(name: string, age: number) {
          this.name = name;
          this.age = age;
      }
        sayHello() {
          console.log(this.name);
      }
  }
    let zs: Person = new Person('张三', 18);

构造函数

继承

class Animal {
      move(distanceInMeters: number = 0) {
          console.log(`Animal moved ${distanceInMeters}m.`);
      }
  }
    class Dog extends Animal {
      bark() {
          console.log('Woof! Woof!');
      }
  }
    const dog = new Dog();
  dog.bark();
  dog.move(10);
  dog.bark();

这个例子展示了最基本的继承:类从基类中继承了属性和方法。 这里, Dog是一个 派生类 ,它派生自 Animal 基类 ,通过 extends关键字。 派生类通常被称作 子类 ,基类通常被称作 超类

因为 Dog继承了 Animal的功能,因此我们可以创建一个 Dog的实例,它能够 bark()move()

下面是一个更复杂的例子:

class Animal {
      name: string;
      constructor(theName: string) { this.name = theName; }
      move(distanceInMeters: number = 0) {
          console.log(`${this.name} moved ${distanceInMeters}m.`);
      }
  }
    class Snake extends Animal {
      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);

与前一个例子的不同点是,派生类包含了一个构造函数,它 必须 调用 super(),它会执行基类的构造函数。 而且,在构造函数里访问 this的属性之前,我们 一定 要调用 super()。 这个是TypeScript强制执行的一条重要规则。

这个例子演示了如何在子类里可以重写父类的方法。 Snake类和 Horse类都创建了 move方法,它们重写了从Animal继承来的 move方法,使得 move方法根据不同的类而具有不同的功能。 注意,即使 tom被声明为Animal类型,但因为它的值是 Horse,调用 tom.move(34)时,它会调用 Horse里重写的方法:

Slithering...
  Sammy the Python moved 5m.
  Galloping...
  Tommy the Palomino moved 34m.

10. 实例成员访问修饰符

a. public 公有的

  • 默认为 public
  • 子类可以访问
  • 实例也可以访问
class Animal {
      public name: string;
      public constructor(theName: string) { this.name = theName; }
      public move(distanceInMeters: number) {
          console.log(`${this.name} moved ${distanceInMeters}m.`);
      }
  }

b. private 私有的

  • 不能被外部访问,class内部可以访问
  • 私有成员子类不会被继承
  • 实例不能访问
class Person {
    public name: string;
    public age: number = 18;
    private type: string = 'human'
    public constructor (name, age) {
      this.name = name
      this.age = age
    }
  }

c. protected 受保护的

  • class内部可以访问
  • private 类似,但是可以被继承
  • 实例不能访问
class Person {
      protected name: string;
      constructor(name: string) { this.name = name; }
  }
    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");
  console.log(howard.getElevatorPitch());
  console.log(howard.name); // 错误

注意,我们不能在 Person类外使用 name,但是我们仍然可以通过 Employee类的实例方法访问,因为Employee是由 Person派生而来的。

readonly 只读的

在参数中使用修饰符

在上面的例子中,我们不得不定义一个受保护的成员 name和一个构造函数参数 theNamePerson类里,并且立刻给 nametheName赋值。 这种情况经常会遇到。 参数属性 可以方便地让我们在一个地方定义并初始化一个成员。

class Person {
        name: string;
        age: number;
      constructor(name: string, age: number) {
          this.name = name;
          this.age = age;
      }
  }

可以简写为:

class Person {
      constructor(public name: string, public age: number) {
      }
  }

11. 属性的存(get)取(set)器

let passcode = "secret passcode";
    class Employee {
        // 私有成员,外部无法访问
      private _fullName: string;
          // 当访问 实例.fullName 的时候会调用 get 方法
      get fullName(): string {
          return this._fullName;
      }
          // 当对 实例.fullName = xxx 赋值的时候会调用 set 方法
      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);
  }

静态成员

  • 不需要实例化访问的成员称之为静态成员,即只能被类访问的成员
  • static 关键字
class Grid {
      static origin = {x: 0, y: 0};
      calculateDistanceFromOrigin(point: {x: number; y: number;}) {
          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}));

12. for-of 循环

  • for 循环
  • forEach
    不支持 break
  • for in
    会把数组当作对象来遍历
  • for of
    支持 break

类型推断(Type Inference)

类型兼容性

模块

概念

模块通信:导出

export default xxx
    export const foo: string = 'bar';
  export const bar: string = 'foo';

模块通信:导入

    // 加载默认成员
import xxx from '模块标识'
    // 按需加载模块成员
import {foo, bar} from '模块'
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342