1. 程式人生 > >Javascript學習---原型繼承

Javascript學習---原型繼承

原型

在Javascript中每一個物件都有一個隱藏的屬性--prototype,prototype的值要麼為null要麼指向一個叫做原型的物件,當我們要呼叫一個物件不存在的屬性時,Javascript會預設從物件的原型獲取該屬性,這也叫做原型繼承。


物件的prototype屬性是內建且隱藏的,這裡有多種方法去設定/獲取它,其中的一種方法是使用__proto__,例如:

let animal = {
  eats: true
};
let rabbit = {
  jumps: true
};

rabbit.__proto__ = animal;

要注意的是,__proto__和prototype是不同的,__proto__是prototype的getter和setter方法


當我們需要讀取rabbit物件中不存在的屬性時,Javascript會自動到rabbit的原型中尋找,因為我們設定了rabbit.__proto__ = animal,所以就到animal物件中尋找屬性,若animal物件中沒有該屬性,則又到animal的原型中查詢,沿著原型鏈尋找下去,例如:

let animal = {
  eats: true
};
let rabbit = {
  jumps: true
};

rabbit.__proto__ = animal; // (*)

// we can find both properties in rabbit now:
alert( rabbit.eats ); // true (**)
alert( rabbit.jumps ); // true

這裡,rabbit.eats就是從animal物件中獲取的


同理,物件方法屬性也是一樣:

let animal = {
  eats: true,
  walk() {
    alert("Animal walk");
  }
};

let rabbit = {
  jumps: true,
  __proto__: animal
};

// walk is taken from the prototype
rabbit.walk(); // Animal walk


下面是一個原型鏈的例子:

let animal = {
  eats: true,
  walk() {
    alert("Animal walk");
  }
};

let rabbit = {
  jumps: true,
  __proto__: animal
};

let longEar = {
  earLength: 10,
  __proto__: rabbit
}

// walk is taken from the prototype chain
longEar.walk(); // Animal walk
alert(longEar.jumps); // true (from rabbit)

要注意的是,原型鏈有以下兩個限制:

(1)原型鏈不能繞成一個圈,否則會出現迴圈引用的問題,編譯器就會報錯;

(2)__proto__的值要麼是null,要麼是原型物件的引用,其他的值都會被忽略;


讀寫規則

我們可以手動對原型進行讀和寫操作。

我們已經知道物件屬性有資料屬性和訪問器屬性這兩種,對於資料屬性,我們可以在該物件直接進行讀寫操作,例如:

let animal = {
  eats: true,
  walk() {
    /* this method won't be used by rabbit */
  }
};

let rabbit = {
  __proto__: animal
}

rabbit.walk = function() {
  alert("Rabbit! Bounce-bounce!");
};

rabbit.walk(); // Rabbit! Bounce-bounce!

這裡,walk()方法就直接新增在rabbit物件裡,並沒有新增在rabbit的原型裡


對於訪問器屬性也是一樣:

let user = {
  name: "John",
  surname: "Smith",

  set fullName(value) {
    [this.name, this.surname] = value.split(" ");
  },

  get fullName() {
    return `${this.name} ${this.surname}`;
  }
};

let admin = {
  __proto__: user,
  isAdmin: true
};

alert(admin.fullName); // John Smith (*)

// setter triggers!
admin.fullName = "Alice Cooper"; // (**)

admin由於沒有getter和setter方法,故它會在原型鏈進行尋找,這裡admin呼叫的是user的getter和setter方法


this的值

原型繼承還有一個問題就是,this所指的物件到底是哪一個?是admin?還是user?

問題的答案很簡單,this跟原型繼承沒有關係,只要是誰呼叫屬性方法,this的值就指向誰。也就是說點號“.”前的物件是哪個,this就指向哪個。例如:

// animal has methods
let animal = {
  walk() {
    if (!this.isSleeping) {
      alert(`I walk`);
    }
  },
  sleep() {
    this.isSleeping = true;
  }
};

let rabbit = {
  name: "White Rabbit",
  __proto__: animal
};

// modifies rabbit.isSleeping
rabbit.sleep();

alert(rabbit.isSleeping); // true
alert(animal.isSleeping); // undefined (no such property in the prototype)
這裡,rabbit.sleep()雖然呼叫的是animal的方法,但是點號“.”前的物件是rabbit,所以this的值為rabbit物件,isSleeping這個屬性就屬於rabbit的了