3. 面相对象

同class类已经写在JS高级第一天:https://www.jianshu.com/writer#/notebooks/49678790/notes/86773079/preview

一、创建类与实例化对象

1.创建类并new一个实例对象

(1)class 类名 { };const 变量名 = new 类名()
(2)类名 首字母大写。类是 构造函数 的语法糖。方法和方法之间 不能使用逗号分隔。
(3)类中包括 \color{red}{构造函数}\color{red}{实例属性}\color{red}{实例方法}\color{red}{静态属性/类属性}\color{red}{静态方法/类方法}
(4)构造函数接收实例对象传递过来的形参,默认返回 实例对象(即this)。
(5)构造函数中的this指向 \color{red}{实例对象}。实例方法中的this指向 方法的调用者
(6)构造函数可以不写,new 实例对象时会自动生成并 调用。
(7)实例属性和实例方法通过 实例对象 调用。静态属性和静态方法通过 类本身 调用。
(8)实例属性挂载到 \color{red}{实例对象}上;实例方法挂载到 \color{red}{原型对象上}

class Person {
    // 1.构造函数
    constructor(name, age) {
        // 2.实例属性。可以通过实例对象访问。
        this.name = name;
        this.age = age;
    }
    // 3.实例方法。挂载到原型对象上。可以通过实例对象访问。
    say() {
        console.log("我是实例方法,我能说");
    }
    // 4.静态属性或类属性。可以通过类访问。
    static info = "我是静态属性";
    // 5.静态方法。可以通过类访问。
    static jump() {
        console.log("我是静态方法,我能跳");
    }
}

// 创建对象
const p = new Person("张三", 18);
// 调用访问实例属性并调用实例方法
console.log(p.name);
p.say();
// 访问静态属性,调用静态方法
console.log(Person.info);
Person.jump();
2.extends与super关键字
class Animal {
  name: string;
  age: number;
  constructor(name: string, age: number) {
    this.name = name;
    this.age = age;
  }
  sayHello(): void {
    console.log("动物叫!");
  }
}

class Dog extends Animal {
  hobby: string; // 子类特有的实例属性
  constructor(name: string, age: number, hobby: string) {
    super(name, age);
    this.hobby = hobby;
  }
  // 子类重写父类方法
  sayHello(): void {
    console.log("汪汪汪!");
  }
}

const dog = new Dog("旺财", 5, "看门");
dog.sayHello() // 汪汪汪!

(9)子类可以通过extends关键字继承父类的属性和方法。继承中,类调用方法时,先从子类中查找并调用。如果子类没有该方法,就去父类中查找并调用。(就近原则
(10)super关键字在 子类构造函数 中当 函数 使用,相当于父类的\color{red}{构造函数},其内部的this指向\color{red}{子类实例对象}

class A {
  constructor() {
    console.log(new.target.name);
  }
}
class B extends A {
  constructor() {
     // 1.这里的super相当于A的构造函数,内部this指向B的实例对象
     // 2.因此super()在这里相当于A.prototype.constructor.call(this)。
    super();
  }
}
new A() // A
new B() // B

(11)super关键字在 子类的构造函数 或者 实例方法 中当 对象 使用,它指向父类的\color{red}{原型对象},其内部的this指向\color{red}{子类实例对象}。它可以访问父类的实例方法,但不能访问父类的实例属性。(原因见第8条)

class A {
    constructor(x) {
        this.x = 1;
    }
    y() {
      return 2;
    }
}
A.prototype.z = 3;
  
class B extends A {
    constructor() {
        super();
        console.log(super.x); // undefined, B不能访问A的实例属性,因为A的实例属性挂载到A的实例对象上,而不是A的原型对象上
        console.log(super.y()); // 2, B可以访问A的实例方法,因为A的实例方法挂载到A的原型对象上
        console.log(super.z); // 3, B可以访问A原型对象上的属性
    }
}
let bb = new B();
  • 下面代码中,super.print()虽然调用的是A.prototype.print(),但是A.prototype.print()内部的this指向子类B的实例,导致输出的是2,而不是1。也就是说,实际上执行的 是super.print.call(this)
class A {
    constructor() {
      this.x = 1;
    }
    print() {
      console.log(this.x);
    }
}
  
class B extends A {
    constructor() {
      super();
      this.x = 2;
    }
    m() {
      super.print();
    }
}
  
let b = new B();
b.m() // 2

特殊情况:如果通过super对某个属性赋值,这时 \color{red}{super就是this}

  • 下面代码中,通过super对x属性赋值,这里的 super相当于子类B中的this
class A {
  constructor() {
    this.x = 1;
  }
}

class B extends A {
  constructor() {
    super();
    this.x = 2;
    super.x = 3; // 这里的super相当于this
    console.log(super.x); // undefined 这里的super相当于父类的原型对象,但是父类的x属性挂载到了父类的实例对象上,而非原型对象上
    console.log(this.x); // 3
  }
}

let b = new B();

(12)super关键字在 子类的静态方法 中当 对象 使用,它指向 \color{red}{父类},其内部的this指向\color{red}{子类}而非子类实例。
(13)子类必须先在构造函数中 调用super()方法之后才能使用this。
(14)类中的构造器函数constructor 不是必须 写的,当需要对实例对象进行初始化操作时才写,如添加属性。
(15)如果 B类继承了A类,且B类中写了构造器函数,则B类构造器函数内,必须写 super()。

类的继承可参考:https://juejin.cn/post/7000891889465425957?searchId=202401091029105CB33A9EDB5C1F69E7B2#heading-18

3.其他

(1)类中的 自定义属性,都放在了类的 实例对象 上。

class MyForm extends React.Component {
  // React的state数据简写方法就是用的这个。
  state = {input: '我是普通input表单',}
}

const form = new Form();
form.hasOwnProperty("state") // true

(2)类中的 自定义方法,都放在了类的 原型对象 上,实例对象的 __proto__ 属性也指向类的原型对象。

class MyForm extends React.Component {
  //这个方法被放到了类的原型对象上
  handler () { }
}

const form = new Form();
form.hasOwnProperty("handler")  // false
form.__proto__.hasOwnProperty("handler")  // true

(3)构造函数中的this指向实例化对象;普通方法中的this指向方法的调用者

        var that;//1.声明全局变量,用来接收类的实例化对象。
        class Star {
          constructor(x, y) {
            that = this;//2.将实例化对象赋值给全局变量that
            this.x = x;
            this.y = y;
            this.sing(); //类中调用其他方法必须加this
            this.btn = document.querySelector('button');//类中获取DOM对象要加this
            this.btn.addEventListener('click',this.sing);//类中使用DOM对象要加this
          }
          sing() {
            console.log(this);//普通方法中的this指向调用者--按钮btn
            console.log(that.x + '会唱' + that.y);//3.类中使用公共属性必须加this(全局变量that)
          }
        }
        var WuLei = new Star('吴磊', '九妹');

(4)向 类自身 添加属性和方法
① 用 static 关键字修饰属性名或方法名。
② 用 函数表达式 向类添加方法。

class MyForm extends React.Component {
  static a = 1;  // 这个属性被放到类自身上
  static fun() {};  // 这个方法被放到类自身上
  handler = function(){};  // 这个方法被放到类自身上
}

(5)类中使用公共属性或者调用类中的其他方法都必须加this

(6)Object.getPrototypeOf方法可以用来从子类上获取父类。Object.getPrototypeOf(子类),返回值是父类。

二、抽象类

1. 抽象类与抽象方法

(1)以 abstract 开头的类叫做 抽象类;在抽象类中,以 abstract 开头的方法是 抽象方法
(2)抽象类不能被 实例化,只能被当做父类继承。
(3)抽象方法不包含具体实现,子类 必须 对抽象方法进行 重写。

// 以abstract开头的类是抽象类。抽象类不能被实例化。
abstract class Job {
  name: string;
  constructor(name: string) {
    this.name = name;
  }
  // 以abstract开头的方法是抽象方法
  // 抽象类中的抽象方法不包含具体实现,子类必须对抽象方法进行重写
  abstract work(): void;
}

class Teacher extends Job {
  constructor(name: string) {
    super(name)
  }
  // 子类必须对抽象方法进行重写
  work(): void {
    console.log("老师正在教课!");
  }
}
const teacher = new Teacher("小王");
teacher.work()

(4)TypeScript中,用interface关键字定义的接口中,属性和方法都是抽象的。

三、属性的封装(TypeScript)

1. public、private、protected

(1)类中,被 public 修饰的 属性 可以在 任意位置 被访问(或修改),且类中的属性默认被public修饰。
(2)类中,被 private 修饰的 属性 只能在 当前类 的内部被访问(或修改)。
(3)类中,被 protected 修饰的 属性 只能在 当前类和子类 的内部被访问(或修改)。

class A {
  public name: string; // 公有属性,默认就是public
  private age: number; // 私有属性,只有类的内部可以访问
  constructor(name: string, age: number) {
    this.name = name;
    this.age = age;
  }
}

const a1 = new A("张三", 20);
a1.name = "李四";       // 可以访问公有属性name
// a1.age = 30;        // 报错,不能在类的外部访问私有属性age
2. 如何访问private私有属性

(1)可以在类的内部 自定义 getter方法和setter方法,来访问和修改private私有属性。

class A {
 private age: number;   // 私有属性
 constructor(age: number) {
   this.age = age;
 }
 // 获取私有属性age的值
 getAge() {
   return this.age;
 }
 // 设置私有属性age的值
 setAge(age: number) {
   if (age >= 0 && age <= 150) {
     this.age = age;
   } else {
     throw new Error("年龄不合法");
   }
 }
}

const a1 = new A(20);
a1.setAge(30);                // 通过公有方法设置私有属性age的值

(2)TypeScript中用 getset关键字来优化getter方法和setter方法。
(3)get、set关键字修饰的方法名不能与属性名重复。
(4)格式上:直接访问私有属性,而不是调用方法。

class A {  private age: number; // 私有属性
  constructor(age: number) {
    this.age = age;
  }
  // 获取私有属性age的值,名称不能与age重复
  get _age() {
    return this.age;
  }
  // 设置私有属性age的值,名称不能与age重复
  set _age(age: number) {
    if (age >= 0 && age <= 150) {
      this.age = age;
    } else {
      throw new Error("年龄不合法");
    }
  }
}

const a1 = new A(20);
a1._age = 30;            // 可以通过getter和setter访问私有属性age
3. 属性声明的简写
// 简写
class B {
  constructor(public name: string, private age: number) { }
}
// 等价于这种写法
class B {
  name: string;
  age: number;
  constructor(name: string, age: number) { 
    this.name = name;
    this.age = age;
  }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容