不要由於沒有掌聲,就放棄了最初的理想
javascript
軟件中的繼承是對真實世界繼承的一種抽象。在面嚮對象語言中,提到繼承,咱們先引出 類 這個概念。java
類 是一種自定義的數據類型,每一個類能夠包含一組數據類型和操做數據的方法。字符串、數字就是數據類型,固然數據類型有不少,咱們能夠有不一樣的組合方式,這樣組合出來的複雜數據類型,咱們就稱之爲類,一般咱們還會向裏面添加操做數據的方法。安全
面嚮對象語言中的 繼承 ,本質上就是 複製 。子類將父類複製一份,那麼父類上有的屬性、方法,在子類上也就有了,咱們就說子類 繼承 了父類。閉包
很惋惜,在Javascript中並無 類 這個概念,在這裏萬物皆對象,因此天然不存在 類複製 這一說法。那Javascript中的 繼承 是怎樣作到的呢?答案是 複製對象 和 委託關聯 。固然最好的實現方式是 委託關聯,就是利用原型鏈機制來達到所謂的 繼承 。簡單理解就是,有一個公有的對象,它上面存有屬性和操做方法,別的對象均可以去它上面拿到想拿的東西,它在這裏共享,這種模式就是Javascript中的 繼承 本質。ide
固然上面只是簡單分析,下面深刻原理。不過在此以前,由於本篇是講 繼承,咱們順便也講講對應面嚮對象語言另外兩大特徵:封裝 和 多態 在Javascript中的實現方式。仍是按 封裝、繼承、多態 的順序講吧,下面就一塊兒進入叢林探險。 函數
顧名思義,封裝 就是將數據密封起來,讓外界訪問不到,而後咱們對外只提供操做數據的方法。封裝 的目的在於隱藏內部實現,保護數據安全,避免錯誤的修改數據。
在Javascript中,咱們利用 閉包 來實現數據的 封裝 。舉例以下:測試
function person(){
let obj = {
name: 'Tom',
age: 18
};
return {
get(){
return obj.name + '====' + obj.age
},
set(name, age){
obj.name = name;
obj.age = age;
}
}
}
var p = person();
p.get(); // "Tom====18"
p.set('Bob', 20);
p.get(); // "Bob====20"
複製代碼
上面咱們沒法直接訪問obj
中的數據,對數據的訪問只能經過返回的方法來進行讀取和操做。固然閉包有閉包的好處,不過也不要濫用,要及時消除閉包。ui
這裏不會講 class、extends 這種語法糖,由於 class 的本質仍是對象(萬物皆對象),咱們只講模擬實現 繼承 的五種方式。this
function mixin( sourceObj, targetObj ) { // 混入的方法
for (var key in sourceObj) { // 只會在不存在的狀況下複製
if (!(key in targetObj)) {
targetObj[key] = sourceObj[key];
}
}
return targetObj;
}
var Animal = {
eyes: 2,
eat: function() {
console.log( "Animal love eat." );
}
};
var Cat = mixin( Animal, {
tail: 1, // 貓有1條尾巴
eat: function() {
Animal.eat.call(this);
console.log( "Cat also love eat." );
}
});
複製代碼
須要注意的就是,這裏的 複製 是 淺複製(淺拷貝),固然安全起見仍是進行 深拷貝 。spa
顯式混入模式的一種變體,它既是顯式的又是隱式的。
function Animal() {
this.eyes = 2;
}
Animal.prototype.eat = function() {
console.log( "Animal love eat." );
};
//「寄生類」Cat
function Cat() { // cat 也是一種 Animal
var cat = new Animal();
cat.tail = 1;
var animalEat = cat.eat;
cat.eat = function() {
animalEat.call( this );
console.log( "Cat also love eat." );
}
return cat;
}
var myCat = new Cat();
myCat.eat(); // Animal love eat. Cat also love eat.
複製代碼
如你所見,首先咱們 複製 了一份 Animal 父類(對象)的定義,而後 混入 Cat 子類(對象)的定義,而後再用這個複合對象構建實例。
var Animal = {
eat: function() {
this.kind = 'Animal'
}
};
Animal.eat();
Animal.kind; // "Animal"
var Cat = {
eat: function() { // 隱式把 Animal 混入 Cat
Animal.eat.call(this);
}
};
Cat.eat();
Cat.kind; // "Animal"
複製代碼
經過在方法調用中使用 Animal.eat.call(this)
,咱們把 Animal 的行爲 混入 到了 Cat 中。
function Foo(name) {
this.name = name;
}
Foo.prototype.myName = function() {
return this.name;
};
function Bar(name,label) {
Foo.call( this, name );
this.label = label;
}
Bar.prototype = Object.create( Foo.prototype );
Bar.prototype.myLabel = function() {
return this.label;
};
var a = new Bar( "a", "obj a" );
a.myName(); // "a"
a.myLabel(); // "obj a"
複製代碼
典型的「原型風格」,核心部分 Bar.prototype = Object.create( Foo.prototype )
, 即建立一個新的 Bar.prototype
對象並把它關聯到 Foo. prototype
。
var Foo = {
init: function(who) {
this.me = who;
},
identify: function() {
return "I am " + this.me;
}
};
Bar = Object.create( Foo );
Bar.speak = function() {
console.log( "Hello, " + this.identify() + "." );
};
var b1 = Object.create( Bar );
b1.init( "b1" );
var b2 = Object.create( Bar );
b2.init( "b2" );
b1.speak();
b2.speak(); // Hello, I am b1. Hello, I am b2.
複製代碼
咱們利用 [[Prototype]]
委託,實現了三個對象之間的關聯,代碼簡潔易懂。
多種狀態,指同一個實體同時具備多種形式。好比水有三種形態,液態、氣態、固態。咱們簡單理解就是子類都由父類派生,但它們的表現形式不一樣。在代碼中的具體體現就是子類方法的覆寫,你們都懂的,這裏就不作贅述了。
這裏注意兩個概念:
instanceof 運算符用於檢測構造函數的 prototype 屬性是否出如今某個實例對象的原型鏈上。
isPrototypeOf() 方法用於測試一個對象是否存在於另外一個對象的原型鏈上。
Tips: 千萬不要按字面意思理解,將instanceof
理解爲誰是誰的實例,將isPrototypeOf
理解爲誰是誰的原型。