標籤:需要 協助 內部實現 做什麼 參數 而不是 情況 logs 集合
什麼是設計模式呢? 就是指對於類似的問題,我們可以用大致相同的思想、方法去解決之,而這種通用的思想、方法就是設計模式。學習設計模式可以協助我們在遇到問題時迅速地搜尋出一種清晰的思路來實現之。
第一部分: 物件導向的JavaScript 1. JavaScript是動態類型語言。
靜態類型語言即強迫規定程式員在使用某個變數時先定義它的類型。而動態類型語言是在程式啟動並執行時候,才會具有某個類型,不需要嚴格定義。顯然,靜態類型語言要求更嚴格,而動態類型語言卻無法保證變數的類型。JavaScript就是這樣的動態類型語言,它的好處是我們可以花更多的時間關注在邏輯上,而不是變數的定義上,代碼會更加簡潔。 靜、動我們就可以理解為變數類型的靜與動。
2. 面向介面編程和鴨子模型
JavaScript王國需要100隻鴨子來組成合唱團,但最後只能找到99隻, 而正巧發現一隻雞的叫聲也是嘎嘎嘎,於是我們就把雞也拉近了鴨子合唱團。 這就是說,我們只關注對象的行為,而不關注對象本身。
基於這種思想,比如一個對象若有push和pop方法,就可以把它當作棧來使用等等,這種思想的編程就是面向介面編程。
3.多態
多態的實際含義是: 同一個操作作用在不同的對象上面,可以產生不同的解釋和不同的執行結果。 如下所示:
var makeSound = function(animal) { if (animal instanceof Duck) { console.log("嘎嘎嘎"); } if (animal instanceof Chicken) { console.log("咯咯咯"); }}var Duck = function() {};var Chicken = function() {};makeSound(new Duck());makeSound(new Chicken());
可以看出makeSound函數對於不同的輸入就有不同的輸出,這就是多態。
但是如果再添加一隻狗呢? 我們不僅要在建立一個狗的建構函式,還要改變makeSound函數,即這樣的可擴充性是十分糟糕的。
解決方案: 多態背後最重要的思想是將“做什麼”和“誰去做以及怎麼樣去做”分離開來,也就是將不變的事物和可變的事物分離開來。
如下所示:
var makeSound = function(animal) { animal.sound(); // 做什麼}var Duck = function() {}; // 誰去做Duck.prototype.sound = function() { console.log("嘎嘎嘎"); // 怎麼做};var Chicken = function() {};Chicken.prototype.sound = function() { console.log("咯咯咯");};var Dog = function() {};Dog.prototype.sound = function() { console.log("汪汪汪");};makeSound(new Duck());makeSound(new Chicken());
可以看出在上面的例子中不變的部分就是animal.sound(),我們將之分離出來(做什麼)。 然後再將誰去做,怎麼做分離出來,這樣函數的可擴充性就非常好了。
4. 封裝
封裝的思想在於隱藏內部的實現、 提高代碼的可重用性、 封裝變化。
5.原型模式
在以類為中心的物件導向變成語言中,類和對象的關係可以想象成鑄模和鑄件的關係,對象總是從類中建立。
但是在原型編程的思想中,類不是必須的,對象未必必須從類中建立而來,一個對象是通過複製另外一個對象而得到的。
第二部分:this、 call、 apply
我在《JavaScript函數之美~》中詳盡的介紹了this的用法。這裡還是要提及一些重點。
我們知道:this總是指向一個對象,也許是window對象,也許是調用它所在的方法的對象,也有可能是新建立的一個對象,具體執行的對象是運行時基於函數的執行環境動態綁定的,而非函數被聲明時的環境。
情況一:作為對象的方法調用,this指向的是該對象。
情況二:作為普通函數調用,而非對象的方法,this指向的全域對象window。
情況三:構造器調用,那麼就指向這個新建立的函數。
情況四:Function.prototype.call或Function.prototype.apply調用。
注意點一: 在一個對象中,如果存在回呼函數,我們還想this存在,該怎麼辦?
可以把 this 在對象的環境中賦值給 that,然後使用that,這時就有正確的指向了。
注意點二: 構造器調用的過程中,如果最後返回了對象,那麼this就會指向這個返回的對象。如下:
var MyClass = function() { this.name = "zzw"; return { name: "htt" };}var person = new MyClass();console.log(person.name); // htt
如果說這裡建構函式並沒有返回一個對象,那麼最終的結果一定是zzw,但是如果返回了對象,那麼這個this指向的一定是這個返回的對象。
注意點三: docuemnt.getElementById()方法需要用到this
var getId = function(id) { return document.getElementById(id); } getId("div").style.color = "red";
這樣我們就可以成功擷取到id為div的元素。但是:
var getId = document.getElementById; getId("div").style.color = "red";
這樣就會報錯。
這時因為用getId來引用document.getElementById()之後,再調用getId,此時就變成額普通函數調用,內部的this就指向了window而不是document。(許多引擎的document.getElementById方法的內部實現需要用到this)。
在JavaScript版本的設計模式中,call和apply方法都是很常用的,能熟練應用這兩個方法是我們真正成為一名JavaScript程式員的重要一步。
Function.prototype.apply 和 Function.prototype.call 顯然都是被一個函數(Function)調用的。
其中apply接受兩個參數,第一個參數指定了函數體內this對象的指向(之前說過,this總是指向一個對象),第二個參數是一個到右下標的集合,這個集合可以是數組,也可以是類數組,apply把這個集合中的元素作為參數傳遞給被調用的函數。 而call也接收兩個參數,同樣的,第一個參數指定了函數體內的this對象的指向,第二個參數是這個函數需要接受的參數(沒有call和apply函數也要接收參數啊!)。舉例如下:
var myObject = { c: 66 }; var anotherObject = { c: 88 } var c = 233; function outputNum(a, b) { var c = 10; console.log([a, b, this.c]); } function outputNumSecond(a, b) { c = 10; console.log([a, b, this.c]); } outputNum(1, 2); // [1, 2, 233] outputNum.apply(null, [1,2]); // [1, 2, 233] outputNum.apply(window, [1,2]); // [1, 2, 233] outputNum.call(null, 1, 2); // [1, 2, 233] outputNum.call(window, 1, 2); // [1, 2, 233] outputNum.apply(myObject, [1, 2]); // [1, 2, 66] outputNum.apply(anotherObject, [1, 2]); // [1, 2, 88] outputNumSecond.apply(null, [1, 2]); // [1, 2, 10]
如果第一個參數是null,那麼this就會指向預設的宿主對象,在瀏覽器中就是window。 但是在strict 模式下, 函數體內的this還是為null。
常見錯誤
JavaScript設計模式(一)