JavaScript之面向對象:說起面向對象,我們最熟悉的一般是C#和JAVA,然而,關於JavaScript是不是一個面向對象的語言各有各的理解,剛才查瞭百科,JavaScript屬於一個基於原型的面向對象,也就是JavaScript算是一個基於對象的編程語言。
但是個人理解上,面向對象是一種編程思想,關於語言是不是面向對象的不必過於糾結,用的多瞭,自然有所理解,很多人包括我在內,其實基本上在用著JAVA這個面向對象的語言編著面向過程的程序;剛學習瞭JavaScript的面向對象編程思路,在這裡整理一下自己的思路,是對自己學習的一個記錄也是幫助初學者做一個入門認識。
最初程序
起初我們創建對象的方式如下代碼所示:
var box = new Object(); //創建一個Object 對象 box.name = 'Lee'; //創建一個name 屬性並賦值 box.age = 100; //創建一個age 屬性並賦值 box.run = function () { //創建一個run()方法並返回值 return this.name + this.age + '運行中...'; }; alert(box.run()); //輸出屬性和方法的值
特點分析:創建一個全局的對象,分別做對象的屬性和方法的初始化。
缺點分析:沒有封裝性,如果再創建一個一樣的對象,需要再重新寫一遍代碼。代碼量大,復用性差。
工廠模式
基於上邊的缺點分析我們將上邊的代碼封裝到一個函數中,來集中做對象的創建和初始化工作。代碼如下:
function createObject(name, age) { //集中實例化的函數 var obj = new Object(); obj.name = name; obj.age = age; obj.run = function () { return this.name + this.age + '運行中...'; }; return obj; } var box1 = createObject('Lee', 100); //第一個實例 var box2 = createObject('Jack', 200); //第二個實例 alert(box1.run()); alert(box2.run()); //保持獨立
特點分析:通過一個函數將對象創建和初始化過程封裝起來,最後將該對象返回。稱這種方式為工廠模式。
缺點分析:對象實例無法區分,因為函數中返回的對象一溜都是Object類型。
構造函數
利用構造函數也可以用來創造特定的對象,代碼如下:
function Box(name, age) { //構造函數模式 this.name = name; this.age = age; this.run = function () { return this.name + this.age + '運行中...'; }; } var box1 = new Box('Lee', 100); //new Box()即可 var box2 = new Box('Jack', 200); alert(box1.run()); alert(box1 instanceof Box); //很清晰的識別他從屬於Box
特點分析:通過構造函數不再在函數中new對象,並最後將對象返回,而是,直接在外部通過new構造函數生成對象,同時自動返回對象。
工廠模式和構造函數方式的區別:對比JAVA的編程過程可以理解,工廠模式純粹就是一個方法,在該方法中創建一個對象,並最後將對象返回,而構造函數是一個特殊的方法,調用該方法會自動返回一個對象,返回對象的類型就是該方法名對應的類型。
缺點分析:上邊的方法看起來已經算是完美瞭,但是,有個缺點就是函數中的run方法不是共享的,也就是說每new一個對象就會創建新的run方法,而run方法對於每一個對象又是相同的,這樣就造成瞭不必要的內存浪費。如果可以將run方法共享出來就可以避免這種問題,函數的prototype(原型)屬性可以幫我們做到,下面我們認識一下函數的prototype(原型)屬性。
原型
首先原型也是一個對象,該對象隨著函數的創建而創建,其特點為,可以讓所有通過該函數創建的對象實例共享它裡面的屬性和方法。訪問過程如下圖所示:
在我們new一個實例時,該實例會有一個__proto__屬性,該屬性執行原型的constructor屬性,這樣就可以訪問到原型裡面的屬性和方法瞭,每一個實例都是如此。
組合構造函數+原型模式
由此,利用原來的構造函數,再加上原型模式就可以解決上邊的問題瞭。代碼如下:
function Box(name, age) { //不共享的使用構造函數 this.name = name; this.age = age; this. family = ['父親', '母親', '妹妹']; }; Box.prototype = { //共享的使用原型模式 constructor : Box, run : function () { return this.name + this.age + this.family; } };
有關代碼中的constructor : Box,是為瞭讓實例的constructor強制指向它的對象所屬,否則創建的實例不是指向它的對象所屬的,也就是box的constructor不指向Box,而是指向Object。
特點分析:這種方法將不需要共享的屬性放在構造函數裡初始化,將需要共享的方法放在原型裡初始化。
缺點分析:其實這種方法算是一種比較好的方法瞭,但是還存在的問題在於,原型中共享的方法無論是否調用到都會初始化,如果該方法比較大的話,也會造成不必要的內存浪費,同時由於構造和原型在形式上是分離的,如果封裝到一起看起來就更直觀和方便。
動態原型:
針對上邊的問題,利用下邊的代碼,動態的調用原型中的方法。
function Box(name ,age) { //將所有信息封裝到函數體內 this.name = name; this.age = age; if (typeof this.run != 'function') { //僅在第一次調用的初始化 Box.prototype.run = function () { return this.name + this.age + '運行中...'; }; } } var box = new Box('Lee', 100); alert(box.run());
繼承
在JAVA的面向對象編程中,實現繼承的方式有接口實現和繼承兩種方式,但是在JavaScript中是不支持接口實現的,在這裡繼承的實現是依靠原型鏈實現的。
基本繼承:
先看代碼
function Box() { //Box 構造 this.name = 'Lee'; } function Desk() { //Desk 構造 this.age = 100; } Desk.prototype = new Box(); //Desc 繼承瞭Box,通過原型,形成鏈條 var desk = new Desk(); alert(desk.age); alert(desk.name); //得到被繼承的屬性
從代碼中我們可以看出,在JavaScript中的繼承實現的過程就是,將父對象的實例放到瞭子對象的原型中實現的。通過上邊的知識我們知道,函數原型中的內容是被函數的實例所共享的,所以就實現瞭子類型訪問父類型屬性和方法的過程。
缺點分析:這種方式無法實現子類型向父類型傳參。
原型鏈+借用構造函數
為瞭解決上邊的問題,我們可以借助借用構造函數也稱對象冒充的方法實現,代碼如下:
function Box(age) { this.name = ['Lee', 'Jack', 'Hello'] this.age = age; } Box.prototype.run = function () { return this.name + this.age; }; function Desk(age) { Box.call(this, age); //對象冒充 } Desk.prototype = new Box(); //原型鏈繼承 var desk = new Desk(100); alert(desk.run());
特點分析:從上邊的代碼可以看出,通過Desk.prototype = new Box(),將Box繼承瞭下來,同時在newDesk(參數) 的過程中傳入參數,調用瞭Box.call(參數) 方法,將參數傳遞給瞭Box的構造函數。
總結
上邊的介紹也隻是利用面向對象的思想指導JavaScript的運用,真正在實際開發中還需要多思考最適合運用的方法,這些也是自己初步的一個認識,也算是給各位初學者提供一個思路,希望在以後能寫出自己的面向對象的代碼,同時也祝福各位!