JavaScript之面向對象

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的運用,真正在實際開發中還需要多思考最適合運用的方法,這些也是自己初步的一個認識,也算是給各位初學者提供一個思路,希望在以後能寫出自己的面向對象的代碼,同時也祝福各位!

發佈留言