实时编译命令:tsc --watch
一、基础类型
数字
let a: number = 10
字符串
let b: string = 'haha'
布尔值
let c: boolean = true
数组
// arr1,arr2 等价的
let arr1: Number[] = [1, 2, 3]
let arr2: Array<number> = [2, 3, 5]
元组类型tuple
数量和类型已知的数组,要一一对应,多和少都不行
let sf: [string, number] = ['sf', 100]
枚举
1. 普通枚举
可以事先考虑到所有变量的可能值,如性别,月份,星期,颜色,日期等值是固定的
enum gender {
boy,
girl
}
编译后:
var gender;
(function (gender) {
gender[gender["boy"] = 0] = "boy";
gender[gender["girl"] = 1] = "girl";
})(gender || (gender = {}));
可以正反两方向取值
console.log(gender.boy, gender[0]); // 0 boy
console.log(gender.girl, gender[1]); // 1 girl
2. 常量枚举
const enum colors {
red, yellow, green
}
let myColor = [colors.red, colors.yellow, colors.green]
编译后:
var myColor = [0 /* red */, 1 /* yellow */, 2 /* green */];
编译完只留值,原来定义代码就不要了,省略了定义的过程了,节约内存开销
任意类型
any可以赋值给任意类型,如类型定义困难,结构太复杂,没有类型声明的时候都可以使用any。
如果变量定义为any类型,就跟js差不多,不进行类型检测。
let root:any = document.getElementById('roow')
root.style.color = 'red'
非空断言!
开启strictNullChecks: true后,ts担心你的变量为null,空指针异常,会报个错,所以在变量后加个!即可。
let element: (HTMLElement | null) = document.getElementById('roow')
element!.style.color = 'green'
null undefined
是其他类型的子类型
let x:number;
x = 1;
x = undefined;
x = null;
但是 tsconfig.json 中设置了 "strictNullChecks": true,就开启严格空检查,为true不能把null,undefined赋值给其他类型。可以单独设置,或者用'|'分隔
let a1: undefined = undefined;
let a2: null = null;
let a3: any = undefined;
let a4: any = null;
let a5: number | undefined | null;
a5 = 1;
a5 = undefined;
a5 = null;
never
是其他类型,null、undefined的子类型,代表不会出现的值
- 作为不会返回的函数的返回值类型(报错,死循环等无法继续的)
function error(message: string): never {
throw Error('报错')
}
function loop(): never {
while (true) { }
}
void
代表没有任何类型,当一个函数没有返回值的时候,它就是void类型。
function test():void{
return undefined
}
return null 在 strictNullChecks 为 false 时可以,true不行。
void 和 never 的 区别
- void 可以被赋值为undefined,null。never不能包含任何类型。
- 返回类型为void的函数能执行,但是返回never的函数无法正常执行。
二、复杂类型
类型断言
// 类型断言
let name1: number | string;
console.log((name1! as number).toFixed(2));
console.log((name1! as string).length);
// 双重断言
console.log(name1! as any as boolean);
字面量类型和类型字面量
字面量类型
const up: 'Up' = 'Up';
const down: 'Down' = 'Down';
const left: 'Left' = 'Left';
const right: 'Right' = 'Right'
type Direction = 'Up' | 'Down' | 'Left' | 'Right'
function move(derection: Direction) { }
move('Right')
类型字面量
type Person = {
name: string,
age: number
}
let p1: Person = {
name: 'hhh',
age: 100
}
字符串字面量和联合类型
字符串字面量
type T1 = '1' | '2' | '3'
联合类型
type T2 = string | number | boolean
变量只能赋值符合条件的,否则会报错
let t1: T1 = '1'
let t2: T2 = true
三、函数
函数定义
约定参数类型和函数返回值类型
function hello(name: string): void {
console.log(name);
}
hello('anan')
函数表达式
函数没有返回值,设置为void即可。
type GetName = (fname: string, lname: string) => string //(void)
let getName: GetName = function (fname: string, lname: string): string {
return fname + lname
}
可选参数
? 可以传参,也可以不传
function print(name: string, age?: number) {}
print('hello')
默认参数
function ajax(url: string, method: string = 'GET') {}
ajax('/')
剩余参数
function sum(...numbers: number[]) {
return numbers.reduce((val, item) => val + item, 0)
}
console.log(sum(1, 2, 3));
函数的重载
- 函数根据传入不同的参数而返回不同类型的数据
- 为同一个函数提供多个函数类型定义来进行函数重载 例如:参数为字符串,给name赋值,为number给age赋值
let obj: any = {}
function attr(val: string): void
function attr(val: number): void
function attr(val: any): void {
if (typeof val === 'string') {
obj.name = val
} else if (typeof val === 'number') {
obj.age = val
}
}
attr('aa')
attr(10)
四、类
多个ts文件中,如果有重复名称的变量或类,都会报错,因为把它们当做全局的文件,解决这个问题,可以在文件中用export {} ,ts会认为这是一个模块,里面的变量都是私有的
export { }
class Person {
name: string
getNage(): void {
console.log(this.name);
}
}
let p1 = new Person();
p1.name = 'zt'
p1.getNage()
定义存取器
通过存取器改变一个类中属性的读取和赋值操作
class User {
myName: string
constructor(myName: string) {
this.myName = myName
}
get name() {
return this.myName
}
set name(value) {
this.myName = value
}
}
let user = new User('name1')
user.name = 'haha'
console.log(user.name); //haha
编译后:
var User = /** @class */ (function () {
function User(myName) {
this.myName = myName;
}
Object.defineProperty(User.prototype, "name", {
get: function () {
return this.myName;
},
set: function (value) {
this.myName = value;
},
enumerable: false, // 是否可枚举:能否用for in迭代出来
configurable: true // 是否可配置:delete,属性可删?
});
return User;
}());
var user = new User('name1');
user.name = 'haha';
console.log(user.name);
参数属性
readonly
如果是公开只读的属性,它只能在构造函数中赋值
class Animal {
public readonly name: string;
constructor(name: string) {
this.name = name
}
changeName(val: string) {
this.name = val
}
}
如果修改只读属性会报错
继承
class Person {
name: string
age: number
constructor(name: string, age: number) {
this.name = name
this.age = age
}
getName(): string {
return this.name
}
setName(name: string): void {
this.name = name
}
}
class Student extends Person {
stuNo: number
constructor(name: string, age: number, stuNo: number) {
super(name, age)
this.stuNo = stuNo
}
getStuNo(): number {
return this.stuNo
}
}
let s1 = new Student('zt', 20, 2014)
public,private,protected
将代码简化写法 constructor(public myName: string)
* : 自动把myName变量赋值给当前实例。
class User {
// myName: string (省略啦!)
constructor(public myName: string) { // *
// this.myName = myName (省略啦!)
}
get name() {
return this.myName
}
set name(value) {
this.myName = value
}
}
- public:自己,自己的子类,其他类都能访问
- protected:自己和自己的子类可访问,其他类不可访问
- private:自己能访问,子类和其他类都不可访问
// 父类
class Father {
public name: string
protected age: number
private money: number
constructor(name: string, age: number, money: number) {
this.name = name
this.age = age
this.money = money
}
getName(): string {
return this.name // 自己可访问
}
getAge(): number {
return this.age // 自己可访问
}
getMoney(): number {
return this.money // 自己可访问
}
}
// 子类
class Child extends Father {
constructor(name: string, age: number, money: number) {
super(name, age, money)
}
desc() {
console.log(this.name, this.age); // 子类可访问
console.log(this.money); // 报错
}
}
// 外部
let child = new Child('ztt', 11, 22)
console.log(child.name); // 外部可访问
console.log(child.age); // 报错: 属性“age”受保护,只能在类“Father”及其子类中访问。
console.log(child.money); // 报错:属性“money”为私有属性,只能在类“Father”中访问。
子类,父类方法重名,子类会覆盖父类方法
class Father1 {
toString() {
console.log('Father1');
}
}
class Child1 extends Father1 {
toString() {
console.log('Child1');
}
}
let father1 = new Father1()
father1.toString() // Father1
let child1 = new Child1()
child1.toString() // Child1
子类调用父类方法
class Father1 {
toString() {
console.log('Father1');
}
}
class Child1 extends Father1 {
toString() {
super.toString() // Father1
}
}
let child1 = new Child1()
child1.toString()
装饰器
装饰器是一种特殊的声明,可以被附加到类声明,方法,属性和参数上,它可以修改类的行为。
有类装饰器,方法装饰器,参数装饰器。
类装饰器
在类声明之前声明,用来监视修改和替换类的定义「装饰器是个函数」
用@xxx一个方法放在类前,装饰这个类
function addNameEat(constructor: Function) {
constructor.prototype.name = 'hello'
constructor.prototype.eat = function () { }
}
@addNameEat
class Person {
name: string
eat: Function
constructor() { }
}
let p: Person = new Person()
console.log(p.name);
p.eat()
替换类
namespace c {
function replaceClass(constructor: Function) {
return class {
name: string
eat: Function
age: number // 可以多一个属性,但不能少
constructor() { }
}
}
@replaceClass
class Person {
name: string
eat: Function
constructor() { }
}
let p: Person = new Person()
console.log(p.name);
p.eat()
}
返回一个新的类,把老类替换掉,多一个属性可以,但不能少。
装饰器工厂
上段代码中希望@addNameEat可以传参,目前看来传不了,所以就给addNameEat方法再套一层addNameEatFactory,并返回addNameEat
function addNameEatFactory(name:string) {
return function addNameEat(constructor: Function) {
constructor.prototype.name = name
constructor.prototype.eat = function () { }
}
}
@addNameEatFactory('hello')
class Person {
name: string
eat: Function
constructor() {}
}
class Person {
name: string
eat: Function
constructor() { }
}
先让@addNameEatFactory('hello')执行,返回一个addNameEat函数,它再装饰类
命名空间
如果想在一个文件里或一个模块里声明2个一样的类型,可以用命名空间分隔,就不冲突了。
命名空间本质是一个闭包
namespace a {
function addNameEat(constructor: Function) {
constructor.prototype.name = 'hello'
constructor.prototype.eat = function () { }
}
@addNameEat
class Person {
name: string
eat: Function
constructor() { }
}
let p: Person = new Person()
console.log(p.name);
p.eat()
}
namespace b {
function addNameEatFactory(name: string) {
return function addNameEat(constructor: Function) {
constructor.prototype.name = name
constructor.prototype.eat = function () { }
}
}
@addNameEatFactory('world')
class Person {
name: string
eat: Function
constructor() { }
}
let p: Person = new Person()
console.log(p.name);
p.eat()
}
属性装饰器
属性装饰器会在运行时当做函数被调用,可以装饰属性和方法。
namespace d {
/**
* @param target 如果装饰的是实例属性,target是构造函数的原型
* @param propertyKey
*/
function upperCase(target: any, propertyKey: string) {
let value = target[propertyKey]
const getter = () => value
const setter = (newValue: string) => { value = newValue.toUpperCase() }
if (delete target[propertyKey]) {
Object.defineProperty(target, propertyKey, {
get: getter,
set: setter,
enumerable: true,
configurable: true
})
}
}
/**
* @param target 如果装饰的是静态属性,target是构造函数本身
* @param propertyKey 属性名
*/
function staticPropertyDecorator(target: any, propertyKey: string) {
console.log(target);
console.log(propertyKey);
}
/**
* @param target 原型
* @param propertyKey 方法名
* @param descriptor 属性描述器
*/
function noEnumerable(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
descriptor.enumerable = false // 设置为不可枚举
}
/**
* 这个方法主要用来求和,但参数是any类型,有可能传入字符串等类型导致结果不正确,所以需要处理成数字
* @param target
* @param propertyKey
* @param descriptor
*/
function toNumber(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
let oldMethod = descriptor.value // 老方法
descriptor.value = function (...args: any[]) { // 重写value,整个新函数
args = args.map(item => parseFloat(item))
return oldMethod.apply(this, args)
}
}
class Person {
@upperCase
name: string = 'zt' // 实例属性
@staticPropertyDecorator
public static age: number = 10; //静态属性
@noEnumerable
getName() { console.log(this.name) } // 实例方法
@toNumber
sum(...args: any[]) { // 实例方法
return args.reduce((accu: number, item: number) => accu + item, 0)
}
}
let p = new Person()
console.log(p.name); //
console.log(p.sum('1', '2', '3'));
}
参数装饰器
( Nest.js大量用到了参数装饰器 )
namespace e {
/**
*
* @param target 静态成员就是构造函数,非静态成员就是构造函数原型
* @param methodName 方法名称(login)
* @param paramIndex 参数在函数参数列表中的索引
*/
function addAge(target: any, methodName, paramIndex: number) {
console.log(target, methodName, paramIndex);
target.age = 10
}
class Person {
age: number
login(username: string, @addAge password: string) {
console.log(this.age, username, password); // 10, 1, 2
}
}
let p = new Person()
p.login('1', '2')
}
装饰器的执行顺序
如果有类装饰器、属性装饰器、参数装饰器,他们之间的顺序如下:
- 类装饰器是最后执行,后写的类装饰器先执行
- 方法和方法参数中的装饰器,先执行参数,后执行方法
- 方法和属性装饰器,谁在前面先执行谁 一般由内往外执行,先内后外,先上后下
namespace f {
function ClassDecorator1() {
return function (target) {
console.log('ClassDecorator1');
}
}
function ClassDecorator2() {
return function (target) {
console.log('ClassDecorator2');
}
}
function PropertyDecoractor(name: string) {
return function (target, propertyName) {
console.log('PropertyDecoractor', propertyName, name);
}
}
function methodDecoractor() {
return function (target, propertyName) {
console.log('methodDecoractor', propertyName);
}
}
function parameterDecoractor() {
return function (target, methodName, index) {
console.log('parameterDecoractor', methodName, index);
}
}
@ClassDecorator1()
@ClassDecorator2()
class Person {
@PropertyDecoractor('name')
name: string = ''
@PropertyDecoractor('age')
age: number = 10
@methodDecoractor()
hello(@parameterDecoractor() p1: string, @parameterDecoractor() p2: string) { }
}
}
抽象类
- 抽象描述一种抽象的概念,无法被实例化,只能被继承。
- 无法创建抽象类的实例。
- 抽象方法不能在抽象类中实现,只能在抽象类的具体子类中实现,而且必须实现。
抽象方法
- 抽象类和方法不包含具体实现,必须在子类中实现
- 抽象方法只能出现在抽象类中
- 子类可以对抽象类进行不同的实现
多态:同一个方法不同的子类有不同的实现
重写(override):子类重写继承自父类的方法
重载(overload):函数的重载,一个函数有多个定义
abstract class Animal {
name: string
abstract speak(): void // 抽象方法的关键字abstract
}
class Cat extends Animal {
speak(): void {
console.log('miao miao miao ');
}
}
class Dog extends Animal {
speak(): void {
throw new Error("wang wang wang");
}
}
五、接口
- 接口一方面可以在面向对象编程中表示为行为的抽象,另外可以用来描述对象的形状。
- 接口就是把一些类中共有的属性和方法抽象出来,可以用来约束实现此接口的类
- 一个类可以继承另一个类并实现多个接口
- 接口像插件一样是用来增强类的,而抽象类是具体类的抽象概念
- 一个类可以实现多个接口,一个接口也可以被多个类实现,但一个类可以有多个子类,但只能有一个父类。
同名的接口可以写多个,类型会自动合并
interface Speakable {
name: string,
speak(): void
}
interface Speakable {
speak(): void
}
interface Eatable {
eat(): void
}
// Person 实现接口Speakable,Eatable
class Person implements Speakable, Eatable {
name: string
speak(): void {
throw new Error("Method not implemented.")
}
eat(): void {
throw new Error("Method not implemented.")
}
}
------------------还没完-------------------