【基础知识】TypeScript学习

与JS相似部分,本文不作说明(运算符、条件语句、循环语句、函数、基本类型等)javascript

前言

  • TypeScript的设计目的应该是解决JavaScript的痛点弱类型没有命名空间,致使很难模块化,不适合开发大型程序。另外它还提供了一些语法糖来帮助你们更方便地实践面向对象的编程。前端

  • TypeScript并无抛弃JavaScript的语法另起炉灶,而是作成了JavaScript的超集,这样任何合法的JavaScript的语句在TypeScript下都是合法的,也就是说学习成本很低java

  • 语法糖:TypeScript能够实现类,接口,枚举,泛型,方法重载等,用简洁的语法丰富了JavaScript的使用。node

  • 类型检查:谷歌的Flow也能够作到ajax

  • ES6用类Class(语法糖)也挺好用的啦......typescript

因此为啥要用TypeScript

  1. TypeScript开发实战
  2. 从移动终端到后端服务,从 IoT 到神经网络,JavaScript 几乎无处不在。如此广阔的应用领域,天然对语言的安全性、健壮性和可维护性有更高的要求。
  3. 尽管 ES 标准在近几年有了长足的进步,但在类型检查方面依然无所建树。你们可能经常会遇到这样到场景:
  4. 你调用一个别人写的函数,很不幸,这个家伙没有留下任何注释,为了搞清楚参数类型,你只能硬着头皮去看里面的逻辑。
  5. 为了保证代码的健壮性,你颇有责任心,对一个函数的输入参数进行各类假设,最终给老板盛上了一碗香喷喷的意大利面。
  6. 领导看好你,让你维护一个重要的底层类库,你殚精竭虑,优化了一个参数类型,但不知道有多少处引用,在提交代码前,是否感到脊背发凉?
  7. 明明定义好了接口,可一联调就报错了——“TypeError: Cannot read property 'length' of undefined”,因而你怒气冲冲地去找后端理论:“嘿,哥们儿!这个字段是数组!这个字段是数组!这个字段是数组!”
  8. 归根结底,是由于 JavaScript 是一门动态弱类型语言, 对变量的类型很是宽容,并且不会在这些变量和它们的调用者之间创建结构化的契约。若是你长期在没有类型约束的环境下开发,就会形成“类型思惟”的缺失,养成不良的编程习惯,这也是作前端开发的短板之一,值得咱们警醒。
  9. 幸运的是,TypeScript 的出现很好地弥补了 JavaScript 在静态类型检查方面的缺陷。它为 JavaScript 提供了良好的类型检查支持,并且可以编译成标准的 JavaScript。
  10. 目前, Angular 已经使用 TypeScript 重构了代码,另外一大前端框架 Vue 的新版本也将使用 TypeScript 进行重构。在可预见的将来,TypeScript 将成为前端开发者必须掌握的开发语言之一。
  11. 那么, TypeScript 究竟有哪些特性使得它成为你们的”刚需“?
    • 第一,类型检查。TypeScript 会在编译代码时进行严格的静态类型检查,这意味着你能够在编码阶段发现可能存在的隐患,而没必要把它们带到线上。
    • 第二,语言扩展。TypeScript 会包括来自 ES 6 和将来提案中的特性,好比异步操做和装饰器;也会从其余语言借鉴某些特性,好比接口和抽象类。
    • 第三,工具属性。TypeScript 可以编译成标准的 JavaScript,能够在任何浏览器、操做系统上运行,无需任何运行时的额外开销。从这个角度上讲,TypeScript 更像是一个工具,而不是一门独立的语言。
    • 除此以外,TypeScript 还能够帮助团队重塑“类型思惟”,接口提供方将被迫去思考 API 的边界,他们将从代码的编写者蜕变为代码的设计者。

第一个TypeScript 实例

//var [变量名] : [类型] = 值;
const hello : string = "Hello World!"
console.log(hello)
复制代码

TypeScript 安装【即在cmd中将ts编译成js的命令】

  1. 本地环境安装 npm 工具,使用如下命令来安装:
npm install -g typescript
复制代码
  1. 安装后咱们使用 tsc 命令来执行 TypeScript 的相关代码:
//如下是查看版本号
$ tsc -v
Version 3.2.2
复制代码
  1. 而后咱们新建一个 test.ts
var message:string = "Hello World" 
console.log(message)
复制代码
  1. 执行 tsc 命令将 TypeScript 转换为 JavaScript 代码:
tsc test.ts
复制代码
  1. 这时候再当前目录下(与 test.ts 同一目录)就会生成一个 test.js 文件,代码以下:
var message = "Hello World";
console.log(message);
复制代码
  1. 使用 node 命令来执行 test.js 文件:
$ node test.js 
Hello World
复制代码
  1. TypeScript 转换为 JavaScript 过程以下图: npm

  2. 咱们能够同时编译多个 ts 文件:编程

tsc file1.ts, file2.ts, file3.ts
复制代码
  1. tsc 经常使用编译参数以下表所示:
1. --help显示帮助信息
2. --module载入扩展模块
3. --target设置 ECMA 版本
4. --declaration额外生成一个 .d.ts 扩展名的文件。
    tsc ts-hw.ts --declaration生成 ts-hw.d.ts、ts-hw.js 两个文件。
5. --removeComments删除文件的注释
6. --out编译多个文件并合并到一个输出的文件
7. --sourcemap生成一个 sourcemap (.map) 文件。sourcemap 是一个存储源代码与编译代码对应位置映射的信息文件。
8. --module noImplicitAny在表达式和声明上有隐含的 any 类型时报错
9. --watch在监视模式下运行编译器。会监视输出文件,在它们改变时从新编译。
复制代码

TypeScript元祖&联合类型

  1. 数组中的元素通常认为都是相同数据类型的
  2. 若是存储的元素数据类型不一样,则须要使用元组
  3. 至于元祖的操做,和数组并没有差异
  4. 联合类型:经过管道(|)将变量设置多种类型
var val:string|number 
val = 12 
console.log("数字为 "+ val) 
val = "Runoob" 
console.log("字符串为 " + val)
复制代码
  1. 也能够将联合类型做为函数参数使用
function disp(name:string|string[]) { 
    if(typeof name == "string") { 
        console.log(name) 
    } else { 
        var i; 
        for(i = 0;i<name.length;i++) { 
            console.log(name[i])
        } 
    } 
} 
复制代码
  1. 联合类型数组var arr:number[]|string[]

TypeScript接口

接口是一系列抽象方法的声明,是一些方法特征的集合,这些方法都应该是抽象的,须要由具体的类去实现json

一个简单的接口

/** TypeScript 接口定义以下 interface interface_name { } 如下实例中,咱们定义了一个接口 IPerson, 接着定义了一个类型为IPerson变量 customer customer 实现了接口 IPerson 的属性和方法。 注意接口不能转换为 JavaScript。 它只是 TypeScript 的一部分。 */
interface IPerson { 
    firstName:string, 
    lastName:string, 
    sayHi: ()=>string 
} 
 
var customer:IPerson = { 
    firstName:"Tom",
    lastName:"Hanks", 
    sayHi: ():string =>{return "Hi there"} 
} 
 
console.log("Customer 对象 ") 
console.log(customer.firstName) 
console.log(customer.lastName) 
console.log(customer.sayHi())  
复制代码

联合类型和接口

interface RunOptions { 
    program:string; 
    commandline:string[]|string|(()=>string); 
} 
 
// commandline 是字符串
var options:RunOptions = {program:"test1",commandline:"Hello"}; 
console.log(options.commandline)  
 
// commandline 是字符串数组
options = {program:"test1",commandline:["Hello","World"]}; 
console.log(options.commandline[0]); 
console.log(options.commandline[1]);  
 
// commandline 是一个函数表达式
options = {program:"test1",commandline:()=>{return "**Hello World**";}}; 
 
var fn:any = options.commandline; 
console.log(fn());
复制代码

接口和数组

/** 接口中能够将数组的索引值和元素设置为不一样类型, 索引值能够是数字或字符串 */
interface namelist { 
   [index:number]:string 
} 
// 错误元素 1: 不是 string 类型
var list2:namelist = ["John",1,"Bran"] 

interface ages { 
   [index:string]:number 
} 
var agelist:ages; 
agelist["John"] = 15  // 正确 
agelist[2] = "nine"   // 错误
复制代码

接口继承

/** 接口继承就是说接口能够经过其余接口来扩展本身。 Typescript 容许接口继承多个接口。 继承使用关键字 extends。 */

//单接口继承语法格式:
Child_interface_name extends super_interface_name

//多接口继承语法格式:
Child_interface_name extends super_interface1_name,
super_interface2_name,…,super_interfaceN_name

//单继承实例
interface Person { 
   age:number 
} 
interface Musician extends Person { 
   instrument:string 
} 
var drummer = <Musician>{}; 
drummer.age = 27 
drummer.instrument = "Drums" 
console.log("年龄: "+drummer.age)
console.log("喜欢的乐器: "+drummer.instrument)

//多继承实例
interface IParent1 { 
    v1:number 
} 
interface IParent2 { 
    v2:number 
} 
interface Child extends IParent1, IParent2 { } 
var Iobj:Child = { v1:12, v2:23} 
console.log("value 1: "+Iobj.v1+" value 2: "+Iobj.v2)
复制代码

TypeScript

类描述了所建立的对象共同的属性和方法。后端

类的定义

//语法:class class_name { }
class Person {}
//编译成js就是:
var Person = /** @class */ (function () {
    function Person() {
    }
    return Person;
}());

//建立类的数据成员
class Car {  
    engine:string; // 字段 
    constructor(engine:string) { // 构造函数
        this.engine = engine 
    }     
    disp():void { // 方法 
        console.log("发动机为 : "+this.engine) 
    } 
}
//建立实例
var obj = new Car("Engine 1")
obj.disp()
复制代码

类的继承

子类除了不能继承父类的私有成员(方法和属性)和构造函数,其余的均可以继承。TypeScript 一次只能继承一个类,不支持继承多个类,但 TypeScript 支持多重继承(A 继承 B,B 继承 C)。

//语法:class child_class_name extends parent_class_name
/** 下面实例中建立了 Shape 类, Circle 类继承了 Shape 类, Circle 类能够直接使用 Area 属性 */
class Shape { 
   Area:number   
   constructor(a:number) { 
      this.Area = a 
   } 
} 
class Circle extends Shape { 
   disp():void { 
      console.log("圆的面积: "+this.Area) 
   } 
}
var obj = new Circle(223); 
obj.disp()

/** 类继承后,子类能够对父类的方法从新定义,这个过程称之为方法的重写。 其中super关键字是对父类的直接引用,该关键字能够引用父类的属性和方法。 */
class PrinterClass { 
   doPrint():void {
      console.log("父类的 doPrint() 方法。") 
   } 
} 
class StringPrinter extends PrinterClass { 
   doPrint():void { 
      super.doPrint() // 调用父类的函数
      console.log("子类的 doPrint()方法。")
   } 
}
复制代码

static 关键字

static 关键字用于定义类的数据成员(属性和方法)为静态的,静态成员能够直接经过类名调用。

class StaticMem {  
   static num:number;    
   static disp():void { 
      console.log("num 值为 "+ StaticMem.num) 
   } 
} 
StaticMem.num = 12     // 初始化静态变量
StaticMem.disp()       // 调用静态方法

//上述代码编译成js就是
var StaticMem = /** @class */ (function () {
    function StaticMem() {
    }
    StaticMem.disp = function () {
        console.log("num 值为 " + StaticMem.num);
    };
    return StaticMem;
}());
StaticMem.num = 12; // 初始化静态变量
StaticMem.disp(); // 调用静态方法
复制代码

instanceof 运算符

instanceof 运算符用于判断对象是不是指定的类型,若是是返回 true,不然返回 false

class Person{ } 
var obj = new Person() 
var isPerson = obj instanceof Person; 
console.log("obj 对象是 Person 类实例化来的吗? " + isPerson);
复制代码

访问控制修饰符

可使用访问控制符来保护对类、变量、方法和构造方法的访问

/** public(默认) : 公有,能够在任何地方被访问。 protected : 受保护,能够被其自身以及其子类和父类访问。 private : 私有,只能被其定义所在的类访问。 */
class Encapsulate { 
   str1:string = "hello" 
   private str2:string = "world" 
}
 
var obj = new Encapsulate() 
console.log(obj.str1)     // 可访问 
console.log(obj.str2)   // 编译错误, str2 是私有的
复制代码

类和接口

类能够实现接口,使用关键字 implements

interface ILoan { 
   interest:number 
} 
 
class AgriLoan implements ILoan { 
   interest:number 
   rebate:number 
   constructor(interest:number,rebate:number) { 
      this.interest = interest 
      this.rebate = rebate 
   } 
} 
 
var obj = new AgriLoan(10,1) 
console.log("利润为 : "+obj.interest+",抽成为 : "+obj.rebate )
复制代码

TypeScript 对象

  1. 乍一看,好像和普通的JavaScript对象没啥区别
/** 对象是包含一组键值对的实例。 值能够是标量、函数、数组、对象等,以下实例 */
var object_name = { 
    key1: "value1", // 标量
    key2: "value",  
    key3: function() {
        // 函数
    }, 
    key4:["content1", "content2"] //集合
}
console.log(object_name.key1) 
复制代码
  1. 再来看下面的实例,他们仍是不同滴【TypeScript 类型模板】
//假如咱们在 JavaScript 定义了一个对象:
var sites = { 
   site1:"Runoob", 
   site2:"Google" 
};
//这时若是咱们想在对象中添加方法,能够作如下修改:
sites.sayHello = function(){ return "hello";}

//若是在 TypeScript 中使用以上方式则会出现编译错误,
//由于Typescript 中的对象必须是特定类型的实例。

--------------------------------------------------
//在typescript中得以下定义:
var sites = {
    site1: "Runoob",
    site2: "Google",
    sayHello: function () { } // 类型模板
};
sites.sayHello = function () {
    console.log("hello " + sites.site1);
};
sites.sayHello();

--------------------------------------------------
/** 鸭子类型(Duck Typing): 是动态类型的一种风格,是多态(polymorphism)的一种形式。 在这种风格中,一个对象有效的语义, 不是由继承自特定的类或实现特定的接口, 而是由"当前方法和属性的集合"决定。 能够这样表述: "当看到一只鸟走起来像鸭子、游泳起来像鸭子、叫起来也像鸭子, 那么这只鸟就能够被称为鸭子。" */
interface IPoint { 
    x:number 
    y:number 
} 
function addPoints(p1:IPoint,p2:IPoint):IPoint { 
    var x = p1.x + p2.x 
    var y = p1.y + p2.y 
    return {x:x,y:y} 
} 
 
// 正确
var newPoint = addPoints({x:3,y:4},{x:5,y:1})  
 
// 错误 
var newPoint2 = addPoints({x:1},{x:4,y:3})
复制代码

TypeScript 命名空间

  1. 命名空间一个最明确的目的就是解决重名问题。TypeScript 中命名空间使用 namespace 来定义
//语法以下:
namespace SomeNameSpaceName { 
   export interface ISomeInterfaceName {      }  
   export class SomeClassName {      }  
}

/** 以上定义了一个命名空间 SomeNameSpaceName, 若是咱们须要在外部能够调用 SomeNameSpaceName 中的类类和接口, 则须要在类和接口添加 export 关键字。 要在另一个命名空间调用语法格式为: */
SomeNameSpaceName.SomeClassName;

/** 若是一个命名空间在一个单独的 TypeScript 文件中, 则应使用三斜杠 /// 引用它,语法格式以下: */
/// <reference path = "SomeFileName.ts" />
复制代码
  1. 如下实例演示了命名空间的使用,定义在不一样文件中
IShape.ts 文件代码:
namespace Drawing { 
    export interface IShape { 
        draw(); 
    }
}

Circle.ts 文件代码:
/// <reference path = "IShape.ts" /> 
namespace Drawing { 
    export class Circle implements IShape { 
        public draw() { 
            console.log("Circle is drawn"); 
        }  
    }
}

Triangle.ts 文件代码:
/// <reference path = "IShape.ts" /> 
namespace Drawing { 
    export class Triangle implements IShape { 
        public draw() { 
            console.log("Triangle is drawn"); 
        } 
    } 
}

TestShape.ts 文件代码:
/// <reference path = "IShape.ts" />   
/// <reference path = "Circle.ts" /> 
/// <reference path = "Triangle.ts" />  
function drawAllShapes(shape:Drawing.IShape) { 
    shape.draw(); 
} 
drawAllShapes(new Drawing.Circle());
drawAllShapes(new Drawing.Triangle());

使用 tsc 命令编译以上代码:
tsc --out app.js TestShape.ts 
复制代码
  1. 命名空间支持嵌套,即你能够将命名空间定义在另一个命名空间里头
namespace namespace_name1 { 
    export namespace namespace_name2 {
        export class class_name {    } 
    } 
}
//成员的访问使用点号 . 来实现,以下实例:

//Invoice.ts 文件代码:
namespace Runoob { 
   export namespace invoiceApp { 
      export class Invoice { 
         public calculateDiscount(price: number) { 
            return price * .40; 
         } 
      } 
   } 
}

//InvoiceTest.ts 文件代码:
/// <reference path = "Invoice.ts" />
var invoice = new Runoob.invoiceApp.Invoice(); 
console.log(invoice.calculateDiscount(500));
复制代码

TypeScript 模块

  • TypeScript 模块的设计理念是能够更换的组织代码。
  • 模块是在其自身的做用域里执行,并非在全局做用域,这意味着定义在模块里面的变量、函数和类等在模块外部是不可见的,除非明确地使用 export 导出它们。相似地,咱们必须经过 import 导入其余模块导出的变量、函数、类等。
  • 两个模块之间的关系是经过在文件级别上使用 import 和 export 创建的。
  • 模块使用模块加载器去导入其它的模块。
  • 在运行时,模块加载器的做用是在执行此模块代码前去查找并执行这个模块的全部依赖。
  • 你们最熟知的JavaScript模块加载器是服务于 Node.jsCommonJS 和服务于 Web 应用的 Require.js
  • 此外还有有 SystemJsWebpack
模块导出使用关键字 export 关键字,语法格式以下:
// 文件名 : SomeInterface.ts 
export interface SomeInterface { 
   // 代码部分
}

要在另一个文件使用该模块就须要使用 import 关键字来导入:
import someInterfaceRef = require("./SomeInterface")
复制代码
  • 实例演示
//IShape.ts 文件代码:
/// <reference path = "IShape.ts" /> 
export interface IShape { 
   draw(); 
}
//Circle.ts 文件代码:
import shape = require("./IShape"); 
export class Circle implements shape.IShape { 
   public draw() { 
      console.log("Cirlce is drawn (external module)"); 
   } 
}
//Triangle.ts 文件代码:
import shape = require("./IShape"); 
export class Triangle implements shape.IShape { 
   public draw() { 
      console.log("Triangle is drawn (external module)"); 
   } 
}
//TestShape.ts 文件代码:
import shape = require("./IShape"); 
import circle = require("./Circle"); 
import triangle = require("./Triangle");  
 
function drawAllShapes(shapeToDraw: shape.IShape) {
   shapeToDraw.draw(); 
} 
 
drawAllShapes(new circle.Circle()); 
drawAllShapes(new triangle.Triangle());
复制代码

TypeScript 声明文件[declare]

  • TypeScript 做为 JavaScript 的超集,在开发过程当中不可避免要引用其余第三方的 JavaScript 的库。
  • 虽然经过直接引用能够调用库的类和方法,可是却没法使用TypeScript 诸如类型检查等特性功能。
  • 为了解决这个问题,须要将这些库里的函数和方法体去掉后只保留导出类型声明,而产生了一个描述 JavaScript 库和模块信息的声明文件。
  • 经过引用这个声明文件,就能够借用 TypeScript 的各类特性来使用库文件了。
假如咱们想使用第三方库,好比 jQuery,
咱们一般这样获取一个 id 是 foo 的元素:
$('#foo');
// 或
jQuery('#foo');

可是在 TypeScript 中,
咱们并不知道 $ 或 jQuery 是什么东西:
jQuery('#foo');
// index.ts(1,1): error TS2304: Cannot find name 'jQuery'.

这时,咱们须要使用 declare 关键字来定义它的类型,
帮助 TypeScript 判断咱们传入的参数类型对不对:
declare var jQuery: (selector: string) => any;
jQuery('#foo');

declare 定义的类型只会用于编译时的检查,编译结果中会被删除。
上例的编译结果是:
jQuery('#foo');
复制代码
  • 声明文件
以 .d.ts 为后缀,例如:runoob.d.ts
声明文件或模块的语法格式以下:
declare module Module_Name {
}

TypeScript 引入声明文件语法格式:
/// <reference path = " runoob.d.ts" />

固然,不少流行的第三方库的声明文件不须要咱们定义了,
好比 jQuery 已经有人帮咱们定义好了:
jQuery in DefinitelyTyped。
复制代码
  • 实例
如下定义一个第三方库来演示:
CalcThirdPartyJsLib.js 文件代码:
var Runoob;  
(function(Runoob) {
    var Calc = (function () { 
        function Calc() { 
        } 
    })
    Calc.prototype.doSum = function (limit) {
        var sum = 0; 
 
        for (var i = 0; i <= limit; i++) { 
            sum = sum + i; 
        }
        return sum; 
    }
    Runoob.Calc = Calc; 
    return Calc; 
})(Runoob || (Runoob = {})); 
var test = new Runoob.Calc();

若是咱们想在 TypeScript 中引用上面的代码,则须要设置声明文件 Calc.d.ts,代码以下:
Calc.d.ts 文件代码:
declare module Runoob { 
   export class Calc { 
      doSum(limit:number) : number; 
   }
}

声明文件不包含实现,它只是类型声明,把声明文件加入到 TypeScript 中:
CalcTest.ts 文件代码:
/// <reference path = "Calc.d.ts" /> 
var obj = new Runoob.Calc(); 
// obj.doSum("Hello"); // 编译错误
console.log(obj.doSum(10));

下面这行致使编译错误,由于咱们须要传入数字参数:
obj.doSum("Hello");
复制代码

学完TypeScript,来看下谷歌FLOW

  1. 做用: javascript类型检查

  2. 使用步骤

安装flow, npm init -y -> cnpm i flow-bin -D
package.json中增长执行指令, "flow": "flow"
初始化flow配置文件, npm run flow init
[ignore]: 忽略检测类型的文件
[include]: 须要检测类型的文件


在项目中使用以下:
A. 经过注释(不推荐)
// @flow 注释以后的内容才能被flow检测
/*: number */ 在须要检测的内容这样注释, 说明其中类型
  // @flow
  let a /*: number */ = 3;
  a = 'cc'
  console.log(a)

B. 直接改写js结构(须要babel, 相似ts语法了)
安装bebel, cnpm i babel-cli babel-preset-flow -D
建立.babelrc文件,
  {
    "presets": [
      "flow"
    ]
  }

package.json文件中添加 "build": "babel ./src -d ./dist"
npm run build 经过babel把新增长的: number去除掉, 方便转码上线(与下面的指令区分开来)
  let a: number = 3;
  a = 'abc';
  console.log(a);

npm run flow 仍是会检测数据类型
执行npm run flow, 检测js内容
复制代码
  1. Flow中的数据类型
number类型:
 能够赋值的类型——数值, NaN, Infinity 
 let a: number = NaN
 
 string类型
 
 Boolean类型
 
 void类型: 就是js中的undefined
 
 null
 
 Array类型(须要指定array的元素类型) :
 let arr: Array<number> = []
 
 any类型
 let test: any = 任意数据
复制代码
  1. Flow的函数类型
// 声明一个函数类型, 函数参数声明类型, 返回值也要声明类型
  const sum = (arr: Array<number>): number => {
    let result = 0;
    arr.forEach(item => {
      result += item;
    });
    return result;
  };

  // 当声明一个函数变量时, 说明这个变量是函数, 
  //参数两个为数字, 返回值为数字
  let temp = (a: number, b:number) => number;
  // 最多见的ajax, 参数是函数时, 同时箭头后面表明返回值类型,
  // 不写默认是undefined
  const ajax = (callback: (data: Object) => void) {

  }

//Maybe类型
  // 问号表明能够是null或者undefined, 函数没有声明返回值, 
  // 即返回值也能够是undefined
  const test = (a: ?number) {
    console.log(a)
  } 

//类型的或操做
  // 就是或操做, 二者类型选择一个
  let a = number|string = 10;
  a = 'abc'

//对象类型
  const ajax = (option: { url:string, type: string, 
      success:(data: Object) => void }) {

  }
  ajax()// 报错, 由于函数参数是对象
复制代码
相关文章
相关标签/搜索