TypeScript
类型注解
TypeScript里的类型注解是一种轻量级的为函数或变量添加约束的方式。 在这个例子里,我们希望 greeter
函数接收一个字符串参数。 然后尝试把 greeter
的调用改成传入一个数组:
function greeter(person: string) {
return "Hello, " + person;
}
let user = [0, 1, 2];
document.body.innerHTML = greeter(user);
重新编译,你会看到产生了一个错误。
greeter.ts(7,26): error TS2345: Argument of type 'number[]' is not assignable to parameter of type 'string'.
类似地,尝试删除greeter
调用的所有参数。 TypeScript会告诉你使用了非期望个数的参数调用了这个函数。 在这两种情况中,TypeScript提供了静态的代码分析,它可以分析代码结构和提供的类型注解。
要注意的是尽管有错误,greeter.js
文件还是被创建了。 就算你的代码里有错误,你仍然可以使用TypeScript。但在这种情况下,TypeScript会警告你代码可能不会按预期执行。
接口
作用:限制输入或者输出类型,就像是显示器接口,有好几种型号
让我们开发这个示例应用。这里我们使用接口来描述一个拥有firstName
和lastName
字段的对象。 在TypeScript里,只在两个类型内部的结构兼容那么这两个类型就是兼容的。 这就允许我们在实现接口时候只要保证包含了接口要求的结构就可以,而不必明确地使用 implements
语句。
//属性接口
interface Person {
firstName: string;
lastName: string;
}
function greeter(person: Person) {
return "Hello, " + person.firstName + " " + person.lastName;
}
//函数类型接口
interface Person {
(key:string,value:any):string //左边规定函数参数 右边规定返回值
}
var fn:Person = function(key:string,value:any):string{
}
//可索引接口
//对数组的约束
interface Person{
[index:number]:string //表示索引值是number 值是string
}
var arr:Person = ['zs','ls']
var obj:Person = {name:'zs'}//报错 因为索引不是number类型
//对对象的约束
interface Person{
[index:string]:string
}
var obj:Person = {name:'zs'}//正确
//类类型接口
//接口
interface Animal {
name:string
eat(str:string):void
}
//使用
class Dog implements Animal{ //注意类要使用接口就得用implements
name:string //得符合接口
eat(){ //这里可以不定义形参 如果要写就要像接口那样写 eat(food:string)
this.name = 'zs'
console.log('this.name')
}
}
//接口的继承
interface Animal {
eat():void
}
interface Dog extends {
run():void
}
class miao implements Dog{
run(){}
eat(){}
}
泛型
可以支持不特定的数据类型,也就是将类型作为一个变量
function getData<T>(name:T):T{ //T是在运行的时候确定的,编译时无法确定,所以只能返回一个name或者其它属于T类型的值(属于T类型),返回其他会报错,因为无法确定返回的是否是T类型
return name //这里返回'dad'、123等等都会报错,因为T还没确定
}
getData<string>('zs')//此时T就为string
//或者
function getData<T>(name:T):any{
return 123
}
getData<string>('zs')//此时T就为string
//-----------------调用的时候也可以省略<>根据传入的参数来推断是什么类型 getData('zs') 但是不严格,在复杂的情况下可能出错
//类的泛型
class Animal<T>{
name:T
}
var a = new Animal<string>('zs')
//泛型接口
//接口定义第一种写法
interface Config{
// (value:string):string
<T>(value:T):T
}
var fn:Config = function<T>(name:T):T{
return '我是nihao'
}
//第二种写法
interface Config<T> {
// (value:string):string
(value: T): T
}
function getData<T>(value: T): T {
return value
}
var fn: Config<string> = getData
泛型约束
function loggingIdentity<T>(arg: T): T {
console.log(arg.length); // Error: T doesn't have .length
return arg;
}
为此,我们定义一个接口来描述约束条件。 创建一个包含 .length
属性的接口,使用这个接口和extends
关键字来实现约束:
interface Lengthwise {
length: number;
}
function loggingIdentity<T extends Lengthwise>(arg: T): T {
console.log(arg.length); // Now we know it has a .length property, so no more error
return arg;
}
现在这个泛型函数被定义了约束,因此它不再是适用于任意类型:
loggingIdentity(3); // Error, number doesn't have a .length property
我们需要传入符合约束类型的值,必须包含必须的属性:
loggingIdentity({length: 10, value: 3});
类类型
在TypeScript使用泛型创建工厂函数时,需要引用构造函数的类类型。比如:
//限制传入的参数为类且类型和T一样
function create<T>(c: {new(): T; }): T {
return new c();
}
//另一种写法
function create<T>(c: new () => T ): T {
return new c();
}
一个更高级的例子,使用原型属性推断并约束构造函数与类实例的关系。
class BeeKeeper {
hasMask: boolean;
}
class ZooKeeper {
nametag: string;
}
class Animal {
numLegs: number;
}
class Bee extends Animal {
keeper: BeeKeeper; //keeper属性为Beekeeper类
}
class Lion extends Animal {
keeper: ZooKeeper; //keeper属性为zookeeper类
}
function createInstance<A extends Animal>(c: new () => A): A {
return new c();
}
createInstance(Lion).keeper.nametag; // typechecks!
createInstance(Bee).keeper.hasMask; // typechecks!
类
最后,让我们使用类来改写这个例子。 TypeScript支持JavaScript的新特性,比如支持基于类的面向对象编程。
让我们创建一个Student
类,它带有一个构造函数和一些公共字段。 注意类和接口可以一起共作,程序员可以自行决定抽象的级别。
还要注意的是,在构造函数的参数上使用public
等同于创建了同名的成员变量。
class Student {
fullName: string;
constructor(public firstName, public middleInitial, public lastName) {
this.fullName = firstName + " " + middleInitial + " " + lastName;
}
}
interface Person {
firstName: string;
lastName: string;
}
function greeter(person : Person) {
return "Hello, " + person.firstName + " " + person.lastName;
}
let user = new Student("Jane", "M.", "User");
document.body.innerHTML = greeter(user);
重新运行tsc greeter.ts
,你会看到生成的JavaScript代码和原先的一样。 TypeScript里的类只是JavaScript里常用的基于原型面向对象编程的简写。
公共,私有与受保护的修饰符
public :公有
在类里面、子类、类外面都可以访问
protected:保护类型
在类里面、子类里面可以访问﹐在类外部没法访问
private :私有
在类里面可以访间,子类、类外部都没法访问
默认为public
你也可以明确的将一个成员标记成public
。 我们可以用下面的方式来重写上面的Animal
类:
class Animal {
public name: string;
public constructor(theName: string) { this.name = theName; }
public move(distanceInMeters: number) {
console.log(`${this.name} moved ${distanceInMeters}m.`);
}
}
理解private
当成员被标记成private
时,它就不能在声明它的类的外部访问。比如:
class Animal {
private name: string;
constructor(theName: string) { this.name = theName; }
}
// 通过外部直接访问 错误: 'name' 是私有的. 不能直接访问
new Animal("Cat").name; //报错
var a =new Animal("Cat")
a.name // 报错
//间接访问 通过内部成员方法修改或者访问
class Animal {
private name: string;
constructor(theName: string) { this.name = theName; }
change(x){
this.name = x
}
}
var a =new Animal("Cat")
a.change('dog')
//如果修饰了构造函数则这个类不能被实例化也不能被继承
//来自同一处声明可以兼容
class Animal {
private name: string;
constructor(theName: string) { this.name = theName; }
}
class Rhino extends Animal {
constructor() { super("Rhino"); }
}
class Employee {
private name: string;
constructor(theName: string) { this.name = theName; }
}
let animal = new Animal("Goat");
let rhino = new Rhino();
let employee = new Employee("Bob");
animal = rhino;
animal = employee; // 错误: Animal 与 Employee 不兼容.
原因:这个例子中有Animal
和Rhino
两个类,Rhino
是Animal
类的子类。 还有一个Employee
类,其类型看上去与Animal
是相同的。 我们创建了几个这些类的实例,并相互赋值来看看会发生什么。 因为Animal
和Rhino
共享了来自Animal
里的私有成员定义private name: string
,因此它们是兼容的。 然而Employee
却不是这样。当把Employee
赋值给Animal
的时候,得到一个错误,说它们的类型不兼容。 尽管Employee
里也有一个私有成员name
,但它明显不是Animal
里面定义的那个
理解protected
protected
修饰符与private
修饰符的行为很相似,但有一点不同,protected
成员在派生类中仍然可以访问。
//派生类也不能访问父类private私有成员
class Animal {
private name: string ;
constructor(theName: string) { this.name = theName; }
change() {
console.log(this.name);
}
}
class Dog extends Animal {
constructor() {
super('dog') //报错 派生类也不能访问name
}
print() {
console.log(this.name);
}
}
var dog = new Dog()
//----------------派生类不可访问父类私有成员-----------
dog.print()//报错
dog.name //报错
//派生类可以通过自身方法访问父类proceted成员
class Animal {
protected name: string ;
constructor(theName: string) { this.name = theName; }
change() {
console.log(this.name);
}
}
class Dog extends Animal {
constructor() {
super('dog') //报错 派生类也不能访问name
}
print() {
console.log(this.name);
}
}
var dog = new Dog()
dog.print()//正确 可以访问
dog.name //报错
//构造函数也可以被标记成protected。 这意味着这个类不能在包含它的类外被实例化,但是能被继承。比如,
class Person {
protected name: string;
protected constructor(theName: string) { this.name = theName; }
}
// Employee 能够继承 Person
class Employee extends Person {
private department: string;
constructor(name: string, department: string) {
super(name);
this.department = department;
}
public getElevatorPitch() {
return `Hello, my name is ${this.name} and I work in ${this.department}.`;
}
}
let howard = new Employee("Howard", "Sales");
let john = new Person("John"); // 错误: 'Person' 的构造函数是被保护的
readonly修饰符
你可以使用readonly
关键字将属性设置为只读的。 只读属性必须在声明时或构造函数里被初始化。之后不可以再修改(即使通过成员方法也不行)
class Octopus {
readonly name: string;
readonly numberOfLegs: number = 8;
constructor (theName: string) {
this.name = theName;
}
}
let dad = new Octopus("Man with the 8 strong legs");
dad.name = "Man with the 3-piece suit"; // 错误! name 是只读的
存取器
let passcode = "secret passcode";
class Employee {
private _fullName: string;
get fullName(): string {
return this._fullName;
}
set fullName(newName: string) {
if (passcode && passcode == "secret passcode") {
this._fullName = newName;
}
else {
console.log("Error: Unauthorized update of employee!");
}
}
}
let employee = new Employee();
employee.fullName = "Bob Smith";
if (employee.fullName) {
alert(employee.fullName);
}
静态属性 静态方法
静态属性与方法:不需要实例化就可直接调用,实例化时不会重新拷贝静态方法,省内存,可以被继承,子类继承的静态方法和属性和父类是独立的,修改子类不会影响父类的静态属性与方法
注意:静态属性和方法不可以访问实例方法和实例属性,this.成员方法和属性 都找不到
//es5中的静态
function Person (){
this.run = function(){log('i m 成员 方法')} //
}
Person.run = function(){log('i m 静态 方法')} //名字可以跟实例方法一样,不冲突
Person.run()//可以直接调用 i m 静态 方法
var p = new Person()
p.run()// i m 静态 方法
//typescript中的静态
class Person{
name:string
constructo(n:string){ //构造函数 实例化的时候触发
this.name = n
}
run:void(){
alert(this.name)
}
static print(){
console.log('静态')
}
}
类
继承
//typescript类定义
class Person{
name:string
constructo(n:string){ //构造函数 实例化的时候触发
this.name = n
}
run:void(){
alert(this.name)
}
}
var p = new Person('zs')
//typescript继承
class Person{
name:string
constructo(n:string){ //构造函数 实例化的时候触发
this.name = n
}
run:void(){
alert(this.name)
}
}
class Man extends Person{ //extends关键字可以继承父亲的属性和方法,且this指向子类
constructor(x:string){
super(x)//super就是执行父类的构造函数,并且传递参数
}
run:void(){
console.log('我是子类') //此时调用m.run()调用的是子类的run,如果要调用父类的run要super.run()
//super.run()调用父类的run方法
}
}
var m = new Man('jack')
多态
定义:父类定义一个方法不实现,让继承它的子类去实现,每一个子类有不同的表现
//typescript多态
//人都有名字 让继承人的男人女人去实现叫什么名字
class Person{
name:string
constructo(n:string){ //构造函数 实例化的时候触发
this.name = n
}
run:void(){
alert(this.name)
}
}
class Man extends Person{ //extends关键字可以继承父亲的属性和方法,且this指向子类
constructor(x:string){
super(x)//super就是执行父类的构造函数,并且传递参数
}
}
class Woman extends Person {
constructor(x:string){
super(x)
}
}
var m = new Man('jack')
var wm = new Woman('sandy')
m.run()
wm.run()
抽象类
定义:规定继承的子类必须有什么方法
//错误写法
abstract class Animal{ //不能省略abstract ,要定义抽象方法必须在抽象类里
public name:string
constructor(name:string){
this.name = name
}
abstract eat():any //规定子类必须定义eat方法
}
var a = new Animal()//报错 抽象类不能直接实例化
//正确写法
abstract class Animal{
public name:string
constructor(name:string){
this.name = name
}
abstract eat():any //抽象方法不包含具体实现并且必须在派生类中实现(规定继承它的类必须干什么)
}
class Dog extends Animal{
constructor(name:string){
super(name)
}
eat(){ //必须写名为eat的成员 因为父类用abstract修饰了eat,子类继承不写就会报错
console.log(this.name)
}
}
var d = new Dog('zs')
装饰器
类装饰器
装饰器是一种特殊类型的声明,它能够被附加到类声明,方法, 访问符,属性或参数上。 装饰器使用@expression这种形式,expression求值后必须为一个函数,它会在运行时被调用,被装饰的声明信息做为参数传入。
①类装饰器:普通装饰器
function logClass(params:any){ console.log(params);//打印 function HttpClient(){} // params就是当前类 params.prototype.apiUrl = "动态扩展的属性"; params.prototype.run=function(){ console.log("我是一个run方法"); } } @logClass // 类装饰器,普通装饰器,无法传参,默认吧class传入 此时执行logClass函数 class HttpClient{ constructor(){ } getData(){ } }var http = new HttpClient();console.log(http.apiUrl)//动态扩展的属性http.run() //我是一个run方法
②类装饰器:装饰器工厂
作用:修改构造函数;扩展类属性和方法
function logClass(params:string){// params是下方传过来的参数 return function(target:any){// target相当于是传过来的类 log(target); log(params); target.prototype.apiUrl = params; } } @logClass("https://baidu.com")// 可以传参 class HttpClient{ constructor(){ } getData(){ } } var http:any = new HttpClient(); console.log(http.apiUrl);// https://baidu.com //-------------------------------可以修改构造函数的写法-------------- function logClass(target:any){ log(target); return class extends target{ apiUrl:any = "我是修改后的新数据"; getData(){ this.apiUrl = this.apiUrl + "----"; log(this.apiUrl); } } } @logClass //不加分号 class HttpClient{ public apiUrl:string | undefined; constructor(){ this.apiUrl = "我是构造函数里面的apiUrl" } getData(){ log(this.apiUrl) } var http= new HttpClient(); http.getData();//我是修改后的数据
属性装饰器
作用:可以给属性赋值
// 类装饰器function logClass(params:string){// params是下方传过来的参数 return function(target:any){// target相当于是默认传过来的 log(target); log(params); target.prototype.apiUrl = params; }}// 属性装饰器function logProperty(params:any){ // 固定写法,参数中,target为类对象,attr为参数名称也就是 url return function(target:any, attr:any){ //第一个为参数为类 第二个参数为类的成员名字 log(target); log(attr); target[attr] = params; }}@logClass("https://baidu.com")// 可以传参class HttpClient{ // 这个属性修饰器的作用就是给url赋值初始值 @logProperty("http://baidu.com") // public url:any | undefined; constructor(){ } getData(){ }}var http:any = new HttpClient();console.log(http.apiUrl);// https://baidu.com
方法装饰器
用的是最多的
function get(params:any){ return function(target:any, methodName:any, desc:any){ log(target); // 类属性 log(methodName); // 方法名字 getData log(desc); // 方法的描述,desc.value是方法描述 target.apiUrl = "xxx"; // 扩展类属性 target.run=function(){ log("run"); } target.desc.value = function(){ log('nihao') } }}class HttpClient{ public url:any | undefined; constructor(){ } @get("https://www.baidu.com") getData(){ log(this.url); }}var http:any = new HttpClient();log(http.apiUrl); // https://www.baidu.com‘http.run(); // log run
修改当前的方法(主要作用是装饰方法,并把方法的参数给变换类型):
// 这个方法装饰其主要作用就是把参数都给格式化成string类型function get(params:any){ return function(target:any, methodName:any, desc:any){ log(target); // 类属性 log(methodName); // 方法名字 getData log(desc.value); // 方法 // 想修改下方法,装饰一下,让他们的所有参数变成string类型,并且打印出来 var oMethod = desc.value; desc.value = function(...args:any[]){ args = args.map((value) => { return String(value); }) // 利用apply进行对象冒充,对getdata进行修改,如果没有apply就相当于是把getData方法给替换掉了 oMethod.apply(this, args);// this就是指function(...args:any[])这个函数 } }}class HttpClient{ public url:any | undefined; constructor(){ } @get("https://www.baidu.com") getData(...args:any[]){ log(args); log("我是getData方法"); }}var http:any = new HttpClient();http.getData(123,"xxx"); // 就会先打印["123", "xxx"]后打印 我是getData方法
方法参数装饰器
用的比较少,类装饰器也可以实现这个功能
运行时候当做函数被调用,可以使用参数张诗琪为类的原型增加一些元素数据,传入下列三个参数:
1对于静态成员来说是类的构造函数,对于实例成员是类的原型对象
2方法的名字
3参数在函数参数列表中的索引
function logParams(params:any){ return function(target:any, methodName:any, paramsIndex:any){ log(params);// xxxx log(target); // 原型对象 log(methodName);// getData log(paramsIndex); // 0 } } class HttpClient{ public url:any | undefined; constructor(){ } getData(@logParams("xxxx") uuid:any){ //修饰uuid参数 uuid索引为0 log(uuid); // iii } } var a = new HttpClient(); a.getData("iii"); 先后输出: 1. xxxx 2. 原型对象 3. getData 4. 0 5. iii
方法属性装饰器、类装饰器从后往前执行 方法参数装饰器从右往左执行