下文是之前总结的,太长了,不好再新增了,所以新开了一个口,最新总结,请移步这里
第二篇
文档总结
先装上typescript包,新增脚本
npm i typescript 装ts包
tsc --init 生成tsconfig.js配置文件
@types开头的包 都是ts声明文件,可以进入node_modules/@types/xx/index.d.ts进行查看
ts-loader 可以让wwebpack使用ts的标准配置文件tsconfig.json编译ts代码
"scripts": { "dev":"tsc index.ts --watch" }
npm run dev 执行命令,将index.ts文件 翻译为index.js文件
借用插件,方便学习typescript
vscode
的Code Runner
// 安装ts-node
npm i -g ts-node
这样就可以使用右键使用code runner
执行ts
文件
想报错友好,还可以使用 error lens
插件
typescript声明方式
布尔值
let flag:boolean = true;
数字
let count:number = 1;
let hexLiteral: number = 0xf00d;//十六进制数字
let binaryLiteral: number = 0b1010; //二进制数字
let octalLiteral: number = 0o744;//八进制数字
字符串
let name:string = 'zanlan';
数组 方式一
//直接使用[ ] 前面加上内部的类型标识
let list: number[] = [1, 2, 3];
数组 方式二
// 数组泛型 Array<元素类型>
let list: Array<number> = [1, 2, 3];
元组 Tuple
//元组类型允许表示一个已知元素数量和类型的数组,各元素的类型不必相同。
let x: [string, number] = ['hello', 10];
**类型一 | 类型二 | 类型三 … **
//多种类型并集 只需要满足一种就行
let age:number | undefined | null = 10
枚举
//默认序号 从0开始
enum Types { type1, type2, type3 }
let g1:Types = Types.type1 // => 0
let g2:Types = Types.type2 // => 1
let g3:Types = Types.type3 // => 2
console.log(Types)
{
'0': 'type1', '1': 'type2', '2': 'type3',
type1: 0, type2: 1, type3: 2
}
//自定义序号 从1开始 后面序号自动递增
enum Types { type1 = 1, type2, type3 }
//完全自定义序号
enum Types { type1 = 1, type2 = 3, type3 = 5 }
any
let notSure: any = 4;
notSure = "maybe a string instead";
notSure = false; // okay, definitely a boolean
let list: any[] = [1, true, "free"];
list[1] = 100;
void
//标识函数没有返回值 及时有也只能是返回null或者undefined
function greeting(name: string):void {}
//声明一个void类型的变量没有什么大用,因为你只能为它赋予undefined和null:
let unusable: void = undefined;
Null 和 Undefined
//设置的值时固定的 所以没有太大用处
let u: undefined = undefined;
let n: null = null;
//默认情况下null和undefined是所有类型的子类型。 可以把 null和undefined赋值给number类型的变量。
let num1:number = undefined;
let num2:number = null;
// 指定strictNullChecks为true时,null和undefined只能赋值给void类型变量
//这种严格情况下,如果确实想给某个变量允许给其设置null和undefined,可以这样,如下
let num3:string | null | undefined = undefined;
never
// 返回never的函数必须存在无法达到的终点
function error(message: string): never {
throw new Error(message);
}
// 推断的返回值类型为never
function fail() {
return error("Something failed");
}
// 返回never的函数必须存在无法达到的终点
function infiniteLoop(): never {
while (true) {
}
}
类型断言
let znames: number | string | null ;
function ww(){
console.log((znames as string).length)
}
znames = "13123"
ww()
let someValue: any = "this is a string";
let strLength1: number = (<string>someValue).length;//和下面的效果一样
let strLength2: number = (someValue as string).length;
不可以这样写
let a = 1
let b = 'b'
let c: a | b = 1
可以这样写
let c: 1 | 'b' = 1
可以这样写
type cType = 1 | 'b'
let c:cType = 1
函数类型声明
type GetFunction = (x:string,y:string)=>string;
let getUsername:GetFunction = function(f:string,l:string):string{
return f+l
}
函数形参声明
function print(name:string,age?:number):void{} // age参数可以不传
function print(name:string,age:number=24):void{} // age默认值为24
函数形参聚合为数组
function sum(...number:Array<number>){
console.log(number)
}
sum(1,2,3,4)
函数一个形参多种类型
function attr(val:string|number){
if(typeof val === 'string'){
console.log(val,'string')
}else{
console.log(val,'number')
}
}
函数一个形参多种类型
function attr(val:string):void;
function attr(val:number):void;
function attr(val:any){
if(typeof val === 'string'){
console.log(val,'string')
}else{
console.log(val,'any')
}
}
attr('asdfad')
attr(123)
声明实例属性myname
class Person {
myname:string; // strictPropertyInitialization 设置为false,否则报错
}
声明实例属性myname
class Person {
myname:string = 'zanlan'; 不会报错
}
声明实例属性myname
class Person {
myname:string'; //不会报错
constructor(x:string){
this.myname = x
}
}
访问控制修饰符 public protected private
- public 都可以访问到,没有限制
class Animal{
public readonly name:string = 'x'
constructor(name:string){
this.name = name
}
}
class Dog extends Animal{
getName(){
console.log(this.name)//能访问
}
}
let a = new Animal('zanlan')
console.log(a.name)//能访问
- private 不能在声明它的类的外部访问
class Animal{
private readonly name:string = 'x'
constructor(name:string){
this.name = name
}
getName(){
console.log(this.name)//能访问
}
}
class Dog extends Animal{
getName(){
console.log(this.name)// 报错 - 报错 - 报错
}
}
new Animal('zanlan').getName() // 能访问
new Animal('zanlan').name // 报错
new Dog('zanlan').name // 报错
class Animal {
private name: string;
constructor(theName: string) { this.name = theName; }
}
class Rhino extends Animal { }
class Employee {
private name: string;
constructor(theName: string) { this.name = theName; }
}
let animal = new Animal("Goat");
let rhino = new Rhino("Rhino");
let employee = new Employee("Bob");
animal = rhino; // 可以的
animal = employee; // 错误: Animal 与 Employee 不兼容.
- protected 只能是在当前的class内部使用,和他的子类的内部使用
class Animal{
protected readonly name:string = 'x'
constructor(name:string){
this.name = name
}
getName(){
console.log(this.name)//能访问 输出 "zanlan"
}
}
class Dog extends Animal{
getName(){
console.log(this.name)//能访问 输出 "zanlan"
}
}
new Animal('zanlan').getName()//能访问
new Dog('zanlan').getName()//能访问
new Animal('zanlan').name // 报错
new Dog('zanlan').name // 报错
//报错信息为
//Property 'name' is protected and only accessible within class 'Animal' and its subclasses.
重写(override)–子类重写继承自父类中的方法
class Animal {
speak(word:string):string{
return '叫' + word
}
}
class Cat {
speak(word:string):string{
return '猫叫' + word
}
}
let cat = new Cat()
console.log(cat.speak('喵喵喵'))
重载(overload)-- 同一个函数提供多个类型定义
function double(val:number):number
function double(val:string):string
function double(val:any):any{
if(typeof val === 'number'){
return val*2
}
return val+val
}
接口interface
只要满足接口的必要条件就可以 但是需要用变量赋值 否则必须是充要条件
interface LabelledValue{
label:string
}
function printLabel(labelledObj: LabelledValue) {
console.log(labelledObj.label);
}
let myObj = {size: 10, label: "Size 10 Object"};
printLabel(myObj);// 不报错
//对象字面量会被特殊对待而且会经过 额外属性检查,当将它们赋值给变量或作为参数传递的时候。
//如果一个对象字面量存在任何“目标类型”不包含的属性时,你会得到一个错误。
printLabel({size: 10, label: "Size 10 Object"});//报错
? 代表是 可以不传的意思
interface Speakable{
name?:string;
}
let speakman:Speakable = {}
可索引的类型 表示数组
interface UserInterface {
[index: number]: string
}
let arr: UserInterface = ['zf', 'jg']
console.log(arr) // ['zf', 'jg']
可索引的类型 表示对象
interface UserInterface2 {
name:string;
[index: string]: string;
}
let obj2: UserInterface2 = { name: 'zhufeng' }
console.log(obj2)// { name: 'zhufeng' }
接口相互继承用extends class类实现接口特征
interface AnimalLike{
eat():void;
}
interface PersonLike extends AnimalLike{
speak():void;
}
// 如果继承多个接口 则用逗号隔开连续写 implements Flying,Eating
class Girl implements PersonLike{ //implements 工具,实现
eat(){}
speak(){}
}
class类作为参数传递 接口可以规定class类应该有哪些属性
interface WithNameClass{
new(name:string):Animal // 在new Animal时,构造函数要传个字符串
age:number // Animal类的静态属性age 为number 也可以是 Animal.prototype上上的属性
getAge():void// Animal类的静态属性getAge为number 也可以是 Animal.prototype上上的属性
}
class Animal {
constructor(public name:string){}
static age:number = 1; // 声明Animal.age
getAge(){ // 声明Animal.prototype.getAge
console.log(Animal.age)
}
}
function creaetClass(clazz:WithNameClass,name:string){
//书写clazz会报错 解决办法是 "noImplicitAny": false
return new clazz(name)
}
console.log(creaetClass(Animal,'123'))//Animal { name: 'zanlan' }
使用implements,接口只能修饰class类的静态属性 以及类的prototype属性,不可以修饰实例属性
interface ClockConstructor {
new (hour: number, minute: number);
}
class Clock implements ClockConstructor {//无法修饰constructor内数属性,所以报错
constructor(h: number, m: number) { }
}
//下面是解决办法 只有当被new时候,才会修饰到constructor函数
interface ClockConstructor {
new (hour: number, minute: number):Clock;
}
class Clock{//无法修饰constructor内数属性,所以报错
constructor(h: number, m: number) { }
}
对多个class类的constructor 和属性做统一修饰
interface A {// A为构造函数所用
new(a: string, b: number): B;
}
interface B {//B为实例方法所用。
tick();
}
class F1 implements B {
constructor(a: string, b: number) { }
tick() { }
}
class F2 implements B {
constructor(a: string, b: number) { }
tick() { }
}
function createF(F: A, a: string, b: number):B {
return new F(a, b)
}
createF(F1, '1', 1)
createF(F2, '2', 2)
接口继承类
类定义会创建两个东西:类的实例类型和一个构造函数。 因为类可以创建出类型,所以你能够在允许使用接口的地方使用类
class Point {
x: number;
y: number;
}
interface Point3d extends Point {
z: number;
}
let point3d: Point3d = {x: 1, y: 2, z: 3};
//接口继承了一个类的类型时,它会继承类的成员 但不包括其实现
class A {
private state: any;
}
interface F extends A { // F接口
select(): void;
}
class C implements F {
state = 1 // 报错 “state”在类型“F”中是私有属性
select() { }
}
只读属性 readonly ReadonlyArray
interface Point {
readonly x: number;
readonly y: number;
}
let p1: Point = { x: 10, y: 20 };
p1.x = 5; // error!
let ro: ReadonlyArray<any> = ['1', true, 1];
ro[0] = 12; // error!
ro.push(5); // error!
ro.length = 100; // error!
接口修饰 普通函数 规定传参 以及返回值
interface SearchFunc {
(source: string, subString: string): boolean;
}
let mySearch: SearchFunc = function(source: string, subString: string) {
return true;
}
修饰符
public
在TypeScript里,成员都默认为 public。
private
被修饰属性,只能在当前类里面使用,其他地方都不可以用,包括实例
protected
被修饰属性,只能在当前类,以及子类里面使用,其他地方都不可以用,包括实例
protected constructor
class F {
protected constructor() { }
}
new F(); // 错误: 'F' 的构造函数是被保护的,只能通过继承才能访问F的构造函数
两个不相干的类 内部有private或者protected属性,则它们类的实例不可以相互做赋值操作
class A {
private name: string;
constructor(theName: string) { this.name = theName; }
}
class B extends A {
constructor() { super("B"); }
}
class C {
private name: string;
constructor(theName: string) { this.name = theName; }
}
let a = new A("aaaaa");
let b = new B();
let c = new C("ccccc");
a = b;
a = c; // 错误: a 与 c 不兼容.
readonly
只读属性必须在声明时或构造函数里被初始化。
class Octopus {
readonly name: string;
readonly age: number = 8;
constructor (v: string) {
this.name = v;
}
}
存取器
TypeScript支持通过getters/setters来截取对对象成员的访问
只带有 get不带有 set的存取器自动被推断为 readonly
class Person {
myname:string = 'zanlan'; //声明this.myname = "zanlan"
get name() { //声明 Person.prototype.name
return this.myname.toUpperCase()
}
set name(newName: string) {
this.myname = newName
}
}
一般情况下回报错
Accessors are only available when targeting ECMAScript 5 and higher
解决办法是 指定编译版本为es5的
tsc index.ts --watch -t es5
抽象类
抽象类只能被继承,且其内部申明的抽象方法,必须在子类中实现
abstract class A {
f1(): void {}
abstract f2(): void; // 必须在派生类中实现
}
class B extends A {
f2(): void {}
f3(): void {}
}
let a: A; // 允许创建一个对抽象类型的引用
a = new A(); // 错误: 不能创建一个抽象类的实例
a = new B(); // 允许对一个抽象子类进行实例化和赋值
a.f1();
a.f2();
a.f3(); // 错误: 方法在声明的抽象类中不存在
typeof class 取class类的类型
typeof A,意思是取A类的类型,而不是实例的类型。 或者更确切的说,“告诉我 A标识符的类型”,也就是构造函数的类型。 这个类型包含了类的所有静态成员和构造函数
class A {}
let B: typeof A = A;
TypeScript 函数传参
不可多传 不可少传,除非是?号参数
function F(a: string, a: string) {
return a+ " " + b;
}
F("a"); // error, too few parameters
F("a", "b", "c"); // error, too many parameters
F("a", "b"); // ah, just right
参数合并
function F(a: string, ...arr: string[]) {
return a + " " + arr.join(" ");
}
F("a", "b", "c", "d");
泛型
T变量表示类型,捕获用户传入的类型
方式一 需要用户手动传类型
function F<T>(length: number, value: T): T[] {}
F<string>(3, 'x')
方式二 系统自动识别类型 这种更加普遍
F(3, 'x')
如果编译器不能够自动地推断出类型的话,只能像上面那样明确的传入T的类型,在一些复杂的情况下,这是可能出现的。
泛型 在 接口里应用
interface InterF<T> {
(arg: T): T;
}
function F<T>(arg: T): T {
return arg;
}
let myIdentity: InterF<number> = F;
多个泛型
function F<A, B>(arr: [A, B]): [B, A] {
return [arr[1], arr[0]]
}
F([1, 'a']) //['a',1]
泛型继承接口 接口描述泛型的属性类型
interface Len{
length:number
}
function logger<T extends Len >(val:T){
console.log(val.length)
}
logger('xxx-')
接口使用泛型
interface Cart<T>{
list:T[]
}
let cart:Cart<number> = {list:[1,2,3]}
泛型别名
type Cart<T> = { list: T[] } | T[]
let c1: Cart<number> = { list: [1, 2, 3] }
let c2: Cart<number> = [1, 2, 3]
类数组
let root = document.getElementById('root')
let children:HTMLCollection = root!.children;
let childNodes:NodeListOf<ChildNode> = root!.childNodes
strictNullChecks为true,则代码中不能调用可能为null的变量的属性了
function getFirstLetter(str:string | null){
console.log(typeof str.trim())//str可能为null,所以会报错
}
getFirstLetter(null)
?. 是否为undefined/null,是则返回undefined
a?.b;
a == null ? undefined : a.b;
let a:A['job'] = { jobname:'程序员' }
type C = A | B;
索引类型查询操作符
interface Person {
name: string;
age: number;
gender: 'male' | 'female'
}
type PersonKey = keyof Person;//索引类型查询
function getValueByKey(p: Person, key: PersonKey) {
return p[key]
}
let val = getValueByKey({ name: 'zhufeng', age: 10, gender: 'male' }, 'name')
console.log(val)
in 结合keyof,不加问号会报错
类型“{ name: string; }”缺少类型“Part”中的以下属性: age, gender
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5vbRQ4jL-1656646965842)(https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/79296adfcd484f61bed45580c1da23d4~tplv-k3u1fbpfcp-zoom-1.image)]
in 结合keyof,加了问号后,是允许不传name,age,gender属性
interface Person{
name:string;
age:number;
gender:'male'|'female'
}
type Part<T> = {
[key in keyof T]?:T[key]
}
let p:Part<Person> = {name:'zanlan'}
console.log(p)// { name: 'zanlan' }
Partial 可以将传入的属性由可选变为可选,具体使用如下
interface A {
a1: string;
a2: number;
a3: boolean;
}
type aPart = Partial<A>;
const a: aPart = {a1:'zanlan'};
console.log(a)//{a1:'zanlan'}
内置的Partial方法实现
type Partial<T> = {
[p in keyof T]?: T[p];
}
Required可以将传入的属性中的可选项变成必选项,这里用了-?修饰符
interface Person{
name:string;
age:number;
gender?:'male' | 'famale'
}
let p1:Person = {
name:'zhufeng', // 没有加Required 这里不会报错
age:10
}
let p2:Required<Person> = {
name:'zhufeng',
age:10,
gender:'male' // 必须要写gender 否则报错
}
内置的Required方法实现
type Required<T> = {
[p in keyof T]-?: T[p];
}
Readonly通过为传入的属性每一项都加上Readonly修饰符来实现。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-E95c2pJX-1656646965850)(https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/3c60a7224e1347ccacde3ff278015a32~tplv-k3u1fbpfcp-zoom-1.image)]
内置的Readonly方法实现
type Readonly<T> = {readonly [p in keyof T]:T[p]}
Pick
interface Animal{
name:string;
age:number;
getName():void;
}
type AnimalSub = Pick<Animal,'name'|'age'>;
let a:AnimalSub = {
name:'zhufeng',
age:10
}
实现方法
type Pick<T, K extends keyof T> = {[P in K]: T[P]};
Exclude 除了某某之外
type A = Exclude<string|number|boolean,string>;
let a:A = 10;
let b:A = true;
let c:A = 'xx';//报错
interface Face1{};
interface Face2{};
type C = Exclude<Face1|Face2,Face1>;
Extract 提取出某某有效
type A = Extract<string | number | boolean,string>;
let a:A = 'hello';
let b:A = 1;//报错
let c:A = true;//报错
NonNullable 剔除null 和 undefined类型
type D =NonNullable<string | number | null | undefined>;
let e:D = null;//不能将类型“null”分配给类型“D”
let f:D = undefined;//不能将类型“undefined”分配给类型“D”
ReturnType获取函数类型的返回类型
function getUserInfo(){
return {name:"zhufeng",age:10}
}
type UserInfo = ReturnType<typeof getUserInfo>;
const userA:UserInfo = {
name:'zhufeng',
age:true,//报错
ww:'fsadf'//报错
}
局部声明全局属性
declare global{ // 代表在局部内 声明全局的属性
interface String{
double():string;
}
interface Window {
myname:string;
}
}
String.prototype.double = function(){
return this+this
}
console.log('hello'.double())
window.myname = '123'
console.log(window.myname)
export {} // 加了这个 代表是上面的声明是局部的
class和interface区别
class Person {
name:stirng = 'hello'
}
// Person 既可以作为类使用 也可以做值来使用
let p1 = Person;
let p2 = new Person()
//接口只能作为类使用,不可以作为值赋值给变量
interface Person1 {
name:string;
}
let p3:Person1;
let p4 = Person1; //报错 “Person1”仅表示类型,但在此处却作为值使用
合并声明
interface Person {//一个文件声明了Person
name: string;
}
interface Person {//另一个文件也声明了Person
age: number;
}
let p: Person = { //因为同名 在ts文件使用时候 就会被合并
name: '123',
age: 123
}
namespace扩展类,用于表示内部类
class Form {
username:Form.Item = '';
password:Form.Item = '';
}
namespace Form{
export class Item {}
}
let item:Form.Item = new Form.Item()
console.log(item)
enum Color {
red = 1,
yellow = 2
}
namespace Color {
export const green = 4;
export const purple = 5;
}
console.log(Color.green) // 4
导出接口
export declare type PropType<T> = PropConstructor<T> | PropConstructor<T>[];//vue中导出
export interface ListItem {//自定义的
avatar: string;
title: string;
datetime: string;
type: string;
description: string;
status?: "" | "success" | "warning" | "info" | "danger";
extra?: string;
}
const props = defineProps({
noticeItem: {
type: Object as PropType<ListItem>,
default: () => {title:'123'},//定义default 和 validator 必须用到箭头函数
validator: (book: Book) => !!book.title
}
});
{{ props.noticeItem.title }} //定义好props参数 直接使用
{{ props.noticeItem?.extra }}
定义computed返回类型
computed: {
greeting(): string {// 需要注解
return this.message + '!'
}
}
泛型表示法
const year = ref<string | number | boolean>("2020");
year.value = true;
父组件 调用子组件方法
import { defineComponent, ref } from 'vue'
const MyModal = defineComponent({
setup() {
const isContentShown = ref(false)
const open = () => (isContentShown.value = true)
return {
isContentShown,
open
}
}
})
const app = defineComponent({
components: {
MyModal
},
template: `
<button @click="openModal">Open from parent</button>
<my-modal ref="modal" />
`,
setup() {
const modal = ref()
const openModal = () => {
modal.value.open()
}
return { modal, openModal }
}
})
它可以工作,但是没有关于 MyModal 及其可用方法的类型信息。为了解决这个问题,你应该在创建引用时使用 InstanceType:
setup() {
const modal = ref<InstanceType<typeof MyModal>>()
const openModal = () => {
modal.value?.open()
}
return { modal, openModal }
}
类型声明 reactive
import { defineComponent, reactive } from 'vue'
interface Book {
title: string
year?: number
}
export default defineComponent({
name: 'HelloWorld',
setup() {
const book = reactive<Book>({ title: 'Vue 3 Guide' })
// or
const book: Book = reactive({ title: 'Vue 3 Guide' })
// or
const book = reactive({ title: 'Vue 3 Guide' }) as Book
}
})
类型声明 computed
import { defineComponent, ref, computed } from 'vue'
export default defineComponent({
name: 'CounterButton',
setup() {
let count = ref(0)
// 只读
const doubleCount = computed(() => count.value * 2)
const result = doubleCount.value.split('')
// => Property 'split' does not exist on type 'number'
}
})
事件绑定e.target需要声明类型
<template>
<input type="text" @change="handleChange" />
</template>
<script lang="ts">
import { defineComponent } from 'vue'
export default defineComponent({
setup() {
// `evt` 将会是 `any` 类型
const handleChange = evt => {
console.log(evt.target.value) // 此处 TS 将抛出异常
}
return { handleChange }
}
})
</script>
如你所见,在没有为 evt 参数正确地声明类型的情况下,当我们尝试获取 元素的值时,TypeScript 将抛出异常。解决方案是将事件的目标转换为正确的类型:
const handleChange = (evt: Event) => {
console.log((evt.target as HTMLInputElement).value)
}
面试题
1. 什么是TypeScript?
Typescript 是一个强类型的 JavaScript 超集,支持ES6语法,支持面向对象编程的概念,如类、接口、继承、泛型等。Typescript并不直接在浏览器上运行,需要编译器编译成纯Javascript来运行。
2. 为什么要使用 TypeScript ? TypeScript 相对于 JavaScript 的优势是什么?
增加了静态类型,可以在开发人员编写脚本时检测错误,使得代码质量更好,更健壮。
优势:\
- 杜绝手误导致的变量名写错;\
- 类型可以一定程度上充当文档;\
- IDE自动填充,自动联想;
3. TypeScript 中 const 和 readonly 的区别?枚举和常量枚举的区别?接口和类型别名的区别?
const 和 readonly
: const可以防止变量的值被修改,readonly可以防止变量的属性被修改。
枚举和常量枚举
: 常量枚举只能使用常量枚举表达式,并且不同于常规的枚举,它们在编译阶段会被删除。 常量枚举成员在使用的地方会被内联进来。 之所以可以这么做是因为,常量枚举不允许包含计算成员。
接口和类型别名
: 两者都可以用来描述对象或函数的类型。与接口不同,类型别名还可以用于其他类型,如基本类型(原始值)、联合类型、元组。
4. TypeScript 中 any 类型的作用是什么?
为编程阶段还不清楚类型的变量指定一个类型。 这些值可能来自于动态的内容,比如来自用户输入或第三方代码库。 这种情况下,我们不希望类型检查器对这些值进行检查而是直接让它们通过编译阶段的检查。
5. TypeScript 中 any、never、unknown、null & undefined 和 void 有什么区别?
any
: 动态的变量类型(失去了类型检查的作用)。
never
: 永不存在的值的类型。例如:never 类型是那些总是会抛出异常或根本就不会有返回值的函数表达式或箭头函数表达式的返回值类型。
unknown
: 任何类型的值都可以赋给 unknown 类型,但是 unknown 类型的值只能赋给 unknown 本身和 any 类型。
null & undefined
: 默认情况下 null 和 undefined 是所有类型的子类型。 就是说你可以把 null 和 undefined 赋值给 number 类型的变量。当你指定了 --strictNullChecks 标记,null 和 undefined 只能赋值给 void 和它们各自。
void
: 没有任何类型。例如:一个函数如果没有返回值,那么返回值可以定义为void。
6. TypeScript 中 interface 可以给 Function / Array / Class(Indexable)做声明吗?
/* 可以 */ // 函数声明 interface Say { (name: string): viod; } let say: Say = (name: string):viod => {} // Array 声明 interface NumberArray { [index: number]: number; } let fibonacci: NumberArray = [1, 1, 2, 3, 5]; // Class 声明 interface PersonalIntl { name: string sayHi (name: string): string } 复制代码
7. TypeScript 中可以使用 String、Number、Boolean、Symbol、Object 等给类型做声明吗?
/* 可以 */
let name: string = “bob”;
let decLiteral: number = 6;
let isDone: boolean = false;
let sym: symbol = Symbol();
interface Person {
name: string;
age: number;
}
复制代码
8. TypeScript 中的 this 和 JavaScript 中的 this 有什么差异?
- TypeScript:noImplicitThis: true 的情况下,必须去声明 this 的类型,才能在函数或者对象中使用this。
- Typescript 中箭头函数的 this 和 ES6 中箭头函数中的 this 是一致的。
9. TypeScript 中使用 Union Types 时有哪些注意事项?
属性或方法访问: 当 TypeScript 不确定一个联合类型的变量到底是哪个类型的时候,我们只能访问此联合类型的所有类型里共有的属性或方法。
function getLength(something: string | number): number {
return something.length;
}
// index.ts(2,22): error TS2339: Property ‘length’ does not exist on type >‘string | number’.
// Property ‘length’ does not exist on type ‘number’.function getString(something: string | number): string {
return something.toString();
}
// 公共方法和属性可以访问
复制代码
10. TypeScript 如何设计 Class 的声明?
class Greeter {
greeting: string;
constructor(message: string) {
this.greeting = message;
}
greet(): string{
return "Hello, " + this.greeting;
}
}
let greeter = new Greeter(“world”);
// 在声明类的时候,一般类中都会包含,构造函数、对构造函数中的属性进行类型声明、类中的方法。
复制代码
11. TypeScript 中如何联合枚举类型的 Key?
enum str {
A,
B,
C
}
type strUnion = keyof typeof str; // ‘A’ | ‘B’ | ‘C’
复制代码
12. TypeScript 中 type 和 interface 的区别?
相同点:\
- 都可以描述 ‘对象’ 或者 ‘函数’\
- 都允许拓展(extends)
不同点:\
- type 可以声明基本类型,联合类型,元组\
- type 可以使用 typeof 获取实例的类型进行赋值\
- 多个相同的 interface 声明可以自动合并\
使用 interface 描述‘数据结构’,使用 type 描述‘类型关系’
13. TypeScript 中 ?.、??、!、!.、_、 等符号的含义?**
?. 可选链
遇到 null 和 undefined 可以立即停止表达式的运行。
?? 空值合并运算符
当左侧操作数为 null 或 undefined 时,其返回右侧的操作数,否则返回左侧的操作数。
! 非空断言运算符
x! 将从 x 值域中排除 null 和 undefined
!.
在变量名后添加,可以断言排除undefined和null类型
_ 数字分割符
分隔符不会改变数值字面量的值,使人更容易读懂数字 .e.g 1_101_324。
**
求幂\
15. 简单介绍一下 TypeScript 模块的加载机制?
假设有一个导入语句
import { a } from "moduleA"
;\
- 首先,编译器会尝试定位需要导入的模块文件,通过绝对或者相对的路径查找方式;\
- 如果上面的解析失败了,没有查找到对应的模块,编译器会尝试定位一个
外部模块声明
(.d.ts);\- 最后,如果编译器还是不能解析这个模块,则会抛出一个错误
error TS2307: Cannot find module 'moduleA'.
16. 简单聊聊你对 TypeScript 类型兼容性的理解?
ts 类型兼容:
当一个类型 Y 可以赋值给另一个类型 X 时, 我们就可以说类型 X 兼容类型 Y。也就是说两者在结构上是一致的,而不一定非得通过 extends 的方式继承而来
接口的兼容性:X = Y
只要目标类型 X 中声明的属性变量在源类型 Y 中都存在就是兼容的( Y 中的类型可以比 X 中的多,但是不能少)
函数的兼容性:X = Y
Y 的每个参数必须能在 X 里找到对应类型的参数,参数的名字相同与否无所谓,只看它们的类型(参数可以少但是不能多。与接口的兼容性有区别,原因参考第 17 问)
17. 协变、逆变、双变和抗变的理解?
协变:X = Y
Y 类型可以赋值给 X 类型的情况就叫做协变,也可以说是 X 类型兼容 Y 类型
interface X { name: string; age: number; }
interface Y { name: string; age: number; hobbies: string[] }
let x: X = { name: ‘xiaoming’, age: 16 }
let y: Y = { name: ‘xiaohong’, age: 18, hobbies: [‘eat’] }
x = y
复制代码
逆变:printY = printX
函数X 类型可以赋值给函数Y 类型,因为函数Y 在调用的时候参数是按照Y类型进行约束的,但是用到的是函数X的X的属性和方法,ts检查结果是类型安全的。这种特性就叫做逆变,函数的参数有逆变的性质。
let printY: (y: Y) => void
printY = (y) => { console.log(y.hobbies) }
let printX: (x: X) => void
printX = (x) => { console.log(x.name) }
printY = printX
复制代码
双变(双向协变):X = Y;Y = X
父类型可以赋值给子类型,子类型可以赋值给父类型,既逆变又协变,叫做“双向协变”(ts2.x 之前支持这种赋值,之后 ts 加了一个编译选项 strictFunctionTypes,设置为 true 就只支持函数参数的逆变,设置为 false 则支持双向协变)
抗变(不变):
非父子类型之间不会发生型变,只要类型不一样就会报错
18. TypeScript 中对象展开会有什么副作用吗?
- 展开对象后面的属性会覆盖前面的属性;
- 仅包含对象自身的可枚举属性,不可枚举的属性将会丢失。
19. 类型的全局声明和局部声明
如果声明文件内不包含
import、export
,那么这个文件声明的类型就会变成全局声明。反之,若是这个文件包含了import、export
,那么这个文件包含的类型声明则会是局部声明,不会影响到全局声明。
20. TypeScript 中同名的 interface 或者同名的 interface 和 class 可以合并吗?
同名的interface会自动合并,同名的interface和class会自动聚合。
21. 如何使 TypeScript 项目引入并识别编译为 JavaScript 的 npm 库包?
- 选择安装 ts 版本,
npm install @types/包名 --save
;- 对于没有类型的 js 库,需要编写同名的.d.ts文件
22. TypeScript 的 tsconfig.json 中有哪些配置项信息?
{
“files”: [],
“include”: [],
“exclude”: [],
“compileOnSave”: false,
“extends”: “”,
“compilerOptions”: { … }
}
files
是一个数组列表,里面包含指定文件的相对或绝对路径,用来指定待编译文件,编译器在编译的时候只会编译包含在files中列出的文件。
include & exclude
指定编译某些文件,或者指定排除某些文件。
compileOnSave:true
让IDE在保存文件的时候根据tsconfig.json重新生成文件。
extends
可以通过指定一个其他的tsconfig.json文件路径,来继承这个配置文件里的配置。
compilerOptions
编译配置项,如何对具体的ts文件进行编译
23. TypeScript 中如何设置模块导入的路径别名?
通过 tsconfig.json 中的 paths 项来配置:
{
"compilerOptions":
{
"baseUrl": ".",
"paths": {
"@helper/*": ["src/helper/*"],
"@utils/*": ["src/utils/*"],
...
}
}
}
24. declare,declare global是什么?
declare
是用来定义全局变量、全局函数、全局命名空间、js modules、class等
declare global
为全局对象 window
增加新的属性
declare global {
interface Window {
csrf: string;
}
}
25. 对 TypeScript 类中成员的 public、private、protected、readonly 修饰符的理解?
`public`: 成员都默认为`public`,被此限定符修饰的成员是可以被外部访问;
`private`: 被此限定符修饰的成员是只可以被类的内部访问;
`protected`: 被此限定符修饰的成员是只可以被类的内部以及类的子类访问;
`readonly`: 关键字将属性设置为只读的。 只读属性必须在声明时或构造函数里被初始化。
26. keyof 和 typeof 关键字的作用?
keyof 索引类型查询操作符
获取索引类型的属性名,构成联合类型。
typeof
获取一个变量或对象的类型。
27. 简述工具类型 Exclude
、Omit
、Merge
、Intersection
、Overwrite
的作用。
`Exclude<T, U>` 从 `T` 中排除出可分配给 `U`的元素。
`Omit<T, K>` 的作用是忽略`T`中的某些属性。
`Merge<O1, O2>` 是将两个对象的属性合并。
`Compute<A & B>` 是将交叉类型合并\
`Intersection<T, U>`的作用是取`T`的属性,此属性同样也存在与`U`。
`Overwrite<T, U>` 是用`U`的属性覆盖`T`的相同属性。
28. 数组定义的两种方式
type Foo= Array<string>;
interface Bar {
baz: Array<{ name: string, age: number}>
}
type Foo = string[];
interface Bar {
baz : { name: string, age: number }[]
}