# 类的继承

Class 可以通过extends关键字实现继承,这比 ES5 的通过修改原型链实现继承,要清晰和方便很多。不过考虑到兼容性和灵活度,很多框架依然使用prototype方式去编写。

class Point {
}
class ColorPoint extends Point {
  constructor(x, y, color) {
    super(x, y); // 调用父类的constructor(x, y)
    this.color = color;
  }

  toString() {
    return this.color + ' ' + super.toString(); // 调用父类的toString()
  }
}

上面代码中,constructor方法和toString方法之中,都出现了super关键字,它在这里表示父类的构造函数,用来新建父类的this对象。

子类必须在constructor方法中调用super方法,否则新建实例时会报错。 这是因为子类自己的this对象,必须先通过父类的构造函数完成塑造,得到与父类同样的实例属性和方法,然后再对其进行加工,加上子类自己的实例属性和方法。如果不调用super方法,子类就得不到this对象。

class Point { /* ... */ }

class ColorPoint extends Point {
  constructor() {
  }
}

let cp = new ColorPoint(); // ReferenceError
// 上面代码中,ColorPoint继承了父类Point,但是它的构造函数没有调用super方法,导致新建实例时报错。

super:当 super 作为方法使用时,有以下 6 个注意点。

  1. super()方法相当于父类的构造函数。
  2. 只有在子类的构造函数中才能调用 super()方法。
  3. 如果子类显式地定义了构造函数,那么必须调用 super()方法,否则会报错。
  4. 如果子类没有定义构造函数,那么会自动调用 super()方法。
  5. 当子类的构造函数显式地返回一个对象时,就能避免调用 super()方法。
  6. 在使用 this 之前,必须先调用 super()方法。
// 在子类 Man的构造函数中注释了 super()方法,并将其返回结果改成了一个空对象,这段代码能够正确执行
class Man extends People {
 constructor() {
 //super();
  return {};
 }
} 

super 作为对象使用时,在不同的位置,其指向将不同,具体分为两种情况,如下所列。

  1. 如果在子类的原型方法中使用 super,那么 super 指向父类的原型。
  2. 如果在子类的静态方法中使用 super,那么 super 指向父类。
class People {
 getAge() {
 return 28;
 }
 static getAge() {
 return 30;
 }
 getName() {
 return "freedom";
 }
 static getName() {
 return "strick";
 }
}
class Man extends People {
 get age() {
 return super.getAge();
 }
 static get name() {
 return super.getName();
 }
}
var man = new Man();
man.age; //28
Man.name; //"strick"

在 JavaScript 中,extends 关键字通常用于类(class)继承,但它右侧确实可以更加灵活,包括继承函数、内置对象,甚至是表达式。

function BaseFunction(name) {
	  this.name = name;
	}
	
	BaseFunction.prototype.greet = function() {
	  console.log(`Hello, my name is ${this.name}.`);
	};
	
	class DerivedFunction extends BaseFunction {
	  constructor(name, age) {
	    super(name);
	    this.age = age;
	  }
	
	  greet() {
	    super.greet();
	    console.log(`I am ${this.age} years old.`);
	  }
	}
	
	const df = new DerivedFunction('Alice', 30);
	df.greet();
	// Hello, my name is Alice.
	// I am 30 years old.

在 ES5 时代,像 Array、Error 等内置对象是不能被继承的,而 ES6 突破了这个限制,因为 ES6 的子类能通过 this 访问父类的内部属性和方法,这样就能继承内置对象的所有功能。

class List extends Array { }
var list = new List();
list.length; //0
list.push("a");
list.length; //1
class BaseClassA {
	  describe() {
	    return 'I am from BaseClassA';
	  }
	}
	
	class BaseClassB {
	  describe() {
	    return 'I am from BaseClassB';
	  }
	}
	
	function getBaseClass(condition) {
	  return condition ? BaseClassA : BaseClassB;
	}
	
	class DynamicClass extends getBaseClass(true) {
	  describe() {
	    return `${super.describe()}, and I am DynamicClass`;
	  }
	}
	
	const d = new DynamicClass();
	console.log(d.describe()); // I am from BaseClassA, and I am DynamicClass

类的这个特性还能解决无法 多重继承 的问题

在代码中先初始化两个对象:man 和 woman,然后调用 mixin()函数,将两个对象的方法合并到内部函数 middle()的原型上,最后让 Person 类继承 middle()函数,这样就能调用两个对象中的方法了。

这种将多个对象或类合并成一个,间接实现多重继承的作法叫作类的模板,也叫作抽象子类或 mixin(混合)。

function mixin(...objects) {
 function middle() {}
 Object.assign(middle.prototype, ...objects);
 return middle;
}
var man = {
 getMan() {
 return "男";
 }
};
var woman = {
 getWoman() {
 return "女";
 }
};
class Person extends mixin(man, woman) { }
var person = new Person();
person.getMan(); //"男"
person.getWoman(); //"女"

# 子类重写父类方法后调用父类方法super.xxx()

除了重写,也可以在子类中直接使用父级的方法,当然,如果在ts中,加了关键字private限制,就不能使用

class A {
    constructor(x,y){
      this.t=x+y
    }
    getName(){
        console.log(1)
    }
    lv(){
        console.log('lv')
    }
  }

  class B extends A{
      
      getName(){
          console.log(this.t)
          this.lv()
          console.log(200)
          super.getName()
      }
  }

  let b =new B(1,2,3)
  b.getName()
// 3
// lv
// 200
// 1
class A {
	
	constructor(b){
        this.b=b;
        console.log(this.b)
	}
	addnum(){
		console.log(this.a+this.b)
	}
}
class B extends A{
    constructor(f){
        super(100)
        console.log(f)
    }
}
let a=new B(4)
//100
//4

不仅可以通过构造函数时传值,也可以直接在子类中super直接赋值

# new.target

new.target属性允许你检测函数或构造方法是否是通过new运算符被调用的。在通过new运算符被初始化的函数或构造方法中,new.target返回一个指向构造方法或函数的引用。在普通的函数调用中,new.target 的值是undefined。

new.target语法由一个关键字"new",一个点,和一个属性名"target"组成。通常"new."的作用是提供属性访问的上下文,但这里"new."其实不是一个真正的对象。不过在构造方法调用中,new.target指向被new调用的构造函数,所以"new."成为了一个虚拟上下文。

# 函数调用中的 new.target

在普通的函数调用中(和作为构造函数来调用相对),new.target的值是undefined。这使得你可以检测一个函数是否是作为构造函数通过new被调用的。

function Foo() {
  if (!new.target) throw "Foo() must be called with new";
  console.log("Foo instantiated with new");
}

Foo(); // throws "Foo() must be called with new"
new Foo(); // logs "Foo instantiated with new"

# 构造方法中的 new.target

在类的构造方法中,new.target指向直接被new执行的构造函数。并且当一个父类构造方法在子类构造方法中被调用时,情况与之相同。

class A {
  constructor() {
    console.log(new.target.name);
  }
}

class B extends A { constructor() { super(); } }

var a = new A(); // logs "A"
var b = new B(); // logs "B"

class C { constructor() { console.log(new.target); } }
class D extends C { constructor() { super(); } }

var c = new C(); // logs class C{constructor(){console.log(new.target);}}
var d = new D(); // logs class D extends C{constructor(){super();}}

# Symbol.species访问器

有些内置类型的方法会返回新实例,默认情况下与原始类型实例一致。如果需要覆盖,可使用Symbol.species访问器处理。

class SuperArray extends Array {
	static get[Symbol.species](){
		return Array
	}
}

let a1 = new SuperArray(1, 2, 3, 4, 5);
let a2 = a1.filter(x => !!(x%2))

console.log(a1);  // [1, 2, 3, 4, 5]
console.log(a2);  // [1, 3, 5]
console.log(a1 instanceof SuperArray);  // true
console.log(a2 instanceof Array);  // true 
console.log(a2 instanceof SuperArray);  // false

# 抽象基类

可供其他类继承,但是本身不会被实例化。

// Abstract base class
class Vehicle {
  constructor() {
    console.log(new.target);
    if (new.target === Vehicle) {
      throw new Error('Vehicle cannot be directly instantiated');
    }
  }
}

// Derived class
class Bus extends Vehicle {}

new Bus();      // class Bus {}
new Vehicle();  // class Vehicle {}
// Error: Vehicle cannot be directly instantiated

可以检查派生类是否符合要求,通过this关键字来检查相应的方法

// Abstract base class
class Vehicle {
  constructor() {
    console.log(new.target);
    if (new.target === Vehicle) {
      throw new Error('Vehicle cannot be directly instantiated');
    }
	if(!this.foo){
		  throw new Error('error,缺少foo方法不能构造')
	}
  }
 
}

// Derived class
class Bus extends Vehicle {
	constructor(arg) {
		super(arg)
	    console.log('bus')
	}
	foo(){}
}
class Car extends Vehicle {
	constructor(arg) {
		super(arg)
	    console.log('car')
	}
}

new Bus();      // class Bus {}
try{
	new Vehicle();  // class Vehicle {}
}catch(e){
	console.log(e)
	// Vehicle cannot be directly instantiated at new Vehicle
}

new Car();      // class Bus {}
// Uncaught Error: error,缺少foo方法不能构造
//     at new Vehicle (demo.html:21)
//     at new Car (demo.html:37)
//     at demo.html:50

# 类的混淆

class Person {

}

function mixinRunner(BaseClass) {
  class NewClass extends BaseClass {
    running() {
      console.log("running~")
    }
  }
  return NewClass
}

function mixinEater(BaseClass) {
  return class extends BaseClass {
    eating() {
      console.log("eating~")
    }
  }
}

// 在JS中类只能有一个父类: 单继承
class Student extends Person {

}

var NewStudent = mixinEater(mixinRunner(Student))
var ns = new NewStudent()
ns.running()
ns.eating()
最后更新: 11/24/2024, 2:56:55 PM