• 《前端之路》- TypeScript (四) class 中各类属性、方法,抽象类、多态


    在这一章中介绍的 class 类,希望同学们可以在上一章节中 复习下构造函数、原型、原型链等基础知识

    一、TypeScript 中的类

    1、先来举个例子:

    class Persons {
      name: any;
      age: number | undefined;
      constructor(name: string, age: number) {
        this.name = name;
        this.age = age;
      }
      getName(): void {
        console.log(`${this.name}今年已经${this.age}岁了`);
      }
    }
    
    let p11 = new Persons("za", 123);
    console.log(p11.getName()); // za今年已经123岁了
    
    • 转换成 ES5 的代码后:
    var Persons = /** @class */ (function() {
      function Persons(name, age) {
        this.name = name;
        this.age = age;
      }
      Persons.prototype.getName = function() {
        console.log(
          this.name + "u4ECAu5E74u5DF2u7ECF" + this.age + "u5C81u4E86"
        );
      };
      return Persons;
    })();
    var p11 = new Persons("za", 123);
    console.log(p11.getName()); //  za今年已经123岁了
    

    2、这里和我们使用 Es6 中的 class 有一些差别

    // javascript 中 class 的定义
    class An {
      constructor(name) {
        this.name = name;
      }
      getName() {
        console.log(this.name);
      }
    }
    var a = new An("zz");
    a.getName(); // zz
    

    3、差异在于,我们需要去定义 constructor 构造函数中传入的数据参数的类型

    二、TypeScript 中类的继承

    class Animal {
      name: string | undefined;
      food: string;
      constructor(name: string, food: string) {
        this.name = name;
        this.food = food;
      }
      eat() {
        console.log(`${this.name}吃${this.food}`);
      }
    }
    
    class Cat extends Animal {
      constructor(name: string, food: string) {
        super(name, food);
      }
      jump() {
        console.log(`${this.name}正在跳`);
      }
    }
    
    let xiaohhua = new Cat("xiaohua", "猫粮");
    console.log(xiaohhua.eat()); // xiaohua吃猫粮
    console.log(xiaohhua.jump()); // xiaohua正在跳
    

    这里和 ES6 中的 class 继承内容基本上没什么出入

    三、TypeScript 中公共,私有与受保护的修饰符

    这里的修饰符是对类中对 属性和方法的类型的定义

    3-1、属性的 public

    不定义的类心的话,默认就是 public 类型

    class Animals {
      public name: string | undefined;
      constructor(name: string) {
        this.name = name;
      }
      eat() {
        console.log(`${this.name}哇`);
      }
    }
    

    转换成 es5 代码

    "use strict";
    var Animals = /** @class */ (function() {
      function Animals(name) {
        this.name = name;
      }
      Animals.prototype.eat = function() {
        console.log(this.name + "u54C7");
      };
      return Animals;
    })();
    // 和没定义之前一样
    

    3-2、属性的 private

    当成员被标记成 private 时,它就不能在声明它的类的外部访问

    class Animal2 {
      private name: string | undefined;
      constructor(name: string) {
        this.name = name;
      }
      eat() {
        console.log(`${this.name}哇`);
      }
    }
    
    var a = new Animal2("private");
    a.name = "123"; // 报错,name 属性只能在 Animal2 内部使用
    new Animal2("private").name = "432"; // 报错: 属性“name”为私有属性,只能在类“Animal2”中访问。
    

    3-3、属性的 protected

    当成员被标记成 protected 时,它就不能在声明它的类的外部访问,但是该类的子类可以访问

    class Person2 {
      protected name: string;
      constructor(name: string) {
        this.name = name;
      }
    }
    
    class exPerson extends Person2 {
      public age: number;
      constructor(age: number, name: string) {
        super(name);
        this.age = age;
        this.name = name;
      }
      public getInfo() {
        console.log(`${this.name}哈哈哈哈${this.age}`);
      }
    }
    
    let ps = new exPerson(123, "za"); // 派生类可以继承 protected 属性,但是
    
    ps.name = "zz"; // 报错 外部无法直接访问
    console.log(ps); // { name: 'za', age: 123 }
    

    构造函数也能够被 设置成 protected 属性

    class Person22 {
      protected name: string;
      protected constructor(name: string) {
        this.name = name;
      }
    }
    
    class exPerson2 extends Person2 {
      public age: number;
      constructor(age: number, name: string) {
        super(name);
        this.age = age;
        this.name = name;
      }
      public getInfo() {
        console.log(`${this.name}哈哈哈哈${this.age}`);
      }
    }
    
    let exp = new exPerson2(21, "exp-name");
    let per22 = new Person22("zs"); // 报错 类“Person22”的构造函数是受保护的,仅可在类声明中访问
    

    3-4、readonly 修饰符

    使用 readonly 关键字将属性设置为只读的。 只读属性必须在声明时或构造函数里被初始化

    class octPers {
      readonly name: string;
      readonly age: number = 8;
      constructor(name: string, age: number) {
        this.name = name;
        this.age = age;
      }
    }
    
    let ns = new octPers("zz", 123);
    console.log("---1", ns);
    ns.age = 456; // 报错 Cannot assign to 'age' because it is a read-only property.
    console.log("---2", ns); // 这里会执行什么内容呢?
    

    四、TypeScript 中 静态方法

    这里所谓的静态方法,其实就是将方法直接定义在了 构造函数对象上,只有构造函数本身才能去使用它,任何其他都无法使用(包括它的 派生类)

    class staticPerson {
      public name: string;
      public age: number = 8;
      constructor(name: string, age: number) {
        this.name = name;
        this.age = age;
      }
      static getName1() {
        console.log("---static-getName---", this);
      }
      protected getName(): void {
        console.log("---protected-getName---", this);
      }
    }
    
    let ress = new staticPerson("zzs", 123);
    console.log("---instancing getName", staticPerson.getName1()); // 属性“getName”受保护,只能在类“staticPerson”及其子类中访问。
    

    五、TypeScript 中 继承与多态

    这里面其实更多的是 JS 的继承与多态,我们以 ES5 和 ES6 分别对继承和多态进行对比

    5-1 ES5 中是如何实现 继承的?

    这里我们想想继承,到底是继承什么?如何继承?为什么要继承?

    5-1-1 通过类式继承

    类的方式,其核心在于将 子类的 prototype 指向了 父类的实例,这样的话,子类的实例的 __proto__ 指向子类的 prototype, 然而 子类的 prototype 被赋予了 父类的实例。我们制作一个简单的图,来说明一下这里如何实现的继承。

    var SuperClass = function(name) {
    	var id = 1;
    	this.name = name;
    	this.work = function() {
    		console.log(this.name + 'in SuperClass');
    	};
    };
    SuperClass.prototype.getSuperName = function() {
    	return this.name;
    };
    
    var SubClass = function() {
    	this.getSubName = function() {
    		console.log('this is subname');
    	};
    };
    
    SubClass.prototype = new SuperClass('superClass');
    var sub = new SubClass();
    
    // 这样有缺点么? 当然有,下面我们来通过例子来说明一下
    

    这种继承的方式的缺点、

    var SuperClass = function(name) {
    	var id = 1;
    	this.name = name;
    	this.todo = [1, 2, 3, 4];
    	this.work = function() {
    		console.log(this.name + 'in SuperClass');
    	};
    };
    SuperClass.prototype.getSuperName = function() {
    	return this.name;
    };
    
    var SubClass = function() {
    	this.getSubName = function() {
    		console.log('this is subname');
    	};
    };
    
    SubClass.prototype = new SuperClass('superClass');
    var sub = new SubClass();
    sub.todo.push('subClass name');
    var sub2 = new SubClass();
    console.log(sub2.todo); // [ 1, 2, 3, 4, 'subClass name']
    // 这里是缺陷一,父类属性会被实例子类修改、污染
    
    console.log(sub.name); //superClass
    console.log(sub2.name); //superClass
    
    // 子类的实例只能有一个name,这很显然也是不够灵活的,这里就是缺陷二
    

    这里因为子类实例对象1,对于父类共有属性进行了修改,导致子类实例对象2 的对应属性受到了污染。那有没有什么办法可以避免这种污染呢?当然是有的,后面我们会介绍到的。

    5-1-2 通过构造函数继承
    // 声明父类
    function Animal(color) {
    	this.name = 'animal';
    	this.type = ['pig', 'cat'];
    	this.color = color;
    }
    
    // 添加原型方法
    Animal.prototype.eat = function(food) {
    	console.log(food);
    };
    
    // 声明子类
    function Dog() {
    	Animal.apply(this, arguments);
    	// 这一步的操作就是改变 Animal 方法的上下文,然后让 Dog 也具备了 父类构造函数内的属性和方法
    }
    
    var dog1 = new Dog('blue'); // dog1.color -> blue
    var dog2 = new Dog('red'); // dog2.color -> red
    
    dog1.type.push('haha');
    console.log(dog2.type); // [ 'pig', 'cat' ]
    

    我没看到 dog1 修改了继承自父类的属性 type ,但是 dog2 的 type 属性并为被影响到。原因就是我们实例化的时候,创建的实例对象的指针指向的位置是不同的,所以对应的 __proto__ 指向的是 不同的子类构造函数的 prototype。可能会比较绕口,但是本质就是 new 操作生成了2个不同的对象,各自有各自的原型属性,互不干扰。

    但是上面也有一个缺陷就是,子类没办法继承到父类原型上的方法和属性

    那聪明的前端开发者们,就想到了 集合前2者的优势,进行了 组合式继承。

    5-1-3 组合式继承
    // 声明父类
    function Animal(color) {
    	this.name = 'animal';
    	this.type = ['pig', 'cat'];
    	this.color = color;
    }
    
    // 添加原型方法
    Animal.prototype.eat = function(food) {
    	console.log(food);
    };
    
    // 声明子类
    function Dog() {
    	Animal.apply(this, arguments);
    	// 这一步的操作就是改变 Animal 方法的上下文,然后让 Dog 也具备了
    	// 父类构造函数内的属性和方法
    }
    Dog.prototype = new Animal('Animal Color');
    
    var dog1 = new Dog();
    console.log((dog1.color = 'dog1.name'));
    var dog2 = new Dog();
    
    console.log(dog2.color); // undefined
    
    这里为什么 dog2.color 是 undefined 而不是 'dog1.name' 呢?
    因为,我们子类的构造函数,已经继承了 父类的构造函数内部的属性和方法,然后,在实例我们 子类的时候,子类的实例对象就会有先从本身的对象中去寻找 color 属性。
    当找到对应属性的时候,无论是否有值,都会优先返回 实例化对象本身的属性,而不再需要从原型链中查找对应属性。
    

    5-2 ES6 中是如何实现 继承的?

    这里我们想想继承,到底是继承什么?如何继承?为什么要继承?

    5-2-1 ES6 的继承方式
    class Animal {
    	constructor(name) {
    		this.name = name;
    	}
    	eat(food) {
    		console.log(`${this.name}吃${food}`);
    	}
    }
    
    class Dog extends Animal {
    	constructor(name) {
    		super(name);
    		this.name = name;
    	}
    	run() {
    		console.log('小狗泡泡跑');
    	}
    }
    
    let dog1 = new Dog('小狗');
    let dog2 = new Dog('小花');
    console.log(dog1.name); // 小狗
    console.log(dog2.name); // 小花
    
    dog1.__proto__ === Dog.prototype	// true
    Dog.__proto__ === Animal			// true
    
    这里 Dog 的 __proto__ 指向的是 Animal 这个类
    
    因为 Animal 这个类中的 constructor 就是原来的构造函数, 其中剩下的方法、属性都是 prototype 上的公共方法与属性。是可以被子类继承
    

    六、总结

    这里全篇文章又总结了下 JS 中继承的原理以及一些我们平时可能忽略的问题,这里就相当于在 学习 ts 之前,带着大家再一起复习一下。好了,本篇文章就先到这里了。


    GitHub 地址:(欢迎 star 、欢迎推荐 : )
    《前端之路》 - TypeScript(四)class 篇

  • 相关阅读:
    209. Minimum Size Subarray Sum
    208. Implement Trie (Prefix Tree)
    207. Course Schedule
    206. Reverse Linked List
    205. Isomorphic Strings
    204. Count Primes
    203. Remove Linked List Elements
    201. Bitwise AND of Numbers Range
    199. Binary Tree Right Side View
    ArcGIS API for JavaScript 4.2学习笔记[8] 2D与3D视图同步
  • 原文地址:https://www.cnblogs.com/erbingbing/p/12578749.html
Copyright © 2020-2023  润新知