【北京分院一百七十一期】JS中的面向?qū)ο缶幊?/h2>

1.背景介紹

什么是面向?qū)ο缶幊蹋?/p>

“面向?qū)ο缶幊獭保∣bject OrientedProgramming,縮寫為OOP)是目前主流的編程范式。它的核心思想是將真實(shí)世界中各種復(fù)雜的關(guān)系,抽象為一個(gè)個(gè)對(duì)象,然后由對(duì)象之間的分工與合作,完成對(duì)真實(shí)世界的模擬。

主要概念為:把一組數(shù)據(jù)結(jié)構(gòu)和處理它們的方法組成對(duì)象(object),把相同行為的對(duì)象歸納為類(class),通過類的封裝(encapsulation)隱藏內(nèi)部細(xì)節(jié),通過繼承(inheritance)實(shí)現(xiàn)類的特化(specialization)/泛化(generalization),通過多態(tài)(polymorphism)實(shí)現(xiàn)基于對(duì)象類型的動(dòng)態(tài)分派(dynamicdispatch)。

Javascript是一種基于對(duì)象(object-based)的語言,遇到的東西幾乎都是對(duì)象,但是它不是一種面對(duì)對(duì)象的語言。像其他語言里面的class(類),它就沒辦法直接用了。(聽說ES 6可以用了,筆者一直學(xué)的ES5,6暫未研究,有興趣的同學(xué)可以去看看教程)

2.知識(shí)剖析

2.1對(duì)象的概念

因?yàn)镴S是一個(gè)基于對(duì)象的語言,所以我們遇到的大多數(shù)東西幾乎都是對(duì)象。例如函數(shù)就是一個(gè)對(duì)象,如果你要在js里面新建一個(gè)對(duì)象,這樣寫其實(shí)就是創(chuàng)建了一個(gè)object的實(shí)例。對(duì)象就是一個(gè)容器,封裝了屬性和方法。屬性就是對(duì)象的狀態(tài),比如下面的name屬性。方法就是寫在對(duì)象里面的函數(shù),也就是對(duì)象的行為,比如下面的sayName方法。

var person = new object();

person.name = "Tom";

person.sayNmae = function() {

alert(this.name);

}

2.2 工廠模式
“面向?qū)ο缶幊獭钡牡谝徊?,就是要生成“?duì)象”。但是很多時(shí)候我們不得不面臨重復(fù)生成很多對(duì)象的情況,如果我有一千個(gè)人要記錄他們的信息,像上面這種方法寫的話,大大增加了代碼的重復(fù)量,為了解決這個(gè)問題,人們開始使用工廠模式的一種變體,寫法如下頁。雖然工廠模式解決了代碼復(fù)用的問題,但是卻沒辦法顯示實(shí)例(person1)和對(duì)象o之間的關(guān)系,比如aler(person1 instanceof o);

代碼演示:
function Person(name,age, job) {

this.name = name;

this.age = age;

this.job = job;

this.sayName = function() {

alert(this.name)

};

}

person1 = new Person("Tom",20,"Engineer");

person2 = new Person("Damon",22,"Waiter");

2.2 構(gòu)造函數(shù)

后來就出現(xiàn)了構(gòu)造函數(shù),用來創(chuàng)建特定類型的對(duì)象,可以將實(shí)例和對(duì)象聯(lián)系起來,用到了JS中的“this”,寫法如下:

這樣對(duì)象和實(shí)例之間就有關(guān)系了,以new這種方式調(diào)用構(gòu)造函數(shù)會(huì)經(jīng)歷4個(gè)步驟:

(1)創(chuàng)建一個(gè)新對(duì)象。

(2)將構(gòu)造函數(shù)的作用域賦給新對(duì)象(這個(gè)this就指向了這個(gè)新對(duì)象)。

(3)執(zhí)行函數(shù)內(nèi)代碼(給對(duì)象添加屬性)

(4)返回新對(duì)象。

代碼演示:

function Person(name,age, job) {

this.name = name;

this.age = age;

this.job = job;

this.sayName = function() {

alert(this.name)

};

}

person1 = new Person("Tom",20,"Engineer");

person2 = new Person("Damon",22,"Waiter");

構(gòu)造函數(shù)特點(diǎn):

上面代碼中,Persoon就是構(gòu)造函數(shù),它提供模板,用來生成對(duì)象實(shí)例。為了與普通函數(shù)區(qū)別,構(gòu)造函數(shù)名字的第一個(gè)字母通常大寫。

構(gòu)造函數(shù)的兩個(gè)特點(diǎn):

1.函數(shù)體內(nèi)部使用了this關(guān)鍵字,代表了所要生成的對(duì)象實(shí)例。

2.生成對(duì)象的時(shí)候,必需用new命令,調(diào)用函數(shù)。

如果忘了使用new命令,直接調(diào)用構(gòu)造函數(shù)會(huì)導(dǎo)致構(gòu)造函數(shù)變成普通函數(shù),就不會(huì)生成實(shí)例對(duì)象,并且此時(shí)的this這時(shí)代表全局對(duì)象,將造成一些意想不到的結(jié)果。

var Vehicle = function (){

this.price = 1000;

};

var v = Vehicle();

v.price

// Uncaught TypeError: Cannot read property 'price' of undefined

上面代碼中,調(diào)用Vehicle構(gòu)造函數(shù)時(shí),忘了加上new命令。結(jié)果,price屬性變成了全局變量,而變量v變成了undefined。

因此必須小心,記得使用new命令。

2.3原型和原型鏈

原型prototype

JavaScript的每個(gè)對(duì)象都繼承另一個(gè)對(duì)象,后者稱為“原型” (prototype)對(duì)象。只有null除外,它沒有自己的原型對(duì)象。

原型對(duì)象上的所有屬性和方法,都能被派生對(duì)象共享。這就是JavaScript繼承機(jī)制的基本設(shè)計(jì)。

通過構(gòu)造函數(shù)生成實(shí)例對(duì)象時(shí),會(huì)自動(dòng)為實(shí)例對(duì)象分配原型對(duì)象。每一個(gè)構(gòu)造函數(shù)都有一個(gè)prototype屬性,這個(gè)屬性就是實(shí)例對(duì)象的原型對(duì)象。

原型鏈

對(duì)象的屬性和方法,有可能是定義在自身,也有可能是定義在它的原型對(duì)象。由于原型本身也是對(duì)象,又有自己的原型,所以形成了一條原型鏈(prototype

chain)。比如,a對(duì)象是b對(duì)象的原型,b對(duì)象是c對(duì)象的原型,以此類推。

“原型鏈”的作用是,讀取對(duì)象的某個(gè)屬性時(shí),JavaScript引擎先尋找對(duì)象本身的屬性,如果找不到,就到它的原型去找,如果還是找不到,就到原型的原型去找。如果直到最頂層的Object.prototype還是找不到,則返回undefined。

需要注意的是,一級(jí)級(jí)向上,在原型鏈尋找某個(gè)屬性,對(duì)性能是有影響的。所尋找的屬性在越上層的原型對(duì)象,對(duì)性能的影響越大。如果尋找某個(gè)不存在的屬性,將會(huì)遍歷整個(gè)原型鏈。

利用原型(prototype)的繼承特性,我們可以將我們的函數(shù)寫成

function Person() {

};

Person.prototype.name = "Tom";

Person.prototype.age = "20";

Person.prototype.job = "engineer";

Person.prototype.sayName = function() {

alert(this.name);

};

var person1 = new Person();

var person2 = new Person();

alert(person1.sayName == person2.sayName); //true

因?yàn)樵偷睦^承,person1和person2的prototype都指向Person的prototype,所以這兩個(gè)函數(shù)其實(shí)是相等的。但是用工廠函數(shù)或者構(gòu)造模式, alert(person1.sayName == person2.sayName);就絕對(duì)不會(huì)為真了。

奇淫巧技1:每次寫屬性都要加一個(gè)prototype是不是很麻煩,其實(shí)還有另外一種寫法

function Person() {

}

Person.prototype = {

name : "Tom";

age? : "20";

job : "engineer";

sayName : function() {

alert(this.name);

}

}

var person1 = new Person();

var person2 = new Person();

alert(person1.sayName == person2.sayName); //true

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

讓一個(gè)構(gòu)造函數(shù)繼承另一個(gè)構(gòu)造函數(shù),是非常常見的需求。

也有多種方法實(shí)現(xiàn),各有優(yōu)缺點(diǎn)。比如現(xiàn)在有一個(gè)動(dòng)物對(duì)象的構(gòu)造函數(shù),和一個(gè)貓對(duì)象的構(gòu)造函數(shù)。

function Animal() {

this.species = “動(dòng)物”;

};

function Cat(name,color) {

this.name = name;

this.color = color;

}

如何才能使Cat繼承Animal呢?

2.4.1 構(gòu)造函數(shù)綁定

第一種方法也是最簡單的方法,使用call或apply方法,將父對(duì)象的構(gòu)造函數(shù)綁定在子對(duì)象上,即在子對(duì)象構(gòu)造函數(shù)中加一行:

function Cat(name,color){

Animal.apply(this, arguments); //加的

this.name = name;

this.color = color;

}

var cat1 = new Cat("大毛","黃色");

alert(cat1.species); // 動(dòng)物

2.4.2 prototype(原型)模式

第二種方法更常見,使用prototype屬性。

如果"貓"的prototype對(duì)象,指向一個(gè)Animal的實(shí)例,那么所有"貓"的實(shí)例,就能繼承Animal了。

Cat.prototype = new Animal();

Cat.prototype.constructor = Cat;

var cat1 = new Cat("大毛","黃色");

alert(cat1.species); // 動(dòng)物

代碼的第一行,我們將Cat的prototype對(duì)象指向一個(gè)Animal的實(shí)例。相當(dāng)于將Cat原先的原型對(duì)象刪除,重新賦一個(gè)Animal實(shí)例的值。但是任何一個(gè)prototype對(duì)象都有一個(gè)constructor屬性,指向它的構(gòu)造函數(shù)。這個(gè)時(shí)候Cat的構(gòu)造函數(shù)也改變了,變成了Animal。

2.4.2 prototype(原型)模式

所以我們需要“Cat.prototype.constructor = Cat”將Cat的構(gòu)造函數(shù)重新指向?yàn)镃at,不然的話會(huì)很容易出問題。

這是很重要的一點(diǎn),編程時(shí)務(wù)必要遵守。如果替換了prototype對(duì)象,

b.prototype = new a();

那么,下一步必然是為新的prototype對(duì)象加上constructor屬性,并將這個(gè)屬性指回原來的構(gòu)造函數(shù)。b.prototype.constructor = b;

2.4.3 直接繼承prototype(原型)

第三種方法是對(duì)第二種方法的改進(jìn)。由于Animal對(duì)象中,不變的屬性都可以直接寫入Animal.prototype。所以,我們也可以讓Cat()跳過 Animal(),直接繼承Animal.prototype。現(xiàn)在我們將Animal對(duì)象改寫

function Animal() {

Animal.prototype.species = "動(dòng)物";

}

然后,將Cat的prototype對(duì)象,指向Animal的prototype對(duì)象,這樣就完成了繼承。

Cat.prototype = Animal.prototype;

Cat.prototype.constructor = Cat;

var cat1 = new Cat("大毛","黃色");

alert(cat1.species); // 動(dòng)物

2.4.3 直接繼承prototype(原型)

與前一種方法相比,這樣做的優(yōu)點(diǎn)是效率比較高(不用執(zhí)行和建立Animal的實(shí)例了),比較省內(nèi)存。缺點(diǎn)是 Cat.prototype和Animal.prototype現(xiàn)在指向了同一個(gè)對(duì)象,那么任何對(duì)Cat.prototype的修改,都會(huì)反映到Animal.prototype。所以Animal.prototype的構(gòu)造函數(shù)也變成了Cat。

這個(gè)時(shí)候我們就需要引入一個(gè)空對(duì)象作為中轉(zhuǎn)的中介,無論Cat的constructor如何變,只會(huì)影響到中轉(zhuǎn)對(duì)象F而無法影響到父對(duì)象Animal了。

var F = function(){};

F.prototype = Animal.prototype;

Cat.prototype = new F();

Cat.prototype.constructor = Cat;

2.4.3 直接繼承prototype(原型)

然后我們將上述方法封裝成為一個(gè)函數(shù),使用起來就很方便了

function extend(Child, Parent) {

var F = function(){};

F.prototype = Parent.prototype;

Child.prototype = new F();

Child.prototype.constructor = Child;

Child.uber = Parent.prototype;

}

2.4.3 直接繼承prototype(原型)

使用的時(shí)候方法如下:

extend(Cat,Animal);

var cat1 = new Cat("大毛","黃色");

alert(cat1.species); // 動(dòng)物

奇淫巧技2:封裝函數(shù)的時(shí)候怎么方便怎么寫不必太過考慮語義化的東西,比如寫個(gè)狀態(tài)機(jī),直接將狀態(tài)用數(shù)字表示,這樣比字符串的形式好判斷多了。但是一點(diǎn)也不語義化。

3.常見問題

必須要聲明new來創(chuàng)建實(shí)例對(duì)象嗎?

4.解決方案

1.必須要聲明new來創(chuàng)建實(shí)例對(duì)象嗎?

為了保證構(gòu)造函數(shù)必須與new命令一起使用,一個(gè)解決辦法是,在構(gòu)造函數(shù)內(nèi)部使用嚴(yán)格模式,即第一行加上use strict。

function Fubar(foo, bar){

'use strict';

this._foo = foo;

this._bar = bar;

}

Fubar();

// TypeError: Cannot set property '_foo' of undefined

上面代碼的Fubar為構(gòu)造函數(shù),use

strict命令保證了該函數(shù)在嚴(yán)格模式下運(yùn)行。由于在嚴(yán)格模式中,函數(shù)內(nèi)部的this不能指向全局對(duì)象,默認(rèn)等于undefined,導(dǎo)致不加new調(diào)用會(huì)報(bào)錯(cuò)(JavaScript不允許對(duì)undefined添加屬性)。

另一個(gè)解決辦法,是在構(gòu)造函數(shù)內(nèi)部判斷是否使用new命令,如果發(fā)現(xiàn)沒有使用,則直接返回一個(gè)實(shí)例對(duì)象。

function Fubar(foo, bar){

if (!(this instanceof Fubar)) {

return new Fubar(foo, bar);

}

this._foo = foo;

this._bar = bar;

}

Fubar(1, 2)._foo // 1

(new Fubar(1, 2))._foo // 1

上面代碼中的構(gòu)造函數(shù),不管加不加new命令,都會(huì)得到同樣的結(jié)果。

>5.編碼實(shí)戰(zhàn)

用面對(duì)對(duì)象編程的思想寫狀態(tài)機(jī)

6.擴(kuò)展思考

面向?qū)ο笈c面向過程的區(qū)別?

傳統(tǒng)的過程式編程(procedural programming)由一系列函數(shù)或一系列指令組成;而面向?qū)ο缶幊痰某绦蛴梢幌盗袑?duì)象組成。

每一個(gè)對(duì)象都是功能中心,具有明確分工,可以完成接受信息、處理數(shù)據(jù)、發(fā)出信息等任務(wù)。因此,面向?qū)ο缶幊叹哂徐`活性、代碼的可重用性、模塊性等特點(diǎn),容易維護(hù)和開發(fā),非常適合多人合作的大型應(yīng)用型軟件項(xiàng)目。

7.參考文獻(xiàn)

參考一:http://javascript.ruanyifeng.com/oop/basic.html">阮一峰


參考二:

href="http://www.ruanyifeng.com/blog/search.html?cx=016304377626642577906%3Ab_e9skaywzq&cof=FORID%3A11&ie=UTF-8&q=Javascript+%E9%9D%A2%E5%90%91%E5%AF%B9%E8%B1%A1%E7%BC%96%E7%A8%8B&sa.x=9&sa.y=8">阮一峰

參考三:《Javascript高級(jí)程序設(shè)計(jì)》chapter 6

8.更多討論

new命令的原理?

構(gòu)造函數(shù)中的return語句的作用?

面向?qū)ο缶幊痰睦^承原理?

鳴謝

感謝大家觀看

PTT鏈接


JS中的面向?qū)ο缶幊蘝騰訊視頻


------------------------------------------------------------------------------------------------------------------------

技能樹.IT修真院

“我們相信人人都可以成為一個(gè)工程師,現(xiàn)在開始,找個(gè)師兄,帶你入門,掌控自己學(xué)習(xí)的節(jié)奏,學(xué)習(xí)的路上不再迷茫”。

這里是技能樹.IT修真院,成千上萬的師兄在這里找到了自己的學(xué)習(xí)路線,學(xué)習(xí)透明化,成長可見化,師兄1對(duì)1免費(fèi)指導(dǎo)。快來與我一起學(xué)習(xí)吧 !http://www.jnshu.com/login/1/96194340

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容