继承,继承,继承,哪里有家产可以继承

118 阅读4分钟

核心概念

如果说继承到底是怎么实现的,其实很简单-JavaScript的原型链的机制。想象一下,每一个对象之间都有一个隐形的链条🔗到了一起,我们可以顺着这个⛓️看到甚至使用另一个对象的一些东西。 但是知道这个⛓️并不能保证学会继承,我们通过⛓️连接到了一起,但是你那里的一个属性或者方法是你的呢,还是我的呢,我如果用了,你还有吗,你如果还想要怎么办,这才是理解JavaScript继承的必须要掌握的内容,通过这些继承可以分为

  1. 原型链继承
  2. 构造函数继承
  3. 组合继承
  4. 原型式继承
  5. 寄生式继承
  6. 寄生组合继承
  7. class继承

关键原理

原型链继承

将子类的原型对象prototype指向父类的一个实例

	function Parent() {
	  this.parentProperty = true;
	  this.colors = ['red', 'blue', 'green']; // 引用类型属性
	}
	Parent.prototype.getParentProperty = function() {
	  return this.parentProperty;
	};
	
	function Child() {
	  this.childProperty = false;
	}
	
	// 实现继承的关键:将 Child 的原型指向 Parent 的实例
	Child.prototype = new Parent();
	
	// 修复 constructor 指向,这一步很重要
	Child.prototype.constructor = Child;
	
	const instance1 = new Child();
	const instance2 = new Child();
	
	instance1.colors.push('black');
	
	console.log(instance1.getParentProperty()); // true
	console.log(instance1.colors); // ['red', 'blue', 'green', 'black']
	console.log(instance2.colors); // ['red', 'blue', 'green', 'black'] (注意:引用类型被共享了!)

缺点:

  • 引用类型的属性被所有实例共享
  • 无法在创建子类实例时向父类构造函数传参

逻辑图表

flowchart TD
    subgraph G1 [原型链继承结构]
        A[实例 instance]
        B[子类构造函数 SubType]
        C[子类原型对象 SubType.prototype]
        D[父类实例 parentInstance]
        E[父类原型对象 SuperType.prototype]
        F[Object.prototype]
        G[null]

        A -- __proto__ --> C
        B -- prototype --> C
        C -- __proto__ --> E
        D -- __proto__ --> E
        E -- __proto__ --> F
        F -- __proto__ --> G
    end

    subgraph G2 [属性/方法查找顺序]
        H[访问 instance.someProperty]
        I{实例自身存在?}
        J{子类原型存在?}
        K{父类原型存在?}
        L[返回属性值]
        M[返回 undefined]

        H --> I
        I -- 否 --> J
        J -- 否 --> K
        K -- 是 --> L
        I -- 是 --> L
        K -- 否 --> M
    end

构造函数继承

在子类构造函数内部调用父类构造函数(使用 callapply方法改变执行上下文)

	function Parent(name) {
	  this.name = name;
	  this.colors = ['red', 'blue', 'green'];
	}
	Parent.prototype.sayName = function() {
	  console.log(this.name);
	};
	
	function Child(name, age) {
	  Parent.call(this, name); // 在子类构造函数中调用父类构造函数,并传递参数
	  this.age = age;
	}
	
	const instance1 = new Child('Alice', 10);
	const instance2 = new Child('Bob', 12);
	
	instance1.colors.push('black');
	
	console.log(instance1.name); // 'Alice'
	console.log(instance1.colors); // ['red', 'blue', 'green', 'black']
	console.log(instance2.colors); // ['red', 'blue', 'green'] (引用类型属性是独立的)
	console.log(instance1.sayName); // undefined (无法继承父类原型上的方法)

缺点:

  • 无法继承父类原型上的方法和属性

逻辑图表

flowchart TD
    subgraph Parent构造函数
        Parent["Parent(name)"]
        ParentProto["Parent.prototype"]
        ParentMethod["sayName: function()"]
    end

    subgraph Child构造函数
        Child["Child(name, age)"]
        ChildProto["Child.prototype"]
    end

    subgraph instance1实例
        instance1["instance1: Child"]
        instance1Props["name: 'Alice'<br>age: 10<br>colors: ['red','blue','green','black']"]
    end

    subgraph instance2实例
        instance2["instance2: Child"]
        instance2Props["name: 'Bob'<br>age: 12<br>colors: ['red','blue','green']"]
    end

    Parent -.->|prototype| ParentProto
    ParentProto -.->|constructor| Parent
    ParentProto --> ParentMethod

    Child -.->|prototype| ChildProto
    ChildProto -.->|constructor| Child

    instance1 --> |__proto__| ChildProto
    instance2 --> |__proto__| ChildProto

    Child --> |new 操作符创建| instance1
    Child --> |new 操作符创建| instance2

    Child --> |Parent.callthis, name| Parent

组合继承

结合了原型链继承和构造函数继承。使用构造函数继承实例属性,使用原型链继承原型方法。

	function Parent(name) {
	  this.name = name;
	  this.colors = ['red', 'blue', 'green'];
	}
	Parent.prototype.sayName = function() {
	  console.log(this.name);
	};
	
	function Child(name, age) {
	  Parent.call(this, name); // 第二次调用 Parent,继承实例属性
	  this.age = age;
	}
	
	Child.prototype = new Parent(); // 第一次调用 Parent,继承原型方法
	Child.prototype.constructor = Child; // 修复 constructor 指向
	Child.prototype.sayAge = function() {
	  console.log(this.age);
	};
	
	const instance1 = new Child('Nicholas', 29);
	const instance2 = new Child('Greg', 27);
	
	instance1.colors.push('black');
	
	console.log(instance1.colors); // ['red', 'blue', 'green', 'black']
	instance1.sayName(); // 'Nicholas'
	instance1.sayAge(); // 29
	
	console.log(instance2.colors); // ['red', 'blue', 'green']
	instance2.sayName(); // 'Greg'

缺点:

  • 父类构造函数被调用了两次

逻辑图表

flowchart TD
    subgraph Parent构造函数
        ParentCTOR["Parent(name)"]
        ParentProto["Parent.prototype"]
        ParentMethod["sayName: function()"]
    end

    subgraph Child构造函数
        ChildCTOR["Child(name, age)"]
        ChildProto["Child.prototype"]
        ChildMethod["sayAge: function()"]
    end

    subgraph instance1实例
        instance1["instance1: Child"]
        instance1Props["name: 'Alice'<br>age: 10<br>colors: ['red','blue','green','black']"]
    end

    subgraph instance2实例
        instance2["instance2: Child"]
        instance2Props["name: 'Bob'<br>age: 12<br>colors: ['red','blue','green']"]
    end

    ParentCTOR -.->|prototype| ParentProto
    ParentProto -.->|constructor| ParentCTOR
    ParentProto --> ParentMethod

    ChildCTOR -.->|prototype| ChildProto
    ChildProto -.->|constructor| ChildCTOR
    ChildProto --> ChildMethod

    %% 关键继承关系
    ChildCTOR --> |Parent.callthis, name| ParentCTOR
    ChildProto --> |Prototype| ParentProto

    instance1 --> |__proto__| ChildProto
    instance2 --> |__proto__| ChildProto

    ChildCTOR --> |new 操作符创建| instance1
    ChildCTOR --> |new 操作符创建| instance2

原型式继承

基于一个现有对象创建一个新对象,而不必创建自定义类型。

	function object(o) {
	  function F() {}
	  F.prototype = o;
	  return new F();
	}
	
	const person = {
	  name: 'Nicholas',
	  friends: ['Shelby', 'Court', 'Van']
	};
	
	const anotherPerson = object(person);
	anotherPerson.name = 'Greg';
	anotherPerson.friends.push('Rob'); // 修改会影响到原对象
	
	const yetAnotherPerson = object(person);
	yetAnotherPerson.name = 'Linda';
	yetAnotherPerson.friends.push('Barbie'); // 修改会影响到原对象和其他实例
	
	console.log(person.friends); // ['Shelby', 'Court', 'Van', 'Rob', 'Barbie']

其实就是Object.create()

	const person = {
	  name: 'Nicholas',
	  friends: ['Shelby', 'Court', 'Van']
	};
	
	const anotherPerson = Object.create(person); // 本质上与上面的 object 函数相同
	anotherPerson.name = 'Greg';
	anotherPerson.friends.push('Rob');

缺点:

  • 与原型链继承类似,引用类型的属性值始终会被所有实例共享

逻辑图表

	flowchart TD
	    subgraph "现有对象 (parentObj)"
	        parent["parentObj"]
	        parentProps["name: 'Nicholas'<br>friends: ['Shelby', 'Court', 'Van']"]
	    end
	
	    subgraph "新对象 (childObj)"
	        child["childObj"]
	        childProps["新增的属性..."]
	    end
	
	    parent --> |Prototype| ObjectProto["Object.prototype"]
	    ObjectProto --> |Prototype| null
	
	    child --> |Prototype| parent
	    child --> |自身属性| childProps

寄生式继承

创建一个仅用于封装继承过程的函数,该函数在内部以某种方式来增强对象

	function createAnother(original) {
	  const clone = Object.create(original); // 通过调用函数创建一个新对象
	  clone.sayHi = function() { // 以某种方式来增强这个对象
	    console.log('hi');
	  };
	  return clone;
	}
	
	const person = {
	  name: 'Nicholas',
	  friends: ['Shelby', 'Court', 'Van']
	};
	
	const anotherPerson = createAnother(person);
	anotherPerson.sayHi(); // 'hi'

缺点​:与原型式继承相同,​引用类型属性被共享;同时,为对象添加函数会导致函数难以复用

逻辑图表

flowchart TD
    subgraph "原始对象 (originalObj)"
        original["originalObj"]
        originalProps["name: '李白'<br>friends: ['杜甫','陆游']"]
    end

    subgraph "寄生函数 (createEnhancedObj)"
        direction TB
        create["createEnhancedObj(original)"]
        createStep1["以 original 为原型<br>创建新对象 (clone)"]
        createStep2["增强 clone 对象<br>添加新方法/属性"]
        createStep3["返回增强后的 clone 对象"]
        
        create --> createStep1 --> createStep2 --> createStep3
    end

    subgraph "新对象 (enhancedObj)"
        enhanced["enhancedObj"]
        enhancedProps["新增的属性或方法..."]
    end

    original --作为原型--> enhanced
    createStep3 --返回--> enhanced
    enhanced --自身属性--> enhancedProps

寄生组合式继承

通过借用构造函数继承属性,但使用一种更高效的方式继承方法(不调用父类构造函数为子类原型赋值,而是直接获取父类原型的一个副本)

	function inheritPrototype(child, parent) {
	  const prototype = Object.create(parent.prototype); // 创建父类原型的副本
	  prototype.constructor = child; // 修复副本的 constructor 指向
	  child.prototype = prototype; // 将副本赋值给子类的原型
	}
	
	function Parent(name) {
	  this.name = name;
	  this.colors = ['red', 'blue', 'green'];
	}
	Parent.prototype.sayName = function() {
	  console.log(this.name);
	};
	
	function Child(name, age) {
	  Parent.call(this, name); // 只调用一次 Parent 构造函数
	  this.age = age;
	}
	
	// 实现继承:不再需要 new Parent()
	inheritPrototype(Child, Parent);
	
	Child.prototype.sayAge = function() {
	  console.log(this.age);
	};
	
	const instance = new Child('Nicholas', 29);
	instance.sayName(); // 'Nicholas'
	instance.sayAge(); // 29

这种方式只调用了一次父类构造函数,避免了在子类原型上创建不必要的属性,同时保持了原型链不变。被认为是引用类型最理想的继承范式

逻辑图表

flowchart TD
    subgraph "父类构造函数 (Parent)"
        ParentCTOR["Parent(name)"]
        ParentProto["Parent.prototype"]
        ParentMethod["sayName: function()"]
    end

    subgraph "子类构造函数 (Child)"
        ChildCTOR["Child(name, age)"]
        ChildProto["Child.prototype"]
        ChildMethod["sayAge: function()"]
    end

    subgraph "实例 (instance)"
        instance1["instance: Child"]
        instance1Props["name: 'Nicholas'<br>age: 29<br>colors: ['red','blue','green','black']"]
    end

    ParentCTOR -.->|prototype| ParentProto
    ParentProto -.->|constructor| ParentCTOR
    ParentProto --> ParentMethod

    ChildCTOR -.->|prototype| ChildProto
    ChildProto -.->|constructor| ChildCTOR
    ChildProto --> ChildMethod

    %% 关键继承关系
    ChildProto --> |Object.create<br>Prototype| ParentProto
    ChildCTOR --> |Parent.call<br>继承实例属性| ParentCTOR

    instance1 --> |__proto__| ChildProto
    ChildCTOR --> |new 操作符创建| instance1

Class继承

es6提供的语法糖,引入classextendssuper关键字

	class Parent {
	  constructor(name) {
	    this.name = name;
	    this.colors = ['red', 'blue', 'green'];
	  }
	
	  sayName() {
	    console.log(this.name);
	  }
	}
	
	class Child extends Parent { // 使用 extends 继承
	  constructor(name, age) {
	    super(name); // 必须在构造函数中首先调用 super(),它相当于 Parent.call(this, name)
	    this.age = age;
	  }
	
	  sayAge() {
	    console.log(this.age);
	  }
	}
	
	const instance1 = new Child('Tom', 18);
	const instance2 = new Child('Jerry', 20);
	
	instance1.colors.push('black');
	
	console.log(instance1.colors); // ['red', 'blue', 'green', 'black']
	instance1.sayName(); // 'Tom'
	instance1.sayAge(); // 18
	
	console.log(instance2.colors); // ['red', 'blue', 'green']
	instance2.sayName(); // 'Jerry'

如果把这段语法糖换成底层的代码,类似于寄生组合继承