如何使用 javascript 面向對象編程來唬住面試官(part 2)

續上一集內容,通過構造函數的方式,成功地更新了生產技術,老闆笑呵呵,工人少奔波,只是問題總比辦法多,又遇到一個新問題,就是會造成一些資源的重複和浪費,那麼經過工程師們的智慧交流,他們產生了一個新技術,原型模式

一、使用原型模式

function Food() {}

Food.prototype.name = "蘋果";

Food.prototype.sayName = function() {
  console.log("我是" + this.name);
};

var food1 = new Food();
food1.sayName();

var food2 = new Food();
food2.sayName();

console.log(food1.sayName == food2.sayName); // 返回 true
  • 將所有屬性和方法,包括sayName 方法都放到原型Food的原型上去
  • 跟之前構造函數創建新對象的方式一樣,使用new來創建

這樣就完成了原型模式的使用了,能夠將函數進行共享,不用每次都重複創建不同的函數實例了,而且所有的屬性共享,也能夠很方便節省代碼和簡化結構。

但是比較懵逼,爲什麼這樣就可以了呢?原型是個什麼?怎麼起作用的呢?

理解什麼是原型

javascript 的原型是一個屬性,一般我們叫他原型屬性 prototype,這個屬性是一個內存指針,指向一個對象,而這個對象的用途是包含可以由特定類型的所有實例共享的屬性和方法。

換句大白話來說:

  • 原型就是根,所有東西都有根,是來自於哪裏,是被誰創造出來的,並且能夠通過這個根去追溯父輩祖輩的信息。
  • 全部東西都會由原型創造,所以都會帶有一個原型屬性,只是不同的原型創造出來的東西帶有不同的原型屬性。

例如這裏,可以粗獷地理解爲蛋是雞生的,所以蛋的原型是雞。

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

爲了更方便理解,如下圖,在 javascript 裏面,所有的東西都是對象,這是一個類樹狀結構的組織:

圖片引用自:http://rohitnsit08.blogspot.com/2011/06/javascript-object-system.html

  • 這裏的global object 的意思在後面有解釋,他會分出不同的對象,有 string objectobjecr objectfunction objectarray object、等等。
  • 這裏要特別注意一下,以上說到的對象的原型很多都來自 Function.prototype,而不只是Function.prototype,還有其他的對象原型,又都來自於Object.prototype,所以也這就是平時大家常說的,javascript 裏面一切都是對象的原因了。

在 javascript 裏面,global object 有4種:1. 在瀏覽器裏面,windows 被稱作是 global object2. 在 nodejs 裏面,nodejs 的運行本身也是一個 global objec3. 在 Worker 線程下, WorkerGlobalScope 也叫 global object4. 在一般 javascript 運行過程中,在所有對象被創建之前,會預先創建一個 global object,裏面包含了所有這個 javascript 引擎裏面擁有的屬性和方法,這個也叫做 global object,並且 javascript 的對象系統都是基於這個 global object 建立的。

爲什麼能夠通過原型模式來解決問題呢?

  1. 在 javascript 裏面,創建一個新函數(對象),都會在創建過程裏面增加一個prototype屬性,也就是原型屬性,這個屬性指向函數的原型對象,例如food1 指向Food
  2. 而這個被指向的原型對象裏面也會自動獲得一個constuctor構造函數屬性,這個屬性裏面包含了一個指向,指向之前被創建的對象的prototype屬性的所在位置,相當於原型對象是母體,被創建的對象會關聯到母體身上,並且是一對多的關聯,即一個母體對多個子體。
  3. javascript 解析器讀取到對象之後,會執行一次搜索,如果在當前對象上沒有搜索到目標屬性的話,就會繼續搜索指針指向的原型對象。

這裏有2個圖幫助理解:

  • person1 和 person2 都是實例, Person 是構造函數,Person Prototype 是 Person 是構造函數的原型屬性。
  • Person 是構造函數的prototype 指向了Person Prototype,而Person Prototype的 constructor 也指向了Person 是構造函數。
  • 例如,要確認 person1有沒有 sayName 方法,那麼javascript 引擎會先對person1對象本身進行搜索,如果有就直接返回,如果沒有就繼續搜索他的原型對象 Person Prototype,如果有就返回,如果沒有就報無法找到。

類比到我們的 Food 例子裏面去,food1和 Food 和 Food Prototype的關係。

這是一個圖, 對象->動物->狗->bichong(狗的大種類)->Foo(狗的小種類)->foo(我家的狗),這就是所謂的原型的鏈圖的一種情況,也是原型鏈的一個很形象的介紹。

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

prototype屬性有可能叫做[[prototype]] 或者_proto_

對於原型的一些使用技巧

① 如果需要查找這個實例對象的原型的話,可以使用Object.getPrototypeOf ,他會返回整個原型對象

function Food() {}

Food.prototype.name = "蘋果";

Food.prototype.sayName = function() {
  console.log("我是" + this.name);
};

var food1 = new Food();
console.log(Object.getPrototypeOf(food1)) // 返回 Food { name: '蘋果', sayName: [Function] }

② 只能通過對象實例訪問保存在原型的值,不能通過對象實例來重寫原型中的值③ 對象實例可以重寫從原型對象中“繼承”過來的同名屬性,這時候會切斷對象實例和原型對象的某個同名屬性的聯繫,如果想恢復聯繫即恢復沒改過的同名屬性的話,可以使用delete刪除對象實例的某個屬性④ hasOwnProperty()方法可以檢測一個屬性是存在於實例中還是存在於原型中

function Food() {}

Food.prototype.name = "蘋果";

Food.prototype.sayName = function() {
  console.log("我是" + this.name);
};

var food1 = new Food();

console.log(food1.hasOwnProperty("name")); // 返回 false
food1.name = "bilibili"; // 設置 food1的 name 屬性(也就是改寫從原型對象繼承過來的 name 屬性)
console.log(food1.hasOwnProperty("name")); // 返回 true
console.log(food1.name); // 返回 bilibili

⑤ 更簡單的原型寫法

function Food() {}

Food.prototype = {
  constructor: Food, // 這裏需要注意
  name: '蘋果',
};
  • 如果不寫constructor的話,Food.prototypeconstructor就不再指向 Food ,這樣就沒辦法通過constructor來識別得到改對象實例是屬於哪個原型對象了。
  • 以這種方式編寫原型的時候,因爲constructor需要設置,所以對象的[[Enumerable]] 可遍歷屬性就會被設置爲 true,代表可以被遍歷。

⑥ 在原型對象上直接編輯修改,會即時反應到實例對象上,所以可以隨時進行修改,很方便。⑦ 如果重寫原型對象,要注意原型對象的指向問題:

function Food() {
}
var food1 = new Food("蘋果"); // 繼續指向原來的 Food.prototype(最初的那個原型對象)
// 重寫Food.prototype
Food.prototype = {
  constructor: Food,
  name: '蘋果',
};
console.log(food1.name); // 返回 undefined
function Food() {
}
// 重寫Food.prototype
Food.prototype = {
  constructor: Food,
  name: '蘋果',
};
var food1 = new Food("蘋果"); // 指向新的被重寫後的Food.prototype
console.log(food1.name); // 返回 蘋果

二、文末我們又遇到了新問題了

用了原型模式之後,雖然解決了遇到的一系列問題,但也帶來了一些新的副作用(怎麼副作用那麼多。。。。。),原型模式的共享特性帶來了方便之餘,也造成了一些困擾,如果我們需要一些不想共享的信息,例如 food1 的原產地是巴西,印度,非洲,food2的原產地是巴西,印度,俄羅斯,他們之間有一些區別,不能完全共享,那麼怎麼辦呢?

會通過組合使用構造函數模式和原型模式或者動態原型模式來解決,下回分解。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章