【設計模式+原型理解】第一篇:使用Javascript來巧妙實現經典的設計模式
剛開始學習設計模式之前,我是沒想說要學習設計模式的,我只是因為想學習JS中的原型prototype知識,一開始我想JS中為什麼要存在原型這個東西? 於是慢慢通過原型而接觸到設計模式 ,後來發現我這個過程是非常正確的,即先學習設計模式,然後在剖析原型及其原理 。
我一開始,都是通過針對原型的知識點去看去學,發現還是理解不了,就算理解了原型,可以說只是停留在表面的知道,你並不懂得如何運用原型?後來,我通過用設計模式的角度去理解原型prototype,我才煥然大悟 !
但是在這一篇章裡面,並沒有說到原型、原型模式,因為如果你要進入設計模式的角度去理解原型,你就必須得了解一下用JS來實現單例模式、工廠模式和建構函式模式,看看Javascript是如果巧妙地實現各種設計模式。
【單例模式】
var person1 = { "name": "psg", "age": 24 }; var person2 = { "name": "fsf", "age": 22 }; console.log(person1.name); // ->page console.log(person1.age); // ->24 console.log(person2 .name); // ->fsf console.log(person2 .age); // ->22
上面的person1與person2都不是基本資料型別,都是物件資料型別,物件資料型別的作用 :把描述同一個事物(同一個物件)的屬性和方法放在一個記憶體空間下,起到了分組的作用(例如person1與person2,都是兩個獨立開來的棧記憶體,裡面的屬性和方法互不影響) -> 於是,我們把這種分組編寫程式碼的模式叫做“單例模式”。
-> 在“單例模式”中,我們叫person1、person2叫做名稱空間
單例模式是一種專案開發中經常使用的模式,因為專案中我們可以使用單例模式進行“模組化 ”開發。
名稱空間的作用,看下面程式碼
// searchRender 是一個名稱空間 var searchRender = { change: function() { this.clickEvent(); //這裡this你暫時看不出來是誰,因為只有執行的時候,才知道 }, clickEvent: function() { } }; // 但是想要使用searchRender裡面的change方法,你必須如下呼叫 serachRender.clickEvent(); // 所以change方法裡面的this,只能是searchRender
如果你學的比較深入,看上面的程式碼你就能看出名稱空間的作用是什麼了,就是能靈活運用this。
這裡使用this的好處:當名稱空間改名字的時候,你並不用改裡面的呼叫者,因為this就代表名稱空間。
總結
->單例模式解決了分組的問題
->但是單例模式的弊端也很明顯,因為他仍然是手工作業模式,效率比較低,於是“工廠模式 ”就站出來解決了這個問題,因為工廠模式可以 批量生產 。
【工廠模式】
function createJsPerson(name, age) { var obj = {}; obj.name = name; obj.age = age; obj.writeJs = function() { console.log("My name is " + this.name + ", i can write js."); }; return obj; } var p1 = createJsPerson("psg", 24); var p2 = createJsPerson("fsf", 22) p1.writeJs(); // ->My name is psg, i can write js. p2.writeJs(); // ->My name is fsf, i can write js.
這樣寫,可以減少重複程式碼的產出,你只需要寫好公共的程式碼,把他放進一個方法裡面,然後通過接受不同的引數,可以建立不同的物件,效率比單例模式高多了!
接下來,講一下“建構函式模式”,它的樣子跟“工廠模式”非常相似
【建構函式模式】
function CreateJsPerson(name, age) { // var obj = {}; this.name = name; this.age = age; this.writeJs = function() { console.log("My name is " + this.name + ", i can write js."); } // return obj; } var p = new CreateJsPerson("psg", 24); p.writeJs();// -> My name is psg, i can write js.
看到沒有,CreateJsPerson這個方法, 根本不會像工廠模式那樣最後返回給你一個物件,它不需要,因為當你通過使用new關鍵字,瀏覽器會在後臺中自己新建一個物件,然後通過this來指向該新建物件,最後自動預設返回一個物件給你 。
->其實我們有時也會不經意用到使用建構函式模式新進例項,例如建立一個數組
// 建立一個數組 var ary = []; // 字面量方式 var arry = new Array(); // 例項建立的方式-》建構函式模式執行的方式
->知識點一: 建構函式模式和工廠模式的區別?
1)執行的時候
普通函式執行-> createJsPerson() -> 這時候,createJsPerson是一個普通函式名
建構函式模式-> new CreateJsPerson() -> 通過new執行後,CreateJsPerson就是一個類 -> 開頭大寫是因為約定了,大寫開頭的函式就是表示一個類
new 出來的返回值(p),就是CreateJsPerson這個類的一個例項
2)在函式執行的時候
相同點-> 都是形成私有作用域,然後形參賦值 -> 預解釋 -> 程式碼從上到下執行(類和普通函式一樣,它也有普通函式的一面)
不同點-> 在程式碼執行之前, 不用自己再手動建立物件,瀏覽器會預設的建立一個物件資料型別的值(類的例項),並作為函式返回值自動返回。
->知識點二: 建構函式也是函式資料型別
在JS中,所有類都是函式資料型別,createJsPerson是函式資料型別, new CreateJsPerson也是函式資料型別,CreateJsPerson它通過new執行變成了一類,但它本身也是一個普通的函式。
但是,JS中所有類的例項都是物件資料型別。
->知識點三: 如果給建構函式裡面新增私有變數,它返回值(例項)會存在此變數嗎?
unction Fn() { var num = 10; } var obj = new Fn; console.log(num); // -> undefined console.log(obj.num); // -> undefined
上面例子可以看出,這裡的num只是函式私有作用域裡面的一個私有變數,它跟例項沒有任何關係。
->知識點四: 如果給建構函式直接返回一個物件,或者直接返回一個基本資料型別,那個例項到底還是不是瀏覽器預設返回的例項呢?
1)手動新增自動返回基本資料型別
function Fn() { var num = 10; return 100; // 程式碼無效 } var f1 = new Fn; console.log(f1); // -> Object,這個Object就是空的,也是瀏覽器預設返回的物件 // 也就是說,return 100 這句程式碼無效
2)手動新增自動返回物件資料型別
function Fn() { var num = 10; return {name: 'psg'}; // 程式碼有效 } var f1 = new Fn; console.log(f1); // -> Object,這個Object就是 {name: 'psg'} // 也就是說,強制返回的手動新增的例項物件,完全KO覆蓋掉瀏覽器預設返回的例項物件
總結上面兩點在建構函式模式當中,瀏覽器會預設把我們的例項返回(返回的是一個物件資料型別的值)
// 但是如果我們手動返回值,分兩種情況
// ******情況一,返回的是一個基本資料型別的值,當前例項不變
// 例如: return 100
// 那麼瀏覽器返回的值仍然是瀏覽器預設建立的物件
// ******情況二,返回的是一個引用資料型別的值,當前例項會被自己返回的值覆蓋掉
// 例如: return {name: "psg"}
// 那麼原先瀏覽器返回的預設值,將會被自己的手動建立返回的物件給覆蓋掉。注意,一定要是物件哦
->知識點五: 檢測某一個例項是否屬於這個類 -> instanceof
下面這個例子很有意思!!結合了知識點四。
function Fn() { var num = 10; // return 100 return {name: 'psg'} } var f1 = new Fn; console.log(f1 instanceof Fn); // ->false , 因為f1不是瀏覽器建立的預設例項
->知識點六:檢測某個屬性是否屬於某個例項,檢測某個屬性是否為該物件的公有屬性?
function Fn1() { this.x = 100; this.getX = function() { console.log(this.x); } } var fun1 = new Fn1; var fun2 = new Fn1; console.log(fun1.getX() === fun2.getX()); // -> fun1和fun2都是Fn1這個類的例項,都擁有x和getX這兩個屬性,但是這兩個屬性是各自私有的屬性 // 但是如何檢測某一個屬性是否屬於某一個例項 // in: 檢測某一個屬性是否屬於某一個例項,不管是私有屬性還是公有屬性,用in都是用來檢測這個屬性是否屬於這個物件 console.log("getX" in fun1); // -> true // hasOwnProperty: 用來檢測某個屬性是否為這個物件的私有屬性,這個方法只能檢測私有的屬性 console.log(fun1.hasOwnProperty("getX")); // -> true // 思考,檢測某一個屬性是否為該物件的“公有屬性”,自己寫一個 hasPubProperty function hasPubProperty(obj, attr) { // 首先保證它是一個物件的屬性,並且不是私有屬性,那就肯定就是公有屬性了 return ((attr in obj) && !obj.hasOwnProperty(attr)); } console.log(hasPubProperty(fun1, "getX")); // -> false;
這一章,主要講了單例模式、工廠模式和建構函式模式,同時也因為工廠模式與建構函式模式程式碼上非常相似,但是實現原理完全不相同,所以也講了建構函式模式例項化物件的原理過程,以及他們兩的區別。
下一章 的《【設計模式+原型理解】第二章:JS中為什麼要存在原型prototype這個東西?》 會講到原型模式,準確來說是基於建構函式模式的原型模式,因為只有講到建構函式模式,你才知道建構函式的優缺點,原型模式就是為了解決並改進建構函式的。
待續....