
隨著HTML5標(biāo)準(zhǔn)的成熟和在移動(dòng)開發(fā)領(lǐng)域的大規(guī)模使用,JavaScript正成為Web開發(fā)領(lǐng)域最熱門的開發(fā)語言,而且隨著NodeJS等技術(shù)的發(fā)展,JavaScript的應(yīng)用也從傳統(tǒng)前端開發(fā)領(lǐng)域延伸到了服務(wù)器端開發(fā)。但同時(shí)需要注意的是,我們項(xiàng)目中的JavaScript代碼規(guī)模也變得越來越大和更加復(fù)雜。這就要求開發(fā)人員能夠編寫高效且可維護(hù)的JavaScript代碼,雖然JavaScript不像Java那樣對(duì)面向?qū)ο笤O(shè)計(jì)有那么好的支持,但我們可以通過在JavaScript中應(yīng)用這些面向?qū)ο蟮脑O(shè)計(jì)模式,來使我們寫出更優(yōu)秀的JavaScript代碼。
在這篇教程中,你將學(xué)習(xí)基于JavaScript的面向?qū)ο缶幊?。其中的代碼示例是基于EcmaScript 5(JavaScript的標(biāo)準(zhǔn)定義)來實(shí)現(xiàn)。
Java與JavaScript的比對(duì)
| Java | ** JavaScript** |
|---|---|
| 靜態(tài)類型 | 動(dòng)態(tài)類型 |
| 使用類,接口和枚舉來定義類型 | 使用函數(shù)和原型來定義類型 |
| 在運(yùn)行時(shí)類型無法改變 | 類型可以在運(yùn)行時(shí)變更 |
| 需要給所有變量聲明類型(強(qiáng)類型校驗(yàn)) | 聲明變量時(shí)不需要指定類型 (弱類型校驗(yàn)) |
| 構(gòu)造器是特殊的方法 | 構(gòu)造器也是一個(gè)函數(shù),與其他函數(shù)沒有區(qū)別 |
| 類和對(duì)象是不同的實(shí)體 | 包括構(gòu)造器,函數(shù)原型在內(nèi)的一切都是對(duì)象 |
| 支持靜態(tài)方法和實(shí)例 | 不直接支持靜態(tài)方法和實(shí)例 |
| 通過抽象類和接口支持抽象類型 | 不直接支持抽象類型 |
| 通過private,package,protected,public定義對(duì)象的作用域 | 只支持public成員 |
| 提供豐富的繼承機(jī)制 | 通過原型實(shí)現(xiàn)繼承 |
| 支持方法級(jí)的重寫和重載機(jī)制 | 不直接支持重寫和重載 |
| 提供豐富的反射特性 | 具有一些反射特性 |
| 通過包提供模塊化支持 | 沒有直接的模塊化支持 |
對(duì)象類型定義- Object Type
function MyType(){
if (!(this instanceof MyType))
throw new Error("Constructor can’t be called as a function");
}
var myInstance = new MyType();
MyType(); // Error: Constructor can’t be called as a function
在Eclipse的JavaScript視圖中,構(gòu)造器,實(shí)例成員,靜態(tài)成員和內(nèi)部函數(shù)都能被識(shí)別,并在Outline視圖中顯示出來。

實(shí)例成員 - Instance Members
通過"new"關(guān)鍵字可以創(chuàng)建一個(gè)實(shí)例對(duì)象,而實(shí)例成員(變量或方法)能夠通過這個(gè)實(shí)例對(duì)象來訪問。實(shí)例成員可以通過"this"關(guān)鍵字,原型(prototype),構(gòu)造器或Object.defineProperty來定義。
function Cat(name){
var voice = "Meow";
this.name = name;
this.say = function(){
return voice;
}
}
Cat.prototype.eat = function(){
return "Eating";
}
var cat = new Cat("Fluffy");
Object.defineProperty(cat, "numLegs",{value: 4,writable:true,enumerable:true,configurable:tr
ue});
console.log(cat.name); // Fluffy
console.log(cat.numLegs); // 4
console.log(cat.say()); // Meow
console.log(cat.eat()); // Eating
靜態(tài)成員 - Static Members
JavaScript中并不直接支持靜態(tài)成員。你可以通過構(gòu)造器來創(chuàng)建靜態(tài)成員。靜態(tài)成員不允許通過"this"關(guān)鍵字直接訪問。
公共靜態(tài)成員
function Factory(){
}
// public static method
Factory.getType = function (){
return "Object Factory";
};
// public static field
Factory.versionId = "F2.0";
Factory.prototype.test = function(){
console.log(this.versionId); // undefined
console.log(Factory.versionId); // F2.0
console.log(Factory.getType()); // Object Factory
}
var factory = new Factory();
factory.test();
私有靜態(tài)成員
var Book = (function () {
// private static field
var numOfBooks = 0;
// private static method
function checkIsbn(isbn) {
if (isbn.length != 10 && isbn.length != 13)
throw new Error("isbn is not valid!");
}
function Book(isbn, title) {
checkIsbn(isbn);
this.isbn = isbn;
this.title = title;
numOfBooks++;
this.getNumOfBooks = function () {
return numOfBooks;
}
}
return Book;
})();
var firstBook = new Book("0-943396-04-2", "First Title");
console.log(firstBook.title); // First Title
console.log(firstBook.getNumOfBooks()); // 1
var secondBook = new Book("0-85131-041-9", "Second Title");
console.log(firstBook.title); // First Title
console.log(secondBook.title); // Second Title
console.log(firstBook.getNumOfBooks()); // 2
console.log(secondBook.getNumOfBooks()); // 2
抽象類型 - Abstract Types
JavaScript是一個(gè)弱類型語言,所以當(dāng)你聲明一個(gè)變量時(shí),不需要指定它的類型。這就減弱了對(duì)于像接口這樣的抽象類型的依賴。但有時(shí)候,你仍然希望使用抽象類型來將一些共有的功能放在一起,并采用繼承的機(jī)制,讓其他類型也具有相同的功能,你可以參考下面的示例:
(function(){
var abstractCreateLock = false;
// abstract type
function BaseForm(){
if(abstractCreateLock)
throw new Error("Can’t instantiate BaseForm!");
}
BaseForm.prototype = {};
BaseForm.prototype.post = function(){
throw new Error("Not implemented!");
}
function GridForm(){
}
GridForm.prototype = new BaseForm();
abstractCreateLock = true;
GridForm.prototype.post = function(){
// ...
return "Grid is posted.";
}
window.BaseForm = BaseForm;
window.GridForm = GridForm;
})();
var myGrid = new GridForm();
console.log(myGrid.post()); // Grid is posted.
var myForm = new BaseForm(); // Error: Can’t instantiate BaseForm!
接口 - Interfaces
JavaScript同樣沒有對(duì)接口的直接支持。你可以通過下面代碼中實(shí)現(xiàn)的機(jī)制來定義接口。
var Interface = function (name, methods) {
this.name = name;
// copies array
this.methods = methods.slice(0);
};
Interface.checkImplements = function (obj, interfaceObj) {
for (var i = 0; i < interfaceObj.methods.length; i++) {
var method = interfaceObj.methods[i];
if (!obj[method] || typeof obj[method] !=="function")
thrownewError("Interfacenotimplemented! Interface: " + interfaceObj.name + " Method: " + method);
}
};
var iMaterial = new Interface("IMaterial", ["getName", "getPrice"]);
function Product(name,price,type){
Interface.checkImplements(this, iMaterial);
this.name = name;
this.price = price;
this.type = type;
}
Product.prototype.getName = function(){
return this.name;
};
Product.prototype.getPrice = function(){
return this.price;
};
var firstCar = new Product("Super Car X11",20000,"Car");
console.log(firstCar.getName()); // Super Car X11
delete Product.prototype.getPrice;
var secondCar = new Product("Super Car X12",30000,"Car"); // Error: Interface not implemented!
單例對(duì)象 - Singleton Object
如果你希望在全局范圍內(nèi)只創(chuàng)建一個(gè)某一類型的示例,那么你可以有下面兩種方式來實(shí)現(xiàn)一個(gè)單例。
var Logger = {
enabled:true,
log: function(logText){
if(!this.enabled)
return;
if(console && console.log)
console.log(logText);
else
alert(logText);
}
}
或者
function Logger(){
}
Logger.enabled = true;
Logger.log = function(logText){
if(!Logger.enabled)
return;
if(console && console.log)
console.log(logText);
else
alert(logText);
};
Logger.log("test"); // test
Logger.enabled = false;
Logger.log("test"); //
創(chuàng)建對(duì)象 - Object Creation
通過new關(guān)鍵字創(chuàng)建
可以使用"new"關(guān)鍵字來創(chuàng)建內(nèi)置類型或用戶自定義類型的實(shí)例對(duì)象,它會(huì)先創(chuàng)建一個(gè)空的實(shí)例對(duì)象,然后再調(diào)用構(gòu)造函數(shù)來給這個(gè)對(duì)象的成員變量賦值,從而實(shí)現(xiàn)對(duì)象的初始化。
//or var dog = {};
//or var dog = new MyDogType();
var dog = new Object();
dog.name = "Scooby";
dog.owner = {};
dog.owner.name = "Mike";
dog.bark = function(){
return "Woof";
};
console.log(dog.name); // Scooby
console.log(dog.owner.name); // Mike
console.log(dog.bark()); // Woof
通過字面量直接創(chuàng)建
通過字面量創(chuàng)建對(duì)象非常簡單和直接,同時(shí)你還可以創(chuàng)建嵌套對(duì)象。
var dog = {
name:"Scooby?",
owner:{
name:"Mike"
},
bark:function(){
return "Woof";
}
};
console.log(dog.name); // Scooby
console.log(dog.owner.name); // Mike
console.log(dog.bark()); // Woof
成員作用域 - Scoping
私有字段 - Private Fields
在JavaScript中沒有對(duì)私有字段的直接支持,但你可以通過構(gòu)造器來實(shí)現(xiàn)它。首先將變量在構(gòu)造函數(shù)中定義為私有的,任何需要使用到這個(gè)私有字段的方法都需要定義在構(gòu)造函數(shù)中,這樣你就可以通過這些共有方法來訪問這個(gè)私有變量了。
function Customer(){
// private field
var risk = 0;
this.getRisk = function(){
return risk;
};
this.setRisk = function(newRisk){
risk = newRisk;
};
this.checkRisk = function(){
if(risk > 1000)
return "Risk Warning";
return "No Risk";
};
}
Customer.prototype.addOrder = function(orderAmount){
this.setRisk(orderAmount + this.getRisk());
return this.getRisk();
};
var customer = new Customer();
console.log(customer.getRisk()); // 0
console.log(customer.addOrder(2000)); // 2000
console.log(customer.checkRisk()); // Risk Warning
私有方法 - Private Methods
私有方法也被稱作內(nèi)部函數(shù),往往被定義在構(gòu)造體中,從外部無法直接訪問它們。
function Customer(name){
var that = this;
var risk = 0;
this.name = name;
this.type = findType();
// private method
function findType() {
console.log(that.name);
console.log(risk);
return "GOLD";
}
}
或者
function Customer(name){
var that = this;
var risk = 0;
this.name = name;
// private method
var findType = function() {
console.log(that.name);
console.log(risk);
return "GOLD";
};
this.type = findType();
}
var customer = new Customer("ABC Customer"); // ABC Customer
// 0
console.log(customer.type); // GOLD
console.log(customer.risk); // undefined
如果私有內(nèi)部函數(shù)被實(shí)例化并被構(gòu)造函數(shù)返回,那么它將可以從外部被調(diào)用。
function Outer(){
return new Inner();
//private inner
function Inner(){
this.sayHello = function(){
console.log("Hello");
}
}
}
(new Outer()).sayHello(); // Hello
特權(quán)方法 - Privileged Methods
原型方法中的一切都必須是公共的,因此它無法調(diào)用類型定義中的私有變量。通過在構(gòu)造函數(shù)中使用"this."聲明的函數(shù)稱為特權(quán)方法,它們能夠訪問私有字段,并且可以從外部調(diào)用。
function Customer(orderAmount){
// private field
var cost = orderAmount / 2;
this.orderAmount = orderAmount;
var that = this;
// privileged method
this.calculateProfit = function(){
return that.orderAmount - cost;
};
}
Customer.prototype.report = function(){
console.log(this.calculateProfit());
};
var customer = new Customer(3000);
customer.report(); // 1500
公共字段 - Public Fields
公共字段能夠被原型或?qū)嵗龑?duì)象訪問。原型字段和方法被所有實(shí)例對(duì)象共享(原型對(duì)象本身也是被共享的)。當(dāng)實(shí)例對(duì)象改變它的某一個(gè)字段的值時(shí),并不會(huì)改變其他對(duì)象中該字段的值,只有直接使用原型對(duì)象修改字段,才會(huì)影響到所有實(shí)例對(duì)象中該字段的值。
function Customer(name,orderAmount){
// public fields
this.name = name;
this.orderAmount = orderAmount;
}
Customer.prototype.type = "NORMAL";
Customer.prototype.report = function(){
console.log(this.name);
console.log(this.orderAmount);
console.log(this.type);
console.log(this.country);
};
Customer.prototype.promoteType = function(){
this.type = "SILVER";
};
var customer1 = new Customer("Customer 1",10);
// public field
customer1.country = "A Country";
customer1.report(); // Customer 1
// 10
// NORMAL
// A Country
var customer2 = new Customer("Customer 2",20);
customer2.promoteType();
console.log(customer2.type); // SILVER
console.log(customer1.type); // NORMAL
公共方法 - Public Methods
原型方法是公共的,所有與之關(guān)聯(lián)的對(duì)象或方法也都是公共的。
function Customer(){
// public method
this.shipOrder = function(shipAmount){
return shipAmount;
};
}
// public method
Customer.prototype.addOrder = function (orderAmount) {
var totalOrder = 0;
for(var i = 0; i < arguments.length; i++) {
totalOrder += arguments[i];
}
return totalOrder;
};
var customer = new Customer();
// public method
customer.findType = function(){
return "NORMAL";
};
console.log(customer.addOrder(25,75)); // 100
console.log(customer.shipOrder(50)); // 50
console.log(customer.findType()); // NORMAL
繼承 - Inheritance
有幾種方法可以在JavaScript中實(shí)現(xiàn)繼承。其中"原型繼承"——使用原型機(jī)制實(shí)現(xiàn)繼承的方法,是最常用的。如下面示例:
function Parent(){
var parentPrivate = "parent private data";
var that = this;
this.parentMethodForPrivate = function(){
return parentPrivate;
};
console.log("parent");
}
Parent.prototype = {
parentData: "parent data",
parentMethod: function(arg){
return "parent method";
},
overrideMethod: function(arg){
return arg + " overriden parent method";
}
}
function Child(){
// super constructor is not called, we have to invoke it
Parent.call(this);
console.log(this.parentData);
var that = this;
this.parentPrivate = function(){
return that.parentMethodForPrivate();
};
console.log("child");
}
//inheritance
Child.prototype = new Parent();// parent
Child.prototype.constructor = Child;
//lets add extented functions
Child.prototype.extensionMethod = function(){
return "child’s " + this.parentData;
};
//override inherited functions
Child.prototype.overrideMethod = function(){
//parent’s method is called
return "Invoking from child" + Parent.prototype.
overrideMethod.call(this, " test");
};
var child = new Child();// parent
// parent data
// child
console.log(child.extensionMethod()); //child’s parent data
console.log(child.parentData); //parent data
console.log(child.parentMethod()); //parent method
console.log(child.overrideMethod()); //Invoking from child test
overriden parent method
console.log(child.parentPrivate()); // parent private data
console.log(child instanceof Parent); //true
console.log(child instanceof Child); //true
當(dāng)一個(gè)成員字段或函數(shù)被訪問時(shí),會(huì)首先搜索這個(gè)對(duì)象自身的成員。如果沒有找到,那么會(huì)搜索這個(gè)對(duì)象對(duì)應(yīng)的原型對(duì)象。如果在原型對(duì)象中仍然沒有找到,那么會(huì)在它的父對(duì)象中查找成員和原型。這個(gè)繼承關(guān)系也被成為 "原型鏈"。下面這張圖就反映了原型鏈的繼承關(guān)系。

模塊化 - Modularization
當(dāng)我們的項(xiàng)目中,自定義的對(duì)象類型越來越多時(shí),我們需要更有效地組織和管理這些類定義,并控制他們的可見性,相互依賴關(guān)系以及加載順序。"命名空間"和"模塊"能夠幫助我們很好地解決這個(gè)問題。(EcmaScript 6已經(jīng)實(shí)現(xiàn)了模塊系統(tǒng),但因它還沒有被所有瀏覽器實(shí)現(xiàn),此處我們?nèi)砸訣S5為例來進(jìn)行說明)
命名空間 - Namespaces
JavaScript中并沒有命名空間的概念。我們需要通過對(duì)象來創(chuàng)建命名空間,并將我們定義的對(duì)象類型放入其中。
//create namespace
var myLib = {};
myLib.myPackage = {};
//Register types to namespace
myLib.myPackage.MyType1 = MyType1;
myLib.myPackage.MyType2 = MyType2;
模塊 - Modules
模塊被用來將我們的JavaScript代碼分解到包中。模塊可以引用其他模塊或?qū)⒆约憾x的對(duì)象類型對(duì)外暴露,以供其他模塊使用。同時(shí)它能夠用來管理模塊間的依賴關(guān)系,并按照我們指定的順序進(jìn)行加載。目前有一些第三方庫可以用來實(shí)現(xiàn)模塊的管理。
下面的例子中,我們?cè)谀K里定義新的類型,并且引用其他模塊并將自身的公共類型對(duì)外暴露。
Module.define("module1.js",
["dependent_module1.js","dependent_module2.js",...],
function(dependentMod1, dependentMod2) {//IMPORTS
//TYPE DEFINITIONS
function ExportedType1(){
// use of dependent module’s types
var dependentType = new dependentMod1.DependentType1();
...
}
function ExportedType2(){
}
...
// EXPORTS
return { ExportedType1: ExportedType1, ExportedType2:ExportedType2,...};
});
//To use a module (can work asynchronously or synchronously):
Module.use(["module1.js"], function(aModule){
console.log("Loaded aModule!");
var AType = aModule.AnExportedType;
var atype1Instance = new AType();
});
自定義異常 - Custom Exceptions
JavaScript中有一些內(nèi)部定義的異常,如Error、TypeError和SyntaxError。它們會(huì)在運(yùn)行時(shí)被創(chuàng)建和拋出。所有的異常都是"unchecked"。一個(gè)普通的對(duì)象也可以被用作一個(gè)異常,并在throw語句中拋出。因此,我們可以創(chuàng)建自己定義的異常對(duì)象,并且在程序中捕獲它們進(jìn)行處理。一個(gè)異常處理的最佳實(shí)踐是,擴(kuò)展JavaScript中標(biāo)準(zhǔn)的Error對(duì)象。
function BaseException() {}
BaseException.prototype = new Error();
BaseException.prototype.constructor = BaseException;
BaseException.prototype.toString = function() {
// note that name and message are properties of Error
return this.name + ":"+this.message;
};
function NegativeNumberException(value) {
this.name = "NegativeNumberException";
this.message = "Negative number!Value: "+value;
}
NegativeNumberException.prototype = new BaseException();
NegativeNumberException.prototype.constructor = NegativeNumberException;
function EmptyInputException() {
this.name = "EmptyInputException";
this.message = "Empty input!";
}
EmptyInputException.prototype = new BaseException();
EmptyInputException.prototype.constructor = EmptyInputException;
var InputValidator = (function() {
var InputValidator = {};
InputValidator.validate = function(data) {
var validations = [validateNotNegative, validateNotEmpty];
for (var i = 0; i < validations.length; i++) {
try {
validations[i](data);
} catch (e) {
if (e instanceof NegativeNumberException) {
//re-throw
throw e;
} else if (e instanceof EmptyInputException) {
// tolerate it
data = "0";
}
}
}
};
return InputValidator;
function validateNotNegative(data) {
if (data < 0)
throw new NegativeNumberException(data)
}
function validateNotEmpty(data) {
if (data == "" || data.trim() == "")
throw new EmptyInputException();
}
})();
try {
InputValidator.validate("-1");
} catch (e) {
console.log(e.toString()); // NegativeNumberException:Negative number!Value: -1
console.log("Validation is done."); // Validation is done.
}
自定義事件 - Custom Events
自定義事件能夠幫助我們減小代碼的復(fù)雜度,并且有效地進(jìn)行對(duì)象之間的解耦。下面是一個(gè)典型的自定義事件應(yīng)用模式:
function EventManager() {}
var listeners = {};
EventManager.fireEvent = function(eventName, eventProperties) {
if (!listeners[eventName])
return;
for (var i = 0; i < listeners[eventName].length; i++) {
listeners[eventName][i](eventProperties);
}
};
EventManager.addListener = function(eventName, callback) {
if (!listeners[eventName])
listeners[eventName] = [];
listeners[eventName].push(callback);
};
EventManager.removeListener = function(eventName, callback) {
if (!listeners[eventName])
return;
for (var i = 0; i < listeners[eventName].length; i++) {
if (listeners[eventName][i] == callback) {
delete listeners[eventName][i];
return;
}
}
};
EventManager.addListener("popupSelected", function(props) {
console.log("Invoked popupSelected event: "+props.itemID);
});
EventManager.fireEvent("popupSelected", {
itemID: "100"
}); //Invoked popupSelected event: 100
編寫組件 - Components
JavaScriipt允許開發(fā)人員通過編寫組件來向HTML元素添加行為。下面是一個(gè)典型的應(yīng)用場(chǎng)景,將JavaScript對(duì)象綁定到了DOM元素上。
1-定義JavaScript組件
function InputTextNumberComponent(domElement) {
this.initialize(domElement);
}
InputTextNumberComponent.prototype.initialize =
function(domElement) {
domElement.onchange = function() {
//just a format
domElement.value = "-" +domElement.value + " -";
};
domElement.jsComponent = this; //Expando property
this.domElement = domElement;
};
InputTextNumberComponent.prototype.resetValue = function() {
this.domElement.value = "";
};
2-定義一個(gè)CSS樣式用于與JavaScript組件寫作控制HTML元素
<style type="text/css">
.inputTextNumber { text-align:right; }
</style>
HTML元素的定義如下
<input type="text" class="inputTextNumber" name="NumberField"
size="10" value="Click me!" onClick="this.jsComponent.
resetValue()">
2-當(dāng)頁面加載時(shí)(或DOM元素都已準(zhǔn)備就緒),檢測(cè)HTML元素并創(chuàng)建JavaScript組件
window.onload = function() {
var inputTextNumbers = document.getElementsByClassName("inputTextNumber");
for (var i = 0; i < inputTextNumbers.length; i++) {
var myComp = new InputTextNumberComponent(
inputTextNumbers.item(i));
}
};
簡書簽約作者:技匠,以上內(nèi)容歡迎大家分享到朋友圈/微博等。如需轉(zhuǎn)載,請(qǐng)通過簡信聯(lián)系授權(quán)。謝謝大家!