JavaScript 面向?qū)ο缶幊?/h1>

前言

面向?qū)ο缶幊叹褪菍⒛愕男枨蟪橄蟪梢粋€對象,針對這個對象分析其特征(屬性)和動作(方法)犀被,這個對象稱為“類”椅您。JavaScript 的核心是支持面向?qū)ο蟮模瑫r它也提供了強大靈活的 OOP 語言能力寡键,遺憾的是對于JavaScript這種解釋性的弱類型語言掀泳,沒有強類型語言中那種通過class等關(guān)鍵字實現(xiàn)類的方式,但JavaScript可以通過一些特性模仿實現(xiàn)面向?qū)ο缶幊獭?/p>

面向?qū)ο笥腥齻€基本特征:封裝西轩、繼承员舵、多態(tài)

封裝

封裝藕畔,就是把客觀事物封裝成抽象的類固灵,類中包含了事物的屬性和方法,并且類可以把自己的數(shù)據(jù)和方法只讓可信的類或者對象操作劫流,對不可信的進行信息隱藏。
JavaScript創(chuàng)建一個類很容易,通過聲明一個函數(shù)保存在一個變量里來實現(xiàn)祠汇,這個類的類名通常會采用首字母大寫的形式來表示仍秤,然后在這個函數(shù)(類)的內(nèi)部使用this關(guān)鍵字來定義類的屬性和方法。例如:

var Person = function(name,sex,age){
    this.name = name;
    this.sex = sex;
    this.age = age;
}

也可以在類的原型上添加屬性和方法可很。例如:

Person.prototype.say = function(){
    // Say something
}

或者

Person.prototype = {
    say: function(){ … }
}

這樣就完成了Person類的封裝诗力,當我們要使用這個類時,需要通過new關(guān)鍵字來實例化(創(chuàng)建)一個新的對象我抠,通過.操作符訪問對象的屬性和方法苇本。例如:

var person = new Person('Scott','male',20);
console.log(person.name);  // Scott

通過this添加的屬性和方法是在當前對象上添加的,而JavaScript是一種基于原型prototype的語言菜拓,每創(chuàng)建一種對象時瓣窄,都有一個原型prototype用于指向其繼承的屬性和方法,通過prototype繼承的屬性和方法不是屬于對象本身的纳鼎,在使用這些方法時俺夕,會通過原型鏈進行查找。當創(chuàng)建一個對象時贱鄙,會創(chuàng)建this指向的屬性和方法劝贸,而通過prototype繼承的屬性或方法是該類的每個對象所共有的,不會再次創(chuàng)建逗宁。

當創(chuàng)建一個函數(shù)或者對象時都會為其創(chuàng)建一個prototype對象映九,原對象中的__proto__屬性指向該原型對象,prototype對象中會有一個constructor屬性指向擁有整個原型對象的函數(shù)或者類瞎颗。

通過new關(guān)鍵字創(chuàng)建對象時實際上是對新對象中this的不斷賦值件甥,并將prototype指向類的原型對象,而在類外通過.操作符定義的屬性和方法是不會添加到新建對象上的言缤,通過對象進行訪問的結(jié)果是undefined嚼蚀。例如:

Person.isChinese = true;
Person.eat = function(){ … }

var person = new Person("Alex","female",19);
console.log(person.name);    // Alex
console.log(person.isChinese);    // undefined
console.log(person.eat());    // undefined

如果你忽略了new關(guān)鍵字直接調(diào)用類,如:var person = Person("Alex","female",19);管挟,此時會直接調(diào)用Person這個函數(shù)轿曙,如果這個函數(shù)是在全局作用域里執(zhí)行的,則此時類中的this指向的當前對象就是全局變量僻孝,在頁面中全局變量就是window导帝,所以通過this添加的屬性或方法會被添加到window中,并且最終的person對象會是undefined穿铆。

要解決這個問題可以采用“安全模式”您单,例如:

var Person = function(name,sex,age){
    if(this instanceof Person){
        this.name = name;
        this.sex = sex;
        this.age = age;
    }else{    // 未使用 new
        return new Person(name,sex,age);
    }
}

var person = Person("Scott","male",20);

這樣就不用當心創(chuàng)建對象時忘記使用new關(guān)鍵字了。

繼承

繼承可以使用現(xiàn)有類的所有功能荞雏,并在無需重新編寫原來的類的情況下對這些功能進行擴展虐秦。繼承所涉及的對象不止一個平酿,JavaScript并沒有提供繼承這一現(xiàn)有的機制,也正因為JavaScript少了這些顯性的限制悦陋,使其更具有靈活性蜈彼。在JavaScript中可以使用類式繼承構(gòu)造函數(shù)繼承俺驶、組合繼承來達到繼承的效果幸逆。

類式繼承

// 聲明父類
function Parent(){
    this.parentValue = true;
}
// 為父類添加共有方法
Parent.prototype.getParentValue = function(){
    return this.parentValue;
}

// 聲明子類
function Child(){
    this.childValue = false;
}
// 繼承父類
Child.prototype = new Parent();
// 為子類添加共有方法
Child.prototype.getChildValue = function(){
    return this.childValue;
}

類的原型對象的作用是為類的原型添加共有屬性和方法,但類必須通過原型prototype來訪問這些屬性和方法暮现。當實例化一個父類時还绘,新建對象復(fù)制了父類構(gòu)造函數(shù)內(nèi)的屬性和方法,并且將原型__proto__指向了父類的原型對象栖袋,這樣就擁有了父類原型對象上的屬性和方法拍顷,新建對象可以直接訪問父類原型對象的屬性和方法,接著將這個新建的對象賦值給子類的原型栋荸,那么子類的原型就可以訪問父類的原型屬性和方法菇怀。將這個對象賦值給子類的原型,那么這個子類就可以訪問父類原型上的屬性和方法晌块,并且可以訪問從父類構(gòu)造函數(shù)中復(fù)制的屬性和方法爱沟。我們可以來測試一下:

var child = new Child();
console.log(child.getParentValue());      // true
console.log(child.getChildValue());       // false
console.log(child instanceof Parent);     // true
console.log(child instanceof Child);      // true
console.log(Child instanceof Parent);     // false

但這種繼承方式有2個缺點:

  • 由于子類是通過其原型prototype對父類實例化,如果父類中的共有屬性是引用類型匆背,會被所有實例所共享呼伸,一個子類的實例修改了該屬性會直接影響到所有實例。例如:
function Parent(){
    this.values = ['A','B','C'];
}
function Child(){}
Child.prototype = new Parent();
var child1 = new Child();
var child2 = new Child();
console.log(child2.values);    // ["A","B","C"]
child1.values.push('D');
console.log(child2.values);    // ["A","B","C","D"]
  • 創(chuàng)建父類實例時钝尸,是無法向父類傳遞參數(shù)的括享,也就是無法對父類構(gòu)造函數(shù)內(nèi)的屬性進行初始化。例如這種錯誤的繼承方式:
function Parent(name){
    this.name = name;
}
function Child(){}
Child.prototype = new Parent('name');    // 錯誤

構(gòu)造函數(shù)繼承

// 聲明父類
function Parent(name){
    this.name = name;
    this.values = ['A','B','C'];
}
Parent.prototype.showName = function(){
    console.log(this.name);
}
// 聲明子類
function Child(name){
    Parent.call(this,name);
}
var child1 = new Child('one');
var child2 = new Child('two');
child1.values.push('D');
console.log(child1.name);   // one
console.log(child1.values); // ["A","B","C","D"]
console.log(child2.name);   // two
console.log(child2.values); // ["A","B","C"]
child1.showName();          // TypeError

語句Parent.call(this,name);是構(gòu)造函數(shù)繼承的精華珍促,call方法可以更改函數(shù)的作用環(huán)境铃辖,在子類中執(zhí)行該方法相當于將子類的變量在父類中執(zhí)行一遍,此時父類方法中的this屬性指的是子類中的this猪叙,由于父類中是給this綁定屬性的娇斩,所以子類也就繼承了父類的屬性和方法。構(gòu)造函數(shù)繼承并沒有涉及原型prototype穴翩,所以父類的原型方法不會被子類繼承犬第,子類的每個實例會單獨擁有一份父類的屬性方法而不能共用,如果想被子類繼承就必須放在構(gòu)造函數(shù)中芒帕,要實現(xiàn)這樣的效果可以采用組合繼承的方式歉嗓。

組合繼承

類式繼承是通過子類的原型prototype對父類實例化來實現(xiàn)的,構(gòu)造函數(shù)繼承是通過在子類的構(gòu)造函數(shù)作用環(huán)境中執(zhí)行一次父類的構(gòu)造函數(shù)來實現(xiàn)的背蟆,而組合繼承則同時做到這兩點鉴分。

// 聲明父類
function Parent(name){
    this.name = name;
    this.values = ['A','B','C'];
}
Parent.prototype.getName = function(){
    console.log(this.name);
}
// 聲明子類
function Child(name,id){
    Parent.call(this, name);
    this.id = id;
}
Child.prototype = new Parent();
Child.prototype.getId = function(){
    console.log(this.id);
}

var child1 = new Child('child1', 1);
child1.values.push('D');
console.log(child1.values); // ["A", "B", "C", "D"]
child1.getName();           // child1
child1.getId();             // 1

var child2 = new Child('child2', 2);
console.log(child2.values);    // ["A", "B", "C"]
child2.getName();              // child2
child2.getId();                // 2

子類的實例中更改父類繼承下來的引用類型屬性哮幢,不會影響到其它實例,并且子類實例化過程中又能將參數(shù)傳遞到父類的構(gòu)造函數(shù)中冠场。

多態(tài)

多態(tài)就是同一個方法多種調(diào)用方式家浇,JavaScript可以通過對傳入的參數(shù)列表arguments進行判斷來實現(xiàn)多種調(diào)用方式。例如:

function Add(){
    // 無參數(shù)
    function zero(){
        return 0;
    }
    // 一個參數(shù)
    function one(num){
        return num;
    }
    // 兩個參數(shù)
    function two(num1, num2){
        return num1 + num2;
    }

    this.add = function(){
        // 獲取參數(shù)列表及參數(shù)個數(shù)
        var arg = arguments,
            len = arg.length;
        switch(len){
            case 0:
                return zero();
            case 1:
                return one(arg[0]);
            case 2:
                return two(arg[0], arg[1]);
        }
    }
}

var A = new Add();
console.log(A.add());       // 0
console.log(A.add(1));      // 1
console.log(A.add(1,2));    // 3

當調(diào)用add進行運算時碴裙,會根據(jù)參數(shù)列表的不同做相應(yīng)的運算,這就是JavaScript的多態(tài)實現(xiàn)方式点额。

總結(jié)

面向?qū)ο笤O(shè)計方法的應(yīng)用解決了傳統(tǒng)結(jié)構(gòu)化開發(fā)方法中客觀世界描述工具與軟件結(jié)構(gòu)的不一致性問題舔株,縮短了開發(fā)周期,解決了從分析和設(shè)計到軟件模塊結(jié)構(gòu)之間多次轉(zhuǎn)換映射的繁雜過程还棱,是一種高效率的軟件開發(fā)方式载慈,特別是在多人協(xié)作開發(fā)的情況下,可以提高代碼的可復(fù)用性和維護性珍手,使開發(fā)更有效率办铡。

JavaScript系列文章:

本文為作者kMacro原創(chuàng),轉(zhuǎn)載請注明來源:http://www.reibang.com/p/c2083cf275ec琳要。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者

  • 序言:七十年代末寡具,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子稚补,更是在濱河造成了極大的恐慌童叠,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,734評論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件课幕,死亡現(xiàn)場離奇詭異厦坛,居然都是意外死亡,警方通過查閱死者的電腦和手機乍惊,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,931評論 3 394
  • 文/潘曉璐 我一進店門杜秸,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人润绎,你說我怎么就攤上這事撬碟。” “怎么了凡橱?”我有些...
    開封第一講書人閱讀 164,133評論 0 354
  • 文/不壞的土叔 我叫張陵小作,是天一觀的道長。 經(jīng)常有香客問我稼钩,道長顾稀,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,532評論 1 293
  • 正文 為了忘掉前任坝撑,我火速辦了婚禮静秆,結(jié)果婚禮上粮揉,老公的妹妹穿的比我還像新娘。我一直安慰自己抚笔,他們只是感情好扶认,可當我...
    茶點故事閱讀 67,585評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著殊橙,像睡著了一般辐宾。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上膨蛮,一...
    開封第一講書人閱讀 51,462評論 1 302
  • 那天叠纹,我揣著相機與錄音,去河邊找鬼敞葛。 笑死誉察,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的惹谐。 我是一名探鬼主播持偏,決...
    沈念sama閱讀 40,262評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼氨肌!你這毒婦竟也來了鸿秆?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,153評論 0 276
  • 序言:老撾萬榮一對情侶失蹤儒飒,失蹤者是張志新(化名)和其女友劉穎谬莹,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體桩了,經(jīng)...
    沈念sama閱讀 45,587評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡附帽,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,792評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了井誉。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片蕉扮。...
    茶點故事閱讀 39,919評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖颗圣,靈堂內(nèi)的尸體忽然破棺而出喳钟,到底是詐尸還是另有隱情,我是刑警寧澤在岂,帶...
    沈念sama閱讀 35,635評論 5 345
  • 正文 年R本政府宣布奔则,位于F島的核電站,受9級特大地震影響蔽午,放射性物質(zhì)發(fā)生泄漏易茬。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,237評論 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望抽莱。 院中可真熱鬧范抓,春花似錦、人聲如沸食铐。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,855評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽虐呻。三九已至象泵,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間铃慷,已是汗流浹背单芜。 一陣腳步聲響...
    開封第一講書人閱讀 32,983評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留犁柜,地道東北人。 一個月前我還...
    沈念sama閱讀 48,048評論 3 370
  • 正文 我出身青樓堂淡,卻偏偏與公主長得像馋缅,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子绢淀,可洞房花燭夜當晚...
    茶點故事閱讀 44,864評論 2 354

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