第一部分-基礎(chǔ)知識(1)-面向?qū)ο蟮腏avaScript

JavaScript 沒有提供傳統(tǒng)面向?qū)ο笳Z言中的類式繼承,而是通過原型委托的方式來實現(xiàn)對象與對象之間的繼承。

  • <a href="#no1">1.1 動態(tài)類型語言和鴨子類型</a>
  • <a href="#no2">1.2 多態(tài)</a>
  • <a href="#no3">1.3 封裝</a>
  • <a href="#no4">1.4 原型模式和基于原型繼承的JavaScript對象系統(tǒng)</a>

<a name="no1">1.1 動態(tài)類型語言和鴨子類型</a>

編程語言按照數(shù)據(jù)類型大體可以分為兩類:一類是靜態(tài)類型語言,另一類是動態(tài)類型語言。

  • 靜態(tài)類型語言在編譯時便已確定變量的類型。
  • 動態(tài)類型怨言的變量類型要到程序運行的時候,待變量被賦予某個值之后,才會具有某種類型。

靜態(tài)類型語言的優(yōu)點:1、在編譯時就能發(fā)現(xiàn)類型不匹配的錯誤。2、如果在程序中明確的規(guī)定了數(shù)據(jù)類型,編譯器還可以針對這些信息對程序進行一些優(yōu)化工作,提高程序執(zhí)行速度。

靜態(tài)類型語言的缺點:1、迫使程序員依照強契約來編寫程序,為每個變量規(guī)定數(shù)據(jù)類型。2、類型的聲明會增加更多的代碼,在程序的編寫過程中,這些細節(jié)會讓程序員的 精力從思考業(yè)務(wù)邏輯上分散開來。

動態(tài)類型語言的優(yōu)點:編寫的代碼數(shù)量更少,看起來也更加簡潔,程序員可以把精力更多的放在業(yè)務(wù)邏輯上面。

動態(tài)類型語言的缺點:無法保證變量的類型,從而在程序的運行期有可能發(fā)生跟類型相關(guān)的錯誤。

var duck = {
    duckSinging : function() {
        console.log('嘎嘎嘎');
    }
};

var chicken = {
    duckSinging : function() {
        console.log('嘎嘎嘎');
    }
};

var choir = []; //合唱團

var joinChoir = function(animal) {
    if (animal && typeof animal.duckSinging === 'function') {
        choir.push(animal);
        console.log('恭喜加入合唱團');
        console.log('合唱團已有成員數(shù)量:' + choir.length);
    }
};

joinChoir(duck);    //恭喜加入合唱團
joinChoir(chicken); //恭喜加入合唱團

<a name="no2">1.2 多態(tài)</a>

多態(tài)的實際含義是 : 同一操作作用于不同的對象上面,可以產(chǎn)生不同的解釋和不同的執(zhí)行結(jié)果。換句話說,給不同的對象發(fā)送同一消息的時候,這些對象會根據(jù)這個消息分別給出不同的反饋。

1.2.1 一段“多態(tài)”的JavaScript代碼

var makeSound = function(animal){
    if (animal instanceof Duck) {
        console.log('嘎嘎嘎');
    }else if(animal instanceof Chicken){
        console.log('咯咯咯');
    }
}

var Duck = function(){};
var Chicken = function(){};

makeSound(new Duck());  //嘎嘎嘎
makeSound(new Chicken());  //咯咯咯

多態(tài)背后的思想是將“做什么”和“誰去做以及怎樣去做”分離開來,也就是將“不變的事物”與“可能改變的事物”分離開來。

1.2.2 對象的多態(tài)性

// 把不變的部分隔離出來
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());  //咯咯咯
makeSound(new Dog());  //汪汪汪

1.2.3 類型檢查和多態(tài)

類型檢查是在表現(xiàn)出對象多態(tài)性之前的一個繞不開的話題,但JavaScript是一門不必進行類型檢查的動態(tài)類型語言。

1.2.4 使用繼承得到多態(tài)效果

使用繼承來得到多態(tài)效果,是讓對象表現(xiàn)出多態(tài)性的最常用的手段。繼承通常包括實現(xiàn)繼承和接口繼承。

1.2.5 JavaScript的多態(tài)

JavaScript作為一門動態(tài)類型語言,與生俱來的有多態(tài)性。

1.2.6 多態(tài)在面向?qū)ο蟪绦蛟O(shè)計中的作用

多態(tài)最根本的作用就是通過把過程化的條件分支語句轉(zhuǎn)化為對象的多態(tài)性,從而消除這些條件分支語句。

//假設(shè)我們要編寫一個地圖應(yīng)用,現(xiàn)在有兩家可選的地圖API提供商供我們接入自己的應(yīng)用。
//目前我們選擇的是谷歌地圖,谷歌地圖的API中提供了show方法,負責在頁面上展示整個地圖。示例代碼如下:

var googleMap = {
    show : function() {
        console.log('開始渲染谷歌地圖');
    }
};

var renderMap = function() {
    googleMap.show();
}

renderMap();    //輸出:開始渲染谷歌地圖

// 后來因為某些原因,要把谷歌地圖換成百度地圖,為了讓renderMap函數(shù)保持一定的彈性
// ,我們用一些條件分支來讓renderMap函數(shù)同事支持谷歌地圖和百度地圖:

var googleMap = {
    show : function() {
        console.log('開始渲染谷歌地圖');
    }
};

var baiduMap = {
    show : function() {
        console.log('開始渲染百度地圖');
    }
};


var renderMap = function(type) {
    if (type === 'google') {
        googleMap.show();
    } else if (type === 'baidu') {
        baiduMap.show();
    }
}

renderMap('google');    //輸出:開始渲染谷歌地圖
renderMap('baidu');     //輸出:開始渲染百度地圖



// 將程序中相同的部分抽象出來,那就是顯示某個地圖:
var renderMap = function(map) {
    if (map.show instanceof Function) {
        map.show();
    }
};

renderMap(googleMap);   //輸出:開始渲染谷歌地圖
renderMap(baiduMap);    //輸出:開始渲染百度地圖

1.2.7 設(shè)計模式與多態(tài)

詳見《JavaScript設(shè)計模式與開發(fā)實踐》P11

<a name="no3">1.3 封裝</a>

封裝的目的是將信息隱藏。一般而言,我們討論的封裝是封裝數(shù)據(jù)和封裝實現(xiàn)。這里不僅包括封裝數(shù)據(jù)和封裝實現(xiàn),還包括封裝類型和封裝變化。

1.3.1 封裝數(shù)據(jù)

在JavaScript中,我們只能以來變量的作用域來實現(xiàn)封裝特性,而且只能模擬出 publicprivate 這兩種封裝性。

var myObject = (function() {
    var _name = 'sven'; //私有(private)變量
    return {
        getName: function() { //公開(public)方法
            return _name;
        }
    }
})();

console.log(myObject.getName()); //sven
console.log(myObject._name); //undefined

1.3.2 封裝實現(xiàn)

封裝的目的是將信息隱藏。
從封裝實現(xiàn)細節(jié)來講,封裝使得對象內(nèi)部的變化對其他對象而言是透明的,也就是不可見的。對象對它自己的行為負責。其他對象或用戶不關(guān)心它的內(nèi)部實現(xiàn)。封裝使得對象之間的耦合變松散,對象之間只通過暴露的API接口來通信。

1.3.3 封裝類型

封裝類型是靜態(tài)類型語言中一種重要的封裝方式。一般而言,封裝類型是通過抽象類和接口來進行的。把對象的真正類型隱藏在抽象類或者接口之后,相比對象的類型,客戶更關(guān)心對象的行為。在許多靜態(tài)語言的設(shè)計模式中,想方設(shè)法地去隱藏對象的類型,也是促使這些模式誕生的原因之一。在JavaScrip中,并沒有對抽象類的接口的支持。在封裝類型方面,JavaScript沒有能力,也沒有必要做的更多。

1.3.4 封裝變化

從設(shè)計模式的角度出發(fā),封裝在更重的層面體現(xiàn)為封裝變化

<a name="no4">1.4 原型模式和基于原型繼承的JavaScript對象系統(tǒng)</a>

1.4.1 使用克隆的原型模式

var Plane = function() {
    this.blood = 100;
    this.attackLevel = 1;
    this.defenseLevel = 1;
}

var plane = new Plane();
plane.blood = 500;
plane.attackLevel = 10;
plane.defenseLevel = 7;

var clonePlane = Object.create(plane);
console.log(clonePlane); //輸出: Object{blood:500,attackLevel:10,defenseLevel:7}

// 在不支持Object.create方法的瀏覽器中,則可以使用以下代碼:
Object.create = Object.create || function(obj) {
    var F = function() {};
    F.prototype = obj;
    return new F();
}

1.4.2 克隆是創(chuàng)建對象的手段

1.4.3 體驗lo語言

1.4.4 原型編程范型的一些規(guī)則

  • 所有的數(shù)據(jù)都是對象。
  • 要得到一個對象,不是通過實例化類,而是找到一個對象作為原型并克隆它。
  • 對象會記住它的原型。
  • 如果對象無法響應(yīng)某個請求,它會把這個請求委托給它自己的原型。

1.4.5 JavaScript中的原型繼承

function Person(name) {
    this.name = name;
};

Person.prototype.getName = function() {
    return this.name;
};

var objectFactory = function() {
    var obj = new Object(), //從Object.prototype上克隆一個空的對象
        Constructor = [].shift.call(arguments); //取得外部傳入的構(gòu)造器,這里是Person

    obj.__proto__ = Constructor.prototype;  //指向正確的原型
    var ret = Constructor.apply(obj, arguments);    //借用外部傳入的構(gòu)造器給obj設(shè)置屬性

    return typeof ret === 'object' ? ret : obj; //確保構(gòu)造器總是會返回一個對象
};


var a = objectFactory(Person, 'sven');

console.log(a.name);    //sven
console.log(a.getName());   //sven
console.log(Object.getPrototypeOf(a) === Person.prototype); //true

// 分別調(diào)用下面兩句代碼產(chǎn)生一樣的結(jié)果:
var a = objectFactory(A, 'sven');
var a = new A('sven');

1.4.6 原型繼承的未來

ECMAScritp6帶來了新的Class語法。

class Animal{
    constructor(name) {
        this.name = name;
    }

    getName() {
        return this.name;
    }
}

class Dog extends Animal {
    constructor(name) {
        super(name);
    }

    speak() {
        return 'woof';
    }
}

var dog = new Dog('Scamp');
console.log(dog.getName() + ' says ' + dog.speak());    //Scamp says woof

1.4.2 - 1.4.6

更多請參照《JavaScript設(shè)計模式與開發(fā)實踐》P15 - P23

1.4.7 小結(jié)

本節(jié)講述了第一個設(shè)計模式——原型模式。原型模式是一種設(shè)計模式,也是一種編程泛型,它構(gòu)成了JavaScript這門語言的根本。原型模式十分重要,和JavaScript開發(fā)者的關(guān)系十分密切。通過原型來實現(xiàn)的面向?qū)ο笙到y(tǒng)雖然簡單,但能力同樣強大。

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

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