TS

148 阅读8分钟

目标

  • 基础
  • 原理
  • 常见面试题与实践

知识要点

安装

npm install -g typescript

hello.ts

function sayHello(person:string){
    return 'hello'+person;
}

编译

tsc hello.ts

输出

function sayHello(person) {
    return 'hello' + person;
}

TypeScript 简介

添加了类型系统的JavaScript,适用于任何规模的项目

特性

  • 类型系统
  • 适用于任何规模

Javascript 灵活

  • 没有类型约束,可以转我字符串,数字
  • 存在隐式转换,变量类型运行前无法确定
  • 原型上的属性或者方法可以在运行时被修改
  • 函数可以赋值给变量,参数或者返回值
  • 解析语言,没有编译阶段

双刃剑

  • 无所不能
  • 常见就是隐式转换导致判断错误
  • 代码质量残差不齐,维护成本高,运行时错误多
  • 语言的类型错误,导致运行时的错误

TypeScript 是静态类型,编译阶段就能确定每个变量的类型,编译阶段进行类型检查

可以类型自动推论出没有定义的类型的变量

原始数据类型

//boolean
let isDone:boolean = false;
let booleanObject:Boolean = new Boolean(1);//调用构造函数
let booleanObject:boolean = Boolean(1)//调用函数//number
let decLiteral:number =6;
// ES6 中的二进制表示法
let binaryLiteral: number = 0b1010;
// ES6 中的八进制表示法
let octalLiteral: number = 0o744;
let notANumber: number = NaN;
let infinityNumber: number = Infinity;
​
//string
let myName:string ='Tom';
​
// 模板字符串
let sentence: string = `Hello, my name is ${myName}.
I'll be ${myAge + 1} years old next month.`;
​
//空值,声明一个 void 类型的变量没有什么用,因为你只能将它赋值为 undefined 和 null
//1.没有返回任何值的函数
function setName():void{
    
}
​
let unusable: void = undefined;//没有任何意义//Null 和 Undefined
let u:undefined=undefined;
let n:null = null;
let num:number = undefined;
let u:undefined;
let num:number = u;
​
​

任意值

用来表示允许赋值为任意类型

let myFavoriteNumber:any = 'seven';//可以被改变类型,赋予任何值

变量如果在声明的时候,未指定其类型,那么它会被识别为任意值类型:

let something;
something = 'seven';
something = 7;
something.setName('Tom');

类型推论

如果没有明确的指定类型,那么 TypeScript 会依照类型推论 (Type Inference)的规则推断出一个类型。

let myFavoriteNumber = 'seven';
//它等价于
let myFavoriteNumber: string = 'seven';

如果定义的时候没有赋值,不管之后有没有赋值,都会被推断成 any 类型而完全不被类型检查:

let myFavoriteNumber;
myFavoriteNumber = 'seven';
myFavoriteNumber = 7;

联合类型

取值可以为多种类型中的一种

let myV: string | number;
myV='seven';
myV=7;

访问联合类型的属性或方法

当 TypeScript 不确定一个联合类型的变量到底是哪个类型的时候, 我们只能访问此联合类型的所有类型里共有的属性或方法:

function getLength(something: string | number): number {
    return something.length;
}
​
// index.ts(2,22): error TS2339: Property 'length' does not exist on type 'string | number'.
//   Property 'length' does not exist on type 'number'.
​
function getString(something: string | number): string {
    return something.toString();
}

对象的类型---接口

使用接口定义对象的类型

interface Person{
    name:string;
    age:number;
}
let tom:Person={
    name:'Tom',
    age:25,
}

定义的变量比接口少了一些属性是不允许的,多一些属性也是不允许的

interface Person {
    name: string;
    age: number;
}
​
let tom: Person = {
    name: 'Tom'
};
​
// index.ts(6,5): error TS2322: Type '{ name: string; }' is not assignable to type 'Person'.
//   Property 'age' is missing in type '{ name: string; }'.
interface Person {
    name: string;
    age: number;
}
​
let tom: Person = {
    name: 'Tom',
    age: 25,
    gender: 'male'
};
​
// index.ts(9,5): error TS2322: Type '{ name: string; age: number; gender: string; }' is not assignable to type 'Person'.
//   Object literal may only specify known properties, and 'gender' does not exist in type 'Person'.

可选属性

仍然不允许添加未定义的属性

interface Person{
    name:string,
    age?:number;
}
let tom:Person = {
    name:'Tom'
}

任意属性

一旦定义了任意属性,那么确定属性和可选属性的类型都必须是它的类型的子集:

interface Person{
    name:string;
    age?:number;
    [propName:string]:any;
}
let tom:Person={
    name:'Tom',
    gender:'male'
}
// index.ts(3,5): error TS2411: Property 'age' of type 'number' is not assignable to string index type 'string'.
// index.ts(7,5): error TS2322: Type '{ [x: string]: string | number; name: string; age: number; gender: string; }' is not assignable to type 'Person'.
//   Index signatures are incompatible.
//     Type 'string | number' is not assignable to type 'string'.
//       Type 'number' is not assignable to type 'string'.

任意属性的值允许是 string,但是可选属性 age 的值却是 number,number 不是 string 的子属性,所以报错了。 //使用联合类型

interface Person {
    name: string;
    age?: number;
    [propName: string]: string | number;
}
​
let tom: Person = {
    name: 'Tom',
    age: 25,
    gender: 'male'
};

只读属性

只读的约束存在于第一次给对象赋值的时候,而不是第一次给只读属性赋值的时候

interface Person{
    readonly id:number;
    name:string;
    age?:number;
    [propName:string]:any;
}
let tom:Person={
    id:89797,
    name:'Tom',
    gender:'male'
}
tom.id=9527
// index.ts(14,5): error TS2540: Cannot assign to 'id' because it is a constant or a read-only property.

数组的类型

let fibonacci:number[]=[1,1,2,3,5];

数组的项中不允许出现其他的类型 数组的一些方法的参数也会根据数组在定义时约定的类型进行限制

let fibonacci: number[] = [1, '1', 2, 3, 5];
// Type 'string' is not assignable to type 'number'.
​
let fibonacci: number[] = [1, 1, 2, 3, 5];
fibonacci.push('8');
​
// Argument of type '"8"' is not assignable to parameter of type 'number'.

数组泛型

let fib:Array<number>=[1,1]

类数组

类数组(Array-like Object)不是数组类型,比如 arguments 常用的类数组都有自己的接口定义,如 IArguments, NodeList, HTMLCollection 等

function sum(){
    let args:{
        [index:number]:number;
        length:number;
        callee:Function;
    }=arguments;
}
function sum1() {
    let args: IArguments = arguments;
}
interface IArguments{
    [index:number]:any;
    length:number;
    callee:Function;
}

any 在数组中的应用

let list: any[] = ['xcatliu', 25, { website: 'http://xcatliu.com' }];

函数的类型

  1. 函数声明
function sum(x,y){
    return x+y;
}

2.函数表达式

let mySum = function(x,y) {
  return x+y;
}

一个函数有输入和输出,要在 TypeScript 中对其进行约束

function sum(x:number,y:number):number{
    return x+y;
}
​
let mySum = function(x:number,y:number):number {
  return x+y;
}
​
let mySum1:(x:number,y:number)=>number = function(x:number,y:number):number{
    return x+y;
}

输入多余的参数,少于是不被允许的

function sum(x: number, y: number): number {
    return x + y;
}
sum(1, 2, 3);
​
// index.ts(4,1): error TS2346: Supplied parameters do not match any signature of call target.
function sum(x: number, y: number): number {
    return x + y;
}
sum(1);
​
// index.ts(4,1): error TS2346: Supplied parameters do not match any signature of call target.
​

用接口定义函数的形状

采用函数表达式|接口定义函数的方式时,对等号左侧进行类型限制,可以保证以后对函数名赋值时保证参数个数、参数类型、返回值类型不变。

interface SearchFunc{
    (source:string,subString:string):boolean;
}
let mySearch:SearchFunc;
mySearch = function(source:string,subString:string) {
    return -1;
}

可选参数

可选参数后面不允许再出现必需参数了

function buildName(firstName:string,lastName?:string){
    if(lastName){
        return firstName +''+lastName;
    }else{
        return firstName;
    }
}
let tomcat = buildName('tom','cat');
let tom = buildName('tom');

参数默认值

TypeScript 会将添加了默认值的参数识别为可选参数, 此时就不受「可选参数必须接在必需参数后面」的限制了

function buildName(firstName:string,lastName:string='Cat'){
    return firstName+' '+lastName;
}
let tomcat = buildName('tom','cat');
let tom = buildName('Tom');

剩余参数

function push(array,...items){
    items.forEach(function(item) {
      array.push(item);
    })
}
let a:any[] =[];
push(a,1,2,3);

重载

重载允许一个函数接受不同数量或类型的参数时,作出不同的处理。

function reverse(x:number|string):number|string|void {
  if(typeof x ==='number'){
      return Number(x.toString().split('').reverse().join(''));
  }else if(typeof x ==='string'){
      return x.split('').reverse().join('')
  }
}

然而这样有一个缺点,就是不能够精确的表达,输入为数字的时候, 输出也应该为数字,输入为字符串的时候,输出也应该为字符串。

这时,我们可以使用重载定义多个 reverse 的函数类型

function reverse(x: number): number;
function reverse(x: string): string;
function reverse(x: number | string): number | string | void {
    if (typeof x === 'number') {
        return Number(x.toString().split('').reverse().join(''));
    } else if (typeof x === 'string') {
        return x.split('').reverse().join('');
    }
}

类型断言

值 as 类型 <类型>值 报错

interface Cat {
    name: string;
    run(): void;
}
interface Fish {
    name: string;
    swim(): void;
}
​
function isFish(animal: Cat | Fish) {
    if (typeof animal.swim === 'function') {
        return true;
    }
    return false;
}
​
// index.ts:11:23 - error TS2339: Property 'swim' does not exist on type 'Cat | Fish'.
//   Property 'swim' does not exist on type 'Cat'.

正确断言

interface Cat {
    name: string;
    run(): void;
}
interface Fish {
    name: string;
    swim(): void;
}
​
function isFish(animal: Cat | Fish) {
    if (typeof (animal as Fish).swim === 'function') {
        return true;
    }
    return false;
}

声明文件

ts.xcatliu.com/basics/decl… 当使用第三方库时,我们需要引用它的声明文件,才能获得对应的代码补全、接口提示等功能

什么是声明文件

假如我们想使用第三方库 jQuery,一种常见的方式是在 html 中通过 <script> 标签引入 jQuery,然后就可以使用全局变量 $jQuery 了。

我们通常这样获取一个 idfoo 的元素:

$('#foo');
// or
jQuery('#foo');

但是在 ts 中,编译器并不知道 $jQuery 是什么东西1

jQuery('#foo');
// ERROR: Cannot find name 'jQuery'.

这时,我们需要使用 declare var 来定义它的类型2

declare var jQuery: (selector: string) => any;
​
jQuery('#foo');

上例中,declare var 并没有真的定义一个变量,只是定义了全局变量 jQuery 的类型,仅仅会用于编译时的检查,在编译结果中会被删除。它编译结果是:

jQuery('#foo');
  • declare var 声明全局变量-----declare var 并没有真的定义一个变量,只是定义了全局变量 jQuery 的类型
  • declare function 声明全局方法
  • declare class 声明全局类
  • declare enum 声明全局枚举类型
  • declare namespace 声明(含有子属性的)全局对象
  • interfance 和type
  • export 导出变量
  • export namespace 导出(含有子属性的)对象
  • export default ES6默认导出
  • export = commonjs导出模块
  • export as namespace UMD库声明全局变量
  • declare global扩展全局变量
  • declare module 扩展模块
  • /// 三斜线指令

第三方声明文件

可以在这个网址搜索到 www.typescriptlang.org/dt/search?s…=