https://ts.xcatliu.com/basics/primitive-data-types.html
深受启发
//定义数组一
let arr:[]=[]
let arr:number[]=[1,2,3]
//定义数组二:泛型
let arr:Array<number>=[1,2,3]
object表示非原始类型,除了数字、字符、布尔,都包含于object,包括null和undefined
let obj:object={}
object=null//不会报错
object=undefined//不会报错
object=[]//不会报错
object=new String()//不会报错
object=String//不会报错
不知道类型用any,比方说后台可能会返回1或者true,你不知道,但是两种类型
let newArr:any[]=[100,2,4,"",true]
console.log(newArr[0].split(''))//会报错,any有优点也有缺点,无法判定是哪种类型,导致用不了
表示空值类型,表示没有任何返回值的函数
function fun1():void{//函数定义什么类型就得返回什么类型
}
console.log(fun1())//undefined
let v:void=undefined
以下代码虽然没有指定类型,但是会在编译的时候报错:
let myFavoriteNumber = 'seven';
myFavoriteNumber = 7;
// index.ts(2,1): error TS2322: Type 'number' is not assignable to type 'string'.
如果定义的时候没有赋值,不管之后有没有赋值,都会被推断成 any 类型而完全不被类型检查:
let myFavoriteNumber;
myFavoriteNumber = 'seven';
myFavoriteNumber = 7;
表示取值可以为多种类型中的一种
let myFavoriteNumber: string | number;
myFavoriteNumber = 'seven';
myFavoriteNumber = 7;
console.log(myFavoriteNumber.split(''))//再次复制会走类型判断,类型不对就会报错
推荐接口名称带I大写,如IPerson
interface Person {
name: string;
age: number;
}
let tom: Person = {
name: 'Tom',
age: 25
};
上面的例子中,我们定义了一个接口 Person,接着定义了一个变量 tom,它的类型是 Person。这样,我们就约束了 tom 的形状必须
和接口 Person 一致。
interface Person {
name: string;
age?: number;//可选属性
}
let tom: Person = {
name: 'Tom',
age: 25,
gender: 'male'
};
// examples/playground/index.ts(9,5): error TS2322: Type '{ name: string; age: number; gender: string; }' is not assignable to type 'Person'.
// Object literal may only specify known properties, and 'gender' does not exist in type 'Person'.
interface Person {
name: string;
age?: number;
[propName: string]: any;
}
let tom: Person = {
name: 'Tom',
gender: 'male'
};
需要注意的是,一旦定义了任意属性,那么确定属性和可选属性的类型都必须是它的类型的子集:
interface Person {
name: string;
age?: number;//这个写法必须是string,不然报错
[propName: string]: string;//或者这里写联合类型
}
let tom: Person = {
name: 'Tom',
age: 25,
gender: 'male'
};
// index.ts(3,5): error TS2411: Property 'age' of type 'number' is not assignable to string index type 'string'.
// index.ts(7,5): error TS2322: Type '{ [x: string]: string | number; name: string; age: number; gender: string; }' is not assignable to type 'Person'.
// Index signatures are incompatible.
// Type 'string | number' is not assignable to type 'string'.
// Type 'number' is not assignable to type 'string'.
interface Person {
readonly id: number;
name: string;
age?: number;
[propName: string]: any;
}
let tom: Person = {
id: 89757,
name: 'Tom',
gender: 'male'
};
tom.id = 9527;
// index.ts(14,5): error TS2540: Cannot assign to 'id' because it is a constant or a read-only property.
内置对象
interface IArguments {
[index: number]: any;
length: number;
callee: Function;
}
//函数声明
interface SearchFunc {
(source: string, subString: string): boolean;//boolean是返回值
}
let mySearch: SearchFunc = function(source: string, subString: string): boolean {
return source.search(subString) !== -1;
}
输入多余的(或者少于要求的)参数,是不被允许的
//函数表达式
let mySum: (x: number, y: number) => number = function (x: number, y: number): number {//=> number是返回值的意思,后面跟函数声明一样
return x + y;
};
在 TypeScript 的类型定义中,=> 用来表示函数的定义,左边是输入类型,需要用括号括起来,右边是输出类型。
function buildName(firstName: string, lastName?: string) {
if (lastName) {
return firstName + ' ' + lastName;
} else {
return firstName;
}
}
let tomcat = buildName('Tom', 'Cat');
let tom = buildName('Tom');
可选参数后面不允许再出现必需参数了
function buildName(firstName: string = 'Tom', lastName: string) {
return firstName + ' ' + lastName;
}
let tomcat = buildName('Tom', 'Cat');
let cat = buildName(undefined, 'Cat');
function push(array: any[], ...items: any[]) {
items.forEach(function(item) {
array.push(item);
});
}
let a = [];
push(a, 1, 2, 3);
function reverse(x: number | string): number | string | void {
if (typeof x === 'number') {
return Number(x.toString().split('').reverse().join(''));
} else if (typeof x === 'string') {
return x.split('').reverse().join('');
}
}
然而这样有一个缺点,就是不能够精确的表达,输入为数字的时候,输出也应该为数字,输入为字符串的时候,输出也应该为字符串。
function reverse(x: number): number;//x输入number类型输出还是number类型
function reverse(x: string): string;
function reverse(x: number | string): number | string | void {
if (typeof x === 'number') {
return Number(x.toString().split('').reverse().join(''));
} else if (typeof x === 'string') {
return x.split('').reverse().join('');
}
}
注意,TypeScript 会优先从最前面的函数定义开始匹配,所以多个函数定义如果有包含关系,需要优先把精确的定义写在前面。
手动指定类型
interface Cat {
name: string;
run(): void;
}
interface Fish {
name: string;
swim(): void;
}
function swim(animal: Cat | Fish) {
(animal as Fish).swim();
}
const tom: Cat = {
name: 'Tom',
run() { console.log('run') }
};
swim(tom);
// Uncaught TypeError: animal.swim is not a function`
原因是 (animal as Fish).swim() 这段代码隐藏了 animal 可能为 Cat 的情况,将 animal 直接断言为 Fish 了,而 TypeScript 编译器信任了我们的断言,故在调用 swim() 时没有编译错误。
可是 swim 函数接受的参数是 Cat | Fish,一旦传入的参数是 Cat 类型的变量,由于 Cat 上没有 swim 方法,就会导致运行时错误了。
继承,暂时放一放
window对象
window.a=10
//报错
// index.ts:1:8 - error TS2339: Property 'foo' does not exist on type 'Window & typeof globalThis'.
(window as any).foo = 1;
需要注意的是,将一个变量断言为 any 可以说是解决 TypeScript 中类型问题的最后
一个手段。
function getCacheData(key: string): any {
return (window as any).cache[key];
}
interface Cat {
name: string;
run(): void;
}
const tom = getCacheData('tom') as Cat;//最后返回Cat类型
tom.run();
tom:{name:'',run(){},}
上面的例子中,我们调用完 getCacheData 之后,立即将它断言为 Cat 类型。这样的话明确了 tom 的类型,后续对 tom 的访问时就有了代码补全,提高了代码的可维护性。
type Name = string;
type NameResolver = () => string;
type NameOrResolver = Name | NameResolver;
function getName(n: NameOrResolver): Name {
if (typeof n === 'string') {
return n;
} else {
return n();
}
}
type EventNames = 'click' | 'scroll' | 'mousemove';
function handleEvent(ele: Element, event: EventNames) {
// do something
}
handleEvent(document.getElementById('hello'), 'scroll'); // 没问题
handleEvent(document.getElementById('world'), 'dblclick'); // 报错,event 不能为 'dblclick'
定义一个只有字符串和数字的数组
let tom: [string, number] = ['Tom', 25];//个数和位置全匹配
假如越界的话,越界的元素,它的类型会被限制为元组中每个类型的联合类型
let tom: [string, number];
tom = ['Tom', 25];
tom.push('male');
tom.push(true);
// Argument of type 'true' is not assignable to parameter of type 'string | number'.
我们可以使用枚举为一组数值去赋予名字
enum Days {Sun, Mon, Tue, Wed, Thu, Fri, Sat};
枚举成员会被赋值为从 0 开始递增的数字
var Days;
(function (Days) {
Days[Days["Sun"] = 0] = "Sun";
Days[Days["Mon"] = 1] = "Mon";
Days[Days["Tue"] = 2] = "Tue";
Days[Days["Wed"] = 3] = "Wed";
Days[Days["Thu"] = 4] = "Thu";
Days[Days["Fri"] = 5] = "Fri";
Days[Days["Sat"] = 6] = "Sat";
})(Days || (Days = {}));
里面Days[“Sun”] = 0,外面Days[0] = “Sun”;互相映射
未手动赋值的枚举项会接着上一个枚举项递增
如果未手动赋值的枚举项与手动赋值的重复了,TypeScript 是不会察觉到这一点的
所以使用的时候需要注意,最好不要出现这种覆盖的情况。
手动赋值的枚举项可以不是数字,此时需要使用类型断言来让 tsc 无视类型检查 (编译出的 js 仍然是可用的):
enum Days {Sun = 7, Mon, Tue, Wed, Thu, Fri, Sat = <any>"S"};
当然,手动赋值的枚举项也可以为小数或负数,此时后续未手动赋值的项的递增步长仍为 1
枚举项有两种类型:常数项(constant member)和计算所得项(computed member)。
计算所得项
enum Color {Red, Green, Blue = "blue".length};
上面的例子不会报错,但是如果紧接在计算所得项后面的是未手动赋值的项,那么它就会因为无法获得初始值而报错,就是说计算后的值要放最后,不然报错,前面还没计算出来,后面却要初始化
enum Color {Red = "red".length, Green=11, Blue=12};//这样是合理的
const enum Directions {
Up,
Down,
Left="blue".length,//报错
Right=10+10//可以
}
let directions = [Directions.Up, Directions.Down, Directions.Left, Directions.Right];
常数枚举与普通枚举的区别是,它会在编译阶段被删除,并且不能包含计算成员。
var directions = [0 /* Up */, 1 /* Down */, 2 /* Left */, 3 /* Right */];
declare enum Directions {
Up,
Down,
Left,
Right
}
let directions = [Directions.Up, Directions.Down, Directions.Left, Directions.Right];
declare 定义的类型只会用于编译时的检查,编译结果中会被删除
var directions = [Directions.Up, Directions.Down, Directions.Left, Directions.Right];
主要用在声明文件
class Animal {
name:string;//ts一定要写
constructor(name:string) {//要写,不写是any
this.name = name;
}
sayHi() {
return `My name is ${this.name}`;
}
}
let a = new Animal('Jack');
console.log(a.sayHi()); // My name is Jack
class Cat extends Animal {
constructor(name:string) {
super(name); // 调用父类的 constructor(name)
console.log(this.name);
}
sayHi(str:string) {//重写父类方法
super.sayHi()//执行父类方法
return 'Meow, ' + super.sayHi(); // 调用父类的 sayHi()
}
}
let c = new Cat('Tom'); // Tom
console.log(c.sayHi()); // Meow, My name is Tom
使用 getter 和 setter 可以改变属性的赋值和读取行为:
class Name{
firstName:string,
lastName:string,
constructor(firstName:string,lastName:string) {
this.firstName = firstName;
this.lastName = lastName;
}
get fullName() {//读取器
return this.firstName+this.lastName;
}
set fullName(value) {//设置器
console.log('setter: ' + value);
let names=value.split('-');
this.firstName = names[0];
this.lastName = names[1];
}
}
let a = new Name('张','三'); // setter: Kitty
a.fullName= '张三'; // setter: 张三
console.log(a.fullName); // 张三
只属于类自己的实例和方法
class Animal {
static isAnimal(a) {
return a instanceof Animal;
}
}
let a = new Animal('Jack');
Animal.isAnimal(a); // true
a.isAnimal(a); // TypeError: a.isAnimal is not a function,报错
class Animal {
public name;
public constructor(name) {
this.name = name;
}
}
let a = new Animal('Jack');
console.log(a.name); // Jack
a.name = 'Tom';
console.log(a.name); // Tom
上面的例子中,name 被设置为了 public,所以直接访问实例的 name 属性是允许的。
很多时候,我们希望有的属性是无法直接存取的,这时候就可以用 private 了:
class Animal {
private name;
public constructor(name) {
this.name = name;
}
}
let a = new Animal('Jack');
console.log(a.name);
a.name = 'Tom';
// index.ts(9,13): error TS2341: Property 'name' is private and only accessible within class 'Animal'.
// index.ts(10,1): error TS2341: Property 'name' is private and only accessible within class 'Animal'.
在子类也是不允许访问的
class Animal {
private name;
public constructor(name) {
this.name = name;
}
}
class Cat extends Animal {
constructor(name) {
super(name);
console.log(this.name);
}
}
// index.ts(11,17): error TS2341: Property 'name' is private and only accessible within class 'Animal'.
而如果是用 protected 修饰,则允许在子类中访问:
class Animal {
protected name;
public constructor(name) {
this.name = name;
}
}
class Cat extends Animal {
constructor(name) {
super(name);
console.log(this.name);
}
}
当构造函数修饰为 private 时,该类不允许被继承或者实例化:
class Animal {
public name;
private constructor(name) {
this.name = name;
}
}
class Cat extends Animal {
constructor(name) {
super(name);
}
}
let a = new Animal('Jack');
// index.ts(7,19): TS2675: Cannot extend a class 'Animal'. Class constructor is marked as private.
// index.ts(13,9): TS2673: Constructor of class 'Animal' is private and only accessible within the class declaration.
当构造函数修饰为 protected 时,该类只允许被继承:
class Animal {
public name;
protected constructor(name) {
this.name = name;
}
}
class Cat extends Animal {
constructor(name) {
super(name);
}
}
let a = new Animal('Jack');
// index.ts(13,9): TS2674: Constructor of class 'Animal' is protected and only accessible within the class declaration.
class Animal {
// public name: string;
public constructor(public name) {
// this.name = name;
}
}
class Animal {
readonly name:string;
public constructor(name:string) {//只读属性,在构造函数里可以修改。这里构造函数加readonly(以及三个修饰符)指的是,创建并且初始化name参数
this.name = name;
}
update(){
this.name = '1'//报错
}
}
let a = new Animal('Jack');
console.log(a.name); // Jack
a.name = 'Tom';
// index.ts(10,3): TS2540: Cannot assign to 'name' because it is a read-only property.
注意如果 readonly 和其他访问修饰符同时存在的话,需要写在其后面。
class Animal {
// public readonly name;
public constructor(public readonly name) {
// this.name = name;
}
}
abstract 用于定义抽象类和其中的抽象方法。
首先,抽象类是不允许被实例化的:
abstract class Animal {
public name;
public constructor(name) {
this.name = name;
}
public abstract sayHi();
}
let a = new Animal('Jack');
// index.ts(9,11): error TS2511: Cannot create an instance of the abstract class 'Animal'.
抽象类中的抽象方法必须被子类实现
abstract class Animal {
public name;
public constructor(name) {
this.name = name;
}
public abstract sayHi();
}
class Cat extends Animal {
public eat() {
console.log(`${this.name} is eating.`);
}
}
let cat = new Cat('Tom');
// index.ts(9,7): error TS2515: Non-abstract class 'Cat' does not implement inherited abstract member 'sayHi' from class 'Animal'.