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">阮一峰
參考二:
參考三:《Javascript高級(jí)程序設(shè)計(jì)》chapter 6
8.更多討論
new命令的原理?
構(gòu)造函數(shù)中的return語句的作用?
面向?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