我來重新學習js 的面向對象(part 4)

續(xù)上一篇,隨著業(yè)務越來越大需频,要考慮一些繼承的玩意了丁眼,大千世界,各種東西我們要認識和甄別是需要靠大智慧去分門別類昭殉,生物學中把動植物按界苞七、門、綱挪丢、目蹂风、科、屬乾蓬、種進行分類的方法可能是最有代表的實例之一.........

說人話就是惠啄,我們終于要學習繼承的知識了,然后用這些知識去解決老板的問題任内。

一撵渡、繼承-原型鏈

繼承是 OOP 開發(fā)中的一個極為重要的概念,而在javascript 里面死嗦,實現繼承的方式主要依靠原型鏈來實現的趋距。

image

圖片來自:https://www.lieyuncj.com/p/3087

圖一,一環(huán)扣一環(huán)越除,形成了鏈條节腐,可以適當幫助理解原型鏈的概念,原型鏈摘盆,換言之就是原型對象構成的鏈翼雀。

image

圖片來源于:https://hackernoon.com/understand-nodejs-javascript-object-inheritance-proto-prototype-class-9bd951700b29

回顧一下,構造函數骡澈,原型和實例的關系:每個構造函數都有一個原型對象锅纺,原型對象都包含一個指向構造函數的指針掷空,而實例都包含一個指向原型對象的內部指針肋殴,當我們將原型對象等于另外一個類型的實例的時候囤锉,就會出現原型對象包含一個指向另外一個原型的指針,例如 dog原型對象 指向了 animal原型對象护锤。

繼續(xù)回到現場官地,我們做了一些分類,食物下面分了水果分類:

// 定義一個 Food 的構造函數
function Food() {
  this.type = "食物";
}
// 定義了 Food 的原型對象的一個方法 getType
Food.prototype.getType = function() {
  return this.type;
};
// 定義一個 Fruit 的構造函數
function Fruit() {
  this.type = "水果";
}
// 將 Fruit 的原型對象指向 Food 的實例
Fruit.prototype = new Food();
// 定義 Fruit 的原型對象的一個方法 getType
Fruit.prototype.getType = function() {
  return this.type;
};

var food1 = new Fruit();
console.log(food1.getType()); // 返回 水果
  • 前半段都是一樣的烙懦,直至將 Fruit 的原型對象指向 Food 的實例驱入,于是Fruit原型不僅擁有了 Food 實例的全部屬性和方法,也擁有了 Food 實例的原型對象(因為 Food 實例里面有 prototype 指向Food Prototype
  • 這種粗暴的直接將父對象的實例塞進去子對象的原型里面的方式氯析,直接促成了Fruit 繼承 Food亏较。

我最喜歡用《javascript 高級程序設計》第三版的圖來說明,因為他畫的比較詳細而且容易看明白(雖然我也是看了十來遍才看懂)掩缓,借用他的例子和圖來解釋我們的例子:

image

可以看到現在這里子對象 subtype 的 原型對象是 superType雪情,因為也是直接粗暴的塞進去的。

如果要看完整的他的原型鏈你辣,可以參看這個圖:

image

相當詳細巡通,這里之所以有 Object 是因為 javascript 里面一切皆是對象,默認的最頂級的原型就是Object Prototype舍哄。(怎么看這個圖宴凉,可以翻看之前一集介紹原型的內容)

下面需要注意一些原型對象的問題和技巧

1.1 確定原型和實例的關系

沒辦法準確知道是繼承于哪一個,只要是在鏈條里面的表悬,都會被認為是繼承過來的弥锄。

console.log(food1 instanceof Fruit) // 返回 true
console.log(food1 instanceof Food) // 返回 true
console.log(food1 instanceof Object) // 返回 true

console.log(Fruit.prototype.isPrototypeOf(food1)) // 返回 true
console.log(Food.prototype.isPrototypeOf(food1)) // 返回 true
console.log(Object.prototype.isPrototypeOf(food1)) // 返回 true

這里也跟javascript 的原型搜索機制有關系,當訪問一個實例屬性時候蟆沫,首先會在實例中搜索該屬性叉讥,如果沒有找到該屬性,就會繼續(xù)搜索實例的原型對象饥追,在通過原型鏈實現繼承的情況下图仓,搜索過程就會一直沿著原型鏈繼續(xù)向上搜索。

類似下圖:

image

圖片來源于:http://www.cnblogs.com/keepfool/p/5573121.html

1.2 謹慎定義方法

① 給原型添加方法的代碼一定要放在替換原型的語句之后

正確的例子:

// 定義一個 Food 的構造函數
function Food() {
  this.type = "食物";
}
// 定義了 Food 的原型對象的一個方法 getType
Food.prototype.getType = function() {
  return "food 的 getType 方法";
};
// 定義一個 Fruit 的構造函數
function Fruit() {
  this.type = "水果";
}
// 將 Fruit 的原型對象指向 Food 的實例
Fruit.prototype = new Food();
// 給子類 Fruit 的原型添加一個新方法getSubType
Fruit.prototype.getSubType = function() {
  return "Fruit 的getSubType";
};
// 重寫父類 Food 的方法getType
Food.prototype.getType = function() {
  return false;
};
var food1 = new Fruit();

console.log(food1.getSubType()); // 返回 Fruit 的getSubType
console.log(food1.getType()); // 返回 false
  • 子類 Fruit 重寫父類(超類)的原型對象的方法getType但绕,在調用的時候會覆蓋屌父類 Food的原型對象的getType方法救崔,直接使用子類Fruit的getType
  • 子類 Fruit 添加一個方法到自己的原型對象里面,也是很正常的捏顺,能夠被直接使用六孵。

錯誤的例子:

// 定義一個 Food 的構造函數
function Food() {
  this.type = "食物";
}
// 定義了 Food 的原型對象的一個方法 getType
Food.prototype.getType = function() {
  return "food 的 getType 方法";
};
// 定義一個 Fruit 的構造函數
function Fruit() {
  this.type = "水果";
}
// 給子類 Fruit 的原型添加一個新方法getSubType
Fruit.prototype.getSubType = function() {
  return "Fruit 的getSubType";
};
// 重寫父類 Food 的方法getType
Food.prototype.getType = function() {
  return false;
};
// 將 Fruit 的原型對象指向 Food 的實例
Fruit.prototype = new Food();

var food1 = new Fruit();

console.log(food1.getSubType()); // 拋出 error 異常
console.log(food1.getType()); // 返回 false
  • food1.getSubType() 直接拋出異常,提示說方法找不到或者未定義

主要就是因為子原型對象被替換的時候會被完全覆蓋幅骄。

1.3 在通過原型鏈實現繼承時劫窒,不能使用對象字面量方法創(chuàng)建原型

主要是因為對象字面量方法會重寫原型鏈,這個原理在之前章節(jié)說過拆座,這里只是再次提醒主巍。

// 省略冠息。。孕索。
Fruit.prototype = new Food();

Fruit.prototype = { // 被重寫了原型鏈逛艰,就不屬于原來的原型鏈范圍了。
//  xxxxxxx
}
// 省略搞旭。散怖。。

1.4 原型鏈的問題

  1. 原型鏈最大的問題是來自包含引用類型值的原型肄渗,這種類型值的原型屬性會被所有實例共享镇眷,導致沒辦法很好隔離,所以之前也是使用構造函數和原型模式組合使用來解決這個問題翎嫡,但當時沒有觸及真正的繼承偏灿。
  2. 原型鏈另外一個問題是,在創(chuàng)建子類型的實例時钝的,不能向超類型的構造函數中傳遞參數翁垂,或者說,是沒辦法在不影響所有對象實例情況下硝桩,給超類型的構造函數傳遞參數沿猜。

基于以上2個問題,導致了實際環(huán)境中碗脊,很少會單獨使用原型鏈啼肩,會結合其他方式來使用原型鏈,畢竟 javascript 里衙伶,所有的繼承其實也是以原型鏈為基礎的祈坠。

二、繼承-借用構造函數矢劲、偽造對象赦拘、經典繼承

image

圖片來自:https://www.tvmao.com/drama/KyEwYiY=

鑒于之前原型鏈的問題兩大問題,所以機智的工程師想出來利用構造函數來搭配使用芬沉,這個技術就叫做借用構造函數 constructor stealing(很 low 有沒有L赏),有時候叫偽造對象丸逸,或者叫經典繼承(逼格瞬間飆升到完全看不懂蹋艺,但覺得很厲害,有木有;聘铡)

核心思想是在子類型構造函數的內部調用超類型改造函數捎谨。

單純使用原型鏈繼承的時候:

function Food() {
  this.colors = ["red", "blue"];
}

function Fruit() {}

Fruit.prototype = new Food();

var food1 = new Fruit();
var food2 = new Fruit();
console.log(food1.colors); // 返回 [ 'red', 'blue' ]
console.log(food2.colors); // 返回 [ 'red', 'blue' ]
food1.colors.push("yellow");
console.log(food1.colors); // 返回 [ 'red', 'blue', 'yellow' ]
console.log(food2.colors); // 返回 [ 'red', 'blue', 'yellow' ]

使用借用構造函數模式繼承的時候:

function Food() {
  this.colors = ["red", "blue"];
}

function Fruit() {
  Food.call(this); // call 可以改變函數的this對象的指向
}

var food1 = new Fruit();
console.log(food1.colors); // 返回 [ 'red', 'blue' ]

food1.colors.push("yellow");
console.log(food1.colors); // 返回 [ 'red', 'blue', 'yellow' ]

var food2 = new Fruit();
console.log(food2.colors); // 返回 [ 'red', 'blue' ]

可以看到截然不同的兩種效果,后者的實例的數組(引用類型的數據)并沒有跟隨其他實例變化而變化,是互相獨立的涛救。

為什么可以這樣呢畏邢?

  • 利用了函數的執(zhí)行環(huán)境上下文,這里的“繼承”的目的只是為了能夠使用超類的屬性和方法(不算是真正的繼承)州叠,所以直接將超類的構造函數放到子類的構造函數里面執(zhí)行,從而將他們進行合體凶赁。
  • 利用了 call(或者 apply 或者 bind 這種函數)改變了構造函數的 this 指向咧栗,才得以實現上面說到的將不同的構造函數放到同一個執(zhí)行環(huán)境中執(zhí)行。

2.1 傳參

下面兩個例子分別說明了虱肄,這種繼承方式可以傳參的致板,并且傳參之后也是可以重寫超類的屬性的。

例子1:

function Food(name) {
  this.name = name;
  this.colors = ["red", "blue"];
}

function Fruit() {
  Food.call(this, "蘋果"); // call 可以改變函數的this對象的指向
}

var food1 = new Fruit();
console.log(food1.name); // 返回 蘋果

例子2:

function Food(name) { // 參數
  this.name = name;
  this.colors = ["red", "blue"];
}

function Fruit() {
  Food.call(this, "蘋果"); // call 可以改變函數的this對象的指向咏窿,加上了傳參
  this.place = "非洲"; // 添加屬性
  this.name = "香蕉"; // 重寫超類屬性
}

var food1 = new Fruit();
console.log(food1.name); // 返回 蘋果
console.log(food1.place); // 返回 非洲

2.2 這種方式的問題

image

圖片來自:https://www.youtube.com/watch?v=UNiHF-Z0BM0

正如之前所說斟或,這種不是真正的繼承,只是想子類和父類進行了強行合體罷了集嵌,這種合體方式能夠滿足一般繼承的要求萝挤,但是帶了其他問題:

  • 沒辦法使用超類的原型對象里面定義的方法。
function Food() {
  this.colors = ["red", "blue"];
}
Food.prototype.getType = function () {
  console.log("我是 food 的getType");
}
function Fruit() {
  Food.call(this); // call 可以改變函數的this對象的指向
}

var food1 = new Fruit();
console.log(food1.getType()); // 拋出異常根欧,沒有這個 function
  • 因為子類和超類都是構造函數怜珍,那么就會有之前說的,構造函數在 new 的時候凤粗,里面的方法(函數)會重復創(chuàng)建 function 實例酥泛, 導致資源浪費丧蘸。
function Food() {
  this.colors = ["red", "blue"];
}

function Fruit() {
  Food.call(this); // call 可以改變函數的this對象的指向
  this.getType = function() {
    console.log("我是 food 的getType");
  };
}

var food1 = new Fruit();
var food2 = new Fruit();

console.log(food1.getType == food2.getType); // 返回 false

鑒于這種問題蒸走,在小規(guī)模程序設計里面還好提鸟,但是一旦規(guī)模稍微變得復雜之后是整,就沒法控制代碼了庭猩,那我們機智的工程師們還要繼續(xù)想想辦法飒焦。

參考內容

  1. 紅寶書因妇,javascript 高級程序設計第三版

原文轉載:https://www.godblessyuan.com/2018/08/%E6%88%91%E6%9D%A5%E9%87%8D%E6%96%B0%E5%AD%A6%E4%B9%A0%20javascript%20%E7%9A%84%E9%9D%A2%E5%90%91%E5%AF%B9%E8%B1%A1%EF%BC%88part%204%EF%BC%89.html

?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末董朝,一起剝皮案震驚了整個濱河市灰瞻,隨后出現的幾起案子情组,更是在濱河造成了極大的恐慌,老刑警劉巖箩祥,帶你破解...
    沈念sama閱讀 211,743評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件院崇,死亡現場離奇詭異,居然都是意外死亡袍祖,警方通過查閱死者的電腦和手機底瓣,發(fā)現死者居然都...
    沈念sama閱讀 90,296評論 3 385
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人捐凭,你說我怎么就攤上這事拨扶。” “怎么了茁肠?”我有些...
    開封第一講書人閱讀 157,285評論 0 348
  • 文/不壞的土叔 我叫張陵患民,是天一觀的道長。 經常有香客問我垦梆,道長匹颤,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,485評論 1 283
  • 正文 為了忘掉前任托猩,我火速辦了婚禮印蓖,結果婚禮上,老公的妹妹穿的比我還像新娘京腥。我一直安慰自己赦肃,他們只是感情好,可當我...
    茶點故事閱讀 65,581評論 6 386
  • 文/花漫 我一把揭開白布公浪。 她就那樣靜靜地躺著他宛,像睡著了一般。 火紅的嫁衣襯著肌膚如雪欠气。 梳的紋絲不亂的頭發(fā)上堕汞,一...
    開封第一講書人閱讀 49,821評論 1 290
  • 那天,我揣著相機與錄音晃琳,去河邊找鬼讯检。 笑死,一個胖子當著我的面吹牛卫旱,可吹牛的內容都是我干的人灼。 我是一名探鬼主播,決...
    沈念sama閱讀 38,960評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼顾翼,長吁一口氣:“原來是場噩夢啊……” “哼投放!你這毒婦竟也來了?” 一聲冷哼從身側響起适贸,我...
    開封第一講書人閱讀 37,719評論 0 266
  • 序言:老撾萬榮一對情侶失蹤灸芳,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后拜姿,有當地人在樹林里發(fā)現了一具尸體烙样,經...
    沈念sama閱讀 44,186評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 36,516評論 2 327
  • 正文 我和宋清朗相戀三年蕊肥,在試婚紗的時候發(fā)現自己被綠了谒获。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,650評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖批狱,靈堂內的尸體忽然破棺而出裸准,到底是詐尸還是另有隱情,我是刑警寧澤赔硫,帶...
    沈念sama閱讀 34,329評論 4 330
  • 正文 年R本政府宣布炒俱,位于F島的核電站,受9級特大地震影響爪膊,放射性物質發(fā)生泄漏权悟。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,936評論 3 313
  • 文/蒙蒙 一惊完、第九天 我趴在偏房一處隱蔽的房頂上張望僵芹。 院中可真熱鬧处硬,春花似錦小槐、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,757評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至疮方,卻和暖如春控嗜,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背骡显。 一陣腳步聲響...
    開封第一講書人閱讀 31,991評論 1 266
  • 我被黑心中介騙來泰國打工疆栏, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人惫谤。 一個月前我還...
    沈念sama閱讀 46,370評論 2 360
  • 正文 我出身青樓壁顶,卻偏偏與公主長得像,于是被迫代替她去往敵國和親溜歪。 傳聞我的和親對象是個殘疾皇子若专,可洞房花燭夜當晚...
    茶點故事閱讀 43,527評論 2 349

推薦閱讀更多精彩內容

  • 作者: 陜縣2932沈莉紅 作者簡介: 沈莉紅,1998年畢業(yè)于豫西師范蝴猪,從教20年來调衰,一直擔任語文教育教學工作。...
    西外小學部雷書紅閱讀 422評論 0 2
  • 這些天發(fā)現一個bilibili一個非常惡心的地方: 使用客戶端掃碼登錄網站自阱,網站cookie的有效期只有一天嚎莉。如果...
    pockry閱讀 428評論 0 0
  • 生活本不易,閑暇時看一部動畫片沛豌,保持一顆童真的心萝喘! 一切會變得不一樣! 有人問我為什么喜歡用圓珠筆畫畫,開始覺得方...
    木婉清愛吃肉閱讀 1,834評論 43 74
  • 今天想寫一寫黃梅戲《小辭店》阁簸。 繼前兩天偶然在電視上聽了《小辭店》選段之后爬早,它的旋律就一直在我耳邊...
    胖桐閱讀 1,574評論 2 3
  • 急匆匆去昆明一日游,更換護照快要過期了不夠半年內有效启妹,先生來接機已經是晚上11:00了. 每次飛機行程我習慣是一定...
    假愛之名閱讀 178評論 0 1