【TS】TypeScript学习
2019-08-15 本文已影响0人
前端菜篮子
与JS类似部分,本文不做说明(运算符、条件语句、循环语句、函数、基本类型等)
前言
-
TypeScript
的设计目的应该是解决JavaScript的痛点
:弱类型和没有命名空间,导致很难模块化,不适合开发大型程序。另外它还提供了一些语法糖来帮助大家更方便地实践面向对象的编程。 -
TypeScript
并没有抛弃JavaScript
的语法另起炉灶,而是做成了JavaScript的超集,这样任何合法的JavaScript的语句在TypeScript下都是合法的,也就是说学习成本很低 -
语法糖:
TypeScript
可以实现类,接口,枚举,泛型,方法重载等,用简洁的语法丰富了JavaScript
的使用。 -
类型检查:谷歌的
Flow
也可以做到 -
ES6用类Class
(语法糖)也挺好用的啦......
所以为啥要用TypeScript
- 从移动终端到后端服务,从 IoT 到神经网络,JavaScript 几乎无处不在。如此广阔的应用领域,自然对语言的安全性、健壮性和可维护性有更高的要求。
- 尽管 ES 标准在近几年有了长足的进步,但在类型检查方面依然无所建树。大家可能常常会遇到这样到场景:
- 你调用一个别人写的函数,很不幸,这个家伙没有留下任何注释,为了搞清楚参数类型,你只能硬着头皮去看里面的逻辑。
- 为了保证代码的健壮性,你很有责任心,对一个函数的输入参数进行各种假设,最终给老板盛上了一碗香喷喷的意大利面。
- 领导看好你,让你维护一个重要的底层类库,你殚精竭虑,优化了一个参数类型,但不知道有多少处引用,在提交代码前,是否感到脊背发凉?
- 明明定义好了接口,可一联调就报错了——“TypeError: Cannot read property 'length' of undefined”,于是你怒气冲冲地去找后端理论:“嘿,哥们儿!这个字段是数组!这个字段是数组!这个字段是数组!”
- 归根结底,是因为 JavaScript 是一门动态弱类型语言, 对变量的类型非常宽容,而且不会在这些变量和它们的调用者之间建立结构化的契约。如果你长期在没有类型约束的环境下开发,就会造成“类型思维”的缺失,养成不良的编程习惯,这也是做前端开发的短板之一,值得我们警醒。
- 幸运的是,TypeScript 的出现很好地弥补了 JavaScript 在静态类型检查方面的缺陷。它为 JavaScript 提供了良好的类型检查支持,而且能够编译成标准的 JavaScript。
- 目前, Angular 已经使用 TypeScript 重构了代码,另一大前端框架 Vue 的新版本也将使用 TypeScript 进行重构。在可预见的未来,TypeScript 将成为前端开发者必须掌握的开发语言之一。
- 那么, TypeScript 究竟有哪些特性使得它成为大家的”刚需“?
- 第一,类型检查。TypeScript 会在编译代码时进行严格的静态类型检查,这意味着你可以在编码阶段发现可能存在的隐患,而不必把它们带到线上。
- 第二,语言扩展。TypeScript 会包括来自 ES 6 和未来提案中的特性,比如异步操作和装饰器;也会从其他语言借鉴某些特性,比如接口和抽象类。
- 第三,工具属性。TypeScript 能够编译成标准的 JavaScript,可以在任何浏览器、操作系统上运行,无需任何运行时的额外开销。从这个角度上讲,TypeScript 更像是一个工具,而不是一门独立的语言。
- 除此之外,TypeScript 还可以帮助团队重塑“类型思维”,接口提供方将被迫去思考 API 的边界,他们将从代码的编写者蜕变为代码的设计者。
第一个TypeScript
实例
//var [变量名] : [类型] = 值;
const hello : string = "Hello World!"
console.log(hello)
TypeScript
安装【即在cmd中将ts编译成js的命令】
- 本地环境安装 npm 工具,使用以下命令来安装:
npm install -g typescript
- 安装后我们使用 tsc 命令来执行 TypeScript 的相关代码:
//以下是查看版本号
$ tsc -v
Version 3.2.2
- 然后我们新建一个 test.ts
var message:string = "Hello World"
console.log(message)
- 执行
tsc
命令将 TypeScript 转换为 JavaScript 代码:
tsc test.ts
- 这时候再当前目录下(与 test.ts 同一目录)就会生成一个 test.js 文件,代码如下:
var message = "Hello World";
console.log(message);
- 使用 node 命令来执行 test.js 文件:
$ node test.js
Hello World
-
TypeScript 转换为 JavaScript 过程如下图:
image -
我们可以同时编译多个 ts 文件:
tsc file1.ts, file2.ts, file3.ts
- 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
元祖&联合类型
- 数组中的元素一般认为都是相同数据类型的
- 如果存储的元素数据类型不同,则需要使用元组
- 至于元祖的操作,和数组并无差别
- 联合类型:通过管道(|)将变量设置多种类型
var val:string|number
val = 12
console.log("数字为 "+ val)
val = "Runoob"
console.log("字符串为 " + val)
- 也可以将联合类型作为函数参数使用
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])
}
}
}
- 联合类型数组
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
对象
- 乍一看,好像和普通的
JavaScript
对象没啥区别
/**
对象是包含一组键值对的实例。
值可以是标量、函数、数组、对象等,如下实例
*/
var object_name = {
key1: "value1", // 标量
key2: "value",
key3: function() {
// 函数
},
key4:["content1", "content2"] //集合
}
console.log(object_name.key1)
- 再来看下面的实例,他们还是不一样滴【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
命名空间
- 命名空间一个最明确的目的就是解决重名问题。
TypeScript
中命名空间使用namespace
来定义
//语法如下:
namespace SomeNameSpaceName {
export interface ISomeInterfaceName { }
export class SomeClassName { }
}
/**
以上定义了一个命名空间 SomeNameSpaceName,
如果我们需要在外部可以调用 SomeNameSpaceName 中的类类和接口,
则需要在类和接口添加 export 关键字。
要在另外一个命名空间调用语法格式为:
*/
SomeNameSpaceName.SomeClassName;
/**
如果一个命名空间在一个单独的 TypeScript 文件中,
则应使用三斜杠 /// 引用它,语法格式如下:
*/
/// <reference path = "SomeFileName.ts" />
- 以下实例演示了命名空间的使用,定义在不同文件中
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
- 命名空间支持嵌套,即你可以将命名空间定义在另外一个命名空间里头
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.js
的CommonJS
和服务于Web
应用的Require.js
。 - 此外还有有
SystemJs
和Webpack
。
模块导出使用关键字 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
-
作用: javascript类型检查
-
使用步骤
安装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内容
- Flow中的数据类型
number类型:
可以赋值的类型——数值, NaN, Infinity
let a: number = NaN
string类型
Boolean类型
void类型: 就是js中的undefined
null
Array类型(需要指定array的元素类型) :
let arr: Array<number> = []
any类型
let test: any = 任意数据
- 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()// 报错, 因为函数参数是对象