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的了