1. 面向?qū)ο蟮腏avaScript

本文源于本人關(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ù)邏輯上分散
  • 動(dòng)態(tài)類型:變量類型要到程序運(yùn)行的時(shí)候,待變量被賦予某個(gè)值之后,才會(huì)具有某種類型。
    • 優(yōu)點(diǎn):
      • 代碼簡潔
      • 精力集中在業(yè)務(wù)邏輯上
    • 缺點(diǎn):
      • 無法保證變量類型,從而程序運(yùn)行期有可能發(fā)生跟類型相關(guān)的錯(cuò)誤。

鴨子類型

我需要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,并返回該值。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容