續上一集內容,經過構造函數的方式,成功地更新了生產技術,老闆笑呵呵,工人少奔波,只是問題總比辦法多,又遇到一個新問題,就是會形成一些資源的重複和浪費,那麼通過工程師們的智慧交流,他們產生了一個新技術,原型模式。javascript
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(); // 建立無限多的 food 。。。。。。 console.log(food1.sayName == food2.sayName); // 返回 true
sayName
方法都放到原型Food
的原型上去new
來建立這樣就完成了原型模式的使用了,可以將函數進行共享,不用每次都重複建立不一樣的函數實例了,並且全部的屬性共享,也可以很方便節省代碼和簡化結構,其餘人也能夠很方便地進行使用。html
可是比較懵逼,爲何這樣就能夠了呢?原型是個什麼東西?怎麼起做用的呢?java
在《javascript 高級程序設計》裏面是這樣說的,咱們建立的每一個函數都有一個 prototype
,這個屬性是一個內存指針,指向一個對象,而這個對象的用途是包含能夠由特定類型的全部實例共享的屬性和方法。node
換句大白話來講:編程
例如圖1,能夠粗獷地理解爲蛋是雞生的,因此蛋的原型是雞。
圖片引用來自:https://hackernoon.com/unders...windows
或者用親屬關係來理解,原型就是你的父輩祖輩。
圖片引用自http://china.findlaw.cn/info/...瀏覽器
例如圖2,這是一個類樹狀結構的組織:
圖片引用自:http://rohitnsit08.blogspot.c...函數
global object
的意思在後面有解釋。string
的原型就是 string.prototype
,function
的原型就是 string.prototype
等等,而這些原型的原型就是Object.prototype
了,因此就有那麼一句話,在 javascript 裏面,全部的東西都是對象在 javascript 裏面,global object 有4種:學習
- 在瀏覽器裏面,windows 被稱做是 global object
- 在 nodejs 裏面,nodejs 的運行自己也是一個 global objec
- 在 Worker 線程下, WorkerGlobalScope 也叫 global object
- 在通常 javascript 運行過程當中,在全部對象被建立以前,會預先建立一個 global object,裏面包含了全部這個 javascript 引擎裏面擁有的屬性和方法,這個也叫作 global object,而且 javascript 的對象系統都是基於這個 global object 創建的。
其實原型是很好理解的東西,就是原來的形態,例如string
的原型就是 string.prototype
,字符串的原來的形態就是字符串原型,可是還有一些比較影響理解和學習的東西,例如constructor
prototype
[[prototype]]
這些。網站
① 先來講constructor
:
其實構造函數也有constructor
,原型對象有constructor
,實例有constructor
也有,或者更加籠統的說,全部對象都是有constructor
的。
// 構造函數的constructor,默認構造函數的原型對象是Function對象 function Food() {} console.log(Food.constructor); // 返回[Function: Function] // 實例的constructor是Food var food1 = new Food("蘋果"); console.log(food1.constructor); // 返回 [Function: Food] // 構造函數的原型對象Food.prototype 也有constructor,指向構造函數 console.log(Food.prototype.constructor); // 返回 [Function: Food]
constructor
默認會指向[Function: Function]
,也就是函數原型對象是他的原型對象。new
來實例化對象的時候,實例的constructor
會默認指向構造函數,證實是哪個來自於哪個構造函數,可是僅此而已,只是一個標識,因此是 Food
。 Food Prototype
上的constructor
會指向構造函數 Food
。能夠看出,經過constructor
能夠看到他們之間的關係,可是經過constructor
鏈接的關係是很脆弱的 (容易變化,不可靠),也由於 javascript 在設計當初並無太多考慮這個狀況,因此constructor
比較雞肋。
另外雖然有這麼多constructor
,可是咱們通常討論的比較多的是,原型對象的constructor
,他會默認指向構造函數的 prototype
屬性,僅此而已,若是他沒有被改變的話,也能夠充當一種標誌,表明經過這個構造函數生成的實例是來自哪一個原型的,從而判斷對象的類型是哪種(但不可靠)。
舉例說明:
function Food() {} console.log(Food.constructor); // 返回[Function: Function] // 當重寫原型的時候,實例的constructor 就變成了[Function: Object] Food.prototype = { name: "蘋果" }; var food2 = new Food("蘋果"); console.log(food2.constructor); // 返回 [Function: Object]
這裏實例的constructor
就被改變了,因此通常咱們能夠看到重寫原型的時候(原型鏈被切斷,會默認指向默認的原型對象),會手動加入一個 constructor
屬性來指定它的值,以方便識別。
function Food() {} console.log(Food.constructor); // 返回[Function: Function] // 咱們須要主動標記 constructor屬性 Food.prototype = { constructor: Food, name: "蘋果" }; var food2 = new Food("蘋果"); console.log(food2.constructor); // 返回 [Function: Food]
constructor
其實沒有什麼用處,他是JavaScript語言設計的歷史遺留物。因爲constructor
屬性是能夠變動的,因此未必真的指向對象的構造函數,只是一個提示。不過,從編程習慣上,咱們應該儘可能讓對象的constructor
指向其構造函數,以維持這個慣例。--by 賀師俊
② 再來講prototype
在 javascript 裏面,只要是函數,都會有這樣一個屬性prototype
,這個屬性指向函數的原型對象,在默認狀況下,全部原型對象都會自動得到一個 constructor
屬性,指向prototype
所在的函數。
function Food() {} console.log(Food.prototype) // 返回Food {} var food1 = new Food("蘋果"); console.log(food1.prototype) // 返回 undefined
prototype
屬性是一個內存指針,最終是指向 Food 的原型對象 Food Prototype
的,這裏須要注意個人寫法,是 Food Prototype
,這裏雖然很類似,但其實不是。prototype
屬性沒辦法直接查看,因此返回 undefined
,須要用別的方法來查看。③ 最後說說[[prototype]]
和__proto__
這是存在於實例身上的 prototype
屬性,可是沒辦法直接查看,只能經過某些方式來獲取和判斷。不一樣的瀏覽器有不一樣的叫法,的屬性名字也多是[[prototype]]
或者_proto_
。
// Object.getPrototypeOf會返回原型對象,但看起來跟普通構造函數沒區別 console.log(Object.getPrototypeOf(food1)); // 返回 Food {} // 因此通常會使用這個方式來判斷原型對象是否一致 console.log(Object.getPrototypeOf(food1) === Food.prototype); // 返回 true // 或者這個方式,isPrototypeOf會直接判斷 console.log(Food.prototype.isPrototypeOf(food1)) // 返回 true
原型的做用主流程:
prototype
屬性,也就是原型屬性,這個屬性指向構造函數的原型對象,例如food1
的prototype
屬性指向Food
的原型對象Food prototype
。constuctor
構造函數屬性,這個屬性裏面包含了一個指向,指向以前被建立的對象的prototype
屬性的所在位置,至關於原型對象是母體,被建立的對象會關聯到母體身上,參考前面所說的原型,constructor 和 prototype 的內容來理解
在《javascript 高級程序設計》第三版裏面的有一幅圖:
Person
是構造函數,Person Prototype
是 Person 構造函數的原型對象。[[Prototype]]
都指向了Person Prototype
。問什麼這裏會有一條線指向了構造函數?
由於Person
構造函數的prototype
指向了Person Prototype
,而Person Prototype
的 constructor
也指向了Person
構造函數,他們之間經過這樣來互相確認「關聯狀態」,但僅僅互相確認關係而已(由於 constructor
容易被改變)。
類比到咱們的 Food 例子裏面去,food1和Food
和Food Prototype
的關係就跟 person1和 person2和Person
和Person Prototype
的關係是同樣的。
① 若是須要查找這個實例對象的原型的話,可使用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.prototype
的constructor
就再也不指向 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); // 返回 蘋果
在 new 建立完實例以後,實例的原型對象是構造函數的原型對象,若是在這時候重寫了構造函數的原型對象的話,那麼原來實例跟原來構造函數的原型對象的連接就會被切斷,就沒法使用原型對象上的數據了。
用了原型模式以後,雖然解決了遇到的一系列問題,但也帶來了一些新的反作用(怎麼反作用那麼多。。。。。),原型模式的共享特性帶來了方便之餘,也形成了一些困擾,若是咱們須要一些不想共享的信息,例如 food1 的原產地是巴西,印度,非洲,food2的原產地是巴西,印度,俄羅斯,他們之間有一些區別,不能徹底共享,那麼怎麼辦呢?
會經過組合使用構造函數模式和原型模式或者動態原型模式來解決,下回分解。
做者: 慫如鼠
網站: https://www.whynotbetter.com 本做品著做權歸做者全部,商業轉載請聯繫做者得到受權,非商業轉載請註明出處。