Javascript高級技術篇(2): 深入理解面向對象


我們常說Javascript是一種面向對象的語言,那也就是說具有面向對象的一些基本特性。比如包含對象、類、屬性、方法以及構造函數等基本元素,很多人在想:JS類到底是什么玩意?其實很簡單,就是一個function,正所謂"簡單就是美"嘛。在自定義類的同時,我們也回顧一下JS基本的類:Math,Array,Object以及String等。

//定義JS類的兩種方式(注意這里是大寫開頭)
function EmailMessage() { 
}
var EmailMessage = function() { 
}

有類就有對象存在,同時構造函數也應運而生。常常在構造函數中使用this.**來訪問屬於當前對象的屬性或方法。對於由同一個類生成的多個對象之間是松耦合的,相互獨立。

//當創建對象時會觸發構造函數(這里無參)
var EmailMessage = function() {
    alert("New message created.");
}
var myMessage = new EmailMessage(); // 輸出 "New message created."
var anotherMessage = new EmailMessage(); // 輸出 "New message created."

當你想傳遞參數給對象時,就會使用帶參構造函數。

//帶參構造函數
var EmailMessage = function(message) {
    alert(message);
}
// 輸出 "Return to sender"
var myMessage = new EmailMessage("Return to sender");

剛才講過,可以使用this來訪問屬性,其實在JS中還有一種方式來添加屬性: 利用prototype。不僅能添加屬性,還能添加方法。

//使用this來訪問當前對象的屬性和方法
var EmailMessage = function(subject) {
    this.subject = subject; 
    this.send = function() {
        alert("Message '" + this.subject + "' sent!");
    }
}
var myMessage = new EmailMessage("Check this out...");
var anotherMessage = new EmailMessage("Have you seen this before?");
//輸出屬性和方法
alert(myMessage.subject); // 輸出 "Check this out..."
alert(anotherMessage.subject); // 輸出 "Have you seen this before?"
myMessage.send();// 輸出 "Message 'Check this out...' sent!"
anotherMessage.send();// 輸出 "Message 'Have you seen this before?' sent!"

利用prototype來添加屬性和方法(主要適合於對已有類進行功能擴展)。

var EmailMessage = function(subject) {
    this.subject = subject; 
}
//使用prototype來添加方法
EmailMessage.prototype.send = function() {
    alert("Message '" + this.subject + "' sent!");
}
var myMessage = new EmailMessage("Check this out...");
var anotherMessage = new EmailMessage("Have you seen this before?");
//輸出屬性和方法
alert(myMessage.subject); // 輸出 "Check this out..."
alert(anotherMessage.subject); // 輸出 "Have you seen this before?"
myMessage.send();// 輸出 "Message 'Check this out...' sent!"
anotherMessage.send();// 輸出 "Message 'Have you seen this before?' sent!"

通常有朋友在講:要是想JS類只有一個對象存在時,即常說的單例模式如何實現呢?其實JS的內置類就包含了很多單例,如:Math等。在這里,我給出兩種實現方式。

var User = function() {
    this.username = "";
    this.password = "";
    this.login = function() {
        return true;
    }
}
// 創建User類的對象並存儲為相同的對象實例,而原始的類將被移除(可以看成起了個別名而已)
User = new User();
// 使用單例對象來訪問對應的方法(類似於C#的靜態方法)
User.login();

另外一種實現方式就是"自我初始化",即在類聲明時就立即執行,而該類就只包含一個對象。

var Inbox = new function() {
    this.messageCount = 0;
    this.refresh = function() {
        return true;
    }
}();
//聲明就立即執行
Inbox.refresh();

接下來,我談一下在已有類的基礎上添加新的功能以實現擴展,我們稱之為"繼承"。注意以下代碼的高亮片段。

var EmailMessage = function(subject) {
    this.subject = subject;
    this.send = function() {
        alert("Message '" + this.subject + "' sent!");
    }
}
// 創建一個新的空類
var EventInvitation = function() {};
// 繼承已有類EmailMessage的屬性和方法
EventInvitation.prototype = new EmailMessage(); // EventInvitation將構造函數設置為自身
EventInvitation.prototype.constructor = EventInvitation; // 重置設置已有的屬性subject
EventInvitation.prototype.subject = "You are cordially invited to...";
// 創建EventInvitation的對象
var myEventInvitation = new EventInvitation();
// 輸出 "Message 'You are cordially invited to...' sent!"
myEventInvitation.send();

在繼承已有類的同時,子類便獲取了父類的所有屬性和方法,自身只需要定義額外與自己相關的屬性和方法。我們來解釋一下封裝與多態的概念:所有封裝,即每個類只關注與自身相關的屬性和方法。所謂多態,即子類在包含父類同名屬性或方法時,而又不想繼承來自父類的同名元素,則可以重新定義該元素(如subject)。

var EmailMessage = function(subject) {
    this.subject = subject;
    this.send = function() {
        alert("Email message sent!");
    }
}
// 繼承EmailMessage
var EventInvitation = function() {};
EventInvitation.prototype = new EmailMessage("You are cordially invited to...");
EventInvitation.prototype.constructor = EventInvitation;
// 重寫send方法
EventInvitation.prototype.send = function() { alert("Event invitation sent!"); } var myEmailMessage = new EmailMessage("A new email coming your way.");
var myEventInvitation = new EventInvitation();
myEmailMessage.send(); // 輸出 "Email message sent!"
myEventInvitation.send(); // 輸出 "Event invitation sent!"

現在假設子類需要重寫父類的同時,又需要調用父類的方法,我們來看怎么實現。

var EmailMessage = function(subject) {
    this.subject = subject;
    this.send = function() {
        alert("Email message sent!");
    }
}
// 繼承EmailMessage
var EventInvitation = function() {};
EventInvitation.prototype = new EmailMessage("You are cordially invited to...");
EventInvitation.constructor.prototype = EventInvitation;
// 重寫send方法
EventInvitation.prototype.send = function() {
    alert("Event invitation sent!");
    // 使用this.constructor.prototype來指向父類並執行同名方法
    this.constructor.prototype.send.call(this); }
var myEmailMessage = new EmailMessage("A new email coming your way.");
var myEventInvitation = new EventInvitation();
myEmailMessage.send();// 輸出 "Email message sent!"
myEventInvitation.send();// 輸出 "Event invitation sent!"、"Email message sent!"

從以上的講解中,我們不難發現this的大量應用,可能很多朋友也知道this代表當前執行的對象實例。我這里詳細解釋一下this的普遍意義和用法。給一段很簡單的代碼,大家試着想一想結果是什么?主要是搞清楚此時this到底代表什么?

var showSubject = function() {
    alert(this.subject);
}
showSubject();// 輸出 "undefined"

剛才提過,this代表當前執行的實例對象,可是現在並沒有像之前的代碼先定義一個類,然后定義對象,在使用this代表這個定義的對象。准確的解釋應該是: this代表其所在的作用域內類自身或正在執行的對象,若this超出類的作用域則代表全局對象window對象。故此時應該輸出undefined。接下來我們把這個function移植到已有類EmailMessage上。

var showSubject = function() {
    alert(this.subject);
}
showSubject();// 輸出"undefined"
this.subject = "Global subject";// 設置全局屬性
showSubject();// 輸出 "Global subject"
// 定義EmailMessage類
var EmailMessage = function(subject) {
    this.subject = subject;
}
// 將showSubject添加到EmailMessage,注意這里showSubject不含()
EmailMessage.prototype.showSubject = showSubject;
var myEmailMessage = new EmailMessage("I am the subject.");
myEmailMessage.showSubject();// 輸出 "I am the subject.",因為現在this變成myEmailMessage
showSubject();// 輸出"Global subject",因為此時this仍然為window
EmailMessage.prototype.outputSubject = function() { //現在為EmailMessage添加新方法outputSubject來調用showSubject
    showSubject();
}
myEmailMessage.outputSubject();// 輸出 "Global subject.",因為盡管添加了新方法,但this仍然是window

如果希望能強制切換當前引用的對象this,有兩種方法: call、apply。兩者的區別很小,前者傳遞的是參數列表,后者傳遞的是參數數組。

var showSubject = function() {
    alert(this.subject);
}
var setSubjectAndFrom = function(subject, from) {
    this.subject = subject;
    this.from = from;
}
//this代表全局對象window
showSubject(); // 輸出"undefined"
setSubjectAndFrom("Global subject", "miracle@cnblogs.com");
showSubject(); // Outputs "Global subject"
//定義EmailMessage類
var EmailMessage = function() {
    this.subject = "";
    this.from = "";
};
var myEmailMessage = new EmailMessage();
//call或apply將this從全局對象window切換到myEmailMessage
setSubjectAndFrom.call(myEmailMessage, "New subject", "miracle@sina.com"); setSubjectAndFrom.apply(myEmailMessage, [ "New subject", "miracle@sina.com" ]); showSubject.call(myEmailMessage);// 輸出"New subject"

到此為止,我們所定義的屬性和方法,在類外部都能訪問。如果我們希望能將一些屬性和方法僅供類內部使用,即所謂的private變量,我們改如何實現呢?

var EmailMessage = function(subject) {
    // 公有的屬性和方法
    this.subject = subject;
    this.send = function() {
        alert("Message sent!");
    }
    // 私有的屬性和方法(使用var而不是this)
    var messageHeaders = "";
    var addEncryption = function() {
        return true;
    }
    // 特權的屬性和方法(對外開放讀取接口但不能修改,類似於只讀)
    var messageSize = 1024;
    this.getMessageSize = function() {
        alert(messageSize);
    }
}

接下來我們(在類外)開始使用這些變量,看看他們的表現如何?

var myEmailMessage = new EmailMessage("Save these dates...");
alert(myEmailMessage.subject); // 輸出 "Save these dates..."
myEmailMessage.send(); // 輸出 "Message sent!"
// 輸出"undefined"因為messageHeaders是私有屬性
alert(myEmailMessage.messageHeaders);
// addEncryption()是私有方法,外部不能訪問因此拋出異常
try {
    myEmailMessage.addEncryption();
} catch (e) {
    alert("Method does not exist publicly!");
}
// 輸出"undefined"因為messageSize是私有屬性
alert(myEmailMessage.messageSize);
// 輸出"1024",特權屬性可通過方法在外部訪問
myEmailMessage.getMessageSize();

通過以上的學習,大家對面向對象的基礎知識點已經有所了解了把。接下來,我在最后簡單聊一下關於"對象字面量(Object Literal)"的知識。常聽別人談起這個概念,那對象字面量到底是什么呢?簡單的說:就是將一系列屬性和方法組合起來的集合體,可以用來創建單一對象(Singleton),創建類,設置函數輸入參數等。下面我來一一講解。首先,對象字面量是一個變量,然后將所有的屬性和方法以"鍵值對"的方式全部包含在{}中,這跟后面的系列JSON數據組織格式很相似。

var earth = {
    name: "Terra Firma", // 字符串
    planet: true, // 布爾變量
    moons: 1, // 整數
    diameter: 12756.36, // 小數
    oceans: ["Atlantic", "Pacific", "Indian", "Arctic", "Antarctic"], // 數組
    poles: { // 嵌套對象字面量
        north: "Arctic",
        south: "Antarctic"
    },
    setDiameter: function(diameter) { // 函數
        this.diameter = diameter; // 此時this代表earth
    }
}
// 注意:此處不再聲明
alert(earth.diameter); // 輸出 "12756.36"
earth.setDiameter(12756.37);
alert(earth.diameter); // 輸出 "12756.37"

同樣對象字面量也能創建類(將對象字面量映射到類的prototype上)。

var EmailMessage = function() {};
EmailMessage.prototype = {
    subject: "",
    from: "",
    send: function() {
        alert("Message sent!");
    }
}
var myEmailMessage = new EmailMessage();
myEmailMessage.subject = "Come over for a party.."
myEmailMessage.send(); // 輸出"Message sent!"

那對象字面量咋作為函數的輸入參數呢?很簡單,當我們有時傳遞的輸入參數過多時,而這些參數之間又彼此關聯時,我們不妨用對象字面量來作為輸入參數。

// 使用多個輸入參數
var sendEmail = function(to, from, subject, body) {
    alert("Message '" + subject + "' from '" + from + "' sent to '" + to + "'!");
}
// 調用必須按照順序
sendEmail("miracle@cnblogs.com", "miracle.he@cnblogs.com", "Dinner this week?", ?
"Do you want to come over for dinner this week? Let me know.");
// 用對象字面量來作為輸入參數(將4個合成為1個)
var sendEmail = function(message) {
    alert("Message '" + message.subject + "' from '" + message.from + "' sent to '" + message.to + "'!");
}
// 此時調用不再區分順序,只要將對象字面量的屬性賦值即可
sendEmail({
    from: 'miracle@cnblogs.com',
    to: 'miracle.he@cnblogs.com',
    subject: 'Dinner this week?',
    body: 'Do you want to come over for dinner this week? Let me know.'
});

是不是覺得調用起來更加靈活和方便呢?再次回到上面的話題:變量作用域,其實這個在大家日常的實際項目經驗中應該更加引以重視?我們說了window在全局作用域中均有效,但是我們的JS程序如果過多或不當使用全局變量,導致全局作用域內存在很多全局變量,將使應用程序的安全性面了巨大挑戰,對於有些有心機的黑客來說,隨意讓別人獲取全局變量並做惡意的修改,將導致應用程序的崩潰。那如何才能有效避免呢?我的建議:可以采用私有變量對我們的私密數據加以保護,其次還可以采用命名空間來建立模塊層次以達到合理的保護。

// 這里MyCompany已經成為一個Singleton
var MyCompany = new function(){
    this.MyClient = { 
        WebMail: function() {
            alert("Creating WebMail application...");
        }
    };
}(); 
// 輸出 "Creating WebMail application..."
var myWebMail = new MyCompany.MyClient.WebMail();

到此為止,關於JS面向對象的知識點就介紹到這里,以后的系列還將講解Javascript的性能調優以及測試框架的搭建。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM