TypeScript学习篇-类型介绍使用、ts相关面试题

19,764次阅读
没有评论

共计 14473 个字符,预计需要花费 37 分钟才能阅读完成。

文章目录

  • 基础知识
    • 基础类型: number, string, boolean, object, array, undefined, void(代表该函数没有返回值)
      • unknown
      • enum(枚举): 定义一个可枚举的对象
      • 联合类型: | (联合类型一次只能一种类型;而交叉类型每次都是多个类型的合并类型。)
      • 交叉类型: & (联合类型一次只能一种类型;而交叉类型每次都是多个类型的合并类型。)
      • any 类型
      • null 和 undefined
      • never 类型
      • type
      • interface:接口,可以描述一个对象或函数
    • class 类
    • 函数
    • 泛型: 不定义为具体类型
    • 迭代器和生成器
    • 装饰器
    • 继承、多态、重载、重写
      • 继承
      • 多态
      • 重载
      • 重写
      • Record
      • Paritial
      • Reuqired
      • Readonly
      • Exclude
      • Extract
      • in
  • TypeScript 编译原理
  • 面试题及实战
    • 什么是类型推论,以下代码 ts 推论出的类型是什么
    • 1. 你觉得使用 ts 的好处是什么?
    • 2. type 和 interface 的异同
      • 2.1 都可以描述一个对象或者函数
      • 2.2 都允许拓展(extends)
      • 2.3 只有 type 可以做的:声明基本类型别名,联合类型,元组等类型
      • 2.4 interface 可以多次定义并被视为合并所有声明成员,type 不支持
      • 2.5 type 能使用 in 关键字生成映射类型,interface 不行
    • 3. 如何基于一个已有类型, 扩展出一个大部分内容相似, 但是有部分区别的类型?
    • 4. 什么是泛型, 泛型的具体使用?
    • 5. 写一个计算时间的装饰器
    • 6. 写一个缓存的装饰器
    • 7. 实现一个路由跳转 通过 ts 约束参数的 routeHelper
    • 8. 实现一个基于 ts 和事件模式的 countdown 基础类
    • 9. npm install 一个模块,都发生了什么
    • 10. eventemitter3

执行 ts 文件命令:

tsc 文件.ts

基础知识

基础类型: number, string, boolean, object, array, undefined, void(代表该函数没有返回值)

类型 关键字 描述
任意类型 any 声明为 any 的变量可以赋予任意类型的值。能不用 any 就不用 any。
数字类型 number 双精度 64 位浮点值, 它可以用来表示整数和分数:
let num: number = 666;
字符串类型 string let str: string = '呀哈哈';
布尔类型 boolean let flag: boolean = true;
数组类型 声明变量为数组
在元素类型后面加 []: let arr: number[] = [1,2];,
或使用数组泛行: let arr:Array = [1,2];
元组 元组类型用来表示已知元素数量和类型的数组, 各元素的类型不必相同, 对应位置的类型需要相同:
let x:[string,number];
正确: x = ['呀哈哈', 2];
错误: x = [2, '呀哈哈'];
枚举 enum 枚举类型用于定义数值集合
enum Color {Red, Green, Blue};
let c : Color = Color.Blue;
console.log(c); // 2
void void 用于标识方法返回值的类型, 表示该方法没有返回值,不返回或返回 undefined
function hello(): void{ alert('Hello World'); }。常用于表示函数没有返回值。
null null 表示对象值缺失
undefined undefined 用于初始化变量为一个未定义的值
never never never 是其他类型 (包括 null 和 undefined) 的子类型, 代表从不会出现的值. 对于函数而已 never 表示函数用于执行不到返回值哪一步(抛出异常或死循环) 的返回值类型。常用于构造条件类型来组合出更灵活的类型定义。
unknown unknown 和 any 类型,但使用前必须进行断言或守卫。声明时如果不确定具体类型则可以使用,在使用时用类型断言或类型守卫进行类型收缩。

unknown

let e : unknown = '123';
if(typeof e == 'string'){
  e = 111;
}else{
  e = 123;
}
console.log("🚀 ~ e:", e) 
console.log("🚀 ~ e type:", typeof e) 

enum(枚举): 定义一个可枚举的对象

数字枚举的声明可以分为两大类:带有初始化器和不带初始化器

  • 不带初始化器,枚举成员默认从 0 开始,依次递增
  • 带有初始化器,可以分为两种
    • 使用初始化器并指定初始化的常数,未使用初始化器的成员取值是在上一个成员的基础上 +1
    • 使用初始化器并初始化值是对已经声明的枚举的枚举成员的引用
const Enum1 = {
    'RUN':'run', 
    'EAT':'eat', 
}
enum Enum2 {
    'RUN',
    'EAT', 
    'YHH' = 10,
    'YHH2', 
    'ABC' = 'abc', 
    'YHH3', 
}

enum Enum3 {
    one: Enum2.YHH, 
    two, 
}

const eat = Enum2.EAT;

const getValue = () => {
  return 0
}

enum List {
  A = getValue(),
  B = 2,  
  C
}
console.log(List.A) 
console.log(List.B) 
console.log(List.C) 

联合类型: | (联合类型一次只能一种类型;而交叉类型每次都是多个类型的合并类型。)

交叉类型: & (联合类型一次只能一种类型;而交叉类型每次都是多个类型的合并类型。)

any 类型

任意值是 typescript 针对编程时类型不明确的变量使用的一种数据类型. 常用于以下三种情况


let x: any = 1;
x = 'yhh';
x = false;


let x: any = 4;
x.isEmpty(); 
x.toFixed(); 


ler arrayList: any[] = [1,false,'fine'];
arratList[1] = 100;

null 和 undefined

null

nullJavaScript 中 表示’什么都没有’.

null 是一个只有一个值的特殊类型, 表示一个空对象引用.

typeof 检测 null 返回是 object.

undefined

undefinedjavascript 中是一个没有设置值的变量.

typeof 一个没有值的变量会返回 undefined.

NullUndefined 是其他任何类型 (包括void) 的子类型. 可以赋值给其他类型, 比如数字类型, 赋值后的类型会变成 nullundefined.

typescript 中启用严格的空检验 (--strictnullChecks) 特性, 就可以使 nullundefined 只能被赋值给 void 或本身对应的类型.


let x : number;
x = 1;
x = undefined; 
x = null; 



let x : number | null | undefined;
x = 1;
x = undefined; 
x = null; 

never 类型

never 是其他类型 (包括nullundefined)的子类型, 代表从不会出现的值. 这意味着声明 never 类型的变量只能被 never 类型所赋值, 在函数中通常表现为抛出异常或无法执行到终止点.

let x : never;
let y : number;

x = 123; 


x = (()=>{ throw new Error('exception') })();

y = (()=>{ throw new Error('exception') })();


function error(message:string): never{
    throw new Error(message);
}

function loop(): never{
    while (true){}
}

type

type Action = 'eat'  | 'run';

const a:Action = 'eat';

interface:接口,可以描述一个对象或函数

interfact object 对象类型


interface User{
  name: string,
  age?: number, 
  
}


axios.postUser>().then();


function printUser(user: User): void{
  console.log("🚀 ~ printUser ~ user.name:", user.name)
  console.log("🚀 ~ printUser ~ user.age:", user.age)
}
const user1 : User = {
  name: "张三",
  age: 18
}
printUser(user1);

interface function 函数类型

interface UserFun{
    ( name: string , age: number ): void;
}
const myFunction: UserFun = (name, age)=>{
    
}

interface array 可索引类型


interface StringArray {
  
  [index: number]: string;
}
let arr1 : StringArray;
arr1 = ["1","2","3"];
console.log("🚀 ~ arr1:", arr1)

let arr2 : StringArray;
arr2 = {1: '呀哈哈', 2: '18'};
console.log("🚀 ~ arr2:", arr2)

class 类

函数

泛型: 不定义为具体类型



function funcTT>(arg: T): T{
  return arg;
}
funcT('呀哈哈');
funcT(19);
funcT({name:'呀哈哈'});
funcT({age:18});


function func1(arg: string): string{
  return arg;
}
function func2(arg: number): number{
  return arg;
}

function funcTET extends User>(arg: T): T{
  return arg;
}
funcTE({age:18, name: '呀哈哈'});

迭代器和生成器

装饰器

执行顺序:
1、有多个参数装饰器时,从最后一个参数一次向前执行
2、方法和方法参数中参数装饰器先执行
3、类装饰器总是最后执行
4、方法和属性装饰器,谁在前面谁先执行,因为参数属于方法一部分,所以参数会一直紧紧挨着方法执行

类装饰器

function Log(target: any){
    console.log(target);
    console.log('in log decorator');
}
@Log
class A{
    constructor(){
        console.log('constructor');
    }
}
new A();

继承、多态、重载、重写

继承

有时候我们定义的泛型不想过于灵活或者说想继承某些类等,可以通过 extends 关键字添加泛型约束。

interface ILengthwise {
  length: number;
}

function loggingIdentityT extends ILengthwise>(arg: T): T {
  console.log(arg.length);
  return arg;
}

loggingIdentity(3);
loggingIdentity({length: 10, value: 3});

多态

指允许一个接口表示多种形态,多态允许一个类实例的对象使用相同的方法名,具有不同的表现行为。在 TypeScript 中,多态通常是通过类的继承和子类重写父类的方法来实现的。

class Animal {
    makeSound() {
        console.log("Some generic animal sound");
    }
}
 
class Dog extends Animal {
    makeSound() {
        console.log("Woof!");
    }
}
 
let animal = new Animal();
let dog = new Dog();
 
animal.makeSound(); 
dog.makeSound();    

重载

指允许一个函数接受不同数量或类型的参数,并执行不同的操作。在 TypeScript 中,通过为同名函数定义不同的函数签名来实现重载。


function overload(a: number, b: number): number;
function overload(a: string, b: string): string;
function overload(a: any, b: any): any {
    if (typeof a === "string" || typeof b === "string") {
        return a.toString() + b.toString();
    }
    return a + b;
}
 
console.log(overload(1, 2)); 
console.log(overload("Hello", "World")); 
 

重写

指子类重新定义了父类的方法,以改变(重写)其行为。在 TypeScript 中,子类可以通过使用 super 关键字来调用父类的方法,并在此基础上添加或修改自己的实现。

class Printer {
    print(s: string) {
        console.log('Printer:' + s);
    }
}
 
class ColorPrinter extends Printer {
    print(s: string, color: string) {
        console.log('ColorPrinter:' + s + 'in' + color);
    }
}
 
let printer = new Printer();
let colorPrinter = new ColorPrinter();
 
printer.print('Hello');        
colorPrinter.print('World', 'blue'); 

Record

Record 的作用是将 K 中所有的属性的值转化为 T 类型。

interface PageInfo {
  title: string;
}

type Page = "home" | "about" | "contact";

const x: RecordPage, PageInfo> = {
  about: { title: "about" },
  contact: { title: "contact" },
  home: { title: "home" }
};

Paritial

Partial 的作用就是将某个类型里的属性全部变为可选项 ?。

Reuqired

Required 的作用就是将某个类型里的属性全部变为必选项。

Readonly

Readonly 的作用是将某个类型所有属性变为只读属性,也就意味着这些属性不能被重新赋值。

Exclude

Exclude 的作用是将某个类型中属于另一个的类型移除掉。

type T0 = Exclude"a" | "b" | "c", "a">; 
type T1 = Exclude"a" | "b" | "c", "a" | "b">; 

Extract

Extract 的作用是从 T 中提取出 U。

type T0 = Extract"a" | "b" | "c", "a" | "f">; 
type T1 = Extractstring | number | (() => void), Function>; 

in

in 用来遍历枚举类型:

type Keys = "a" | "b" | "c"

type Obj =  {
  [p in Keys]: any
} 

TypeScript 编译原理

Scanner 扫描仪,Parser 解析器,Binder 绑定器,Checker 检查器,Emitter 发射器
TypeScript 学习篇 - 类型介绍使用、ts 相关面试题

面试题及实战

什么是类型推论,以下代码 ts 推论出的类型是什么

1、类型推论 :指在编写 TypeScript 代码时,即使没有明确指定变量的类型,编译器也 能根据上下文推断出变量的类型 。这种类型推断机制使得 TypeScript 代码更加简洁,减少了需要显式声明类型的需要。
例如,如果声明一个变量但没有为其赋值,TypeScript 会将其类型推断为 any 类型。但如果为变量赋了值,TypeScript 会根据赋的值推断出更具体的类型,如字符串、数字等。这种类型推断的过程发生在初始化变量、设置默认参数值、决定函数返回值时,以及在需要确定变量类型的其他情况下。

let a = 1024; 
let b = '1024'; 
const c = 'yhh'; 
let d = [true, false, true]; 
let e = {name: 'yhh'}; 
let f = null; 

TypeScript 学习篇 - 类型介绍使用、ts 相关面试题
可赋值性 :数组,布尔,数字,对象,函数,类,字符串,字面量类型,满足以下任一条件时,A 类型可以赋值给 B 类型:
1、A 是 B 的子类型
2、A 是 any 的类型
规则 2 是规则 1 的例外

let aa = 1024;
let aa1 : number = 1024;
aa = aa1;
aa1 = aa;

let bb : string = 'bb';
let bb1 : string | number = 'bb1';
bb = bb1;
bb1 = bb;
bb1 = 2;


let i : 3 = 3;


let j = [1,2,3];
j.push(4);



let k : any = 1;
k = '1';
k = {name: 'yhh'};
k = null;
k = undefined;
k = 1;

let n = []; 


let m; 
m = []; 
m.push(1); 

2、类型断言 :可以用来手动指定一个值的类型, 即允许变量从一种类型更改为另一种类型
语法格式:值或 值 as 类型
在 tsx 语法(React 的 jsx 语法的 ts 版)中必须使用值 as 类型。

以下写法在编译时不会报错。但在运行时会报错,因为 123 为 number,不用 slice。
因此类型断言尽量避免使用,只在明确知道类型时可以使用。

function formatInput(input: string):string{
    return input.slice(0,10);
}
function getUserInput(): string | number{
    return 123;
}
let input = getUserInput();
formatInput(input as string);
formatInput(string>input);

1. 你觉得使用 ts 的好处是什么?

1、TypeScript 是 JavaScript 的加强版,它给 JavaScript 添加了可选的静态类型和基于类的面向对象编程,它拓展了 JavaScript 的语法。所以 ts 的功能比 js 只多不少.
2、Typescript 是纯面向对象的编程语言,包含类和接口的概念.
3、TS 在开发时就能给出编译错误,而 JS 错误则需要在运行时才能暴露。
4、可以明确知道数据的类型。代码可读性极强,几乎每个人都能理解。
5、ts 中有很多很方便的特性, 比如可选链.

2. type 和 interface 的异同

重点:用 interface 描述数据结构,用 type 描述类型

2.1 都可以描述一个对象或者函数

interface User {
  name: string
  age: number
}

interface SetUser {
  (name: string, age: number): void;
}

type User = {
  name: string
  age: number
};

type SetUser = (name: string, age: number)=> void;

2.2 都允许拓展(extends)

interface 和 type 都可以拓展,并且两者并不是相互独立的,也就是说 interface 可以 extends type, type 也可以 extends interface。虽然效果差不多,但是两者语法不同。


interface Name { 
  name: string; 
}
interface User extends Name { 
  age: number; 
}


type Name = { 
  name: string; 
}
type User = Name & { age: number  };


type Name = { 
  name: string; 
}
interface User extends Name { 
  age: number; 
}


interface Name { 
  name: string; 
}
type User = Name & { 
  age: number; 
}

2.3 只有 type 可以做的:声明基本类型别名,联合类型,元组等类型

type 可以声明基本类型别名,联合类型,元组等类型


type Name = string


interface Dog {
    wong();
}
interface Cat {
    miao();
}

type Pet = Dog | Cat


type PetList = [Dog, Pet]


let div = document.createElement('div');
type B = typeof div

2.4 interface 可以多次定义并被视为合并所有声明成员,type 不支持

interface Point {
    x: number
}
interface Point {
    y: number
}
const point: Point = {x: 1, y: 2};

interface User {
    name: string,
    age: number
}
interface User{
    id: number
}

const user: User = {
    name: 'yhh',
    age: 18,
    id: 1
}

2.5 type 能使用 in 关键字生成映射类型,interface 不行

type Keys = 'firstname' | 'surname';
type DudeType = {
    [key in Keys]: string;
}
const dude: DudeType = {
    firstname: 'y',
    surname: 'hh'
}

3. 如何基于一个已有类型, 扩展出一个大部分内容相似, 但是有部分区别的类型?

首先可以通过 Pick 和 Omit

interface Test {
    name: string;
    sex: number;
    height: string;
}

type Sex = PickTest, 'sex'>;

const a: Sex = { sex: 1 };

type WithoutSex = OmitTest, 'sex'>;

const b: WithoutSex = { name: '1111', height: 'sss' };

比如 Partial, Required.

再者可以通过泛型.

4. 什么是泛型, 泛型的具体使用?

泛型是指在定义函数、接口或类的时候,不预先指定具体的类型,使用时再去指定类型的一种特性。

可以把泛型理解为代表类型的参数

interface TestT = any> {
    userId: T;
}

type TestA = Teststring>;
type TestB = Testnumber>;

const a: TestA = {
    userId: '111',
};

const b: TestB = {
    userId: 2222,
};

5. 写一个计算时间的装饰器

export function before(beforeFn: any) {
    return function(target: any, name: any, descriptor: any) {
        const oldValue = descriptor.value;

        descriptor.value = function() {
            beforeFn.apply(this, arguments);
            return oldValue.apply(this, arguments);
        };

        return descriptor;
    };
}

export function after(afterFn: any) {
    return function(target: any, name: any, descriptor: any) {
        const oldValue = descriptor.value;

        descriptor.value = function() {
            const ret = oldValue.apply(this, arguments);
            afterFn.apply(this, arguments);
            return ret;
        };

        return descriptor;
    };
}

export function measure(target: any, name: any, descriptor: any) {
    const oldValue = descriptor.value;

    descriptor.value = async function() {
        const start = Date.now();
        const ret = await oldValue.apply(this, arguments);
        console.log(`${name}执行耗时 ${Date.now() - start}ms`);
        return ret;
    };

    return descriptor;
}

6. 写一个缓存的装饰器

const cacheMap = new Map();

export function EnableCache(target: any, name: string, descriptor: PropertyDescriptor) {
    const val = descriptor.value;
    descriptor.value = async function(...args: any) {
        const cacheKey = name + JSON.stringify(args);
        if (!cacheMap.get(cacheKey)) {
            const cacheValue = Promise.resolve(val.apply(this, args)).catch((_) => cacheMap.set(cacheKey, null));
            cacheMap.set(cacheKey, cacheValue);
        }
        return cacheMap.get(cacheKey);
    };
    return descriptor;
}

7. 实现一个路由跳转 通过 ts 约束参数的 routeHelper

import { Dictionary } from 'vue-router/types/router';
import Router, { RoutePath } from '../router';

export type BaseRouteType = Dictionarystring>;

export interface IndexParam extends BaseRouteType {
    name: string;
}

export interface AboutPageParam extends BaseRouteType {
    testName: string;
}

export interface UserPageParam extends BaseRouteType {
    userId: string;
}

export interface ParamsMap {
    [RoutePath.Index]: IndexParam;
    [RoutePath.About]: AboutPageParam;
    [RoutePath.User]: UserPageParam;
}


export class RouterHelper {
    public static replaceT extends RoutePath>(routePath: T, params: ParamsMap[T]) {
        Router.replace({
            path: routePath,
            query: params,
        });
    }

    public static pushT extends RoutePath>(routePath: T, params: ParamsMap[T]) {
        Router.push({
            path: routePath,
            query: params,
        });
    }
}

8. 实现一个基于 ts 和事件模式的 countdown 基础类

import { EventEmitter } from 'eventemitter3';

export interface RemainTimeData {
    
    days: number;
    
    hours: number;
    
    minutes: number;
    
    seconds: number;
    
    count: number;
}

export type CountdownCallback = (remainTimeData: RemainTimeData, remainTime: number) => void;

enum CountdownStatus {
    running,
    paused,
    stoped,
}

interface User {
    name: string
    age: number
}
function testT extends User>(params: T): T{
    return params
}
test({name:'1',age:1});

export enum CountdownEventName {
    START = 'start',
    STOP = 'stop',
    RUNNING = 'running',
}
interface CountdownEventMap {
    [CountdownEventName.START]: [];
    [CountdownEventName.STOP]: [];
    [CountdownEventName.RUNNING]: [RemainTimeData, number];
}

export function fillZero(num: number) {
    return `0${num}`.slice(-2);
}

export class Countdown extends EventEmitterCountdownEventMap> {
    private static COUNT_IN_MILLISECOND: number = 1 * 100;
    private static SECOND_IN_MILLISECOND: number = 10 * Countdown.COUNT_IN_MILLISECOND;
    private static MINUTE_IN_MILLISECOND: number = 60 * Countdown.SECOND_IN_MILLISECOND;
    private static HOUR_IN_MILLISECOND: number = 60 * Countdown.MINUTE_IN_MILLISECOND;
    private static DAY_IN_MILLISECOND: number = 24 * Countdown.HOUR_IN_MILLISECOND;

    private endTime: number;
    private remainTime: number = 0;
    private status: CountdownStatus = CountdownStatus.stoped;
    private step: number;

    constructor(endTime: number, step: number = 1e3) {
        super();

        this.endTime = endTime;
        this.step = step;

        this.start();
    }

    public start() {
        this.emit(CountdownEventName.START);

        this.status = CountdownStatus.running;
        this.countdown();
    }

    public stop() {
        this.emit(CountdownEventName.STOP);

        this.status = CountdownStatus.stoped;
    }

    private countdown() {
        if (this.status !== CountdownStatus.running) {
            return;
        }

        this.remainTime = Math.max(this.endTime - Date.now(), 0);

        this.emit(CountdownEventName.RUNNING, this.parseRemainTime(this.remainTime), this.remainTime);

        if (this.remainTime > 0) {
            setTimeout(() => this.countdown(), this.step);
        } else {
            this.stop();
        }
    }

    private parseRemainTime(remainTime: number): RemainTimeData {
        let time = remainTime;

        const days = Math.floor(time / Countdown.DAY_IN_MILLISECOND);
        time = time % Countdown.DAY_IN_MILLISECOND;

        const hours = Math.floor(time / Countdown.HOUR_IN_MILLISECOND);
        time = time % Countdown.HOUR_IN_MILLISECOND;

        const minutes = Math.floor(time / Countdown.MINUTE_IN_MILLISECOND);
        time = time % Countdown.MINUTE_IN_MILLISECOND;

        const seconds = Math.floor(time / Countdown.SECOND_IN_MILLISECOND);
        time = time % Countdown.SECOND_IN_MILLISECOND;

        const count = Math.floor(time / Countdown.COUNT_IN_MILLISECOND);

        return {
            days,
            hours,
            minutes,
            seconds,
            count,
        };
    }
}

使用

template>
  div class="home" @click="toAboutPage">
    img alt="Vue logo" src="../assets/logo.png">
    HelloWorld msg="Welcome to Your Vue.js + TypeScript App"/>
    div>
        倒计时:{{timeDisplay}}
    div>
  div>
template>

script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import HelloWorld from '../components/HelloWorld.vue';
import { RouterHelper } from '../lib/routerHelper';
import { Countdown, CountdownEventName, fillZero } from '../lib/countdown';
import { RoutePath } from '../router';
import { measure } from '../decorator';

@Component({
  components: {
    HelloWorld,
  },
})
export default class Home extends Vue {
    public timeDisplay: string = '';

    @measure
    public toAboutPage() {
        RouterHelper.push(RoutePath.About, {  testName: '1111' });
    }

    public created() {
        const countdown = new Countdown(Date.now() + 60 * 60 * 1000, 10);
        countdown.on(CountdownEventName.RUNNING, (remainTimeData) => {
            const { hours, minutes, seconds, count} = remainTimeData;
            this.timeDisplay = [hours, minutes, seconds, count].map(fillZero).join(':');
        });
    }
}
script>

9. npm install 一个模块,都发生了什么

npm install 命令输入 > 检查 node_modules 目录下是否存在指定的依赖 > 如果已经存在则不必重新安装 > 若不存在,继续下面的步骤 > 向 registry(本地电脑的.npmrc 文件里有对应的配置地址)查询模块压缩包的网址 > 下载压缩包,存放到根目录里的.npm 目录里 > 解压压缩包到当前项目的 node_modules 目录中。

10. eventemitter3

TypeScript 编译器提供了两个发射器:

emitter.ts: 它是 TS -> JavaScript 的发射器
declarationEmitter.ts: 用于为 TypeScript 源文件(.ts)创建声明文件

原文地址: TypeScript 学习篇 - 类型介绍使用、ts 相关面试题

    正文完
     0
    Yojack
    版权声明:本篇文章由 Yojack 于2024-10-27发表,共计14473字。
    转载说明:
    1 本网站名称:优杰开发笔记
    2 本站永久网址:https://yojack.cn
    3 本网站的文章部分内容可能来源于网络,仅供大家学习与参考,如有侵权,请联系站长进行删除处理。
    4 本站一切资源不代表本站立场,并不代表本站赞同其观点和对其真实性负责。
    5 本站所有内容均可转载及分享, 但请注明出处
    6 我们始终尊重原创作者的版权,所有文章在发布时,均尽可能注明出处与作者。
    7 站长邮箱:laylwenl@gmail.com
    评论(没有评论)