JS中的面向對象編程

這裏是修真院前端小課堂,每篇分享文從javascript

【背景介紹】【知識剖析】【常見問題】【解決方案】【編碼實戰】【擴展思考】【更多討論】【參考文獻】html

八個方面深度解析前端知識/技能,本篇分享的是:前端

【 JS中的面向對象編程】java

1.背景介紹git

什麼是面向對象編程?github

「面向對象編程」(Object OrientedProgramming,縮寫爲OOP)是目前主流的編程範式。它的核心思想是將真實世界中各類複雜的關係,抽象爲一個個對象,而後由對象之間的分工與合做,完成對真實世界的模擬。編程

主要概念爲:把一組數據結構和處理它們的方法組成對象(object),把相同行爲的對象概括爲類(class),經過類的封裝(encapsulation)隱藏內部細節,經過繼承(inheritance)實現類的特化(specialization)/泛化(generalization),經過多態(polymorphism)實現基於對象類型的動態分派(dynamicdispatch)。數據結構

Javascript是一種基於對象(object-based)的語言,遇到的東西幾乎都是對象,可是它不是一種面對對象的語言。像其餘語言裏面的class(類),它就沒辦法直接用了。(據說ES 6能夠用了,筆者一直學的ES5,6暫未研究,有興趣的同窗能夠去看看教程)app

2.知識剖析函數

2.1對象的概念

由於JS是一個基於對象的語言,因此咱們遇到的大多數東西幾乎都是對象。例如函數就是一個對象,若是你要在js裏面新建一個對象,這樣寫其實就是建立了一個object的實例。對象就是一個容器,封裝了屬性和方法。屬性就是對象的狀態,好比下面的name屬性。方法就是寫在對象裏面的函數,也就是對象的行爲,好比下面的sayName方法。

var person = new object();

person.name = "Tom";

person.sayNmae = function() {

alert(this.name);

}

2.2 工廠模式
「面向對象編程」的第一步,就是要生成「對象」。可是不少時候咱們不得不面臨重複生成不少對象的狀況,若是我有一千我的要記錄他們的信息,像上面這種方法寫的話,大大增長了代碼的重複量,爲了解決這個問題,人們開始使用工廠模式的一種變體,寫法以下頁。雖然工廠模式解決了代碼複用的問題,可是卻沒辦法顯示實例(person1)和對象o之間的關係,好比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 構造函數

後來就出現了構造函數,用來建立特定類型的對象,能夠將實例和對象聯繫起來,用到了JS中的「this」,寫法以下:

這樣對象和實例之間就有關係了,以new這種方式調用構造函數會經歷4個步驟:

(1)建立一個新對象。

(2)將構造函數的做用域賦給新對象(這個this就指向了這個新對象)。

(3)執行函數內代碼(給對象添加屬性)

(4)返回新對象。

代碼演示:

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");

構造函數特色:

上面代碼中,Persoon就是構造函數,它提供模板,用來生成對象實例。爲了與普通函數區別,構造函數名字的第一個字母一般大寫。

構造函數的兩個特色:

1.函數體內部使用了this關鍵字,表明了所要生成的對象實例。

2.生成對象的時候,必需用new命令,調用函數。

若是忘了使用new命令,直接調用構造函數會致使構造函數變成普通函數,就不會生成實例對象,而且此時的this這時表明全局對象,將形成一些意想不到的結果。

var Vehicle = function (){

this.price = 1000;

};

var v = Vehicle();

v.price

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

上面代碼中,調用Vehicle構造函數時,忘了加上new命令。結果,price屬性變成了全局變量,而變量v變成了undefined。

所以必須當心,記得使用new命令。

2.3原型和原型鏈

原型prototype

JavaScript的每一個對象都繼承另外一個對象,後者稱爲「原型」 (prototype)對象。只有null除外,它沒有本身的原型對象。

原型對象上的全部屬性和方法,都能被派生對象共享。這就是JavaScript繼承機制的基本設計。

經過構造函數生成實例對象時,會自動爲實例對象分配原型對象。每個構造函數都有一個prototype屬性,這個屬性就是實例對象的原型對象。

原型鏈

對象的屬性和方法,有多是定義在自身,也有多是定義在它的原型對象。因爲原型自己也是對象,又有本身的原型,因此造成了一條原型鏈(prototype

chain)。好比,a對象是b對象的原型,b對象是c對象的原型,以此類推。

「原型鏈」的做用是,讀取對象的某個屬性時,JavaScript引擎先尋找對象自己的屬性,若是找不到,就到它的原型去找,若是仍是找不到,就到原型的原型去找。若是直到最頂層的Object.prototype仍是找不到,則返回undefined。

須要注意的是,一級級向上,在原型鏈尋找某個屬性,對性能是有影響的。所尋找的屬性在越上層的原型對象,對性能的影響越大。若是尋找某個不存在的屬性,將會遍歷整個原型鏈。

利用原型(prototype)的繼承特性,咱們能夠將咱們的函數寫成

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

由於原型的繼承,person1和person2的prototype都指向Person的prototype,因此這兩個函數實際上是相等的。可是用工廠函數或者構造模式, alert(person1.sayName == person2.sayName);就絕對不會爲真了。

奇淫巧技1:每次寫屬性都要加一個prototype是否是很麻煩,其實還有另一種寫法

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 構造函數的繼承

讓一個構造函數繼承另外一個構造函數,是很是常見的需求。

也有多種方法實現,各有優缺點。好比如今有一個動物對象的構造函數,和一個貓對象的構造函數。

function Animal() {

this.species = 「動物」;

};

function Cat(name,color) {

this.name = name;

this.color = color;

}

如何才能使Cat繼承Animal呢?

2.4.1 構造函數綁定

第一種方法也是最簡單的方法,使用call或apply方法,將父對象的構造函數綁定在子對象上,即在子對象構造函數中加一行:

function Cat(name,color){

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

this.name = name;

this.color = color;

}

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

alert(cat1.species); // 動物

2.4.2 prototype(原型)模式

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

若是"貓"的prototype對象,指向一個Animal的實例,那麼全部"貓"的實例,就能繼承Animal了。

Cat.prototype = new Animal();

Cat.prototype.constructor = Cat;

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

alert(cat1.species); // 動物

代碼的第一行,咱們將Cat的prototype對象指向一個Animal的實例。至關於將Cat原先的原型對象刪除,從新賦一個Animal實例的值。可是任何一個prototype對象都有一個constructor屬性,指向它的構造函數。這個時候Cat的構造函數也改變了,變成了Animal。

2.4.2 prototype(原型)模式

因此咱們須要「Cat.prototype.constructor = Cat」將Cat的構造函數從新指向爲Cat,否則的話會很容易出問題。

這是很重要的一點,編程時務必要遵照。若是替換了prototype對象,

b.prototype = new a();

那麼,下一步必然是爲新的prototype對象加上constructor屬性,並將這個屬性指回原來的構造函數。b.prototype.constructor = b;

2.4.3 直接繼承prototype(原型)

第三種方法是對第二種方法的改進。因爲Animal對象中,不變的屬性均可以直接寫入Animal.prototype。因此,咱們也可讓Cat()跳過 Animal(),直接繼承Animal.prototype。如今咱們將Animal對象改寫

 

function Animal() {

Animal.prototype.species = "動物";

}

而後,將Cat的prototype對象,指向Animal的prototype對象,這樣就完成了繼承。

Cat.prototype = Animal.prototype;

Cat.prototype.constructor = Cat;

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

alert(cat1.species); // 動物

2.4.3 直接繼承prototype(原型)

與前一種方法相比,這樣作的優勢是效率比較高(不用執行和創建Animal的實例了),比較省內存。缺點是 Cat.prototype和Animal.prototype如今指向了同一個對象,那麼任何對Cat.prototype的修改,都會反映到Animal.prototype。因此Animal.prototype的構造函數也變成了Cat。

這個時候咱們就須要引入一個空對象做爲中轉的中介,不管Cat的constructor如何變,只會影響到中轉對象F而沒法影響到父對象Animal了。

var F = function(){};

F.prototype = Animal.prototype;

Cat.prototype = new F();

Cat.prototype.constructor = Cat;

2.4.3 直接繼承prototype(原型)

而後咱們將上述方法封裝成爲一個函數,使用起來就很方便了

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(原型)

使用的時候方法以下:

extend(Cat,Animal);

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

alert(cat1.species); // 動物

奇淫巧技2:封裝函數的時候怎麼方便怎麼寫沒必要太過考慮語義化的東西,好比寫個狀態機,直接將狀態用數字表示,這樣比字符串的形式好判斷多了。可是一點也不語義化。

3.常見問題

必需要聲明new來建立實例對象嗎?

4.解決方案
1.必需要聲明new來建立實例對象嗎?

爲了保證構造函數必須與new命令一塊兒使用,一個解決辦法是,在構造函數內部使用嚴格模式,即第一行加上use strict。

 

function Fubar(foo, bar){

'use strict';

this._foo = foo;

this._bar = bar;

}

Fubar();

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

上面代碼的Fubar爲構造函數,use

strict命令保證了該函數在嚴格模式下運行。因爲在嚴格模式中,函數內部的this不能指向全局對象,默認等於undefined,致使不加new調用會報錯(JavaScript不容許對undefined添加屬性)。

另外一個解決辦法,是在構造函數內部判斷是否使用new命令,若是發現沒有使用,則直接返回一個實例對象。

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

上面代碼中的構造函數,無論加不加new命令,都會獲得一樣的結果。

5.編碼實戰

用面對對象編程的思想寫狀態機

6.擴展思考
面向對象與面向過程的區別?

傳統的過程式編程(procedural programming)由一系列函數或一系列指令組成;而面向對象編程的程序由一系列對象組成。

每個對象都是功能中心,具備明確分工,能夠完成接受信息、處理數據、發出信息等任務。所以,面向對象編程具備靈活性、代碼的可重用性、模塊性等特色,容易維護和開發,很是適合多人合做的大型應用型軟件項目。

7.參考文獻
參考一:http://javascript.ruanyifeng....l">阮一峯

 

參考二:

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高級程序設計》chapter 6

8.更多討論
new命令的原理?

構造函數中的return語句的做用?

面向對象編程的繼承原理?

鳴謝

感謝你們觀看

PTT連接

相關文章
相關標籤/搜索