這裏是修真院前端小課堂,每篇分享文從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語句的做用?
面向對象編程的繼承原理?
鳴謝
感謝你們觀看