TypeScript---Web学习总结
1 TypeScript 简介
TypeScript 是微软开发的一个开源的编程语言,通过在JavaScript的基础上添加静态类型定义构建而成。TypeScript通过TypeScript编译器或Babel转译为JavaScript代码,可运行在任何浏览器,任何操作系统。
- 它是JavaScript的一个超集,而且本质上向这个语言添加了可选的静态类型和基于类的面向对象编程。
- TypeScript扩展了JavaScript的语法,所以任何现有的JavaScript程序可以运行在TypeScript环境中。TypeScript是为大型应用的开发而设计,并且可以编译为JavaScript。
- TypeScript 支持为已存在的 JavaScript 库添加类型信息的头文件,扩展了它对于流行库的支持,如 jQuery,MongoDB,Node.js 和 D3.js 等。
2 TypeScript基本内容
2.1 let 和 const
- let
关键字let
是ES6
中新增的特性,它的出现是为了解决 var 变量声明所存在的一些问题。
不使用var
,使用let
或const
申明变量,并加上类型说明,且作用域为块级即以{}
为界。
在let
中引入了块级作用域(也可称为词法作用域)。
示例:
我们可以看下面这段代码,在函数 myFunc 中使用 let 声明了一个变量 a,且在 if 语句中声明了一个变量b
:
function myFunc(){
let a = 10;
if(a > 5) {
let b = a;
return b;
}
return b;
}
可以知道变量 a 的作用域就在整个 myFunc() 函数中,而变量 b 的作用域在 if 语句块中,变量 a 可以在函数内使用,但是如果我们在 if 语句以外的地方使用变量 b,则会出错。
- const
const
也是块级作用域。
它与let
唯一不同在于,const
声明的变量只能在声明时被赋值,之后不能再次被赋值,也就是说通过const
声明的变量被赋值后不能再改变。
let lang: string = 'TypeScript';//如果省略类型说明,TS也可进行自动推断
lang = 1010;//error! 如果需要可以使用联合类型:let lang: number | string = 'TS';
let age: number = 89;
let age = 64;//error!
const pi: number = 3.14159;//pi以后不可改变,类似常量
pi = 3.14;//error!
在实际使用中,最好尽量使用 let
和 const
来声明变量,减少var
的使用。所有变量除了需要修改的都应该使用 const 声明。
2.2 解构
TypeScript支持以下形式的解构(以解构的名义命名,即分解结构):
- 对象分解
- 阵列解构
- 解构数组
最简单的解构莫过于数组的解构赋值,
可以从数组中提取值,按照对应位置,对变量赋值
//解构数组
let input = [89, 64, 2018, 10];
let [first, second] = input;//注意使用[]
console.log(first); // 89
console.log(second); // 64
let [one, ...others] = input; //剩余变量
console.log(...others);
//展开
let newArr = [89, ...others, 18];
console.log(newArr);
- 解构对象
对象也可以解构:
let o = {
a: "foo",
b: 12,
c: "bar"
};
let { a, b } = o;
就像数组解构,可以用没有声明的赋值:
({ a, b } = { a: "baz", b: 101 });
也可以使用…语法:
let { a, ...passthrough } = o;
let total = passthrough.b + passthrough.c.length;
2.3 函数
- 函数类型
- 使用完整函数类型定义
// 第一种es5写法:
function result(arg1: number, arg2: number): number {
return arg1 + arg2
}
// 第二种es6写法:
const result1 = (arg1: number, arg2: number): number => arg1 + arg2
- 完整的函数类型
// 顶一个变量,变量的值是函数
// 传入参数类型、返回值类型都是number
let result2: (x: number, y: number) => number
result2 = (arg1: number, arg2: number) => arg1 + arg2 // 正确
result2 = (arg1: string, arg2: number) => arg1 + arg2 // 错误,arg1类型和规定的不一致
- 使用接口定义函数类型
// 方式一:
interface MyFunc {
(x:number, y: number): number
}
// 方式二:
type MyFunc2 = (x:number, y: number) => number
- 使用类型别名定义函数类型
// 定义函数,参数类型是number返回值类型也是number
type MyFunc = (x: number, y: number) => number
// 使用,声明一个变量,变量的类型就是上面定义的函数
let sameMyFunc : MyFunc
sameMyFunc = (arg1: number, arg2: number) => arg1 + arg2
let sameMyFunc : MyFunc = (arg1: number, arg2: number) => arg1 + arg2
- 函数参数
- 可选参数
// 函数的可选参数必须放置在必选参数后面,也就是最后面
// 定义函数类型
type FuncType = (arg1: number, arg2: number, arg3?:number) => number
//定义函数,使用上面规定的函数类型
// const useFuncType: FuncType = (x: number, y: number) => x + y
const useFuncType2: FuncType = (x: number, y: number, z: number) => x + y + z
- 默认参数
用户没有传值,参数有自己的默认值
// 函数的默认参数最好也放在最后面,放在前面也可以的,但是不推荐
// 函数的默认参数可以不写类型,ts会根据默认值自己判断是哪种类型
const defaultValFun = (x: number, y: number = 5) => x + y // 推荐
const defaultValFun = (x: number, y = 5) => x + y // 推荐
const defaultValFun = (x: number = 10, y: number) => x + y // 不推荐
- 剩余参数
必要参数,默认参数和可选参数有个共同点:它们表示某一个参数。
有时,你想同时操作多个参数,或者你并不知道会有多少参数传递进来, 在TypeScript里,可以把所有参数收集到一个变量里
// 使用 ...args 来代替就可以
// ...args 也要放到参数的最后面
// ...args:number[] 剩余参数是number类型的数组
const func = (args1:number,args2:number,...args:number[])=>{}
- 箭头函数
特点:简化函数定义、解决this问题
箭头函数能保存函数创建时的 this值,而不是调用时的值。
//无参数,函数体代码只有一行,则该行结果即为函数返回值
let greeting1 = () => `Hello TS!`;
console.log(greeting1());
//一个参数,函数体代码只有一行,则该行结果即为函数返回值
let greeting2 = (name: string) => `Hello ${name}`;
console.log(greeting2('QiGe'));
//两个及以上的参数,函数体代码只有一行,则该行结果即为函数返回值
let add1 = (n1: number, n2: number) => n1 + n2;
console.log(add1(1, 2));
//两个及以上的参数,函数体代码多于一行,则必须用{}包裹,且显式给出return
let add2 = (n1: number, n2: number) => {
let sum = n1 + n2;
return sum;//改为sum++结果如何?
}
console.log(add2(1, 2));
2.4 类class
类是属性(有些什么)和函数(能干什么)的集合,是生成对象(Object)或类实例的模板。(请注意,我们要用的Angular框架大量使用类)
- 类的定义和使用
class Greeter {
greeting: string;
constructor(message: string) {
this.greeting = message;
}
greet() {
return "Hello, " + this.greeting;
}
}
let greeter = new Greeter("world");
声明一个 Greeter类。这个类有3个成员:一个叫做 greeting的属性,一个构造函数和一个 greet方法。
在引用任何一个类成员的时候都用了 this。 它表示我们访问的是类的成员。
最后一行,使用 new构造了 Greeter类的一个实例。 它会调用之前定义的构造函数,创建一个 Greeter类型的新对象,并执行构造函数初始化它。
- 类的属性和函数的访问权限
类中的属性和函数都有访问权限,默认为public即全局可访问,其次为protected即可在类的内部和其子类的内部可访问,最后为private,只能在该类内部可访问。
//访问权限
class MyInfo { //class是关键字,类名默认全部大写首字母
public name: string; //public属性,可省略
private _weather: string; //私有属性,习惯以_开头进行命名
constructor(name: string, weather: string){ //构造函数,一般用于初始化
this.name = name;
this._weather = weather;
}
printInfo(): void { //其它函数
this._test();
console.log(`Hello, ${this.name}.`);
console.log(`Today is ${this._weather}.`);
}
private _test(): void {
console.log('You can not call me outside!');
}
}
let myData = new MyInfo('QiGe', 'raining'); //使用new关键字生成对象
console.log(myData._weather); //error!
myData._test(); //error
myData.printInfo();
- 存取器-getter、setter
TypeScript支持通过getters/setters
来截取对对象成员的访问。 它能帮助你有效的控制对对象成员的访问。
下面来看如何把一个简单的类改写成使用 get和 set。
class Employee {
fullName: string
}
let employee = new Employee();
employee.fullName = "durban zhang";
if (employee.fullName) {
console.log(employee.fullName);
}
运行后结果如下
$ npx ts-node src/classes_4.ts
durban zhang
可以随意的设置 fullName
对于存取器有下面几点需要注意的:首先,存取器要求你将编译器设置为输出ECMAScript 5或更高。 不支持降级到ECMAScript 3。 其次,只带有 get不带有 set的存取器自动被推断为 readonly。 这在从代码生成 .d.ts文件时是有帮助的,因为利用这个属性的用户会看到不允许够改变它的值。
- 静态属性
如果类的某些属性属于全体类对象共有的,那么可以把这些属性通过static
声明为静态属性。以避免每个对象都生成一份同样的数据成员,浪费内存。
类的静态属性属于类,因此是通过类名来访问。
类的静态属性共用于全体对象,如果运行时对其进行了更改,会影响所有对象。
class UChar{
static MAX : number = 255;
static MIN : number = 0;
private data : number;
constructor(data : number)
{
if(data > UChar.MAX){
data = UChar.MAX
}
if(data < UChar.MIN){
data = UChar.MIN;
}
this.data = data;
}
print():void{
console.log("data=", this.data);
}
}
let u1 = new UChar(1000); //初始值溢出
u1.print(); //输出:data= 255
UChar.MAX = 10000; //改变了静态属性
let u2 = new UChar(1000); //更改值不再溢出
u2.print(); //输出:data= 1000
可见有时在运行时更改静态属性并不是一个好的方式,因此建议将静态属性声明为readonly或private,禁止随意的在类的外部对静态属性进行更改。
- 继承
可以通过extends关键字继承其它类,从而成为其子类。
Typescript在编译的时候会提供对象继承的模拟实现。
实现对象继承时,需要实现下面两点:
- 实现类成员的继承
- 实现实例成员的继承
Typescript实现类成员继承的方法如下:
var extendStatics = Object.setPrototypeOf || {__proto__:[]} instanceOf Array && function(child, parent) {child.__proto__ = parent} || function(child, parent) { for (var p in parent) if (parent.hasOwnProperty(p)) child[p] = parent[p]; }
在Typescript的实现中,处理了三种情形:
- 如果浏览器支持Object.setPrototypeOf,就直接使用
- 如果浏览器支持__proto__属性,就将子类的__proto__属性设置为父类
- 如果上述都不支持,就通过for循环复制父类的类成员
Typescript实现实例成员继承的方法如下:
function(child, parent) {
function base() { base.constructor = child}
child.prototype = (parent == null) ? Object.create(null) : base.prototype = parent.prototype, new base();
}
将上述两个方法合并,可以得到对象继承的实现方法:
function extends(child, parent) {
extendStatics(child, parent);
function base() { this.constructor = child; }
child.prototype = (parent == null) ? Object.create(null) : base.prototype = parent.prototype, new base();
}
2.5 模块Module
JavaScript 有一个很长的处理模块化代码的历史,TypeScript 从 2012 年开始跟进,现在已经实现支持了很多格式。不过随着时间流逝,社区和 JavaScript 规范已经收敛为名为 ES 模块(或者 ES6 模块)的格式,这也就是我们所知的 import/export 语法。
对于模块而言它自身是运行在一个局部的作用域中的,这一点非常重要,这也意味着在一个模块中的变量,函数,类等,除非你导出,外部程序是无法使用的,因此 TypeScript 也遵循了 import 和 export 的机制,反之如果一个文件内没有这两个关键字存在的话,那么它的内容将会被视为全局。
2.6 装饰器
装饰器是一种特殊类型的声明,它能够被附加到类声明,方法, 访问符,属性或参数上。 装饰器使用 @expression
这种形式,expression
求值后必须为一个函数,它会在运行时被调用,被装饰的声明信息做为参数传入。
装饰器的写法:普通装饰器(无法传参)、装饰器工厂(可传参)
- 装饰器工厂
如果要定制一个修饰器如何应用到一个声明上,得写一个装饰器工厂函数。 装饰器工厂就是一个简单的函数,它返回一个表达式,以供装饰器在运行时调用。 - 装饰器组合
多个装饰器可以同时应用到一个声明上。
//书写在同一行上:
@f @g x
//书写在多行上:
@f
@g
x
同样的,在TypeScript里,当多个装饰器应用在一个声明上时会进行如下步骤的操作:
① 由上至下依次对装饰器表达式求值。
② 求值的结果会被当作函数,由下至上依次调用。
- 常见的装饰器
- 类装饰器
类装饰器在类声明之前被声明〈紧靠着类声明)。类装饰器应用于类构造函数,可以用来监视,修改或替换类定义。 - 属性装饰器
属性装饰器表达式会在运行时当作函数被调用,传入下列2个参数:
装饰的实例。对于静态成员来说是类的构造函数,对于实例成员是类的原型对象
装饰的属性名
/**
* 属性装饰器
* params就是装饰器传入的参数
* target就是装饰的实例
* attr就是装饰的属性
*/
function logProperty(params: any) {
return function (target: any, attr: string) {
//通过这样的方式就可以通过装饰器来修改属性值
target[attr] = params
}
}
class HttpClient {
@logProperty('属性装饰器赋值')
public apiUrl: string | undefined
constructor() {
}
getData() {
console.log(this.apiUrl)
}
}
const http = new HttpClient()
http.getData() // 属性装饰器赋值
- 方法装饰器
它会被应用到方法的属性描述符上,可以用来监视,修改或者替换方法定义。方法装饰会在运行时传入下列个参数:
装饰的实例。对于静态成员来说是类的构造函数,对于实例成员是类的原型对象
成员的名字
成员的属性描述符
/**
* params 传递给装饰器的值
* target 装饰器的实例
* methodName 方法名称
* descriptor 描述
*/
function get(params: any) {
console.log(params) // http://www.baidu.com
return function (target: any, methodName: string, descriptor: PropertyDescriptor) {
console.log(target)
console.log(methodName)
console.log(descriptor)
//修改前保存原始传入的方法
let originalMethod = descriptor.value
//重写传入的方法
descriptor.value = function (...args: any[]) {
//执行原来的方法
originalMethod.apply(this, args)
args = args.map(val => +val)
console.log(args)
}
}
}
class HttpClient {
constructor() {
}
@get('http://www.baidu.com')
getApi() {
}
}
const http: any = new HttpClient()
http.getApi('123', '456', '789') //打印[123, 456, 789]
- 参数装饰器
运行时会被当做函数被调用,可以使用参数装饰器为类的原型增加一些元素数据,传入3个参数:
装饰的实例。对于静态成员来说是类的构造函数,对于实例成员是类的原型对象
方法名
参数在函数参数列表中的索引
function logParams(param: any) {
return function (target: any, methodName: string, paramIndex: number) {
console.log(target) // httpClient实例
console.log(methodName) // getApi
console.log(paramIndex) // 0
}
}
class HttpClient {
constructor() {
}
getApi(@logParams('id') id: number) {
console.log(id)
}
}
const http = new HttpClient()
http.getApi(123456)
2.7 依赖注入
依赖:如果 A类 的功能实现依赖于 B类的功能,那么 类B 就是 类A 的依赖;
注入: 通过 构造函数……方法动态的把 B类的实例,传递给 A类就是注入;【反之在A类的内部实例化一个B类的实例,就不是注入】
declare namespace Type{
interface Phone {
call: Phone.Call;
play: Phone.Play;
}
namespace Phone {
interface Call{
(n: number): void
}
interface Play{
(): void
}
}
interface Person{
name: string;
phone: Phone;
call: Phone.Call
play: Phone.Play
}
}
class Phone implements Type.Phone{
constructor(){
}
call(n: number){
console.log('call', n)
}
play(){
console.log('play')
}
}
// 非依赖注入方法
class Person implements Type.Person {
// 添加了新的类关系 Phone
phone: Type.Phone = new Phone()
name: string;
constructor(
name: string
){
this.name = name;
}
call(n: number){
return this.phone.call(n)
}
play(){
return this.phone.play();
}
}
// 依赖注入的方式
class Person2 implements Type.Person {
phone: Type.Phone;
name: string;
// 把phone 的实例 通过构造函数注入进去
// 这样 类 Person2 只依赖接口
// 没有依赖具体的 Phone 类;
constructor(name: string, phone: Type.Phone){
this.phone = phone;
this.name = name;
}
call(n: number){
return this.phone.call(n)
}
play(){
return this.phone.play();
}
}
// 测试
const bill = new Person2('bill', new Phone())
bill.call(11)
bill.play()