对typescript的理解

前言

最近面试的时候被问到了关于typescript的问题,答得稀烂,故在此重新梳理知识体系巩固一下。

TypeScript的在线编辑器推荐

TypeScript Playground、playcode.io、stackblitz.com、codesandbox.io

es6类的概念以及和构造函数的联系

在es6以前,js是通过构造函数生成实例对象,以一种基于原型的方式实现,这和传统的面向对象语言不一样,因此也增加了开发者的理解成本。而es6之后,引入了类的概念,它必须由new调用,以class关键字定义,但是它的绝大部分功能依然可以用es5去实现,只是写法更贴近传统面向对象语言基于类的写法,Js的class依然有一些特性没有实现,他本质上还是构造函数,因此也可以把它看成一个语法糖。

使用 getter 和 setter 可以改变属性的赋值和读取行为。

es6类的写法:

class Point{
	constructor(x,y){
		this.x = x;
		this.y = y;
	}
	notStatic(){
		console.log('not static func');
	}
	static dips(){
		console.log('static func');
	}
}

如果上述代码想要用构造函数去实现:

function Point(x,y){
	this.x = x;
	this.y = y;
	Point.dips = function(){
		console.log('static func');
	}
}
Point.prototype.notStatic= function(){
		console.log('not static func');
	}
//或者静态方法/属性在函数体外写
Point.dips = function(){
		console.log('static func');
}

验证一下结果:

let point1 = new Point(1,2)
console.log(JSON.stringify(point1))
//'{"x":1,"y":2}'

point1.dips()
//Error: point1.dips is not a function

Point.prototype.notStatic()
//"not static func"
point1.notStatic()
//"not static func"

Point.dips()
//"static func"

Point.notStatic()
//Error: Point.notStatic is not a function

总结一下就是:类中若非显式的定义在this上,则都是定义在类的原型上,而如果用了static修饰符,则是定义在该类本身,不会被实例对象继承,而是以类.静态属性/方法的方式访问。

另外,私有属性/方法用#实现,继承可以让子类extends父类,或者在子类中用super调用父类的方法,当然在子类中重写父类的方法也是可以的。

特别地,这里给出一个例子

class MyPromise{
  #result;
  #state;
  constructor(executor){
    this.#state = 'pending'
    this.x=1
    console.log(JSON.stringify(this))
    /*constructor中的this指向实例对象,this.resolve会沿着原型链
    找到原型上的resolve函数*/
    executor(this.resolve,this.reject)
    this.resolve(2)
  }

  resolve(value){
    console.log(value)
    console.log(JSON.stringify(this))
    // this.#state = 'fulfilled'
    // this.#result = value
  }
  
  reject(value){
    this.#state = 'rejected'
    this.#result = value
  }

  then(onFulfilled,onRejected){
    if(this.#state==='fulfilled'){
      onFulfilled(this.#result)
    }
    if(this.#state==='rejected'){
      onRejected(this.#result)
    }
  }
}
const data = new MyPromise((resolve,reject)=>{
//相当于window调用
  resolve(1)
})

> '{"x":1}'      7 line
> 1              13
> undefined		 14
> 2				 13
> '{"x":1}'		 14

至于js中类和构造函数的关系,我们可以看以下这一段来自阮一峰es6网站的一段示例:

class Point {
  // ...
}

typeof Point // "function"
Point === Point.prototype.constructor // true

上面代码表明,类的数据类型就是函数,类本身就指向构造函数。

使用的时候,也是直接对类使用new命令,跟构造函数的用法完全一致。

构造函数的prototype属性,在 ES6 的“类”上面继续存在。事实上,类的所有方法都定义在类的prototype属性上面。

因此,在类的实例上面调用方法,其实就是调用原型上的方法。

唯一存在差异的就是:类的内部所有定义的方法,都是不可枚举的,而es5则相反

ts类与es6类的差异

ts的类本质也是构造函数+原型链。它和es6中的类的概念差异不大,但是它会支持面向对象的所有特性。
它包含以下模块:

  • 字段(类里面声明的遍历,表示对象的有关数据)
  • 构造函数(类实例化时会调用)
  • 方法

它还支持许多修饰符:

  • public 可以自由访问类内的成员
  • private 私有 只可以在该类内访问
  • protected 受保护的 只能在该类或者子类内访问,不能在实例对象中访问
  • readonly 只读 必须在类内声明时或者构造函数内初始化
  • static 在类本身定义的方法或属性,不会被实例对象继承
  • abstract 抽象 用于定义抽象类和抽象类里的抽象方法【也就是没有具体的实现细节】

抽象类一般用作其他派生类的基类,不能被实例化
和接口不同的是,接口只有属性和抽象方法【必须由子类具体实现】,而抽象类可以有方法的具体实现细节

接口和类的区别

接口是一系列抽象方法声明的集合,只声明但不实现;而类可以声明并实现方法。
类可以用implements关键字去实现接口;接口之间也可以继承;接口甚至也可以继承类。

那么就会有一个问题,既然接口能做的类也能做,那直接用类不行吗?为什么还需要接口?↓
当我们想用某个函数统一处理所有具有某个方法的对象作为入参时,接口就显得很重要。

如果用类写:

class Essay{
	getContent(){
		return 'Essay!';
	}
}
function print(obj: Essay): void {
    console.log(obj.getContent());
}
let essay1 = new Essay();
print(essay1);

但是这样就只能处理Essay类的对象。
当然也可以让每个类都具有getContent方法的实现,或者继承公共父类去重写这个方法也可以。

class Essay {
  getContent() {
    return 'Essay!';
  }
}
class EssayChild1 {
  getContent() {
    return 'EssayChild1!';
  }
}
class EssayChild2 {
  getContent() {
    return 'EssayChild2!';
  }
}
function print(obj:Essay): void {
  console.log(obj.getContent());
}
let essay = new Essay();
let essay1 = new EssayChild1();
let essay2 = new EssayChild2();
print(essay);
print(essay1);
print(essay2);
//Essay!
//EssayChild1!
//EssayChild2!

当然用接口约束函数参数类型,然后用类去实现接口的方法也是可以的:

interface Essay {
  getContent() :string;
}
class EssayChild1 implements Essay{
  getContent() {
    return 'EssayChild1!';
  }
}
class EssayChild2 implements Essay{
  getContent() {
    return 'EssayChild2!';
  }
}
function print(obj:Essay): void {
  console.log(obj.getContent());
}
let essay1 = new EssayChild1();
let essay2 = new EssayChild2();
print(essay1);
print(essay2);
//EssayChild1!
//EssayChild2!

因此,在某些应用场景中,也可以直接把类当作接口使用。

泛型

在定义类、接口或函数时无法直接确定具体类型时,就可以使用泛型。尤其是当碰到需要入参和出参的类型一样的场景时,泛型就可以发挥很大作用,而如果直接用any,就相当于关闭了ts类型检查的优势,如果同时写很多个类型的函数,那么代码的重复性又会很高。

使用方法:

function test<T, K>(a: T, b: K): K{
    return b;
}
test<number>(10)
//不指定类型也可以,它会根据入参自动推断类型

类中也可以使用:

class MyClass<T>{
    prop: T;

    constructor(prop: T){
        this.prop = prop;
    }
}

除此之外,也可以对泛型的范围进行约束

interface MyInter{
    length: number;
}

function test<T extends MyInter>(arg: T): number{
    return arg.length;
}

直接把泛型当成一个数据类型去使用就行。

类型别名type和接口的区别

一、表示范围
接口只能用来声明对象类型;而类型别名只是为类型创建一个新名称,可以定义基本类型的别名,也可以用来声明联合类型,如 type paramType = number | string,元组类型,如type arrType = [string, string, number];
二、是否可重复声明
接口可重复声明,ts会将其合并;而type重复声明会报错
三、继承和实现
接口可以被其他接口继承,也可以被类实现

一些小的知识点

当属性是可有可无时,用可选属性?
[propName:String]:any代表后面可以加任意及任意多的属性
类型断言:变量 as 类型 直接告诉编译器变量的类型

ts新增的类型还有:

新增类型例子描述
字面量其本身限制变量的值就是该字面量的值
any*任意类型
unknown*类型安全的any
void空值(undefined)没有值(或undefined)
never没有值不能是任何值
tuple[4,5]元组,固定长度数组
enumenum{A, B}枚举

ts中的两种文件格式

  1. .ts
  2. .d.ts

.ts文件:
1.既包含类型信息又可执行代码。
2.可以被编译为.js文件,然后,执行代码。
3.用途∶编写程序代码的地方。

.d.ts 文件:
1.只包含类型信息的类型声明文件。
2.不会生成.js 文件,仅用于提供类型信息。
3.用途∶为JS提供类型信息。

引入第三方库的时候,不像js环境了,必须要引入其对应的类型声明文件。

在vue中使用typescript

我是直接用的vue-cli脚手架去vue create一个项目,然后选择带有typescript的预设。

参考文章

TypeScript编程入门(三)类和接口

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值