【設計模式+原型理解】第二章:基於建構函式擴展出來的原型模式
在第一章的時候,說過了單例模式、工廠模式、建構函式模式,你還記得構造模式是怎麼樣的嗎?
function CreateJsPerson(name, age) { this.name = name; this.age = age; this.writeJs = function() { console.log("my name is " + this.name + ", i can write js."); } } var p1 = new CreateJsPerson("p1", 18); var p2 = new CreateJsPerson("p2", 17); p1.writeJs(); p2.writeJs(); console.log(p1.writeJs() === p2.writeJs()); //-> false // 建構函式模式中擁有了類和例項的概念,並且例項和例項之間是相互獨立開的 // -> 叫做例項識別
上面的就是建構函式模式,你知道,為什麼p1、p2的writeJs()方法為什麼不相等嗎?這是因為正如上面所說的,兩個例項是相互獨立的,也就是說,兩個例項的屬性都是各自私有屬性。
-> 問題來了,兩個例項裡面的屬性都是私有的以外,是不是還得有公有的部分?
一、【 基於建構函式的原型模式 】
使用基於建構函式模式的原型模式,能夠實現把writeJs()方法 變成公有的 ,程式碼如下:
function CreateJsPerson(name, age) { this.name = name; this.age = age; // this.writeJs = function() { // console.log("my name is " + this.name + ", i can write js."); // } } CreateJsPerson.prototype.writeJsG = function() { console.log("my name is " + this.name + ", i can write js."); }; var p1 = new CreateJsPerson("p1", 18); var p2 = new CreateJsPerson("p2", 17); p1.writeJs(); p2.writeJs(); console.log(p1.writeJsG() === p2.writeJsG()); //->true
為什麼這樣寫,就能把屬性變為公有的呢?
基於建構函式模式擴展出來的原型模式:
->它解決了方法或者屬性公有的問題
->即把例項之間公有的屬性和方法提出成公有的屬性和方法
->想讓誰公有,就把它放在prototype上即可
二、【原型基礎3句話】
想要學習原型,要記住下面的三句話( 不要問為什麼 ~~):
1)每一個函式資料型別(普通函式、類)都有一個天生自帶的屬性,prototype(原型),並且這個屬性是一個物件資料型別的值。
2)並且在prototype上瀏覽器會天生給它加上一個屬性contructor(建構函式),屬性值是當前函式(類)本身。
3)每一個物件資料型別(普通物件,例項,prototype...)也天生自帶一個屬性:__proto__,屬性值是當前例項所屬類的原型(prototype)。
下面這段程式碼,可以說明一下上面的3句話:
function Fn() { this.x = 100; }; Fn.prototype.getX = function() { console.log(this.x); }; var f1 = new Fn; var f2 = new Fn; console.log(Fn.prototype.constructor === Fn); //->true // 堆記憶體:儲存 物件、函式裡面的程式碼字串
上面程式碼的原型鏈如下圖所示,正方形代表棧記憶體(即函式作用域),橢圓正方形代表堆記憶體(即物件)
從畫圖可以很清晰看到,整個基於建構函式擴展出來的原型鏈函式,類與例項的原型鏈,例項與JS基類Object的原型鏈關係,一覽無遺。
上圖+程式碼,可以總結出:
1、Object是JS中所有物件資料型別的基類(最頂層的類)
1) f1 instanceof Object -> true,這是因為f1通過__proto__可以向上級查詢,不管有多少級,最後總能找到Object
2)在Object.prototype上沒有__proto__這個屬性(因為自己指向自己沒意義)
2、 原型鏈模 式
舉個簡單的例子,f1.hasOwnProperty("x"); //->hasWwnProperty是f1的一個屬性,但是我們發現f1的私有屬性上並沒有這個方法,那如何處理的呢?
通過 物件名.屬性名 的方式獲取屬性值的時候,首先在物件的私有的屬性上進行查詢,如果私有中存在這個屬性,則獲取的是私有的屬性值;
->如果私有的沒有,則通過__proto__找到所屬類的原型(類的原型上定義的屬性和方法都是當前例項的公有的屬性和方法),原型上存在的話,獲取的是公有的屬性值;
-> 如果原型上也沒有,則繼續通過原型上的__proto__繼續向上查詢,一直找到Object.prototype為止...
->這種查詢的機制就是我們的原型鏈模式
三、【原型知識玩起來】
cconsole.log(f1.getX === f2.getX); //->true console.log(f1.__proto__.getX === f2.getX); //->true console.log(f1.getX === Fn.prototype.getX); //->true // f1.getX 跟 f1.__proto__.getX的區別 // 前者是瀏覽器先找私有作用域,找不到再找公有作用域 // 後者是瀏覽器直接查詢公有作用域 // console.log(f1.hasOwnProperty === f1.__proto__.__proto__.hasOwnProperty); // 在IE瀏覽器中,我們原型模式也是同樣的原理,但是IE瀏覽器怕你通過__proto__把公有的修改, // 禁止我們使用__proto__,下面的例子就可以很明顯的說明為啥IE禁止了 f1.sum == function() { //修改自己私有的sum }; f1.__proto__.sum = function() { //修改所屬類原型上的sum }; // 所以修改公有的,IE只能通過prototype Fn.prototype.sum = function() { // 修改公有的sum };
四、【總結】
在這一章中,主要說的是原型模式,但是原型模式是通過建構函式擴展出來的,同時也通過程式碼+圖的方式,把原型模式的實現原理給畫了出來。
既然原型這個知識點出來了, 我會在後面介紹一下, 使用原型來實現類的封裝、繼承、多型 (裡面的重寫),並且會介紹 使用原型來實現7中繼承方法 ,將會非常有趣。