typescript学习笔记
内容导读
互联网集市收集整理的这篇技术教程文章主要介绍了typescript学习笔记,小编现在分享给大家,供广大互联网技能从业者学习和参考。文章包含16540字,纯文字阅读大概需要24分钟。
内容图文
ts的存在,是为了解决js是动态类型语言的缺陷。
? 何为弱类型,简单理解就是定义的一个变量,它的类型没有被限定死,可以随意地变更为其他类型,这样一来,等代码复杂了,使用者就无法获知它的具体类型或者其值,从而使得bug易发并且效率降低。
? 所以说,ts的出现,就是将js的动态类型(运行时报错)转变为静态类型(编译时报错)。
? 这一点,体现在ts这门语言的方方面面。
一,声明变量
let arr:number[]=[1,2,3,4]
let arr2: Array<number> = [1, 1, 2, 3, 5]; //数组泛型
let list: any[] = ['xcatliu', 25, { website: 'http://xcatliu.com' }]; //any在数组定义中的应用
这样写,就是限定arr为number类型的数组。
二,定义函数
function fn1(name:string,age:number):string{
return `名字为${name},年龄为${age}`
}
let result=fn1('小华',18)
console.log(result)
也是约束和限定了函数的参数和函数的返回类型
function fn1(name:string,age?:number):void{
console.log(`名字为${name},年龄为${age}`)
}
fn1('小华',18)
函数参数的可选 和函数的无返回的限定
这样写,都是某个参数是否是某种类型,并且是否传。
那如果是或的关系,一个参数可以是某些类型都可以呢?
这就引入了联合类型
三,联合类型
let arr:string[]|number[];
arr=[1,2,3]
arr=['1','2','3']
围绕ts是为了约束限定的中心点来看,联合类型的出现,就是为了解决某个参数可以是多种类型的情况。
但是呢,之前的约束,都是利用基本数据类型如number,string等进行的,有时候我们需要自定义类型,来约束某些复杂的参数,那怎么办?这就引入了接口。
四,接口
接口的出现,就是给使用者自定义用来限定的类型。在 TypeScript
中,我们使用接口(Interfaces)来定义对象
的类型。
在面向对象语言中,接口(Interfaces)
是一个很重要的概念,它是对行为的抽象,而具体如何行动需要由类(classes)
去实现(implement)
。
TypeScript
中的接口是一个非常灵活的概念,除了可用于对类的一部分行为进行抽象以外,也常用于对「对象的形状(Shape)」
进行描述。
interface MyType{
name:string,
age:number,
eat:()=>void
}
let customer :MyType={
name:'小明',
age:18,
eat:()=>{console.log("吃东西")},
}
customer.eat()
上面的例子中,我们定义了一个接口 Mytype
,接着定义了一个变量 customer
,它的类型是 Mytype
。这样,我们就约束了 customer
的形状必须和接口 Mytype
一致。也就是说:赋值的时候,变量的形状必须和接口的形状保持一致。
接口一般首字母大写。有的编程语言中会建议接口的名称加上 I
前缀。
1,接口还能继承,使用extends关键字
interface MyType{
name:string,
age:number,
eat:()=>void
}
interface OtherType extends MyType{
heigh:number,
}
let customer :OtherType={
name:'小明',
age:18,
heigh:178,
eat:()=>{console.log("吃东西")},
}
customer.eat()
2,接口中可以综合使用readonly,联合类型等增加约束能力
interface MyType{
readonly name:string, //只读属性
age:number|string,
eat:()=>void
}
interface OtherType extends MyType{
heigh?:number, //可选属性,此时heigh若是赋值了,就是number类型,不赋值就是underfine类型
[propName: string]: any; //任意属性:(1,其他属性值类型必须是它的子集,2,一个接口中只允许出现一个任意类型,若有多种,则使用联合类型)
}
let customer :OtherType={
name:'小明',
age:'18',
eat:()=>{console.log("吃东西")},
}
customer.eat()
3,接口还可以对可索引类型进行限定
interface User{
[index:number]:string,
}
let arr:User=['1','2']
这样一来,使用User这个接口限定的元素,索引值必须是number,对应值必须是string。
4,当有其他不确定数量的参数时
interface SquareConfig {
color?: string;
width?: number;
[propName: string]: any; //可以有其他任意数量的属性及值
}
5,给函数使用的接口定义-利用接口约束函数
从上文可以看到,接口都是在描述对象拥有的各种各样的外形。
此外,接口也可以描述函数类型。
//1,普通函数定义
function sum(x: number, y: number): number {
return x + y;
}
//2,表达式定义的函数--注意这里不是箭头函数,而是对函数输入和输出的限定
let mySum: (x: number, y: number) => number = function (x: number, y: number): number {
return x + y;
};
//3,函数也有可选参数这些的限定,且可选参数必须接在必需参数后面
//4,函数也有默认参数,且放置的位置不受约束
function buildName(firstName: string, lastName: string = 'Cat'):string {
return firstName + ' ' + lastName;
}
//5,剩余参数使用...items来限定
function push(array, ...items) {
items.forEach(function(item) {
array.push(item);
});
}
//使用接口来约束函数的定义
interface SearchFunc {
(source: string, subString: string): boolean;
//括号中就是对函数参数的限定,:外是对返回值的限定
}
let mySearch: SearchFunc = function(source: string, subString: string) {
let result = source.search(subString);
return result > -1;
}
6,类类型接口-利用接口约束类-implement
先是用接口定义一个数据类型结构,然后使用implements关键字,来实现这个接口,也就是说,新建的这个类,必须满足这个接口的数据结构形式。也就是被这个接口约束限定了,所以说,这里的接口依旧是起约束作用。
interface Animal{
name:string;
eat(s:string):string;
age:number;
}
//实现接口使用implements关键字
//狗类
class Dog implements Animal{
name:string;
age:number=12;
constructor(name:string){
this.name=name;
}
//实现接口中抽象方法
eat(s:string):string{
return this.name+"吃肉:"+s;
}
}
var dog=new Dog("tom");
console.log(dog.eat("五花肉"));
7,接口继承类
当接口继承了一个类类型时,它会继承类的成员但不包括其实现。 就好像接口声明了所有类中存在的成员,但并没有提供具体实现一样。 接口同样会继承到类的private和protected成员。
五,类型断言
值 as 类型
//将某个值断言为某种类型
既然说是断言,就要有程序员比ts更准确地知道该值是某种类型地觉悟。也就是说,ts是在编译时帮助我们检查错误的。而我们断言之后。编译器或者说ts就会无条件地信任我们的断言。从而通过编译阶段,这时如果有错误,则会在运行阶段爆出来。
1,将一个联合类型断言为其中一个类型
interface Cat {
name: string;
run(): void;
}
interface Fish {
name: string;
swim(): void;
}
function isFish(animal: Cat | Fish) {
if (typeof (animal as Fish).swim === 'function') {
return true;
}
return false;
}
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`
这时候就是,tom本来时cat类型,被我们断言成Fish类型,而fish有swim方法,编译器相信了我们的断言,所以就不会报错了。但是运行时,发现它是animal类型,没有swim方法,就报错了。
2,将一个父类断言为更加具体的子类
接口是一个类型,不是一个真正的值,它在编译结果中会被删除,当然就无法使用 instanceof
来做运行时判断了:
interface ApiError extends Error {
code: number;
}
function isApiError(error: Error) {
if (typeof (error as ApiError).code === 'number') {
return true;
}
return false;
}
const aaa:Error={
name: '测试',
message: '只是测试一下哈哈哈'
}
console.log(isApiError(aaa)) //false
3,将 any
断言为一个具体的类型
举例来说,历史遗留的代码中有个 getCacheData
,它的返回值是 any
:
function getCacheData(key: string): any {
return (window as any).cache[key];
}
那么我们在使用它时,最好能够将调用了它之后的返回值断言成一个精确的类型,这样就方便了后续的操作:
function getCacheData(key: string): any {
return (window as any).cache[key];
}
interface Cat {
name: string;
run(): void;
}
const tom = getCacheData('tom') as Cat;
tom.run();
上面的例子中,我们调用完 getCacheData
之后,立即将它断言为 Cat
类型。这样的话明确了 tom
的类型,后续对 tom
的访问时就有了代码补全,提高了代码的可维护性。
4,类型断言的限制
若 A 兼容 B,那么 A 能够被断言为 B,B也能被断言为A
任何类型都可以被断言为 any
any 可以被断言为任何类型
六,类型别名
类型别名用来给一个类型起个新名字。
type Name = string;
type NameResolver = () => string;
type NameOrResolver = Name | NameResolver;
function getName(n: NameOrResolver): Name { //NameOrResolver就成了一个新的类型
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'
// index.ts(7,47): error TS2345: Argument of type '"dblclick"' is not assignable to parameter of type 'EventNames'.
上例中,我们使用 type
定了一个字符串字面量类型 EventNames
,它只能取三种字符串中的一种。
注意,类型别名与字符串字面量类型都是使用 type
进行定义。
八,元组
数组合并了相同类型的对象,而元组(Tuple)合并了不同类型的对象。
let tom: [string, number];
tom[0] = 'Tom';
tom[1] = 25;
tom[0].slice(1);
tom[1].toFixed(2);
//或者只赋值一个
let tom: [string, number];
tom[0] = 'Tom';
但是当直接对元组类型的变量进行初始化或者赋值的时候,需要提供所有元组类型中指定的项。
let tom: [string, number];
tom = ['Tom'];
// Property '1' is missing in type '[string]' but required in type '[string, number]'.
九,枚举
枚举(Enum)类型用于取值被限定在一定范围内的场景,比如一周只能有七天,颜色限定为红绿蓝等。
枚举使用 enum
关键字来定义:
enum Days {Sun, Mon, Tue, Wed, Thu, Fri, Sat};
//枚举成员会被赋值为从 0 开始递增的数字,同时也会对枚举值到枚举名进行反向映射:
console.log(Days["Sun"] === 0); // true
console.log(Days["Mon"] === 1); // true
console.log(Days["Tue"] === 2); // true
console.log(Days["Sat"] === 6); // true
console.log(Days[0] === "Sun"); // true
console.log(Days[1] === "Mon"); // true
console.log(Days[2] === "Tue"); // true
console.log(Days[6] === "Sat"); // true
手动赋值
enum Days {Sun = 7, Mon = 1, Tue, Wed, Thu, Fri, Sat};
//未手动赋值的枚举项会接着上一个枚举项递增。
console.log(Days["Sun"] === 7); // true
console.log(Days["Mon"] === 1); // true
console.log(Days["Tue"] === 2); // true
console.log(Days["Sat"] === 6); // true
如果未手动赋值的枚举项与手动赋值的重复了,TypeScript 是不会察觉到这一点的,但是会导致覆盖,最好不要这样做。
十,类
1,类的概念
类(Class):定义了一件事物的抽象特点,包含它的属性和方法
对象(Object):类的实例,通过 new 生成
面向对象(OOP)的三大特性:封装、继承、多态
封装(Encapsulation):将对数据的操作细节隐藏起来,只暴露对外的接口。外界调用端不需要(也不可能)知道细节,就能通过对外提供的接口来访问该对象,同时也保证了外界无法任意更改对象内部的数据
继承(Inheritance):子类继承父类,子类除了拥有父类的所有特性外,还有一些更具体的特性
多态(Polymorphism):由继承而产生了相关的不同的类,对同一个方法可以有不同的响应。比如 Cat 和 Dog 都继承自 Animal,但是分别实现了自己的 eat 方法。此时针对某一个实例,我们无需了解它是 Cat 还是 Dog,就可以直接调用 eat 方法,程序会自动判断出来应该如何执行 eat
存取器(getter & setter):用以改变属性的读取和赋值行为
修饰符(Modifiers):修饰符是一些关键字,用于限定成员或类型的性质。比如 public 表示公有属性或方法
抽象类(Abstract Class):抽象类是供其他类继承的基类,抽象类不允许被实例化。抽象类中的抽象方法必须在子类中被实现
接口(Interfaces):不同类之间公有的属性或方法,可以抽象成一个接口。接口可以被类实现(implements)。一个类只能继承自另一个类,但是可以实现多个接口
2,类的创建
类是用来实现对象的创建和继承的。里面可以定义具体的值和方法,而接口,它是类型的定义,不能设置具体的值和方法。
即:接口用来定义类型,而类是用来创建对象的
class Greeter {
greeting: string;
constructor(message: string) {
this.greeting = message;
} //构造函数,只在new实例对象的时候执行一次,其中的this指向这个新建的对象
name:string='"孙悟空" //这是实例属性的写法,只有new出实例对象之后才可以访问,只有在类实例化时才执行创建。
static age:number=19 //这是静态属性的写法,只有直接通过Greeter.age才能访问,因为它定义在Greeter上
greet() {
return "Hello, " + this.greeting;
}
}
let greeter = new Greeter("world");
每次实例化一个类,就会执行构造器函数,从而给这个对象初始化赋值,并且构造器函数需要接收的参数,就是这个类要被实例化需要接收的参数。
3,类的继承
class Animal {
name: string;
constructor(theName: string) { this.name = theName; }
move(distanceInMeters: number = 0) {
console.log(`${this.name} moved ${distanceInMeters}m.`);
}
}
class Snake extends Animal {
constructor(name: string) { super(name); }
move(distanceInMeters = 5) {
console.log("Slithering...");
super.move(distanceInMeters);
}
}
let sam = new Snake("Sammy the Python");
sam.move();
Snake
类创建了 move
方法,它重写了从 Animal
继承来的 move
方法,使得 move
方法根据不同的类而具有不同的功能(多态)。
派生类包含了一个构造函数,它必须调用super()
,它会执行基类的构造函数。 而且,在构造函数里访问this
的属性之前,我们一定要调用super()
。 这个是TypeScript
强制执行的一条重要规则。
class Animal {
name:string;
age:number;
constructor(name:string,age:number){
this.name=name
this.age=age
}
sayHello(){
console.log("动物在叫~~~")
}
}
//子类继承父类的所有属性和方法
class Dog extends Animal{
sayHello(){
console.log("汪汪汪!!!!")
}
}
//子类继承父类的所有属性和方法,
//如果子类中添加了和父类相同的方法,则子类方法会覆盖父类方法(方法的重写)
class Cat extends Animal{
constructor(name:string,age:number){
super(name,age)
}
sayHello(){
console.log("喵喵喵")
}
run(){
console.log(`${this.name}在跑~~~`)
}
}
const dog1 = new Dog('大黄',22)
console.log(dog1)
dog1.sayHello()
const cat1 = new Cat('嘻嘻',18)
console.log(cat1)
cat1.sayHello()
cat1.run()
//继承的好处在于,可以在不修改原有类的基础上,继承原有类,从而修改类。
super调用父类
class Animal {
name:string;
age:number;
constructor(name:string,age:number){
this.name=name
this.age=age
}
sayHello(){
console.log("动物在叫~~~")
}
}
class Dog extends Animal{
heigh:number
constructor(name:string,age:number,heigh:number){
super(name,age)
//因为子类写了同名函数,就会覆盖父类的constructor,所以这里需要用super()来调用执行父类的构造函数
this.heigh=heigh
}
sayHello(){
console.log("汪汪汪!!!!")
}
}
const dog1 = new Dog('大黄',22,178)
console.log(dog1)
dog1.sayHello()
4,抽象类
//抽象类,只能被继承,不能创建实例
abstract class Animal {
name:string;
age:number;
constructor(name:string,age:number){
this.name=name
this.age=age
}
abstract sayHello():void
//抽象类中的抽象方法,子类必须重写
}
class Dog extends Animal{
heigh:number
constructor(name:string,age:number,heigh:number){
super(name,age)
this.heigh=heigh
}
sayHello(){
console.log("汪汪汪!!!!")
}
}
const dog1 = new Dog('大黄',22,178)
console.log(dog1)
dog1.sayHello()
5,公共,私有与受保护的修饰符
默认为 public
private私有,只有类的内部可以访问
protected,受保护,类的内部和子类中可以访问
readonly关键字将属性设置为只读的。
6,静态属性
到目前为止,我们只讨论了类的实例成员,那些仅当类被实例化的时候才会被初始化的属性。 我们也可以创建类的静态成员,这些属性存在于类本身上面而不是类的实例上。
class Grid {
static origin = {x: 0, y: 0};
name:string='名字';
constructor (public scale: number) { }
}
let grid1 = new Grid(1.0); // 1x scale
let grid2 = new Grid(5.0); // 5x scale
console.log(grid1.origin) //报错了,没有这个属性
console.log(Grid.origin) //satic搞出来的,只在自己对象上
console.log(grid1.name) //有这个属性,说明正常初始化的属性是在原型对象上的
7,把类当做接口使用
类定义会创建两个东西:类的实例类型和一个构造函数。 因为类可以创建出类型,所以你能够在允许使用接口的地方使用类。
class Point {
x: number;
y: number;
}
interface Point3d extends Point {
z: number;
}
//定义一个新的接口Point3d,它的内容继承自Point
let point3d: Point3d = {x: 1, y: 2, z: 3};
8,存取器
get有点类似于vue中的计算属性,可以直接使用
class Food{
element:HTMLElement;
constructor(){
//获取食物的dom元素节点,getElementById方法返回值可能为element或者null,这里肯定是element,所以加了!
this.element=document.getElementById('food')!;
}
//定义一个获取食物x坐标的方法
get X(){
return this.element.offsetLeft;
}
get Y(){
return this.element.offsetTop;
}
}
const food=new Food()
console.log(food.X,food.Y)
十一,泛型
可以使用泛型
来创建可重用的组件,一个组件可以支持多种类型的数据。 这样用户就可以以自己的数据类型来使用组件。
可以说,接口的出现是为了自定义数据的类型,那么泛型的出现,则是为了让可以定义的类型变得更加灵活。适用范围更广。
//传入啥类型,就会返回啥类型,这里就是利用<T>来先定义一个类型T,然后后续直接使用这个T
function identity<T>(arg: T): T {
return arg;
}
let output = identity<string>("myString"); // type of output will be 'string'
//此时就是通过<string>来手动指定,必须是string类型
let output = identity("myString"); // type of output will be 'string'使用类型推论
1,泛型变量
这时候,是把T作为一个类型变量,参数arg
必须是一个数组,且参数的类型为T。返回值亦然。
function loggingIdentity<T>(arg: T[]): T[] {
console.log(arg.length); // Array has a .length, so no more error
return arg;
}
或者:
function loggingIdentity<T>(arg: Array<T>): Array<T> {
console.log(arg.length); // Array has a .length, so no more error
return arg;
}
这种用法,实际上就是把泛型当作一个指代符,用来暂时性地指代它是某一种类型。
2,泛型接口
interface GenericIdentityFn<T> {
(arg: T): T;
}
function identity<T>(arg: T): T {
return arg;
}
let myIdentity: GenericIdentityFn<number> = identity;
3,泛型类
class GenericNumber<T> {
zeroValue: T;
add: (x: T, y: T) => T;
}
let myGenericNumber = new GenericNumber<number>();
myGenericNumber.zeroValue = 0;
myGenericNumber.add = function (x, y) {
return x + y;
};
//同样是作为泛型变量,来约束这个类的一些参数类型统一。
4,泛型约束
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;
}
//定义一个函数,这个函数使用T来作为类型约束,而T继承了Lengthwise,所以说T必须具备length属性
内容总结
以上是互联网集市为您收集整理的typescript学习笔记全部内容,希望文章能够帮你解决typescript学习笔记所遇到的程序开发问题。 如果觉得互联网集市技术教程内容还不错,欢迎将互联网集市网站推荐给程序员好友。
内容备注
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 gblab@vip.qq.com 举报,一经查实,本站将立刻删除。
内容手机端
扫描二维码推送至手机访问。