• TypeScript 基础入门


    1、TypeScript 基础

    • 本文概述
      • TypeScript 是什么
        • https://www.tslang.cn/
        • www.typescriptlang.org
      • 变量声明
        • var
        • let
        • const
      • 基本数据类型
        • 布尔值 boolean
        • 数字 number
        • 字符串 string
        • 数组 number[] 或者 Array<number>
        • 元祖 [number, string]
        • 对象 object ,了解即可
        • 任意类型 any
        • 函数空返回值 void
        • nullundefined
      • 接口
      • 解构赋值
        • 数组解构
        • 对象解构
      • 展开操作符
        • 展开数组
        • 展开对象
        • 基本语法
        • 构造函数
        • 继承
        • 属性修饰符
        • 属性的 get 和 set
      • 函数
        • 参数
        • 箭头函数
      • for-of 循环
      • 模块
        • 导出
        • 导入

    TypeScript介绍

    • 概念

      • TypeScript 是 JavaScript 的强类型版本。
      • 然后在编译期去掉类型和特有语法,生成纯粹的 JavaScript 代码。
      • 由于最终在浏览器中运行的仍然是 JavaScript,所以 TypeScript 并不依赖于浏览器的支持,也并不会带来兼容性问题。
      • TypeScript 是 JavaScript 的超集,这意味着他支持所有的 JavaScript 语法。
      • 并在此之上对 JavaScript 添加了一些扩展,如 class / interface / module 等。
      • 和 JavaScript 弱类型不同,TypeScript 这种强类型语言最大的优势在于静态类型检查,可以在代码开发阶段就预知一些低级错误的发生。
      • 一种类似于 JavaScript 的语言,在 JavaScript 的基础之上增加了类型,同时增强了 JavaScript 部分语法功能
      • 由微软开发,遵循 EcmaScript 6 标准规范
      • Angular 2 框架采用 TypeScript 编写
      • 背后有微软和谷歌两大公司的支持
      • TypeScript 可以编译成 JavaScript 从而在支持 JavaScript 的环境中运行
      • TypeScript 和 JavaScript 的关系就好比 less 和 css 的关系
    • 静态类型和动态类型

      • 静态(static):无需运行,根据程序代码就能确定结果。
      • 动态(dynamic):只有运行才能确定结果。
      • 类型:对某个数据所具有的性质进行的描述。如它的结构是怎样的,能进行什么操作。
      • 静态类型:数据拥有类型,且仅有数据拥有类型。
      • 动态类型:数据拥有类型,存放数据的变量、表达式也拥有类型,且类型在编译时是固定的。
      • TypeScript提供了静态语言强类型支持,同时兼容动态语言弱类型的语法,使用者根据项目需求自由选择。
      • 这种动静结合的特性,目前还没在其他语言见过。
      • 凡是可以写 JavaScript 的都可以使用 TypeScript。
    • 前置知识

      • EcmaScript 6
      • TypeScript 概念及关系
      • 具有一定的 JavaScript 开发经验
      • 有 Java、C#、C++、C 等静态类型语言使用经验更佳

    2、TypeScript 起步

    搭建 TypeScript 开发环境

    • 开发环境

      • 什么是 compiler?
      • less 编译器:less
      • EcmaScript 6 编译器:babel
      • TypeScript 编译器:typescript
      • 一句话:把 TypeScript 转换为 JavaScript ,浏览器就可以运行了
      • 在线测试编译环境 compiler https://www.typescriptlang.org/play/index.html
      • 本地开发编译环境
    • 安装TypeScript编译器

      # 安装编译器
      npm i -g typescript
    
      # 查看版本号
      tsc --version
    
      # 查看使用帮助
      tsc --help
    
      # 编译TypeScript文件
      tsc greeter.ts
    

    TypeScript 的同步编译

    • vscode配置ts自动转换成js文件
      • 启动 VS Code
      • 右击项目文件夹,点击在集成终端中打开
      • 运行指令 tsc --init
      • 项目文件夹中自动生成 tsconfig.json 配置文件
      • 在配置文件中,修改转成js的输出路径
      • vscode配置任务,运行任务
        • 点击终端运行任务
        • 选择tsc监视(watch)

    TypeScript 实例

      function greeter(person: string) {
          return "Hello, " + person;
      }
      let user = [0, 1, 2];
      document.body.innerHTML = greeter(user);
    
      // 编译后报错
      error TS2345: Argument of type 'number[]' is not assignable to parameter of type 'string'.
    
      let user: {
          name: string,
          age: number
      } = {
          name: 'Jack',
          age: 18
      }
      console.log(user.name+'  '+ user.age);
    
      let user = {
        name: 'Jake',
        age: 18
      }
      // 字符串也支持模板字符串
      let str:string = `
        大家好,我叫:$(user.name)
        我今年$(user.age)岁了
      `
    

    接口(Interface)

      interface Person {
          firstName: string;
          lastName: string;
      }
      function greeter(person: Person) {
          return "Hello, " + person.firstName + " " + person.lastName;
      }
      let user = { firstName: "Jane", lastName: "User" };
      document.body.innerHTML = greeter(user);
    

    类(Class)

      class Student {
          fullName: string;
          constructor(public firstName: string, public middleInitial: string, public lastName: string) {
              this.fullName = firstName + " " + middleInitial + " " + lastName;
          }
      }
      interface Person {
          firstName: string;
          lastName: string;
      }
      function greeter(person : Person) {
          return "Hello, " + person.firstName + " " + person.lastName;
      }
      let user = new Student("Jane", "M.", "User");
      document.body.innerHTML = greeter(user);
    

    3、变量声明

    • var

      • 作用域:全局作用域,局部作用域
      • 声明:可以重复声明
    • let

      • 作用域:块级作用域
      • 声明:在同一个块级作用域中不能重复声明
    • const

      • 作用域:块级作用域
      • 声明:声明同时必须赋值,
        • 声明不可改变,对象可以修改
    • let vs const

      • 最小特权原则:用最准确的词语来描述,引用不变就const,否则let,var可以不用了!
      • 使用最小特权原则,所有变量除了你计划去修改的都应该使用const
      • 基本原则就是如果一个变量不需要对它写入,那么其它使用这些代码的人也不能够写入它们,并且要思考为什么会需要对这些变量重新赋值。
      • 使用 const也可以让我们更容易的推测数据的流动。

    4、基本数据类型

    • 布尔值

      • let isDone: boolean = false;
    • 数字

      • let amount: number = 6;
    • 字符串

      • 和 JavaScript 一样,可以使用双引号,也可以使用单引号,推荐单引号
      • 可以使用模板字符串(换行 + 嵌入表达式)
    let nickname: string = `Gene`;
    let age: number = 37;
    let sentence: string = `Hello, my nickname is ${ nickname }.
    
    I'll be ${ age + 1 } years old next month.`;
    
    • 数组
      • TypeScript像JavaScript一样可以操作数组元素。
      • 第一种,可以在元素类型后面接上[],表示由此类型元素组成的一个数组
        • let list: number[] = [1, 2, 3];`
      • 第二种方式是使用数组泛型,Array<元素类型>
        • let list: Array<number> = [1, 2, 3];
    var arr:Array<number> = [1,2,3];
    arr.push(4)
    console.log(arr.length);
    
    • 元组
      • 元组类型允许表示一个已知元素数量和类型的数组,各元素的类型不必相同。
      • 比如,你可以定义一对值分别为stringnumber类型的元组。
    // Declare a tuple type
    let x: [string, number];
    // Initialize it
    x = ['hello', 10]; // OK
    // Initialize it incorrectly
    x = [10, 'hello']; // Error
    
    • 对象
      • 允许赋任意值
      • 但是不能调用任意方法,即便它真的有
      • object 用的很少,知道即可,没有类型校验和语法提示
      var user: {
          name: string,
          age: number
      } = {
          name: 'Jack',
          age: 18
      }
      console.log(user.age);
    
      // 接口
      interface Person {
          name: string,
          age: number
      }
      var user:Person= {
          name: 'shine',
          age: 18
      }
    
    • Any
      • 不希望类型检查器对这些值进行检查而是直接让它们通过编译阶段的检查
      • 可以使用 any类型来标记这些变量
      var num: any = '10';
      var ret: string = (num as string).substr(1);
    
    • Void
      • void类型像是与any类型相反,它表示没有任何类型
      • 当一个函数没有返回值时,你通常会见到其返回值类型是 void
      // 函数
      var add = function (x: number, y: number): number{
          return x+ y;
      }
      var res:number = add(2, 5);
      console.log(add(1, 2));
      // 没有返回值的函数,void只能用于函数的返回值
      var fn = function():void{
          console.log('hello');
      }
    
    • NullUndefined
      • void相似,它们的本身的类型用处不是很大
      • 默认情况下nullundefined是所有类型的子类型。
      • 就是说你可以把 nullundefined赋值给number类型的变量。
      • 当你指定了--strictNullChecks 标记,nullundefined 只能赋值给 void 和它们各自
      • 许在某处你想传入一个 stringnullundefined,你可以使用联合类型string | null | undefined
      • 我们推荐尽可能地使用--strictNullChecks ,因为它使你的代码更严谨,可以极大的减少出错的几率。
      // 正常编译会通过,但是不严谨
      function fn(): number {
          return null;
      }
      // 编译时,键入编译指令`tsc test.ts --strictNullChecks`
      // 会严格检查,致使此条语句编译不通过
    
    • 类型推断
      • 类型断言好比其它语言里的类型转换,但是不进行特殊的数据检查和解构。
      • 它没有运行时的影响,只是在编译阶段起作用。
      • TypeScript会假设你,程序员,已经进行了必须的检查。
      • 类型断言有两种形式。 其一是“尖括号”语法
      • 另一个为as语法,在TypeScript里使用JSX时,只有 as语法断言是被允许的
      // “尖括号”语法
      let someValue: any = "this is a string";
      let strLength: number = (<string>someValue).length;
    
      // `as`语法
      let someValue: any = "this is a string";
      let strLength: number = (someValue as string).length;
    
    • 其它
      • ReadonlyArray<T> 去除了数组的所有可变方法,确保数组创建后再也不能被修改

    5、解构赋值

    • 数组解构赋值
      • 按数组顺序解构
      let num: number[]= [1, 2, 3];
      let [num1, num2, num3]= num;
    
    • 对象解构赋值
      • 按键值名解构
      • name与window对象重名,所以需要对name重命名
      let user: any= {
        name: 'jack',
        age: 18
      }
      let {name: username, age}= user;
    
    // 就像数组解构,你可以用没有声明的赋值:
    let a: number, b: number;
    ({a, b} = {a: 123, b: 456})
    console.log(a, b) // 123 456
    
    // 在对象里使用 `...` 语法创建剩余变量
    let { a, ...passthrough } = o;
    let total = passthrough.b + passthrough.c.length;
    
    • 函数参数解构赋值
    function add0([x, y]: any): number{
        return x+ y;
    }
    function add1([x, y]: number[]): number{
        return x+ y;
    }
    function add2(x: number, y:number): number{
        return x+ y;
    }
    function add3([x, y]: [number, number]): number{
        return x+ y;
    }
    function add4(arr: number[]): number{
        let ret= 0;
        for(let key in arr){
            ret+= arr[key];
        }
        return ret
    }
    function add5(...args: number[]): number{
        let ret= 0;
        for(let key in arguments){
            ret+= arguments[key];
        }
        return ret;
    }
    function add6(...args: number[]): number{
        let ret= 0;
        args.forEach(function(item){
            ret+= item;
        });
        return ret;
    }
    function add7(x: number, ...args: number[]): number{
        let ret= x;
        args.forEach(function(item){
            ret+= item;
        });
        return ret;
    }
    let ret= add6(35, 15, 15, 35);
    console.log(ret);
    
    • 解构剩余参数
      let [first, ...rest] = [1, 2, 3, 4]
      console.log(first) // 1
      console.log(rest) // [2, 3, 4]
    
    • 忽略其它参数
      let [first] = [1, 2, 3, 4];
      console.log(first); // outputs 1
    
    • 跳过解构
      let [, second, , fourth] = [1, 2, 3, 4];
    
    • 属性解构重命名
      • 可以给属性以不同的名字
      • 这里的冒号不是指示类型的。 如果你想指定它的类型, 仍然需要在其后写上完整的模式。
    let { a: newName1, b: newName2 } = o;
    // 或者 完整版
    let {a, b}: {a: string, b: number} = o;
    

    展开操作符

    • 展开数组
    let arr1= [1, 2, 3];
    let arr2= [4, 5, 6];
    let arr3=[...arr1, ...arr2];
    let arr4= arr1.concat(arr2);
    console.log(arr3);
    console.log(arr4);
    
    • 展开对象
      • 不会展开方法
    // 若是用JavaScript实现,则需要浅拷贝
    let obj1= {
        foo: 'bar'
    };
    let obj2= {
        name: 'Sunny'
    };
    let obj3= {
        ...obj1,
        ...obj2
    }
    console.log(obj3);
    
    • 解构赋值用于函数声明
    type C = {a: string, b?: number}
    function f ({a, b}: C): void {
      // ...
    }
    

    6、接口

    • 概念

      • TypeScript的核心原则之一是对值所具有的结构进行类型检查。
      • 它有时被称做“鸭式辨型法”或“结构性子类型化”。
      • 在TypeScript里,接口的作用就是为这些类型命名和为你的代码或第三方代码定义契约。
    • 普通写法

      function printLabel(labelledObj: { label: string }) {
        console.log(labelledObj.label);
      }
      let myObj = { size: 10, label: "Size 10 Object" };
      printLabel(myObj);
    
    • 接口写法
      interface LabelledValue {
        label: string;
      }
      function printLabel(labelledObj: LabelledValue) {
        console.log(labelledObj.label);
      }
      let myObj = {size: 10, label: "Size 10 Object"};
      printLabel(myObj);
    
    • 可选属性
      • 接口里的属性不全都是必需的。
      • 有些是只在某些条件下存在,或者根本不存在。
      • 可选属性在应用“option bags”模式时很常用,即给函数传入的参数对象中只有部分属性赋值了。
    interface SquareConfig {
        color?: string,
        width?: number
    }
    function createSquare(config: SquareConfig): {color: string; area: number} {
        let newSquare = {color: "white", area: 100};
        if (config.color) {
            newSquare.color = config.color;
        }
        if (config.width) {
            newSquare.area = config.width * config.width;
        }
        return newSquare;
    }
      
      let mySquare = createSquare({color: "black", 2});
      let yourSquare = createSquare({color: "black"});
      console.log(mySquare);
      console.log(yourSquare);
    
    • 只读属性
      • 一些对象属性只能在对象刚刚创建的时候修改其值。
      • 可以在属性名前用 readonly来指定只读属性
    interface Point {
        readonly x: number;
        readonly y: number;
    }
    let p1: Point = { x: 10, y: 20 };
    p1.x = 5; // error!
    
    • readonly vs const
      • 常量使用 const
      • 对象属性使用 readonly

    7、类

    • 基本示例
      • 实际上,类就是构造函数,在类中,先定义属性的数据类型
      • 然后在constructor中定义数据形参,将形参值赋予类的属性
      • 而类中的方法相当于构造函数的原型
    class Person {
        name: string;
        age: number;
        constructor(name: string, age: number) {
            this.name = name;
            this.age = age;
        }
        sayHello() {
            console.log(this.name + ',happy');
        }
    }
    let userName: Person = new Person('sunny', 18);
    userName.sayHello();
    console.log(userName);
    
    class Person {
        name: string;
        age: number;
        constructor(name: string, age: number){
            this.name = name;
            this.age = age;
        }
        eat(){
            console.log(this.name + ',吃饭啊!我今年'+this.age)
        }
    }
    class Student extends Person {
        constructor(name: string, age: number){
            // super()就是父类构造函数
            super(name, age);
        }
    }
    let data: Student = new Student('sunny', 18);
    data.eat();
    

    构造函数

    • 继承
      • 类从基类中继承了属性和方法
      • Dog是一个派生类,它派生自Animal基类,通过 extends关键字
      • 派生类通常被称作子类,基类通常被称作超类
    class Animal {
        move(distanceInMeters: number = 0) {
            console.log(`Animal moved ${distanceInMeters}m.`);
        }
    }
    class Dog extends Animal {
        bark() {
            console.log('Woof! Woof!');
        }
    }
    const dog = new Dog();
    dog.bark();
    dog.move(10);
    dog.bark();
    
    • 派生类
      • 派生类包含了一个构造函数,它必须调用 super()
      • 它会执行基类的构造函数
      • 在构造函数里访问this的属性之前,我们一定要调用super()
      • 这个是TypeScript强制执行的一条重要规则
      • 也就是说,只有通过super()函数,才能传参,在继承类中,super()相当于基类的实例
    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);
        }
    }
    class Horse extends Animal {
        constructor(name: string) { 
            super(name); 
        }
        move(distanceInMeters = 45) {
            console.log("Galloping...");
            super.move(distanceInMeters);
        }
    }
    let sam = new Snake("Sammy the Python");
    let tom: Animal = new Horse("Tommy the Palomino");
    sam.move();
    tom.move(34);
    

    访问修饰符

    • 实例成员访问修饰符

      • public开放的
      • private私有的
      • protected受保护的
    • public开放的

      • 默认为 public
    class Animal {
        public name: string;
        public constructor(theName: string) { 
          this.name = theName; 
        }
        public move(distanceInMeters: number) {
            console.log(`${this.name} moved ${distanceInMeters}m.`);
        }
    }
    
    • private私有的
      • 不能被外部访问,只能在类的内部访问使用
      • 私有成员不会被继承
    class Person {
        public name: string;
        public age: number;
        // 我们可以在声明类成员的同时为其赋值
        private type: string = '人类';
        constructor(name: string, age: number, type: string){
            this.name = name;
            this.age = age;
            this.type = type;
        }
        getType(){
            // 属性“type”为私有属性,只能在类“Person”中访问
            // 但是在公开的方法中可以返回私有成员
            // 在类的内部访问私有成员
            // 但是在外部无法访问
            return this.type;
        }
    }
    let sunny = new Person('Jerry', 18, '愚蠢的人类');
    let data1 = sunny.getType();
    console.log(data1);
    // 正常访问
    console.log(sunny.name);
    //属性“type”为私有属性,只能在类“Person”中访问
    console.log(sunny.type);
    
    class Tom extends Person {
        constructor(name: string, age: number, type: string){
            super(name, age, type);
        }
        move(){
            return super.getType();
        }
    }
    let tom = new Tom('Tom', 28, '可笑的人类');
    let data2 = tom.move();
    console.log(data2);
    // 正常访问
    console.log(tom.name);
    //属性“type”为私有属性,只能在类“Person”中访问
    console.log(tom.type); 
    
    • protected受保护的
      • private 类似,但是可以被继承
    class Person {
        public name: string;
        protected typePro: string = '人类';
        private typePri: string = '人类';
        constructor(name: string, typePro: string, typePri: string){
            this.name = name;
            this.typePro = typePro;
            this.typePri = typePri;
        }
    }
    class Tom extends Person {
        constructor(name: string, typePro: string, typePri: string){
            super(name, typePro, typePri);
        }
        move(){
            // 保护属性的继承
            let typePro = this.typePro;
            // 私有属性的继承
            // 属性“typePri”为私有属性,只能在类“Person”中访问
            let typePri = this.typePri;
            return typePro + typePri;
        }
    }
    let tom = new Tom('Tom', '愚蠢的人类', '可笑的人类');
    let data = tom.move();
    console.log(data);
    console.log(tom.name);
    // 属性“typePro”受保护,只能在类“Person”及其子类中访问
    console.log(tom.typePro);
    // 属性“typePri”为私有属性,只能在类“Person”中访问
    console.log(tom.typePri);
    
    • readonly 只读的
      • 属性值不允许被修改
    class Person {
        readonly name: string = '神经病';
        constructor(name: string){
            this.name = name;
        }
        changeName(){
            // 无法分配到 "name" ,因为它是只读属性
            this.name = '大天才';
        }
    }
    class Tom extends Person {
        constructor(name: string){
            super(name);
        }
    }
    let tom = new Tom('Tom');
    console.log(tom.name);
    
    • 在参数中使用修饰符
    // 正常写法
    class Person {
        name: string;
        age: number;
      constructor(name: string, age: number) {
          this.name = name;
          this.age = age;
      }
    }
    let sunny = new Person('sunny', 28);
    console.log(sunny);
    
    // 简写
    class Person {
      constructor(public name: string, public age: number) {}
    }
    let sunny = new Person('sunny', 28);
    console.log(sunny);
    

    属性的存(get)取(set)器

    • 属性的存(get)取(set)器
      • 对属性值的校验
    class Person {
        constructor(private _age: number){};
        get age(){
            return this._age;
        }
        set age(val){
            if(val > 30){
                throw new Error('年龄偏大呀!');
            }
            this._age = val;
        }
    }
    let sunny = new Person(15);
    try {
        sunny.age = 183;
        console.log(sunny.age);
    }catch{
        console.log('数据输入错误');
    }
    

    静态成员

    • 概念
      • 实例成员:只能通过new出来的实例访问
      • 静态成员:也叫类本身的成员,只能通过类本身访问
      • 不需要实例化访问的成员称之为静态成员,即只能被类访问的成员
      • static 关键字
    class Person {
        static type: string = '神经病';
        name: string = 'Tom';
        age: number = 19;
        static say(){
            console.log('我是神经病!');
        }
    }
    // 静态成员
    console.log(Person.type);
    Person.say();
    // 实例成员
    let Jerry = new Person();
    console.log(Jerry.name);
    
    class Grid {
        static origin = {x: 0, y: 0};
        calculateDistanceFromOrigin(point: {x: number; y: number;}) {
            let xDist = (point.x - Grid.origin.x);
            let yDist = (point.y - Grid.origin.y);
            return Math.sqrt(xDist * xDist + yDist * yDist) / this.scale;
        }
        constructor (public scale: number) { }
    }
    let grid1 = new Grid(1.0);  // 1x scale
    let grid2 = new Grid(5.0);  // 5x scale
    console.log(grid1.calculateDistanceFromOrigin({x: 10, y: 10}));
    console.log(grid2.calculateDistanceFromOrigin({x: 10, y: 10}));
    

    函数

    函数参数

    • 参数及返回值类型
    function add(x: number, y: number): number {
        return x + y
    }
    
    • 可选参数
    function add(x: number, y?: number): number {
        return x + 10
    }
    
    • 默认参数
    function add(x: number, y: number = 20): number {
        return x + y
    }
    
    • 剩余参数
    function sum(...args: number[]): number {
        let ret: number = 0
        args.forEach((item: number): void => {
            ret += item
        })
        return ret
    }
    sum(1, 2, 3)
    
    • 箭头函数
      • 自动绑定this
    let add = (x: number, y: number): number => x + y
    
    • for-of循环
      • for循环
      • forEach循环(不支持break)
      • for...in...(会把数组当作对象来遍历)
      • for...of...(支持break)
    let arr: number[] = [1,2,3,4];
    
    for(let key in arr){
        console.log(arr[key]);
    }
    console.log('+++++++++++++');
    for(let i=0; i<arr.length; i++){
        console.log(arr[i]);
    }
    console.log('+++++++++++++');
    arr.forEach(function(item){
        console.log(item);
    })
    console.log('+++++++++++++');
    for(let val of arr){
        if(val == 3) break;
        console.log(val);
    }
    
    • for-of循环原理
    for (var _i = 0, arr_1 = arr; _i < arr_1.length; _i++) {
        var val = arr_1[_i];
        if (val == 3)
            break;
        console.log(val);
    }
    

    模块

    • 模块通信:导出
    export default xxx
    export const foo: string = 'bar';
    export const bar: string = 'foo';
    
    • 模块通信:导入
    // 加载默认成员
    import xxx from '模块标识'
    // 按需加载模块成员
    import {foo, bar} from '模块'
    
  • 相关阅读:
    《图解HTTP》读书笔记
    【译】关于vertical-align你应知道的一切
    【移动端debug-5】可恶的1px万能实现方案
    《编写高质量代码改善JavaScript程序的188个建议》读书笔记
    【移动端debug-4】iOS下setTimeout无法触发focus事件的解决方案
    一张图看懂Function和Object的关系及简述instanceof运算符
    三张图搞懂JavaScript的原型对象与原型链
    一张图看懂encodeURI、encodeURIComponent、decodeURI、decodeURIComponent的区别
    图解call、apply、bind的异同及各种实战应用演示
    centos vm 桥接 --网络配置
  • 原文地址:https://www.cnblogs.com/SharkJiao/p/13735783.html
Copyright © 2020-2023  润新知