JS类的继承(未完)

发布于:2024-12-06 ⋅ 阅读:(108) ⋅ 点赞:(0)

一.类的概念

在JavaScript中,类是创建实例对象的模板。

在ES6之前,是用构造函数来创建实例对象的。

这里面最重要的就是继承。

二.构造函数

构造函数就是ES6之前的类。

1.类创建的实例对象有两种属性,一种是实例对象自己的,一种是类上公共的。

通过 hasOwnProperty方法,返回 true就是自己的属性,false就不是自己的。

  function Animal() {
    // 类中 this 指的都是创建的实例
    this.type = '哺乳类'
  }
  // 创建 animal 这个实例对象
  let animal = new Animal()
  console.log(animal.type) // 哺乳类
  console.log(animal.hasOwnProperty('type')) // true,type是实例上自己的属性
  // 给构造函数的原型对象添加方法
  Animal.prototype.eat = function () {
    console.log('吃吃吃')
  }
  animal.eat() // 吃吃吃
  console.log(animal.hasOwnProperty('eat')) // false,可以知道这不是实例上的属性    

2.构造函数实现继承

如果我们再创建一个Tiger类。此时 Tiger类是要继承 Animal类的,而Animal类则不能被new,所以需要对Animal类进行处理。

  function Animal() {
    if (new.target === Animal) {
      throw new Error('Animal类可以被继承但是不能被new')
    }
    this.type = '哺乳类'
  }
  function Tiger() {}
  let animal = new Animal()

 

而Tiger类要继承Animal类的话,需要在自己里面去调用Animal类,并且上面说过,类里面的this指向的是实例对象,所以我们需要改变Animal类调用时候的this指向。通过call()方法。

  function Animal() {
    if (new.target === Animal) {
      throw new Error('Animal类可以被继承但是不能被new')
    }
    this.type = '哺乳类'
  }
  function Tiger() {
    // 通过call方法让Animal执行this.type的时候this指向Tiger的this,也就是Tiger创建的实例对象
    Animal.call(this) // 继承了实例上的属性
  }
  let animal = new Tiger()
  console.log(animal.type) // 哺乳类

此时还需要继承Animal类的原型上的属性,不可以像下面这么写,这样虽然可以获取Animal类原型对象的方法,但是在给Tiger类的原型添加属性的时候也会给Animal的原型上添加,这就不是继承而是两边相等了。

  Tiger.prototype = Animal.prototype

我们应该做的是,tiger实例对象找不到属性的时候,先去Tiger的原型对象上找,也就是tiger.__proto__,如果还找不到就去Animal类的原型对象上面找,所以只需要将Tiger类的原型对象的原型链__proto__指向Animal的原型对象就好了。

  Tiger.prototype.__proto__ = Animal.prototype  
  // tiger => tiger.__proto__(Tiger.prototype) => tiger.__proto__.__proto__(Tiger.prototype.__proto__)(也就是Animal.prototype)

 

所以全部应该是:

  function Animal() {
    if (new.target === Animal) {
      throw new Error('Animal类可以被继承但是不能被new')
    }
    this.type = '哺乳类'
  }
  Animal.prototype.eat = function () {
    console.log('吃吃吃')
  }
  function Tiger() {
    Animal.call(this)
  }
  let tiger = new Tiger()
  Tiger.prototype.__proto__ = Animal.prototype
  console.log(tiger.type) // 哺乳类
  tiger.eat() // 吃吃吃

 ES6 更新写法:

  Tiger.prototype.__proto__ = Animal.prototype
  // ES6 换了个更简便的方法
  Object.setPrototypeOf(Tiger.prototype, Animal.prototype)

 或者使用 Object.create() 方法,不过该方法需要在new之前调用。

  function Animal() {
    if (new.target === Animal) {
      throw new Error("Animal类可以被继承但是不能被new");
    }
    this.type = "哺乳类";
  }
  Animal.prototype.eat = function () {
    console.log("吃吃吃");
  };
  function Tiger() {
    Animal.call(this);
  }
  Tiger.prototype = Object.create(Animal.prototype)
  let tiger = new Tiger();
  console.log(tiger.type); // 哺乳类
  tiger.eat(); // 吃吃吃

关于 create() 方法我们可以手动写一下。

  function create(parentPrototype) {
    let Fn = function () {} // 定义一个构造函数 Fn
    Fn.prototype = parentPrototype // 将父类的原型对象赋值给Fn的原型对象,此时实例对象fn就可以通过__proto__找到父类的原型对象
    return new Fn() // 再将fn赋值给Tiger的原型对象,此时 tiger就可以通过__proto__找到fn,从而同fn.__proto__找到父类的原型对象
  }                 // 而且就算给 Tiger.prototype添加属性,也是添加在fn上,而不会影响到Animal.prototype

 

二.类的使用

ES6新增了类。

1.语法

  class Test {
    constructor() {}
  }
  • 使用class关键字声明类。
  • 类名一般为名词,首字母大写。
  • constructor:每个类都会有的特殊方法,是类的构造方法,用来实例化一个由class创建的对象,像上文讲构造函数的时候创建实例的代码就放在constructor里面,在new的时候,会自动执行constructor函数,若没有定义constructor()构造方法,JavaScript会自动声明一个空的constructor()方法。

2.类的属性

类的属性分为三种:实例属性、静态属性、私有属性。(还有一个是原型上的属性,其实是通过get方法去return出去的,不算在其中)

2.1 实例属性

通过实例去访问的属性(定义在实例上),在constructor方法里定义的属性就是实例属性,像上文说的,ES5构造函数创建实例的代码就放在constructor方法里面。

  function Animal() {
    this.type = "哺乳类"; // ES5 实例属性
  }
  class Animal {
    constructor() {
      this.type = "哺乳类"; // ES6 实例属性
    }
  }
  let animal = new Animal();
  console.log(animal.type); // 哺乳类

在constructor外面声明的变量,也算是实例属性。 

  class Animal {
    constructor() {
      this.type = "哺乳类"; 
      // 相当于 this.a = 1
    }
    a = 1;
  }
  let animal = new Animal();
  console.log(animal.hasOwnProperty("a")); // true

但是通过get方法去声明的变量就不是实例属性了,通过hsaOwnProperty() 方法可以知道这是声明在类的原型对象上的属性,其实说是属性,不如说是方法。

  class Animal {
    constructor() {
      this.type = "哺乳类"; // ES6 实例属性
    }
    get a() {
      return 100;
    }
  }
  let animal = new Animal();
  console.log(animal.hasOwnProperty("a")); // false
  console.log(animal.__proto__.hasOwnProperty("a")); // true

2.2 静态属性 

通过类去访问的属性(定义在类上),用static关键字声明。

  class Animal {
    constructor() {
      this.type = "哺乳类"; // ES6 实例属性
    }
    static a = 1;
  }
  let animal = new Animal();
  console.log(animal.a); // undefined
  console.log(Animal.a); // 1

也可也通过类名.的方式去声明。

  class Animal {
    constructor() {
      this.type = "哺乳类"; // ES6 实例属性
    }
  }
  Animal.a = 2;
  let animal = new Animal();
  console.log(animal.a); // undefined
  console.log(Animal.a); // 2

ES6是不支持通过关键字static声明静态属性的,ES7以后就可以,但是ES6支持声明静态方法,所以我们就可以这样。

  class Animal {
    constructor() {}
    static get a() { // ES6中的静态属性
      return 1;
    }
  }
  console.log(Animal.a); // 1
  console.log(Animal.hasOwnProperty("a")); // true

 2.3 私有属性(ES2022引入)

通过#去声明的属性,只能在类的内部访问。

  class Animal {
    // 声明一个私有属性
    #age;
    constructor(age) {
      // 使用私有属性 #age 存放年龄
      this.#age = age;
    }
    getAge() {
      return this.#age;
    }
  }
  let tiger = new Animal(5);
  console.log(tiger.getAge()); // 5
  // 尝试直接访问私有字段会导致语法错误
  // console.log(tiger.#age); // 这里会引发错误,因为#age是私有的
  •  封装性增强:私有字段有效限制了对类内部状态的直接访问,促进了良好的封装实践。
  • 安全性提升:防止外部代码无意或恶意修改类的内部数据,减少潜在的bug。

 

3.方法

类的方法分为四种:构造方法、静态方法、普通方法(也就是定义在类原型对象上的方法)、私有方法。

3.1 构造方法

constructor() 就是类的构造方法,在new的时候自动执行,如果没有定义该方法,js会自动声明一个空的constructor() 方法。

  class Animal {
    constructor(name, age) {
      // 使用'this'关键字给实例绑定属性
      // 'this'在此处指的是通过类Animal创建的实例
      this.name = name; // 绑定name属性
      this.age = age; // 绑定age属性
    }
  }
  const tiger = new Animal("tiger", 5);
  console.log(tiger.name); // 输出: tiger
  • constructor方法名不可以改变。
  • 构造函数可以接收任意数量的参数,这些参数用于初始化实例的属性。
  • 在构造方法里,this指向要创建的实例对象。
  • new的时候自动调用。

3.2 静态方法

通过类去访问的方法(定义在类上),用static关键字声明。

  class Animal {
    constructor() {}
    static age = 5;
    static getAge() {
      return this.age; // 这里的this指的是类,constructor里的this才指向创建的实例
    }
  }
  console.log(Animal.getAge());
  console.log(Animal.hasOwnProperty("getAge")); // true

3.3 普通方法

定义在constructor外面的方法,也就是原型对象上的方法,这种方法叫做普通方法,可以被实例通过__proto__去访问到。

讲构造函数的时候,给类的原型对象上添加公共方法,是通过prototype。

  Animal.prototype.eat = function () {
    console.log("吃吃吃");
  };

类也可以这样去写,但是这样感觉不像一个整体,我们可以直接把方法写constructor外面,在constructor外声明的方法,是原型对象上的方法,也就是普通方法。

  class Animal {
    constructor() {
      this.type = "哺乳类";
    }
    // 这里声明的方法都是在当前类的原型对象上
    eat() {
      console.log("吃吃吃");
    }
  }
  let animal = new Animal();
  animal.eat(); // 吃吃吃
  console.log(animal.hasOwnProperty("eat")); // false
  console.log(animal.__proto__.hasOwnProperty("eat")); // true

3.4 私有方法(ES2020引入)

通过#去声明的方法,只能在类的内部访问。

  class Calculator {
    // 创建一个私有方法
    #multiply(a, b) {
      return a * b;
    }
    calculateProduct(a, b) {
      // 类的内部可以访问私有方法
      return this.#multiply(a, b);
    }
  }
  const calc = new Calculator();
  // 通过公共方法间接调用私有方法,得到正确结果
  console.log(calc.calculateProduct(2, 3)); // 输出: 6
  // 尝试直接访问或调用私有方法会导致语法错误
  // console.log(calc.#multiply(2, 3)); // 这是不允许的,会导致编译或运行时错误


网站公告

今日签到

点亮在社区的每一天
去签到