【TS】TypeScript学习

2019-08-15  本文已影响0人  前端菜篮子

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

前言

所以为啥要用TypeScript

  1. 从移动终端到后端服务,从 IoT 到神经网络,JavaScript 几乎无处不在。如此广阔的应用领域,自然对语言的安全性、健壮性和可维护性有更高的要求。
  2. 尽管 ES 标准在近几年有了长足的进步,但在类型检查方面依然无所建树。大家可能常常会遇到这样到场景:
  3. 你调用一个别人写的函数,很不幸,这个家伙没有留下任何注释,为了搞清楚参数类型,你只能硬着头皮去看里面的逻辑。
  4. 为了保证代码的健壮性,你很有责任心,对一个函数的输入参数进行各种假设,最终给老板盛上了一碗香喷喷的意大利面。
  5. 领导看好你,让你维护一个重要的底层类库,你殚精竭虑,优化了一个参数类型,但不知道有多少处引用,在提交代码前,是否感到脊背发凉?
  6. 明明定义好了接口,可一联调就报错了——“TypeError: Cannot read property 'length' of undefined”,于是你怒气冲冲地去找后端理论:“嘿,哥们儿!这个字段是数组!这个字段是数组!这个字段是数组!”
  7. 归根结底,是因为 JavaScript 是一门动态弱类型语言, 对变量的类型非常宽容,而且不会在这些变量和它们的调用者之间建立结构化的契约。如果你长期在没有类型约束的环境下开发,就会造成“类型思维”的缺失,养成不良的编程习惯,这也是做前端开发的短板之一,值得我们警醒。
  8. 幸运的是,TypeScript 的出现很好地弥补了 JavaScript 在静态类型检查方面的缺陷。它为 JavaScript 提供了良好的类型检查支持,而且能够编译成标准的 JavaScript。
  9. 目前, Angular 已经使用 TypeScript 重构了代码,另一大前端框架 Vue 的新版本也将使用 TypeScript 进行重构。在可预见的未来,TypeScript 将成为前端开发者必须掌握的开发语言之一。
  10. 那么, 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 过程如下图:


    image
  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接口

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

一个简单的接口

/**
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 模块

模块导出使用关键字 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]

假如我们想使用第三方库,比如 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()// 报错, 因为函数参数是对象
上一篇下一篇

猜你喜欢

热点阅读