本文源于本人關(guān)于《JavaScript設(shè)計(jì)模式與開發(fā)實(shí)踐》(曾探著)的閱讀總結(jié)。想詳細(xì)了解具體內(nèi)容建議閱讀該書。
1. 動(dòng)態(tài)類型語言和鴨子類型
語言類型
- 靜態(tài)類型:在編譯時(shí)便已確定變量的類型。
- 優(yōu)點(diǎn):
- 發(fā)現(xiàn)類型匹配不正確
- 提高程序執(zhí)行速度
- 缺點(diǎn):
- 迫使程序員按照強(qiáng)契約來編寫程序
- 類型聲明增加很多代碼
- 讓程序員精力從業(yè)務(wù)邏輯上分散
- 優(yōu)點(diǎn):
- 動(dòng)態(tài)類型:變量類型要到程序運(yùn)行的時(shí)候,待變量被賦予某個(gè)值之后,才會(huì)具有某種類型。
- 優(yōu)點(diǎn):
- 代碼簡潔
- 精力集中在業(yè)務(wù)邏輯上
- 缺點(diǎn):
- 無法保證變量類型,從而程序運(yùn)行期有可能發(fā)生跟類型相關(guān)的錯(cuò)誤。
- 優(yōu)點(diǎn):
鴨子類型
我需要1000只鴨子組成合唱團(tuán)來給村長唱歌,但是找遍全村只有999支鴨子,但是有一只雞的叫聲像鴨子,于是它就加入了這支合唱團(tuán)。
鴨子類型指導(dǎo)我們只用關(guān)心對象的行為,而不需要關(guān)注對象本身。
在動(dòng)態(tài)語言面向?qū)ο笤O(shè)計(jì)中,鴨子類型的概念至關(guān)重要,利用鴨子類型思想,我們不必借助超類型的幫助,就能輕松實(shí)現(xiàn)一個(gè)原則:“面向接口編程,而不是面向?qū)崿F(xiàn)編程”。
2. 多肽:同一操作作用于不同的對象上面,可以產(chǎn)生不同的解釋和不同的執(zhí)行結(jié)果。
var makeSound = function(animal) {
if(animal instanceof Duck){
console.log('gagaga');
}else if(animal instanceof Chicken){
console.log('gogogo');
}
}
var Duck = function(){}
var Chicken = function(){}
makeSound(new Duck()); // gagaga
makeSound(new Chicken()); // gogogo
這是一段多肽,問題在于:如果下一次又加了一條狗,那么我們不得不修改makeSound,我們希望makeSound就像一條指令,指令不會(huì)發(fā)生任何變換,但是響應(yīng)指令的對象會(huì)根據(jù)自身的特性發(fā)出不同的動(dòng)作,類似于電影拍攝:
導(dǎo)演一聲action,主角開始背單詞,照明師負(fù)責(zé)打燈光,后面的群眾演員也根據(jù)自己的劇本上的內(nèi)容作出相應(yīng)反應(yīng)。而不是一聲action之后,需要分別走到主角面前告訴他要干什么,然后又走向燈光師告訴他要做什么(對應(yīng)到上例的條件分枝)。
所以最終我們的代碼應(yīng)該這么寫:
var Duck = function(){}
Duck.prototype.sound = function(){
console.log('gagaga');
}
var Chicken = function(){}
Chicken.prototype.sound = function(){
console.log('gogogo');
}
var makeSound = function(animal){
animal.sound();
}
由于js是動(dòng)態(tài)語言類型,故不需要類型檢查,如果換成Java則需要使用[抽象類動(dòng)物,接口:叫],[鴨子,gagaga],[雞,gogogo]這樣的繼承方式實(shí)現(xiàn)多肽。
3. 封裝
將信息或者其他東西進(jìn)行隱藏,外部不需要關(guān)心內(nèi)部實(shí)現(xiàn),只要接口返回的內(nèi)容保持不變就可以。
封裝數(shù)據(jù)
js沒有private,public,protected這些關(guān)鍵字來提供不同的權(quán)限,,但js能依賴變量的作用域模仿出public和private這兩種封裝性。
var myObject = (function(){
var _name = 'yozo';
return {
getName: function() {
return _name;
},
square: function(a) {
return a*a;
}
}
})()
console.log(_name) // undefined
console.log(myObject.getName()); // yozo
_name在外部不能訪問, 達(dá)到了private的效果,getName暴露了出來,達(dá)到了public的效果。
封裝實(shí)現(xiàn)
類似于上例子的square方法,它的作用是求一個(gè)數(shù)的平方,我們只需要調(diào)用該接口就好,我們并不需要關(guān)注內(nèi)部的具體實(shí)現(xiàn),換句話說我們可以隨意修改內(nèi)部實(shí)現(xiàn),只要對外接口沒有變化,就不會(huì)影響到其他程序,使對象之間的關(guān)系變得松散。
封裝類型
就像鴨子類型,我們不需要關(guān)心具體是什么類型,只要關(guān)注行為就好。js本身為動(dòng)態(tài)類型,我們并不需要再此方面做更多。
封裝變化
var getUserInfo = function(userId, callback){
$.ajax('http://xxx.com/getUserInfo?' + userId, function({
if(typeof callback === 'function'){
callback(data)
}
})
}
getUserInfo(123, function(data){
console.log(data.userName);
})
區(qū)分變化, 不變化的部分是 永遠(yuǎn)都要利用userId獲得用戶信息,變化的部分是,獲取數(shù)據(jù)之后究竟應(yīng)該觸發(fā)什么樣的行為。這可以最大程度保證程序的可擴(kuò)展性和穩(wěn)定性。當(dāng)我們想辦法把程序中變化的部分封裝好了之后,剩下的即是穩(wěn)定而可復(fù)用的部分了。
4. 原型模式和基于原型模式的JavaScript對象系統(tǒng)
使用克隆的原型模式:
創(chuàng)建對象的一種模式,不再關(guān)心對象的具體類型而是找到一個(gè)對象,然后通過克隆創(chuàng)建一個(gè)一摸一樣的對象:
var Plane = function(){
this.blood = 100;
this.attckLevel = 1;
this.defenseLevel = 1;
}
var plane = new Plane();
plane.blood = 1000;
plane.attckLevel = 7;
plane.defenseLevel = 10;
var clonePlane = Object.create(plane);
console.log(clonePlane.blood);
console.log(clonePlane.attckLevel);
console.log(clonePlane.defenseLevel);
JavaScript中的原型繼承
所有的數(shù)據(jù)都是對象
基本類型包括:undefined, number, boolean, string, function, object。
除了undefined之外,一切都應(yīng)是對象,number boolean string也可以通過“包裝類”的方式來變?yōu)閷ο蟆?/p>
事實(shí)上,js中的根對象是Object.prototype,該對象是一個(gè)空對象,我們在js遇到的每個(gè)對象都是從該對象克隆而來。
要得到一個(gè)對象,不是通過實(shí)例化類,而是找到一個(gè)對象作為原型并克隆它。
function Person(name) {
this.name = name;
};
Person.prototype.getName = function () {
return this.name;
};
var a = new Person('yozo');
var objectFactory = function() {
var obj = new Object();
Constructor = [].shift.call(arguments);
obj.__proto__ = Constructor.prototype; // 指向正確的原型
var ret = Constructor.apply(obj, arguments);
return typeof ret === 'object' ? ret : obj; // 確保構(gòu)造器總是返回一個(gè)對象
}
var a1 = objectFactory(Person, 'yoko');
console.log(a.getName());
console.log(a1.getName());
通常在js中我們也會(huì)使用new方法,new這樣的寫法看來像是類的實(shí)例化,其實(shí)此時(shí)的Person就像一個(gè)構(gòu)造器,用new來創(chuàng)建對象的過程,實(shí)際上也只是先克隆Object.prototype對象,再做一些額外操作。我們可以通過objectFactory來理解new的運(yùn)算過程。
對象會(huì)記住它的原型
目前我們一直都在說對象的原型,其實(shí)就js真正的實(shí)現(xiàn)來說,其實(shí)并不能說對象有原型,而只能說對象的構(gòu)造器有原型。
var a = new Object();
console.log( a.__proto__ === Object.prototype ) // true
對象如果無法響應(yīng)某個(gè)請求,它會(huì)把這個(gè)請求委托給它的構(gòu)造器原型
var A = function(){};
a.prototype = { name:'yozo' };
var B = function(){};
B.prototype = new A();
var b = new B();
console.log(b.name); // 輸出yozo
- 首先遍歷b的所有屬性,沒有找到name這個(gè)屬性;
- 委托給構(gòu)造器的原型,由于
B.prototype = new A();
,查找new A()
這個(gè)對象。 - 由于
new A()
這個(gè)匿名對象也沒有name這個(gè)屬性,于是查找A.prototype。 - 找到了name,并返回該值。