OOP - 繼承

繼承技巧

  • JavaScript 中繼承是指一個對象基於另一個對象
  • JavaScript 是利用 prototype 來實現繼承以及來管理繼承。
  • prototype 對象用途是所有實例共享的屬性。
  • 所有對象默認繼承來自 Object.prototype

構造函數竊取繼承

我利用竊取方式來實現繼承,也就是在子類構造函數來竊取父類構造函數。

  • call / apply 來調用父類構造函數,需要把 this 指向調用者。
function Person (name, age, role) {
  this.name = name;
  this.age = age;
  this.role = role;
}

Person.prototype.sayName = function () {
  console.log(`Hello, My name is ${this.name}`);
}

Person.prototype.role = function () {
  console.log(`I'm a ${this.role}`);
}

function Student (name, age, role) {
  Person.call(this, name, age, role)
}

// 使用 Object.create 會 return {} (如下)
// { constructor: Student } -> 對象.__proto__ 指向 Person.prototype
Student.prototype = Object.create(Person.prototype, {
  constructor: {
    configurable: true,
    enumerable: true,
    value: Student,
    writable: true
  }
})

const s = new Student('DecadeHew', 18, 'FrontEnd');
s.name // DecadeHew
s.sayName() // Hello, My name is DecadeHew

Object 繼承

  • Object.create(prototype, defineProperty)
  • 帶給我們最簡潔建立 prototypal inheritance
const person = {
  type: 'Human',
  walk: true
}
const me = Object.create(person, { name: { value: 'DecadeHew' } });

// me 繼承了 person 裡所有屬性,也可以說 me 享有 person 權利。
// me 也繼承了 Object.prototype

me.name // DecadeHew 自有屬性
me.type // Human

// 以下會列出 prototype 關係圖
me.__proto__ === person
me.__proto__.__proto__ === Object.prototype
me.__proto__.__proto__.__proto__ === null // 完畢

Prototype chain 繼承

  • 每個函數都有 prototype 屬性。
  • 每創建函數,就會同時創建它的 prototype 對象,prototype 對象也會自動有 constructor 屬性。
  • prototype 對象也是一個對象,它也有自己的 prototype 對象並繼承其屬性,這就是 Prototype chain。(依上面程式:me 對象繼承了 Object.prototype,享有 Object 屬性和方法 toString, keys…)
function Person (name, age, role) {
  this.name = name;
  this.age = age;
  this.role = role;
}

Person.prototype.sayName = function () {
  console.log(`Hello, My name is ${this.name}`);
}

Person.prototype.role = function () {
  console.log(`I'm a ${this.role}`);
}

function Student (name, age, role) {
  this.name = name;
  this.age = age;
  this.role = role;
}
// 為什麼我不寫 Student.prototype = Person.prototype;
// 因為 兩個構造函數的 prototype 都會指向同一個對象,
// 萬一我修改其中一個函數(Student)的 prototype,會影響另一個函數(Person)

Student.prototype = new Person();
Student.prototype.constructor = Student;
Student.prototype.role = function () {
  console.log(`I'm a ${this.role}`);
}

const me = new Student('DecadeHew, 18)
me.name // DecadeHew
me.sayName() // Hello, My name is DecadeHew

// 以下會列出 prototype 關係圖
me.__proto__ === Student.prototype
me.__proto__.__proto__ === Person.prototype
me.__proto.__proto__.proto__ === Object.prototype
me.__proto.__proto__.proto__.__proto__ === null // 完畢
Object.prototype.__proto__ === null // 完畢

Student.prototype.__proto__ === Person.prototype
Person.prototype.__proto__ === Object.prototype
Object.prototype.__proto__ === null // 完畢

// Function
Function.prototype.__proto__ === Object.prototype
Function.__proto__ === Function.prototype
Object.__proto__ === Function.prototype
Student.__proto__ === Function.prototype
Student.__proto__.__proto__ === Object.prototype
Student.__proto__.__proto__.__proto__ === null // 完畢
Object.prototype.__proto__ === null // 完畢