原生 javascript 物件導向分析及思考

來源:互聯網
上載者:User

物件導向語言有三個特徵:  封裝,繼承,多態。而js初學者一般會覺得js同其他類C語言一樣,有類似於Class這樣的關鍵字可以讓我們在js中更好的進行物件導向的操作。可事實並非如此。

嚴格地說,js不是一門物件導向的語言,但是我們可以利用js裡面的一些進階特性來進行OOP編程。

 
----封裝

在js中,如何來建立一個對象呢?這非常簡單,我們只需要new一個已封裝好的函數(就是類C語言中的類),就可以執行個體化一個對象了。

那我們首先來構造這麼一個"類",在構造之前必須知道一個類需要有"變數"和"方法",接著我們就來構造這個"類":

function Parent(){
    this.name = "Parent";
    this.sayName = function(){
        console.log(this.name);
    }
}
var p1 = new Parent();
p1.sayName();

怎麼樣,很簡單吧?這樣就封裝好了一個"類"了。但是這個類看起來很笨重,因為名字是固定的,所以我們需要進行修改並擴充。

function Parent(name){
    this.name = name;
    this.sayName = function(){
        console.log(this.name);
    }
}
var p1 = new Parent("yxy");
p1.sayName(); //控制台列印yxy

這樣我們封裝的"類"就變得有變數,有方法,有外部參數,可複用。看上去已經非常完美了?

試著這樣想想。每次建立一個對象,都會建立一個變數,同樣也都會建立一個方法。而這個方法對所有對象都只是同一個方法效果,為什麼你還要去對這個方法建立多次呢?學過java或c++的人可能會想,你怎麼知道這個方法是被建立了多次,而不是引用的同一個呢?嗯?我們來做個測試。

function Parent(name){
    this.name = name;
    this.sayName = function(){
        console.log(this.name);
    }
}
var p1 = new Parent("yxy");
var p2 = new Parent("danshengou");
console.log(p1.sayName == p2.sayName); //false

利用"=="可以看到兩個方法是不同的,那就是被建立了多次。所以當我們建立了多個對象後,每個對象的每個方法都是不同的!這顯然會大大消耗記憶體,不利於web開發。那如何解決呢?

前面的文章中我提到的js中的"類"都是帶雙引號的,原因很簡單,js不支援類(在ES6的規範中就可以支援了),但為了方便,我們可以稱之為"偽類"。但在我們之前的例子中都還不能說得上是偽類!因為完全就沒有方法的複用,不是嗎?接下來我們會引入一個概念性很強的一個術語:"原型"。

原型,prototype,每一個函數都有一個原型屬性,這個屬性是一個指標,指向一個對象,而這個對象的用途是包含可以由特定類型的所有執行個體共用的屬性和方法。確實是非常不好理解。簡單的說,原型屬性就是通過調用建構函式而建立的那個對象執行個體的原型對象。如果你還不清楚,那我推薦你好好地看看<<javascript進階程式設計>>一書的第六章。

在這裡先記住為什麼我們要在封裝中使用原型。如果你還對原型的概念模糊不清,不急,先理解使用它的好處。使用原型的好處是可以讓所有對象執行個體共用它所包含的屬性和方法。好像清晰多了?那我們來改裝一下前面提到的那個例子。

function Parent(name){
    this.name = name;
}
Parent.prototype.sayName = function(){
    console.log(this.name);
}
var p1 = new Parent("yxy");
p1.sayName(); //調用原型中的sayName方法,列印出yxy
var p2 = new Parent("danshengou");
console.log(p1.sayName == p2.sayName); //true
console.log(p1.sayName === p2.sayName); //true

在這個例子中,我們用原型模式構造了一個函數。我們執行個體化了兩個對象,用"=="和"==="比較了它的值和地址,發現都是一樣的。看來原型真的是很好用啊,大大節省了我們的記憶體空間。你可能會問,為什麼不把變數也放到原型中呢?因為原型是所有對象所共用的,每個對象的屬性必須是該對象自己持有的,如果放到原型中,那這些對象便沒有任何區別了。這就是我們所謂的"組合繼承"。

到目前為止,我們就成功地建立了一個比較完美的js"偽類"了。而且你應該對原型有了很大的瞭解。

----繼承

在你瞭解了整個封裝過程後,你對js可能很失望了,既然有這樣複雜的封裝,那繼承肯定也很難了。

可是,繼承的文法其實很簡單。

我們先來看看一個繼承:

function Parent(name){
    this.name = name;
}
Parent.prototype.sayName = function(){
    console.log(this.name);
}

function Child(name,position){
    this.position = position;     
    Parent.call(this,name);      
}
Child.prototype = new Parent();
Child.prototype.sayPos = function(){
    console.log(this.position);
}

var p1 = new Child("yxy","student");
p1.sayName();
p1.sayPos();

有兩個地方很吸引我們眼球。

1.Parent.call(this,name)
2.Child.prototype = new Parent();

細講可能容易繞暈,我從繼承的概念入手,首先,子物件繼承的是父物件的變數和方法。那我們可以很清晰地從代碼作用範圍看到,Parent.call(this,name)在建構函式中,顯然是在繼承變數。而Child.prototype = new Parent(),這個Parent對象不帶參數地建立,顯然是在繼承原型。這樣說是不是好理解多了?

Child.prototype = new Parent(),這條語句很簡單,我不再去細究它。這裡重點要講的是Parent.call(this,name)這個東西。

Parent.call(this,name)還有另外幾種寫法:

1.Parent.call(this,arguments[0]);
2.Parent.apply(this,[name]);
3.Parent.apply(this,arguments);

雖然是四個寫法都不同,但是效果都是一樣的 : 將父類建構函式的上下文引用到子類的建構函式上下文中。

call和apply,都可以用來代替另一個對象調用一個方法。兩者可將一個函數的物件內容從初始的上下文改變為由this指定的新對象。又有點繞?簡單地說,就是this對象在當前的上下文中執行了一次Parent函數,並且把參數(this後面那個參數)傳遞給Parent函數。

我們都知道函數怎麼暴露自己內部的屬性,就是把它執行一次,就可以在它的父級範圍中訪問到。那理解這個apply,call的作用機理就很簡單了。

繼承文法很簡單,但是其繼承機制還是需要花時間去理解的。

終於我們把有關js物件導向的步驟給很詳細的做了一次,可能大家覺得自己終於可以開始類比一些有難度的物件導向的執行個體了。這一次我並不會反對大家,但是你難道沒看出來少了什麼嗎?我來運行一下代碼:

function Parent(name){
    this.name = name;
}
Parent.prototype.sayName = function(){
    console.log(this.name);
}

function Child(name,position){
    this.position = position;
    //Parent.call(this,name);
    Parent.apply(this,[name]);
}
Child.prototype = new Parent();
Child.prototype.sayPos = function(){
    console.log(this.position);
}

var p1 = new Child("yxy","student");
console.log(p1.name); //yxy
console.log(p1.position); //student

嗯?我明明想的是將變數私人化啊,可是為什麼能訪問到呢?細心地人可能早就在繼承那個部分的時候就想到了,函數只要一執行,我的變數,方法都會暴露在外面了!其實這根本就不是封裝啊!只是裝而已。

沒錯,js並沒有private,public這些關鍵字來定義屬性和方法的私人和公有性質。可能你會很失望很失望,前面做了那麼多,得來的是一個半瘸子的物件導向。其實不光你這麼想,很多開發人員也覺得,因此他們用複雜的名字來命名一些變數,以保證其不會那麼容易被訪問到。這不失為一種辦法,但是難道就沒有別的辦法?

仔細想想,還有什麼辦法能將類比private,public這樣的操作呢?我給出一個例子和一個概念,然後大家可以在這上開始自己對js真正物件導向的思考。

概念 : "模組模式"

代碼 :

var Parent = function(name,publicAge){
    var myName = name;
    return {
        age : publicAge,
        sayName : function(){
            console.log(myName);
        }
    };
};

var p1 = Parent("yxy","20");
p1.sayName(); //yxy
console.log(p1.age); //20
console.log(p1.myName); //undefined

當你能理解鎖裝中訪問等級的時候,javascript真正的思考才剛剛開始



JavaScript 中物件導向的思想真的很重要嗎?

具體來說的話,怎樣用物件導向的 JavaScript 構造一個網站的前端架構?

最近很多人都提到 JavaScript 的物件導向編程,網路上介紹了很多是想 JavaScript 實現物件導向編程的方法和技巧,自己也掌握瞭解一些,但是每每到實際開發的時候物件導向js開發卻很少用的到,可能是自己的對真正的物件導向思想還不是很熟悉,也擷取自己對一些 OO 的設計模式不是很瞭解。

很想知道 JavaScript 物件導向開發思想在做前端發開時是否真的很重要,如何具體的將其用於開發之中。


物件導向的概念出來很久了,任何一門語言都是可以去物件導向的。   為什麼要用物件導向,原因很多,但就前端開發來說我個人覺得有兩個優點。   第一,是將鬆散的JS代碼進行整合,便於後期的維護  第二,是讓我們的代碼適應更多的商務邏輯。   這兩點,是我在日常工作中深有體會的。   言歸正傳,在前端開發過程中,我們所接觸到最多的事物其實就是各種業務,在我看來,每一個業務都是一個對象,而這些業務必然是由一個個小的功能所組成的,   因此我所理解,物件導向,就是針對這個業務,來構造對應的狀態以及操作。而在這個大的前提下可以繼續對下面的小的功能再次細分與構造。   為了達到這樣的目的,我們通常採用了封裝,繼承以及多態這樣的方法。   最後,應用環節上,就可以利用OO來實現擷取的狀態和方法從而實現業務。   (題外話,因此物件導向和面向過程,有的時候也是交替存在的甚至是相輔相成的)   ===============補充下================  上面??鋁撕芏啵?卑椎乃擔?褪牆?閽誶岸肆煊蛩?齙降奈侍猓ù蟛糠摯贍蓯且滴瘢??ü?庾埃?壇校?嗵?壤嗨頻姆椒??諧橄蟮氖迪鄭?謔褂黴?討校?ü?虻サ氖道??塗梢越?滴穸韻蟾秤瓚雜Φ淖刺?頭椒ù傭?饔謾?nbsp;  而面向過程,實際上就是將你的商務邏輯按照先後順尋進行整理,將每一個小的邏輯認為是一個模組,並套用物件導向的方法。   這就是我所理解的,如何具體的將其應用到開發這個問題的答案。


我覺得在前端物件導向不那麼重要,不是不重要,是【不那麼重要】,或者說沒必要【刻意】追求物件導向物件導向的最大的特徵無非是“繼承、多態、封裝、組合”,我們看一個物件導向的語言或者架構,第一反應就是繼承、多態怎麼實現的但在是在前端環境下,繼承和多態的效益真的夠高嗎?回到問題的本源,繼承和多態的出發點都是“分離關注點”。使程式以最小的代價適應“關注點”的變化。但前端環境和後端不同的一點是,後端程式只有“資料+行為”,關注點不多而且容易預測。而前端是“資料+行為+展現+互動”,多出來的“展現+互動”決定了前端的關注點多且無從預測,除非人為限制關注點,讓UI和互動套在一個相對死的範圍內物件導向做得很徹底的UI架構基本都有幾個共同的特點:“醜,慢,大”,結合以上分析應該不難明白個人認為對前端來說,做好代碼的分層、解耦、結構化極有必要,但做這些事和追求物件導向沒有必然關係。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.