国产亚洲精品福利在线无卡一,国产精久久一区二区三区,亚洲精品无码国模,精品久久久久久无码专区不卡

當(dāng)前位置: 首頁 > news >正文

廣州域名企業(yè)網(wǎng)站建站哪家好百度收錄網(wǎng)站需要多久

廣州域名企業(yè)網(wǎng)站建站哪家好,百度收錄網(wǎng)站需要多久,政協(xié)機(jī)關(guān)網(wǎng)站建設(shè),wordpress 圖片無法上傳原文:Pro JavaScript Development 協(xié)議:CC BY-NC-SA 4.0 六、設(shè)計(jì)模式:結(jié)構(gòu)型 在這一章中,我們將繼續(xù)關(guān)注設(shè)計(jì)模式,重點(diǎn)是結(jié)構(gòu)設(shè)計(jì)模式。我們在前一章中看到的創(chuàng)造性設(shè)計(jì)模式集中在對(duì)象創(chuàng)建上,而結(jié)構(gòu)化設(shè)計(jì)…

原文:Pro JavaScript Development

協(xié)議:CC BY-NC-SA 4.0

六、設(shè)計(jì)模式:結(jié)構(gòu)型

在這一章中,我們將繼續(xù)關(guān)注設(shè)計(jì)模式,重點(diǎn)是結(jié)構(gòu)設(shè)計(jì)模式。我們在前一章中看到的創(chuàng)造性設(shè)計(jì)模式集中在對(duì)象創(chuàng)建上,而結(jié)構(gòu)化設(shè)計(jì)模式幫助你將對(duì)象組合成一個(gè)更大、更結(jié)構(gòu)化的代碼庫。它們是靈活的、可維護(hù)的、可擴(kuò)展的,并且確保如果系統(tǒng)的一部分發(fā)生變化,您不需要完全重寫其余部分來適應(yīng)。結(jié)構(gòu)化設(shè)計(jì)模式還可以用來幫助與其他代碼結(jié)構(gòu)進(jìn)行交互,您需要在應(yīng)用中輕松地使用這些代碼結(jié)構(gòu)。讓我們一起來看看你可能會(huì)發(fā)現(xiàn)在你的代碼中有用的八種結(jié)構(gòu)設(shè)計(jì)模式,以及一些例子。

適配器模式

adapterpattern 是一種有用的設(shè)計(jì)模式,當(dāng)您需要將兩個(gè)或更多通常不會(huì)連接在一起的代碼組件連接在一起時(shí),可以使用它;類似地,當(dāng)您開發(fā)的 API 被更新,不再以相同的方式調(diào)用時(shí),它就變得有用了——提供了一個(gè)適配器來連接新舊版本,有助于 API 用戶的遷移,他們可以利用您代碼中的其他改進(jìn),而不會(huì)破壞他們的代碼。清單 6-1 中的例子展示了如何使用這種模式為你的代碼創(chuàng)建一個(gè)適配器來映射一個(gè)新的 API 接口到一個(gè)舊的接口。

清單 6-1。適配器模式

// Imagine the following interface exists deep in your large code base for making Ajax requests

// over HTTP

var http = {

makeRequest: function(type, url, callback, data) {

var xhr = new XMLHttpRequest(),

STATE_LOADED = 4,

STATUS_OK = 200;

xhr.onreadystatechange = function() {

if (xhr.readyState !== STATE_LOADED) {

return;

}

if (xhr.status === STATUS_OK) {

callback(xhr.responseText);

}

};

xhr.open(type.toUpperCase(), url);

xhr.send(data);

}

};

// The http.makeRequest() method defined above could be called as follows, for getting and

// updating user data in a system for a user with an ID of "12345":

http.makeRequest("get", "/user/12345", function(response) {

alert("HTTP GET response received. User data: " + response);

});

http.makeRequest("post", "/user/12345", function(response) {

alert("HTTP POST response received. New user data: " + response);

}, "company=AKQA&name=Den%20Odell");

// Now imagine in a refactor of your project, you decide to introduce a new structure using a

// namespace and splitting out the makeRequest() method into separate methods for HTTP GET

// and POST requests

var myProject = {

data: {

ajax: (function() {

function createRequestObj(callback) {

var xhr = new XMLHttpRequest(),

STATE_LOADED = 4,

STATUS_OK = 200;

xhr.onreadystatechange = function() {

if (xhr.readyState !== STATE_LOADED) {

return;

}

if (xhr.status === STATUS_OK) {

callback(xhr.responseText);

}

};

return xhr;

}

return {

get: function(url, callback) {

var requestObj = createRequestObj(callback);

requestObj.open("GET", url);

requestObj.send();

},

post: function(url, data, callback) {

var requestObj = createRequestObj(callback);

requestObj.open("POST", url);

requestObj.send(data);

}

};

}())

}

};

// These new get() and post() methods could be called as follows:

myProject.data.ajax.get("/user/12345", function(response) {

alert("Refactored HTTP GET response received. User data: " + response);

});

myProject.data.ajax.post("/user/12345", "company=AKQA&name=Den%20Odell", function(response) {

alert("Refactored HTTP POST response received. New user data: " + response);

});

// To avoid rewriting every call to the http.makeRequest() method in the rest of your code

// base, you could create an adapter to map the old interface to the new methods. The adapter

// needs to take the same input parameters as the original method it is designed to replace,

// and calls the new methods internally instead

function httpToAjaxAdapter(type, url, callback, data) {

if (type.toLowerCase() === "get") {

myProject.data.ajax.get(url, callback);

} else if (type.toLowerCase() === "post") {

myProject.data.ajax.post(url, data, callback);

}

}

// Finaly, apply the adapter to replace the original method. It will then map the old

// interface to the new one without needing to rewrite the rest of your code at the same time

http.makeRequest = httpToAjaxAdapter;

// Use the new adapter in the same way as the original method - internally it will call the

// newer code, but externally it will appear identical to the old makeRequest() method

http.makeRequest("get", "/user/12345", function(response) {

alert("Adapter HTTP GET response received. User data: " + response);

});

http.makeRequest("post", "/user/12345", function(response) {

alert("Adapter HTTP POST response received. New user data: " + response);

}, "company=AKQA&name=Den%20Odell");

適配器模式最適合在需要將本來不能組合在一起的代碼連接在一起時(shí)使用,例如,當(dāng)外部 API 被更新時(shí)——您創(chuàng)建一個(gè)適配器來將新方法映射到舊方法,以避免需要對(duì)依賴于這些方法的代碼的其余部分進(jìn)行更改。

要在線閱讀有關(guān)適配器模式的更多信息,請(qǐng)查看以下資源:

  • “JavaScript 設(shè)計(jì)模式:適配器”,作者 Joseph Zimmerman,Adobe Developer Connection(通過 http://bit.ly/adapter_pattern )
  • 維基百科上的“適配器設(shè)計(jì)模式”(via http://bit.ly/adapter_wiki )

復(fù)合模式

復(fù)合模式為一個(gè)或多個(gè)對(duì)象創(chuàng)建了一個(gè)界面,而最終用戶不需要知道他們正在處理多少個(gè)對(duì)象。當(dāng)你想簡化其他人訪問你的函數(shù)的方式時(shí),它就派上用場了;無論是將單個(gè)對(duì)象還是一組對(duì)象傳遞給同一個(gè)方法,都沒有區(qū)別。清單 6-2 顯示了復(fù)合模式的一個(gè)簡單例子,允許用戶向一個(gè)或多個(gè) DOM 節(jié)點(diǎn)添加類名,而不需要知道他們是否需要向方法傳遞一個(gè)或多個(gè) DOM 節(jié)點(diǎn)。

清單 6-2。復(fù)合模式

// Define a singleton containing methods to get references to page elements and to add

// class names to those elements

var elements = {

// Define a method to get DOM elements by tag name. If one element is found, it is

// returned as an individual node, or multiple elements are found, an array of those

// found elements are returned

get: function(tag) {

var elems = document.getElementsByTagName(tag),

elemsIndex = 0,

elemsLength = elems.length,

output = [];

// Convert the found elements structure into a standard array

for (; elemsIndex < elemsLength; elemsIndex++) {

output.push(elems[elemsIndex]);

}

// If one element is found, return that single element, otherwise return the array

// of found elements

return output.length === 1 ? output[0] : output;

},

// Define a composite method which adds an class name to one or more elements, regardless

// of how many are passed when it is executed

addClass: function(elems, newClassName) {

var elemIndex = 0,

elemLength = elems.length,

elem;

// Determine if the elements passed in are an array or a single object

if (Object.prototype.toString.call(elems) === "[object Array]") {

// If they are an array, loop through each elements and add the class name to each

for (; elemIndex < elemLength; elemIndex++) {

elem = elems[elemIndex];

elem.className += (elem.className === "" ? "" : " ") + newClassName;

}

} else {

// If a single element was passed in, add the class name value to it

elems.className += (elems.className === "" ? "" : " ") + newClassName;

}

}

};

// Use the elements.get() method to locate the single <body> element on the current page, and

// potentially numerous <a> elements

var body = elements.get("body"),

links = elements.get("a");

// The composite elements.addClass() method gives the same interface to single elements

// as it does to multiple elements, simplifying its use considerably

elements.addClass(body, "has-js");

elements.addClass(links, "custom-link");

當(dāng)您不希望與您的方法交互的開發(fā)人員擔(dān)心有多少對(duì)象作為參數(shù)傳遞給它們,從而簡化方法調(diào)用時(shí),最好使用復(fù)合模式。

要在線閱讀關(guān)于復(fù)合模式的更多信息,請(qǐng)查閱以下資源:

  • Joseph Zimmerman 在 Adobe Developer Connection 上發(fā)表的“JavaScript 設(shè)計(jì)模式:復(fù)合”(通過 http://bit.ly/composite_pattern )
  • 維基百科上的“復(fù)合模式”(via http://bit.ly/composite_wiki )

裝飾圖案

裝飾模式是一種擴(kuò)展和定制從“類”創(chuàng)建的對(duì)象的方法和屬性的方式,而不需要?jiǎng)?chuàng)建大量可能變得難以管理的子類。這是通過有效地將對(duì)象包裝在另一個(gè)實(shí)現(xiàn)相同公共方法的對(duì)象中來實(shí)現(xiàn)的,其中相關(guān)方法根據(jù)我們試圖增強(qiáng)的行為被覆蓋。清單 6-3 中的代碼展示了一個(gè)創(chuàng)建幾個(gè)裝飾器的例子,每個(gè)裝飾器都用額外的屬性和行為來擴(kuò)充一個(gè)現(xiàn)有的對(duì)象。

清單 6-3。裝飾圖案

var FormField = function(type, displayText){

this.type = type || "text";

this.displayText = displayText || "";

};

FormField.prototype = {

createElement: function() {

this.element = document.createElement("input");

this.element.setAttribute("type", this.type);

this.element.setAttribute("placeholder", this.displayText);

return this.element;

},

isValid: function() {

return this.element.value !== "";

}

};

// The form field deocorator, which implements the same public methods as FormField

var FormFieldDecorator = function(formField) {

this.formField = formField;

};

FormFieldDecorator.prototype = {

createElement: function() {

this.formField.createElement();

},

isValid: function() {

return this.formField.isValid();

}

};

var MaxLengthFieldDecorator = function(formField, maxLength) {

FormFieldDecorator.call(this, formField);

this.maxLength = maxLength || 100;

};

MaxLengthFieldDecorator.prototype = new FormFieldDecorator() ;

MaxLengthFieldDecorator.prototype.createElement = function() {

var element = this.formField.createElement();

element.setAttribute("maxlength", this.maxLength);

return element;

};

var AutoCompleteFieldDecorator = function(formField, autocomplete) {

FormFieldDecorator.call(this, formField);

this.autocomplete = autocomplete || "on";

};

AutoCompleteFieldDecorator.prototype = new FormFieldDecorator();

AutoCompleteFieldDecorator.prototype.createElement = function() {

var element = this.formField.createElement();

element.setAttribute("autocomplete", this.autocomplete);

return element;

};

清單 6-3 中創(chuàng)建的裝飾器可以如清單 6-4 所示用于生成一個(gè)表示表單中表單字段的對(duì)象,使用這些裝飾器而不是通過子類來擴(kuò)充它的屬性和行為。

清單 6-4。正在使用的裝飾模式

// Create an empty <form> tag and a new FormField object to represent

// a <input type="search"> field

var form = document.createElement("form"),

formField = new FormField("search", "Enter your search term");

// Extend the formField object using our decorators to add maxlength and autocomplete properties

// to the resulting form field element. Note how we pass the extended formField object into each

// decorator in turn, which extends it further.

formField = new MaxLengthFieldDecorator(formField, 255);

formField = new AutoCompleteFieldDecorator(formField, "off");

// Create the HTML form field element and add it to the <form> element

form.appendChild(formField.createElement());

// Add an event handler to the <form> tag's submit event, preventing the form from submitting if

// the form field we added contains no value

form.addEventListener("submit", function(e) {

// Stop the form from submitting

e.preventDefault();

// Test to see if our form field is valid, i.e. that it contains a value

if (formField.isValid()) {

// If it does, go ahead and submit the form

form.submit();

} else {

// If it doesn't, alert the user that something is wrong and they need to correct it

alert("Please correct the issues in the form field.");

}

}, false);

// Add the <form> field to the current page once it has loaded

window.addEventListener("load", function() {

document.body.appendChild(form);

}, false);

當(dāng)您需要快速簡單地增加從一個(gè)“類”創(chuàng)建的對(duì)象實(shí)例的行為,而不必求助于從它創(chuàng)建一長串繼承的子類時(shí),最好使用裝飾模式。要在線閱讀關(guān)于裝飾模式的更多信息,請(qǐng)查閱以下資源:

  • Addy Osmani 的《探索 JavaScript 中的裝飾模式》(via http://bit.ly/decorator_pattern )
  • 斯托揚(yáng)·斯特凡諾夫關(guān)于多布斯博士的“JavaScript 中的裝飾模式”(通過 http://bit.ly/decorator_js )

立面圖案

外立面圖案很常見;它只是編寫一個(gè)函數(shù)來簡化對(duì)一個(gè)或多個(gè)更大、可能更復(fù)雜的函數(shù)的訪問??赡苡腥藭?huì)說,任何簡單地調(diào)用另一個(gè)函數(shù)的函數(shù)都是這種模式的一個(gè)例子,但是我發(fā)現(xiàn)最好是從簡化一些原本需要多個(gè)步驟的事情的角度來考慮,或者提供一個(gè)訪問更大系統(tǒng)的單點(diǎn),這將使其他開發(fā)人員訪問該系統(tǒng)更加容易。清單 6-5 中的代碼演示了一個(gè)簡單的外觀,它提供了一個(gè)包裝器來簡化跨瀏覽器 Ajax 調(diào)用。

清單 6-5。立面圖案

// Define a function which acts as a fa?ade to simplify and facilitate cross-browser Ajax calls,

// supporting browsers all the way back to Internet Explorer 5

function ajaxCall(type, url, callback, data) {

// Get a reference to an Ajax connection object relevant to the current browser

var xhr = (function() {

try {

// The standard method, used in all modern browsers

return new XMLHttpRequest();

}

catch(e) {}

// Older versions of Internet Explorer utilise an ActiveX object installed on the

// user's machine

try {

return new ActiveXObject("Msxml2.XMLHTTP.6.0");

}

catch(e) {}

try {

return new ActiveXObject("Msxml2.XMLHTTP.3.0");

}

catch(e) {}

try {

return new ActiveXObject("Microsoft.XMLHTTP");

}

catch(e) {}

// If no relevant Ajax connection object can be found, throw an error

throw new Error("Ajax not supported in this browser.");

}()),

STATE_LOADED = 4,

STATUS_OK = 200;

// Execute the given callback method once a succesful response is received from the server

xhr.onreadystatechange = function() {

if (xhr.readyState !== STATE_LOADED) {

return;

}

if (xhr.status === STATUS_OK) {

callback(xhr.responseText);

}

};

// Use the browser's Ajax connection object to make the relevant call to the given URL

xhr.open(type.toUpperCase(), url);

xhr.send(data);

}

清單 6-5 中的外觀模式可以用在你的代碼中,如清單 6-6 所示,掩蓋了跨瀏覽器 Ajax 操作背后的復(fù)雜性。

清單 6-6。正在使用的立面圖案

// The ajaxCall() facade function can make cross-browser Ajax calls as follows

ajaxCall("get", "/user/12345", function(response) {

alert("HTTP GET response received. User data: " + response);

});

ajaxCall("post", "/user/12345", function(response) {

alert("HTTP POST response received. New user data: " + response);

}, "company=AKQA&name=Den%20Odell");

當(dāng)您希望通過單個(gè)函數(shù)或方法來提供對(duì)一系列函數(shù)或方法調(diào)用的訪問,以便簡化代碼庫的其余部分,使其更容易遵循,因此將來更易于維護(hù)和擴(kuò)展時(shí),最好使用外觀模式。要在線閱讀有關(guān)外觀模式的更多信息,請(qǐng)查看以下資源:

  • Joseph Zimmerman 在 Adobe Developer Connection 上的“JavaScript 設(shè)計(jì)模式:外觀”(通過 http://bit.ly/facade_pattern )
  • 卡爾·丹利的“正面圖案”(經(jīng)由 http://bit.ly/facade_js )

輕量級(jí)模式

flyweight 模式是一種優(yōu)化模式;這對(duì)于創(chuàng)建大量相似對(duì)象的代碼非常有用,否則這些對(duì)象會(huì)消耗大量內(nèi)存。它用一些共享對(duì)象代替了大量的相似對(duì)象,使得代碼更輕,性能更好;因此得名,它來自拳擊界,指的是最輕重量級(jí)的運(yùn)動(dòng)員,那些最敏捷的運(yùn)動(dòng)員。清單 6-7 顯示了一個(gè)例子,這個(gè)例子說明了 flyweight 模式旨在解決的一個(gè)問題,即對(duì)象的低效存儲(chǔ)。

清單 6-7。低效的對(duì)象實(shí)例

// Create a "class" to store data to related to employees working for one or more different

// companies

function Employee(data) {

// Represent an employee's ID within an organisation

this.employeeId = data.employeeId || 0;

// Represent an employee's social security number

this.ssId = data.ssId || "0000-000-0000";

// Represent an employee's name

this.name = data.name || "";

// Represent an employee's occupation

this.occupation = data.occupation || "";

// Represent an employee's company name, address and country

this.companyName = data.companyName || "";

this.companyAddress = data.companyAddress || "";

this.companyCountry = data.companyCountry || "";

}

// Create three methods to get the employee's name, occupation and company details from the

// stored object

Employee.prototype.getName = function() {

return this.name;

};

Employee.prototype.getOccupation = function() {

return this.occupation;

};

Employee.prototype.getCompany = function() {

return [this.companyName, this.companyAddress, this.companyCountry].join(", ");

};

// Create four employee objects - note that two share the same company information, and two

// share the same ssId and name. As more objects are created, the amount of data repeated will

// grow, consuming more memory due to inefficiency

var denOdell = new Employee({

employeeId: 1456,

ssId: "1234-567-8901",

name: "Den Odell",

occupation: "Head of Web Development",

companyName: "AKQA",

companyAddress: "1 St. John's Lane, London",

companyCountry: "GB"

}),

steveBallmer = new Employee({

employeeId: 3,

ssId: "8376-940-1673",

name: "Steve Ballmer",

occupation: "Ex-CEO",

companyName: "Microsoft",

companyAddress: "1 Microsoft Way, Redmond, WA",

companyCountry: "US"

}),

billGates = new Employee({

employeeId: 1,

ssId: "7754-342-7584",

name: "Bill Gates",

occupation: "Founder",

companyName: "Microsoft",

companyAddress: "1 Microsoft Way, Redmond, WA",

companyCountry: "US"

}),

billGatesPhilanthropist = new Employee({

employeeId: 2,

ssId: "7754-342-7584",

name: "Bill Gates",

occupation: "Philanthropist",

companyName: "Gates Foundation",

companyAddress: "500 Fifth Avenue North, Seattle, WA",

companyCountry: "US"

});

flyweight 模式是通過嘗試解構(gòu)一個(gè)現(xiàn)有的“類”來應(yīng)用的,這樣可以最小化對(duì)象實(shí)例之間可能重復(fù)的任何數(shù)據(jù)。這是通過研究重復(fù)數(shù)據(jù)的任何當(dāng)前對(duì)象實(shí)例并創(chuàng)建單獨(dú)的“類”來表示該數(shù)據(jù)來實(shí)現(xiàn)的。然后,單個(gè)對(duì)象實(shí)例可以表示重復(fù)的數(shù)據(jù),這些數(shù)據(jù)可以從原始“類”的多個(gè)對(duì)象實(shí)例中引用,從而減少存儲(chǔ)的數(shù)據(jù),從而減少應(yīng)用的內(nèi)存占用。

每個(gè)當(dāng)前對(duì)象實(shí)例的任何數(shù)據(jù)核心都稱為該“類”的內(nèi)部數(shù)據(jù),任何可以從對(duì)象中提取、單獨(dú)存儲(chǔ)和引用的數(shù)據(jù)都稱為其外部數(shù)據(jù)。在清單 6-7 中,與雇員相關(guān)的內(nèi)在數(shù)據(jù)——本質(zhì)上是唯一的——是它的employeeIdoccupation值。目前復(fù)制在多個(gè)Employee對(duì)象上的公司數(shù)據(jù)可以單獨(dú)提取和存儲(chǔ);每個(gè)人的數(shù)據(jù)也是如此,比如他們的namessId值。因此,一個(gè)雇員可以用四個(gè)屬性來表示:employeeId、occupation、companyperson。最后兩個(gè)屬性引用其他對(duì)象實(shí)例。

flyweight 模式分三個(gè)階段應(yīng)用,如清單 6-8 所示:首先,創(chuàng)建新的“類”來表示外部數(shù)據(jù);第二,通過應(yīng)用工廠模式來確保先前創(chuàng)建的對(duì)象不會(huì)被重新創(chuàng)建;最后,通過編寫代碼,以與最初相同的方式創(chuàng)建對(duì)象,允許所有 flyweight 的繁重工作在幕后進(jìn)行。

清單 6-8。輕量級(jí)模式

// The first stage of applying the flyweight pattern is to extract intrinsic data from

// extrinsic data in the objects we wish to make more memory-efficient

//

// There are two sets of extrinsic data in an Employee object from Listing 6-7 - people data

// and company data. Let's create two "classes" to represent those types of data

//

// A Person object represents an individual's social security number and their name

function Person(data) {

this.ssId = data.ssId || "";

this.name = data.name || "";

}

// A Company object represents a company's name, address and country details

function Company(data) {

this.name = data.name || "";

this.address = data.address || "";

this.country = data.country || "";

}

// The second stage of the flyweight pattern is to ensure any objects representing unique

// extrinsic data are only created once and stored for use in future. This is achieved by

// harnessing the factory pattern for each of the new extrinsic data "classes" to abstract

// away the creation of the object instance so that if a previously-existing object is found,

// that can be returned instead of creating a new instance

var personFactory = (function() {

// Create a variable to store all instances of the People "class" by their ssId

var people = {},

personCount = 0;

return {

// Provide a method to create an instance of the People "class" if one does not

// already exist by the given ssId provided in the data input. If one exists,

// return that object rather than creating a new one

createPerson: function(data) {

var person = people[data.ssId],

newPerson;

// If the person by the given ssId exists in our local data store, return their

// object instance, otherwise create a new one using the provided data

if (person) {

return person;

} else {

newPerson = new Person(data);

people[newPerson.ssId] = newPerson;

personCount++;

return newPerson;

}

},

// Provide a method to let us know how many Person objects have been created

getPersonCount: function() {

return personCount;

}

};

}()),

// Create a similar factory for Company objects, storing company data by name

companyFactory = (function() {

var companies = {},

companyCount = 0;

return {

createCompany: function(data) {

var company = companies[data.name],

newCompany;

if (company) {

return company;

} else {

newCompany = new Company(data);

companies[newCompany.name] = newCompany;

companyCount++;

return newCompany;

}

},

getCompanyCount: function() {

return companyCount;

}

};

}()),

// The third stage of the flyweight pattern is to allow the creation of objects in a

// simliar way to that in Listing 6-7, providing all the handling of data storage in the

// most efficient way in a transparent way to the end user

//

// Create an object with methods to store employee data and to return data from each

// object by their employeeId. This simplifies the end user's code as they do not need to

// access methods on underlying objects directly, they only need interface with this handler

employee = (function() {

// Create a data store for all employee objects created

var employees = {},

employeeCount = 0;

return {

// Provide a method to add employees to the data store, passing the provided data

// to the Person and Company factories and storing the resulting object, consisting

// of the enployeeId, occupation, person object reference, and company object

// reference in the local data store

add: function(data) {

// Create or locate Person or Company objects that correspond to the provided

// data, as appropriate

var person = personFactory.createPerson({

ssId: data.ssId,

name: data.name

}),

company = companyFactory.createCompany({

name: data.companyName,

address: data.companyAddress,

country: data.companyCountry

});

// Store a new object in the local data store, containing the employeeId,

// their occupation, and references to the company they work for and their

// unique personal data, including their name and social security number

employees[data.employeeId] = {

employeeId: data.employeeId,

occupation: data.occupation,

person: person,

company: company

};

employeeCount++;

},

// Provide a method to return the name of an employee by their employeeId - the

// data is looked up from the associated Person object

getName: function(employeeId) {

return employees[employeeId].person.name;

},

// Provide a method to return the occupation of an employee by their employeeId

getOccupation: function(employeeId) {

return employees[employeeId].occupation;

},

// Provide a method to return the address of the company an employee works for -

// the data is looked up from the associated Company object

getCountry: function(employeeId) {

var company = employees[employeeId].company;

return [company.name, company.address, company.country].join(", ");

},

// Provide a utlility method to tell us how many employees have been created

getTotalCount: function() {

return employeeCount;

}

};

}());

清單 6-8 中的 flyweight 代碼可以如清單 6-9 所示使用,它復(fù)制了清單 6-7 的行為。應(yīng)用 flyweight 模式的原始內(nèi)存消耗對(duì)象中的重復(fù)數(shù)據(jù)越多,共享的對(duì)象就越多,因此減少了應(yīng)用的內(nèi)存占用,證明了這種設(shè)計(jì)模式的有用性。

清單 6-9。正在使用的輕量級(jí)模式

// Create four employee objects - note that two share the same company information, and two

// share the same ssId and name. Behind the scenes, the flyweight pattern from Listing 6-8

// ensures that repeated person and company data is stored in the most efficient way possible.

var denOdell = employee.add({

employeeId: 1456,

ssId: "1234-567-8901",

name: "Den Odell",

occupation: "Head of Web Development",

companyName: "AKQA",

companyAddress: "1 St. John's Lane, London",

companyCountry: "GB"

}),

steveBallmer = employee.add({

employeeId: 3,

ssId: "8376-940-1673",

name: "Steve Ballmer",

occupation: "Ex-CEO",

companyName: "Microsoft",

companyAddress: "1 Microsoft Way, Redmond, WA",

companyCountry: "US"

}),

billGates = employee.add({

employeeId: 1,

ssId: "7754-342-7584",

name: "Bill Gates",

occupation: "Founder",

companyName: "Microsoft",

companyAddress: "1 Microsoft Way, Redmond, WA",

companyCountry: "US"

}),

billGatesPhilanthropist = employee.add({

employeeId: 2,

ssId: "7754-342-7584",

name: "Bill Gates",

occupation: "Philanthropist",

companyName: "Gates Foundation",

companyAddress: "500 Fifth Avenue North, Seattle, WA",

companyCountry: "US"

});

// We've created three objects representing people by ssId and name - Den Odell, Steve Ballmer

// and Bill Gates

alert(personFactory.getPersonCount()); // 3

// We've created three objects representing companies by name, address and country - AKQA,

// Microsoft and the Gates Foundation

alert(companyFactory.getCompanyCount()); // 3

// We've created four objects representing employees, with two unique properties and two

// properties linking to existing person and company objects. The more employee objects we

// create with shared person and company data, the less data we're storing in our application

// and the more effective the flyweight pattern becomes

alert(employee.getTotalCount()); // 4

當(dāng)您有大量具有相似共享屬性名稱-值對(duì)的對(duì)象時(shí),最好使用 flyweight 模式,這些對(duì)象可以被分成更小的對(duì)象,這些對(duì)象之間通過引用共享數(shù)據(jù),以便減少代碼的內(nèi)存占用,提高代碼的效率。要在線關(guān)于 flyweight 模式的內(nèi)容,請(qǐng)查閱以下資源:

  • Addy Osmani 在《MSDN》雜志上發(fā)表的“用 Flyweight 模式管理應(yīng)用資源”(via http://bit.ly/flyweight_pattern )
  • Gurpreet Singh 的“輕量級(jí)模式”(via http://bit.ly/flyweight_js )

混合模式

mixin 模式通過快速方便地將一組方法和屬性從一個(gè)對(duì)象直接應(yīng)用到另一個(gè)對(duì)象,或者直接應(yīng)用到“類”的原型,使得所有對(duì)象實(shí)例都可以訪問這些屬性和方法,從而避免了對(duì)大量子類化和繼承鏈的需求。盡管這聽起來像是“黑客”,特別是對(duì)于那些從傳統(tǒng)的面向?qū)ο蟊尘伴_始接觸 JavaScript 的開發(fā)人員來說,這種模式直接利用了 JavaScript 語言的優(yōu)勢及其對(duì)原型的使用,而不是其他語言所應(yīng)用的嚴(yán)格的經(jīng)典繼承,并且如果小心使用,可以簡化開發(fā)和代碼維護(hù)。清單 6-10 中的代碼展示了如何使用 mixin 模式簡單快速地將一組通用方法應(yīng)用到多個(gè)對(duì)象上。

清單 6-10?;旌夏J?/p>

// Define a mixin which enables debug logging, to be applied to any object or "class"

var loggingMixin = {

// Define a storage array for logs

logs: [],

// Define a method to store a message in the log

log: function(message) {

this.logs.push(message);

},

// Define a method to read out the stored logs

readLog: function() {

return this.logs.join("\n");

}

},

element,

header,

textField,

emailField;

// Function to apply methods and properties from one object to another, which we'll use to apply

// the mixin to other objects

function extendObj(obj1, obj2) {

var obj2Key;

for (obj2Key in obj2) {

if (obj2.hasOwnProperty(obj2Key)) {

obj1[obj2Key] = obj2[obj2Key];

}

}

return obj1;

}

// Define a singleton to which we will apply the mixin, though will function fine without it

element = {

allElements: [],

create: function(type) {

var elem = document.createElement(type);

this.allElements.push(elem);

// Use the mixin method log(), ensuring it exists first before calling it. If the mixin

// is not applied, then the method will still function fine

if (typeof this.log === "function") {

this.log("Created an element of type: " + type);

}

return elem;

},

getAllElements: function() {

return this.allElements;

}

};

// Define a simple "class" to which we will apply the mixin

function Field(type, displayText) {

this.type = type || "";

this.displayText = displayText || "";

// Ensure the mixin method log() exists before executing

if (typeof this.log === "function") {

this.log("Created an instance of Field");

}

}

Field.prototype = {

getElement: function() {

var field = document.createElement("input");

field.setAttribute("type", this.type);

field.setAttribute("placeholder", this.displayText);

if (typeof this.log === "function") {

this.log("Created a DOM element with placeholder text: " + this.displayText);

}

return field;

}

};

// Apply the mixin directly to the 'element' object by essentially copying over methods and

// properties from the mixin to the singleton

element = extendObj(element, loggingMixin);

// Apply the mixin to the Field "class" prototype, making its methods available to each object

// instance created from it

Field.prototype = extendObj(Field.prototype, loggingMixin);

// Create a new DOM element using the element.create() method

header = element.create("header");

// Create two object instances, both of which receive the getElement method from the prototype

textField = new Field("text", "Enter the first line of your address");

emailField = new Field("email", "Enter your email address");

// Add the elements stored in these objects to the current page

document.body.appendChild(textField.getElement());

document.body.appendChild(emailField.getElement());

// Output the logs stored via the mixin

alert(loggingMixin.readLog());

// Outputs the following - note how all the logs from each usage of the mixin are

// stored together:

/*

Created an element of type: header

Created an instance of Field

Created an instance of Field

Created a DOM element with placeholder text: Enter the first line of your address

Created a DOM element with placeholder text: Enter your email address

*/

如果您研究清單 6-10 中的代碼,您可能會(huì)注意到一些意想不到的事情:盡管將 mixin 獨(dú)立地應(yīng)用于 singleton 和“class”,但是所有記錄的數(shù)據(jù)都存儲(chǔ)在一起。對(duì)任何包含該方法的對(duì)象調(diào)用readLog()方法都會(huì)輸出相同的結(jié)果。發(fā)生這種情況是因?yàn)楫?dāng)extendObj()函數(shù)將 objectlike 屬性從一個(gè)對(duì)象復(fù)制到另一個(gè)對(duì)象時(shí),比如本例中的logs數(shù)組(記住數(shù)組是 JavaScript 中的一種對(duì)象類型),這些是通過引用復(fù)制的,而不是實(shí)際的數(shù)據(jù)副本。每次從任何對(duì)象訪問該屬性時(shí),都使用相同的屬性,最初來自loggingMixin對(duì)象。在這個(gè)例子中,我們希望看到所有的日志,所以這是有用的;然而,在您自己的代碼中使用這種模式時(shí),這可能不是您需要的結(jié)果。如果你想為復(fù)制的屬性創(chuàng)建單獨(dú)的副本,更新extendObj()函數(shù),如清單 6-11 所示。

清單 6-11。更新了 extendObj()函數(shù)以復(fù)制屬性,而不是通過引用復(fù)制

// Update extendObj() to duplicate object-based properties rather than point to them

// by reference

function extendObj(obj1, obj2) {

var obj2Key,

value;

for (obj2Key in obj2) {

if (obj2.hasOwnProperty(obj2Key)) {

value = obj2[obj2Key];

// If the value being copied is an array, then copy a duplicate of that array using

// the slice() method

if (Object.prototype.toString.apply(value) === "[object Array]") {

obj1[obj2Key] = value.slice();

// Otherwise, if the value being copied in an object, and not an array, then copy

// across a duplicate of that object using a recursive call to this function

} else if (typeof obj2[obj2Key] === "object") {

obj1[obj2Key] = extendObj({}, value);

// Otherwise, copy across the value as usual

} else {

obj1[obj2Key] = value;

}

}

}

return obj1;

}

當(dāng)您希望快速地將一組屬性和方法直接從一個(gè)對(duì)象應(yīng)用到另一個(gè)對(duì)象,或者應(yīng)用到一個(gè)“類”以供其所有對(duì)象實(shí)例使用時(shí),mixin 模式是最好的選擇,而不需要求助于復(fù)雜的子類化和繼承。要在線關(guān)于 mixin 模式的內(nèi)容,請(qǐng)參考以下資源:

  • 安格斯·克羅爾的《JavaScript Mixins 的新觀點(diǎn)》(via http://bit.ly/mixin_pattern )
  • 《JavaScript Mixins:超越簡單對(duì)象擴(kuò)展》作者吳鎮(zhèn)男·貝利(via http://bit.ly/mixins_beyond )

模塊模式

模塊模式可能是專業(yè) JavaScript 開發(fā)人員最常用的模式。事實(shí)上,我們已經(jīng)在前面的章節(jié)中兩次討論了模式的基礎(chǔ):第一次是在第一章中討論公共、私有和受保護(hù)變量時(shí),第二次是在第四章中討論改進(jìn) JavaScript 壓縮的方法時(shí)。這一切都基于自執(zhí)行函數(shù)閉包,它允許我們創(chuàng)建一個(gè)沙盒代碼區(qū)域,可以訪問全局變量和函數(shù),但不會(huì)將其中聲明的變量或函數(shù)暴露給周圍的作用域,除非使用return語句顯式聲明。自執(zhí)行函數(shù)的最簡單示例如下所示:

(function() {

// Any variables or functions declared within this function aren't accessible outside it

}());

我們可以使用這種模式將我們的代碼庫劃分成更小的、相關(guān)的代碼塊,我們稱之為模塊,這就是該模式的名字。這些模塊中的每一個(gè)都應(yīng)該清楚地說明它們對(duì)代碼的其他部分的依賴性,如果有的話,這些部分應(yīng)該作為參數(shù)傳遞給函數(shù),如下所示:

(function($) {

// We very clearly define jQuery as a dependency for this 'module', making it available

// internally through the $ variable

}(jQuery));

Tip

在函數(shù)內(nèi)訪問 JavaScript 參數(shù)比在函數(shù)外訪問全局變量更快,因?yàn)檎Z言解釋器不必執(zhí)行離開當(dāng)前函數(shù)范圍來搜索變量的額外步驟。

模塊模式的基本形式是通過使用函數(shù)閉包內(nèi)的return語句來傳遞回任何可能對(duì)其他模塊或主應(yīng)用本身有用的聲明代碼來完成的。清單 6-12 顯示了模塊模式的完整形式,基于上一章的清單 5-10。

清單 6-12。模塊模式

// The module pattern is distinctive as it uses a combination of a self-executing anonymous

// function closure, with any dependencies passed in as parameters, and an optional return

// statement which allows code created within the closure to be made available externally

// Our only dependency is the 'document' object which contains the browser's cookie data. As an

// added security measure, we can include a final listed parameter named 'undefined' to which we

// never pass a value. This ensures that the variable named 'undefined' always contains an

// undefined value provided we always ensure we never pass in a value to this parameter.

// Otherwise it might be possible for other code, whether through malicious reasons or

// otherwise, to overwrite this value as it is not a reserved word in the language causing all

// kinds of havoc to the way our code behaves.

var cookie = (function(document, undefined) {

var allCookies = document.cookie.split(";"),

cookies = {},

cookiesIndex = 0,

cookiesLength = allCookies.length,

cookie;

for (; cookiesIndex < cookiesLength; cookiesIndex++) {

cookie = allCookies[cookiesIndex].split("=");

cookies[unescape(cookie[0])] = unescape(cookie[1]);

}

// Return any methods, properties or values that you wish to make available to the rest of

// your code base. In this case, the following two methods will be exposed through the

// 'cookie' variable, creating a singleton

return {

get: function(name) {

return cookies[name] || "";

},

set: function(name, value) {

cookies[name] = value;

document.cookie = escape(name) + "=" + escape(value);

}

};

// Pass in any dependencies at the point of function execution

}(document));

在通過單例對(duì)象結(jié)構(gòu)利用命名空間的大型代碼庫中,模塊模式的使用方式與我們看到的略有不同;在這種情況下,我們傳入一個(gè)依賴項(xiàng),然后在函數(shù)閉包結(jié)束時(shí)返回,使用該模塊用新的屬性和方法來增加單例。清單 6-13 顯示了模塊模式應(yīng)用于名稱空間的擴(kuò)充,這是它最常見的用途之一。

清單 6-13。使用模塊模式擴(kuò)充名稱空間

// Define a namespace which we will populate with code modules

var myData = {};

// Ajax module, added to the myData namespace through augmentation

// The namespace is passed in as a parameter and, once it has been augmented with new method, is

// finally returned back, overwriting the original namespace with the new, augmented one

myData = (function(myNamespace, undefined) {

// Add an 'ajax' object property to the namespace and populate it with related methods

myNamespace.ajax = {

get: function(url, callback) {

var xhr = new XMLHttpRequest(),

LOADED_STATE = 4,

OK_STATUS = 200;

xhr.onreadystatechange = function() {

if (xhr.readyState !== LOADED_STATE) {

return;

}

if (xhr.status === OK_STATUS) {

callback(xhr.responseText);

}

};

xhr.open("GET", url);

xhr.send();

}

};

// Return the new, augmented namespace back to the myData variable

return myNamespace;

// We can use the following defence mecahnism, which reverts to an empty object if the myData

// namespace object does not yet exist. This is useful when you have modules split over several

// files in a large namespace and you're unsure if the namespace passed in has been initialized

// elsewhere before

}(myData || {}));

// Cookies module, added to the myData namespace through augmentation

// As before, the namespace is passed in, augmented, and then returned, overwriting the original

// namespace object. At this point, the myData namespace contains the Ajax module code

myData = (function(myNamespace, undefined) {

// Add a 'cookies' object property to the namespace and populate it with related methods

myNamespace.cookies = {

get: function(name) {

var output = "",

escapedName = escape(name),

start = document.cookie.indexOf(escapedName + "="),

end = document.cookie.indexOf(";", start);

end = end === -1 ? (document.cookie.length - 1) : end;

if (start >=0) {

output = document.cookie.substring(start + escapedName.length + 1, end);

}

return unescape(output);

},

set: function(name, value) {

document.cookie = escape(name) + "=" + escape(value);

}

};

return myNamespace;

}(myData || {}));

// Execute methods directly through the myData namespace object, which now contains both Ajax

// and Cookies modules

myData.ajax.get("/user/12345", function(response) {

alert("HTTP GET response received. User data: " + response);

});

myData.cookies.set("company", "AKQA");

myData.cookies.set("name", "Den Odell");

alert(myData.cookies.get("company")); // AKQA

alert(myData.cookies.get("name"));????// Den Odell

當(dāng)您希望將大型代碼庫分解成更小的、可管理的、自包含的部分時(shí),最好使用模塊模式,每個(gè)部分都有一組清晰的依賴項(xiàng)和定義明確的目的。由于它們的沙箱特性,它們的自執(zhí)行功能塊也是通過混淆和編譯創(chuàng)建較小文件的主要領(lǐng)域,我們在第四章中討論過這些主題。在第九章中,我們將會(huì)看到一種使用異步模塊定義(AMD) API 來定義模塊并將模塊加載到 JavaScript 代碼中的替代方法,但是現(xiàn)在如果你想在線關(guān)于模塊模式的內(nèi)容,請(qǐng)查閱以下資源:

  • 本·切瑞的《JavaScript 模塊模式:深入研究》(via http://bit.ly/module_pattern )
  • 雷蒙德·卡姆登的《JavaScript 設(shè)計(jì)模式——揭示模塊模式》(via http://bit.ly/revealing_module )

代理模式

代理模式是一種定義代理或替代對(duì)象或方法的模式,用于替換或增強(qiáng)現(xiàn)有的對(duì)象或方法,以提高其性能或添加額外的功能,而不會(huì)影響已經(jīng)使用該對(duì)象或方法的代碼的其他部分。我和許多其他專業(yè) JavaScript 開發(fā)人員使用這種模式的最常見方式是在不改變方法或函數(shù)名的情況下包裝現(xiàn)有的方法或函數(shù),如清單 6-14 所示。

清單 6-14。代理模式

// To proxy the myData.cookies.get() method from Listing 6-13, we begin by storing the current

// method in a variable

var proxiedGet = myData.cookies.get;

// Override the get() method with a new function which proxies the original and augments its

// behavior

myData.cookies.get = function() {

// Call the proxied (original) method to get the value it would have produced

var value = proxiedGet.apply(this, arguments);

// Do something with the value returned from the proxied method

value = value.toUpperCase();

// Return the manipulated value with the same type as the proxied method, so that the use of

// this new method does not break any existing calls to it

return value;

};

代理模式的一個(gè)變種叫做虛擬代理,它可以通過延遲對(duì)象實(shí)例化,從而延遲構(gòu)造函數(shù)的執(zhí)行,直到來自對(duì)象實(shí)例的方法被實(shí)際調(diào)用,來提高性能和內(nèi)存使用,如清單 6-15 所示。

清單 6-15。虛擬代理模式

// Define a "class" for constructing an object representing a simple form field

function FormField(type, displayText){

this.type = type || "text";

this.displayText = displayText || "";

// Create and initialize a form field DOM element

this.element = document.createElement("input");

this.element.setAttribute("type", this.type);

this.element.setAttribute("placeholder", this.displayText);

}

// Define two methods for object instances to inherit

FormField.prototype = {

getElement: function() {

return this.element;

},

isValid: function() {

return this.element.value !== "";

}

};

// Now replace the FormField "class" with a proxy that implements the same methods, yet delays

// calling the original constructor function until those methods are actually called, saving on

// memory resources and improving performance

// Optionally, use the module pattern to localise the scope of the proxy "class", passing in the

// original FormField "class" and returning the proxied version of it

FormField = (function(FormField) {

// Define a proxy constructor, similar to the original FormField "class"

function FormFieldProxy(type, displayText) {

this.type = type;

this.displayText = displayText;

}

FormFieldProxy.prototype = {

// Define a property to store the reference to the object instance of the original

// "class" once instantiated

formField: null,

// Define a new 'initialize' method whose task it is to create the object instance of

// FormField if it does not already exist and execute the constructor function from the

// original "class"

initialize: function() {

if (!this.formField) {

this.formField = new FormField(this.type, this.displayText);

}

},

// Proxy the original methods with new ones that call the intialize() method to

// instantiate the FormField "class" only when one of these methods are called

getElement: function() {

this.initialize();

return this.formField.getElement();

},

isValid: function() {

this.initialize();

return this.formField.isValid();

}

};

// Return the proxied "class" to replace the original with

return FormFieldProxy;

}(FormField));

// Create two object instances, both of which will actually be calling the proxy rather than the

// original "class", meaning the DOM elements will not be created at this stage, saving memory

// and improving performance

var textField = new FormField("text", "Enter the first line of your address"),

emailField = new FormField("email", "Enter your email address");

// Add the elements stored in these objects to the current page when loaded - at this point the

// getElement() method is called, which in turn calls initialize(), creating an instance of the

// original "class" and executing its constructor function which performs the actual DOM element

// creation. This ensures the memory used to store the DOM element is only taken up at the exact

// point it is required

window.addEventListener("load", function() {

document.body.appendChild(textField.getElement());

document.body.appendChild(emailField.getElement());

}, false);

// Execute another method from the proxy, this time the object instance of the original "class"

// won't be recreated and the stored instance will be used instead

alert(emailField.isValid()); // false

對(duì)于可能同時(shí)進(jìn)行多個(gè)調(diào)用的對(duì)象,可以通過延遲或分組調(diào)用(如 Ajax 請(qǐng)求或其他與網(wǎng)絡(luò)相關(guān)的調(diào)用)來進(jìn)一步擴(kuò)展代理模式,以提高性能和減少內(nèi)存。

當(dāng)您需要覆蓋一個(gè)對(duì)象或“類”上的特定方法的行為時(shí),最好使用代理模式,或者應(yīng)用代理模式來提高現(xiàn)有“類”的性能,以便在調(diào)用它的一個(gè)方法之前,它不會(huì)被實(shí)際實(shí)例化。要在線閱讀有關(guān)代理模式的更多信息,請(qǐng)查看以下資源:

  • 關(guān)于使用 jQuery 的“JavaScript 中的代理模式”(通過 http://bit.ly/proxy_pattern )
  • “JavaScript 設(shè)計(jì)模式:代理”,作者 Joseph Zimmerman,Adobe Developer Connection(通過 http://bit.ly/proxy_js )

摘要

在這一章中,我們研究了結(jié)構(gòu)化設(shè)計(jì)模式,你可以在適當(dāng)?shù)臅r(shí)候使用這些模式來幫助你構(gòu)建大型的 JavaScript 應(yīng)用并提高它們的性能。這些是 JavaScript 開發(fā)的瑞士軍刀中的工具,但是像所有工具一樣,您需要知道何時(shí)何地最好地使用它們。記住那句古老的格言:“當(dāng)你有一把錘子時(shí),一切看起來都像釘子?!笔煜け菊轮械哪J郊捌溆美?#xff0c;并確保在代碼中認(rèn)識(shí)到需要使用設(shè)計(jì)模式之前,不要使用它。

在下一章中,我們將會(huì)看到一些行為設(shè)計(jì)模式,它們可以用來簡化 JavaScript 應(yīng)用代碼庫中不同對(duì)象之間的通信。

七、設(shè)計(jì)模式:行為型

在這一章中,我們將繼續(xù)關(guān)注設(shè)計(jì)模式,重點(diǎn)是行為設(shè)計(jì)模式。我們在第五章中看到的創(chuàng)造性設(shè)計(jì)模式側(cè)重于對(duì)象創(chuàng)建,而我們在前一章中看到的結(jié)構(gòu)性設(shè)計(jì)模式側(cè)重于對(duì)象結(jié)構(gòu),行為設(shè)計(jì)模式側(cè)重于幫助代碼庫中多個(gè)對(duì)象之間的通信。這里的要點(diǎn)是讓你更容易理解你的代碼是如何作為一個(gè)整體運(yùn)作的,而不是僅僅關(guān)注單個(gè)對(duì)象的構(gòu)造和結(jié)構(gòu)。讓我們一起來看看八種行為設(shè)計(jì)模式,你可能會(huì)發(fā)現(xiàn)它們在你的代碼中很有用,還有一些例子。

責(zé)任鏈模式

當(dāng)基于同一個(gè)“類”的多個(gè)對(duì)象中的任何一個(gè)可以處理一個(gè)請(qǐng)求或方法調(diào)用時(shí),就使用責(zé)任鏈模式。請(qǐng)求被發(fā)送給一個(gè)對(duì)象,如果它不是處理請(qǐng)求的最合適的對(duì)象,它就把請(qǐng)求傳遞給另一個(gè)對(duì)象來處理。所以它會(huì)一直繼續(xù)下去,直到一個(gè)對(duì)象處理了請(qǐng)求,并通過對(duì)象鏈將操作的結(jié)果傳遞回原始請(qǐng)求或方法調(diào)用。鏈中的每個(gè)對(duì)象都知道另一個(gè)對(duì)象,如果鏈中的下一個(gè)對(duì)象不能完成請(qǐng)求,那么它可以處理請(qǐng)求。這種模式最適用于共同形成某種層次結(jié)構(gòu)的對(duì)象,如清單 7-1 所示,你不希望將的實(shí)現(xiàn)暴露給代碼的其他部分。

清單 7-1。責(zé)任鏈模式

// Define an object listing different levels of logging in a system - info, warn, and error –

// each indicating something more severe than the last

var LogLevel = {

INFO: 'INFO',

WARN: 'WARN',

ERROR: 'ERROR'

},

log;

// Define a "class" to create appropriately formatted log messages for different logging levels

function LogFormatter(logLevel) {

this.logLevel = logLevel;

}

LogFormatter.prototype = {

// Define a property to store the successor to this object instance in the chain

// of responsibility

nextInChain: null,

// Define a method to set the successor in the chain of responsibility

setNextInChain: function(next) {

this.nextInChain = next;

},

// Define a method to create an appropriately formatted log message based on the current

// logging level

createLogMessage: function(message, logLevel) {

var returnValue;

// If the logging level assigned to the current object instance is the same as that

// passed in, then format the log message

if (this.logLevel === logLevel) {

// Format the log message as appropriate according to the logging level

if (logLevel === LogLevel.ERROR) {

returnValue = logLevel + ": " + message.toUpperCase();

} else if (logLevel === LogLevel.WARN) {

returnValue = logLevel + ": " + message;

} else {

returnValue = message;

}

// If the logging level assigned to the current object instance does not match that

// passed in, then pass the message onto the next object instance in the chain

// of responsibility

} else if (this.nextInChain) {

returnValue = this.nextInChain.createLogMessage(message, logLevel);

}

return returnValue;

}

};

// Define a singleton we can use for storing and outputting logs in a system

log = (function() {

// Define a storage array for log messages

var logs = [],

// Create object instances representing the three levels of logging - info, warn,

// and error

infoLogger = new LogFormatter(LogLevel.INFO),

warnLogger = new LogFormatter(LogLevel.WARN),

errorLogger = new LogFormatter(LogLevel.ERROR),

// Set the 'error' logging level to be the first and highest level in our chain of

// responsibility, which we'll store in the 'logger' variable

logger = errorLogger;

// Set the chain of responsibility hierarchy using the setNextInChain() method on each

// object instance - we're assuming that the 'error' logging level is the most important and

// is first in the chain

// The next in the logging hierarchy after 'error' should be 'warn' as this is

// less important

errorLogger.setNextInChain(warnLogger);

// The next in the chain after the 'warn' logging level should be 'info' as this is the

// least important level

warnLogger.setNextInChain(infoLogger);

return {

// Define a method for reading out the stored log messages

getLogs: function() {

return logs.join("\n");

},

// Define a method for formatting a log message appropriately according to its

// logging level

message: function(message, logLevel) {

// We call the createLogMessage() method on the first object instance in our

// hierarchy only, which in turn calls those further down the chain if it does not

// handle the specified logging level itself. The message passes further down the

// chain of responsibility until it reaches an object instance who can handle the

// specific logging level

var logMessage = logger.createLogMessage(message, logLevel);

// Add the formatted log message to the storage array

logs.push(logMessage);

}

};

}());

// Execute the message() method of the 'log' singleton, passing in a message and the logging

// level. The first object in the chain of responsibility handles the 'error' logging level, so

// the message is not passed down the chain of responsibility and is returned by the

// errorLogger object

log.message("Something vary bad happened", LogLevel.ERROR);

// This message is passed through the errorLogger object to the warnLogger object through the

// chain of responsibility since the errorLogger object is only told to handle messages with the

// 'error' logging level

log.message("Something bad happened", LogLevel.WARN);

// This message is passed through the errorLogger object to the warnLogger object, and onto the

// infoLogger object which is the one handling 'info' type log messages

log.message("Something happened", LogLevel.INFO);

// Output the stored logs

alert(log.getLogs());

// Outputs the following:

/*

ERROR: SOMETHING VERY BAD HAPPENED

WARN: Something bad happened

Something happened

*/

當(dāng)您有一個(gè)對(duì)象層次結(jié)構(gòu),并且希望在整個(gè)代碼中訪問它而不暴露這個(gè)結(jié)構(gòu)時(shí),最好使用責(zé)任鏈模式。要了解有關(guān)責(zé)任鏈模式的更多信息,請(qǐng)查看以下在線資源:

  • 約瑟夫·齊默曼的《JavaScript 設(shè)計(jì)模式:責(zé)任鏈》(via http://bit.ly/chain_pattern )
  • 維基百科上的“責(zé)任鏈模式”(via http://bit.ly/chain_wiki )

命令模式

command 模式用于在調(diào)用代碼和對(duì)象的特定方法之間提供一個(gè)抽象層,確保所有調(diào)用都是通過該對(duì)象上的一個(gè)公共方法進(jìn)行的,通常命名為run()execute()。使用這種模式提供了在不影響調(diào)用代碼的情況下更改底層代碼和 API 的能力。清單 7-2 中的例子顯示了一個(gè)簡單的命令模式的例子,它把要執(zhí)行的方法名和參數(shù)傳遞給一個(gè)單獨(dú)的execute()方法。

清單 7-2。命令模式

var cookie = (function() {

var allCookies = document.cookie.split(";"),

cookies = {},

cookiesIndex = 0,

cookiesLength = allCookies.length,

cookie;

for (; cookiesIndex < cookiesLength; cookiesIndex++) {

cookie = allCookies[cookiesIndex].split("=");

cookies[unescape(cookie[0])] = unescape(cookie[1]);

}

return {

get: function(name) {

return cookies[name] || "";

},

set: function(name, value) {

cookies[name] = value;

document.cookie = escape(name) + "=" + escape(value);

},

remove: function(name) {

// Remove the cookie by removing its entry from the cookies object and setting its

// expiry date in the past

delete cookies[name];

document.cookie = escape(name) + "=; expires=Thu, 01 Jan 1970 00:00:01 GMT;";

},

// Supply an execute() method, which is used to abstract calls to other methods so that

// other method names can be changed as needs be in future without affecting the API

// available to the rest of the code - provided this execute() method continues to exist

execute: function(command, params) {

// The command parameter contains the method name to execute, so check that the

// method exists and is a function

if (this.hasOwnProperty(command) && typeof this[command] === "function") {

// If the method exists and can be executed, then execute it, passing across the

// supplied params

return this[command].apply(this, params);

}

}

};

}());

// Set a cookie using the execute() method to indirectly call the set() method of the cookie

// singleton and supplying parameters to pass onto that method

cookie.execute("set", ["name", "Den Odell"]);

// Check that the cookie was set correctly using execute() with the "get" method

alert(cookie.execute("get", ["name"])); // Den Odell

命令模式也可以在需要“撤消”功能的應(yīng)用的上下文中使用,其中執(zhí)行的語句可能需要在將來的某個(gè)時(shí)間點(diǎn)被撤銷,例如,在文字處理 web 應(yīng)用的上下文中。在這種情況下,命令是通過一個(gè)命令執(zhí)行對(duì)象來傳遞的,該對(duì)象使用這個(gè)抽象來存儲(chǔ)適當(dāng)?shù)暮瘮?shù),以反轉(zhuǎn)傳遞給它的方法調(diào)用,如清單 7-3 所示,它顯示了一個(gè)簡單的命令執(zhí)行對(duì)象和一個(gè)基于清單 7-2 中的代碼使用 cookies 的例子。

清單 7-3。支持 web 應(yīng)用中多級(jí)撤消的命令執(zhí)行對(duì)象

// Create a singleton for allowing execution of other methods and providing the ability to

// 'undo' the actions of those methods

var command = (function() {

// Create an array to store the 'undo' commands in order, also known as a 'stack'

var undoStack = [];

return {

// Define a method to execute a supplied function parameter, storing a second function

// parameter for later execution to 'undo' the action of the first function

execute: function(command, undoCommand) {

if (command && typeof command === "function") {

// If the first parameter is a function, execute it, and add the second

// parameter to the stack in case the command needs to be reversed at some point

// in future

command();

undoStack.push(undoCommand);

}

},

// Define a method to reverse the execution of the last command executed, using the

// stack of 'undo' commands

undo: function() {

// Remove and store the last command from the stack, which will be the one most

// recently added to it. This will remove that command from the stack, reducing the

// size of the array

var undoCommand = undoStack.pop();

if (undoCommand && typeof undoCommand === "function") {

// Check the command is a valid function and then execute it to effectively

// 'undo' the last command

undoCommand();

}

}

};

}());

// Wrap each piece of functionality that can be 'undone' in a call to the command.execute()

// method, passing the command to execute immediately as the first parameter, and the function

// to execute to reverse that command as the second parameter which will be stored until such

// point as it is needed

command.execute(function() {

// Using the code from Listing 7-2, set a cookie - this will be executed immediately

cookie.execute("set", ["name", "Den Odell"]);

}, function() {

// The reverse operation of setting a cookie is removing that cookie - this operation will

// be stored for later execution if the command.undo() method is called

cookie.execute("remove", ["name"]);

});

// Execute a second piece of functionality, setting a second cookie

command.execute(function() {

cookie.execute("set", ["company", "AKQA"]);

}, function() {

cookie.execute("remove", ["company"]);

});

// Check the value of the two cookies

alert(cookie.get("name"));????// Den Odell

alert(cookie.get("company")); // AKQA

// Reverse the previous operation, removing the 'company' cookie

command.undo();

// Check the value of the two cookies

alert(cookie.get("name"));????// Den Odell

alert(cookie.get("company")); // "" (an empty string), since the cookie has now been removed

// Reverse the first operation, removing the 'name' cookie

command.undo();

// Check the value of the two cookies

alert(cookie.get("name"));????// "", since the cookie has now been removed

alert(cookie.get("company")); // ""

當(dāng)您需要從代碼的其余部分中抽象出特定的方法名時(shí),最好使用 command 模式。通過按名稱引用方法,就像存儲(chǔ)在字符串中一樣,底層代碼可以隨時(shí)更改,而不會(huì)影響代碼的其余部分。要在線閱讀有關(guān)命令模式的更多信息,請(qǐng)查閱以下資源:

  • 《JavaScript 設(shè)計(jì)模式:命令》,作者 Joseph Zimmerman,Adobe Developer Connection(通過 http://bit.ly/command_pattern )
  • 彼得·米肖的《JavaScript 中的命令模式》(經(jīng)由 http://bit.ly/command_js )

迭代器模式

顧名思義,交互模式允許應(yīng)用中的代碼在一組數(shù)據(jù)上迭代或循環(huán),而不需要知道數(shù)據(jù)是如何在內(nèi)部存儲(chǔ)或構(gòu)造的。迭代器通常提供一組標(biāo)準(zhǔn)方法,用于移動(dòng)到集合中的下一項(xiàng),并檢查當(dāng)前項(xiàng)是集合中的第一項(xiàng)還是最后一項(xiàng)。

清單 7-4 顯示了一個(gè)通用“類”的例子,它可以迭代Array類型和Object類型的數(shù)據(jù)。這個(gè)迭代器的實(shí)例可以使用提供的方法rewind()、current()next()、hasNext()first()手動(dòng)操作和查詢,或者可以使用其each()方法提供自動(dòng)自迭代,其中函數(shù)回調(diào)參數(shù)為數(shù)據(jù)集中的每一項(xiàng)執(zhí)行一次,提供了一個(gè)有用的for循環(huán)的等效形式。

清單 7-4。迭代器模式

// Define a generic iterator "class" for iterating/looping over arrays or object-like data

// structures

function Iterator(data) {

var key;

// Store the supplied data in the 'data' property

this.data = data || {};

this.index = 0;

this.keys = [];

// Store an indicator to show whether the supplied data is an array or an object

this.isArray = Object.prototype.toString.call(data) === "[object Array]";

if (this.isArray) {

// If the supplied data is an array, store its length for fast access

this.length = data.length;

} else {

// If object data is supplied, store each property name in an array

for (key in data) {

if (data.hasOwnProperty(key)) {

this.keys.push(key);

}

}

// The length of the property name array is the length of the data to iterate over,

// so store this

this.length = this.keys.length;

}

}

// Define a method to reset the index, effectively rewinding the iterator back to the start of

// the data

Iterator.prototype.rewind = function() {

this.index = 0;

};

// Define a method to return the value stored at the current index position of the iterator

Iterator.prototype.current = function() {

return this.isArray ? this.data[this.index] : this.data[this.keys[this.index]];

};

// Define a method to return the value stored at the current index position of the iterator,

// and then advance the index pointer to the next item of data

Iterator.prototype.next = function() {

var value = this.current();

this.index = this.index + 1;

return value;

};

// Define a method to indicate whether the index position is at the end of the data

Iterator.prototype.hasNext = function() {

return this.index < this.length;

};

// Define a method to reset the index of the iterator to the start of the data and return

// the first item of data

Iterator.prototype.first = function() {

this.rewind();

return this.current();

};

// Define a method to iterate, or loop, over each item of data, executing a callback

// function each time, passing in the current data item as the first parameter to

// that function

Iterator.prototype.each = function(callback) {

callback = typeof callback === "function" ? callback : function() {};

// Iterate using a for loop, starting at the beginning of the data (achieved using the

// rewind() method) and looping until there is no more data to iterate over (indicated

// by the hasNext() method)

for (this.rewind(); this.hasNext();) {

// Execute the callback function each time through the loop, passing in the current

// data item value and incrementing the loop using the next() method

callback(this.next());

}

};

清單 7-4 中的代碼可以像清單 7-5 中所示的那樣使用,它展示了使用通用迭代器“class”對(duì)存儲(chǔ)的數(shù)據(jù)進(jìn)行迭代和循環(huán)的不同方式

清單 7-5。正在使用的迭代器模式

// Define an object and an array which we can use to iterate over

var user = {

name: "Den Odell",

occupation: "Head of Web Development",

company: "AKQA"

},

daysOfWeek = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"],

// Create instances of the Iterator "class" using these two different types of data

userIterator = new Iterator(user),

daysOfWeekIterator = new Iterator(daysOfWeek),

// Create three arrays for storing outputs of interations to be displayed later

output1 = [],

output2 = [],

output3 = [];

// The userIterator is ready for use, so let's use a for loop to iterate over the stored data –

// note how we don't need to supply the first argument to the for loop as the data is already

// reset and initialized in its start position, and we don't require the last argument since the

// next() method call within the for loop body performs the advancement of the index position

// for us

for (; userIterator.hasNext();) {

output1.push(userIterator.next());

}

// Since we iterated over an object, the resulting data consists of the values stored in each of

// the object's properties

alert(output1.join(", ")); // Den Odell, Head of Web Development, AKQA

// Before iterating over the same data again, its index must be rewound to the start

userIterator.rewind();

// Iterate over the object properties using a while loop, which continues to execute until the

// iterator has no further data items

while (userIterator.hasNext()) {

output2.push(userIterator.next());

}

alert(output2.join(", ")); // Den Odell, Head of Web Development, AKQA

// Iterate over the array data using the Iterator's built-in each() method - using this

// approach requires no manual work to manipulate the position of the index, simply pass a

// callback function

daysOfWeekIterator.each(function(item) {

output3.push(item);

});

alert(output3.join(", ")); // Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday

當(dāng)您需要為代碼的其余部分提供一種標(biāo)準(zhǔn)方式來遍歷復(fù)雜的數(shù)據(jù)結(jié)構(gòu),而又不暴露數(shù)據(jù)最終是如何存儲(chǔ)或表示的時(shí)候,最好使用迭代器模式。要在線了解迭代器模式的更多信息,請(qǐng)查閱以下在線資源:

  • DoFactory 上的“JavaScript 迭代器設(shè)計(jì)模式”(通過 http://bit.ly/iterator_pattern )
  • 維基百科上的“迭代器模式”(via http://bit.ly/iterator_wiki )

觀察者模式

觀察者模式用于由許多獨(dú)立的代碼模塊組成的大型代碼庫中,這些代碼模塊相互依賴或者必須相互通信。在這樣的代碼庫中,從一個(gè)模塊到其他模塊的硬編碼引用提供了所謂的緊耦合,即需要明確了解系統(tǒng)中的每個(gè)其他模塊,以便整個(gè)代碼能夠一起正確運(yùn)行。然而,理想情況下,大型代碼庫中的模塊應(yīng)該是松散耦合的。沒有明確地引用其他模塊;相反,整個(gè)代碼庫都會(huì)觸發(fā)和監(jiān)聽系統(tǒng)范圍的事件,就像標(biāo)準(zhǔn) DOM 事件處理的定制版本一樣。

例如,如果一個(gè)模塊負(fù)責(zé)通過 Ajax 進(jìn)行的所有客戶端-服務(wù)器通信,而另一個(gè)模塊負(fù)責(zé)在將表單傳輸?shù)椒?wù)器之前對(duì)其進(jìn)行渲染和驗(yàn)證,那么當(dāng)用戶成功提交和驗(yàn)證表單時(shí),代碼庫可以觸發(fā)一個(gè)全局的“表單已提交”事件以及表單中的數(shù)據(jù),通信模塊將監(jiān)聽這些數(shù)據(jù)。然后,通信模塊將執(zhí)行其任務(wù),向服務(wù)器發(fā)送數(shù)據(jù),并在自身觸發(fā)“接收到響應(yīng)”事件之前接收其響應(yīng),表單模塊將監(jiān)聽該事件。收到該事件后,表單模塊可以顯示一條消息,表明表單已成功提交,所有這些都不需要任何一個(gè)模塊相互了解——每個(gè)模塊唯一知道的是一組全局配置的事件名,系統(tǒng)中的任何模塊都可以觸發(fā)或響應(yīng)這些事件名。

實(shí)現(xiàn)觀察者模式的系統(tǒng)必須有三個(gè)對(duì)系統(tǒng)代碼庫可用的全局方法:publish(),它通過名稱觸發(fā)事件,傳遞任何可選數(shù)據(jù);subscribe(),它允許模塊分配一個(gè)函數(shù),當(dāng)特定的命名事件被觸發(fā)時(shí)執(zhí)行;和unsubscribe(),它取消了函數(shù)的設(shè)計(jì),這樣當(dāng)指定的事件被觸發(fā)時(shí),它將不再被執(zhí)行。清單 7-6 中的代碼演示了一個(gè)簡單的對(duì)象,它可以在您的應(yīng)用中全局使用,以實(shí)現(xiàn) observer 模式中的這些方法。

清單 7-6。觀察者模式

// Define an object containing global publish(), subscribe(), and unsubscribe() methods to

// implement the observer pattern

var observer = (function() {

// Create an object for storing registered events in by name along with the associatedw

// callback functions for any part of the full code base that subscribes to those

// event names

var events = {};

return {

// Define the subscribe() method, which stores a function along with its associated

// event name to be called at some later point when the specific event by that name

// is triggered

subscribe: function(eventName, callback) {

// If an event by the supplied name has not already been subscribed to, create an

// array property named after the event name within the events object to store

// functions to be called at a later time when the event by that name is triggered

if (!events.hasOwnProperty(eventName)) {

events[eventName] = [];

}

// Add the supplied callback function to the list associated to the specific

// event name

events[eventName].push(callback);

},

// Define the unsubscribe() method, which removes a given function from the list of

// functions to be executed when the event by the supplied name is triggered

unsubscribe: function(eventName, callback) {

var index = 0,

length = 0;

if (events.hasOwnProperty(eventName)) {

length = events[eventName].length;

// Cycle through the stored functions for the given event name and remove the

// function matching that supplied from the list

for (; index < length; index++) {

if (events[eventName][index] === callback) {

events[eventName].splice(index, 1);

break;

}

}

}

},

// Define the publish() method, which executes all functions associated with the given

// event name in turn, passing to each the same optional data passed as arguments to

// the method

publish: function(eventName) {

// Store all parameters but the first passed to this function as an array

var data = Array.prototype.slice.call(arguments, 1),

index = 0,

length = 0;

if (events.hasOwnProperty(eventName)) {

length = events[eventName].length;

// Cycle through all of the functions associated with the given event name and

// execute them each in turn, passing along any supplied parameters

for (; index < length; index++) {

events[eventName][index].apply(this, data);

}

}

}

};

}());

清單 7-7 中的代碼演示了如何使用清單 7-6 中給出的觀察者模式的publish()、subscribe()unsubscribe()方法。假設(shè)它運(yùn)行在一個(gè) HTML 頁面的上下文中,該頁面包含一個(gè)具有有效action屬性的<form id= "my-form" >標(biāo)簽,并且包含幾個(gè)表示表單字段的<input type= "text" >標(biāo)簽。

清單 7-7。正在使用的觀察者模式

// Define a module for Ajax communication, with a dependency on the observer object

// from Listing 7-6

(function(observer) {

// Define a function for performing an Ajax POST based on a supplied URL, form-encoded data

// string, and a callback function to execute once a response has been received from

// the server

function ajaxPost(url, data, callback) {

var xhr = new XMLHttpRequest(),

STATE_LOADED = 4,

STATUS_OK = 200;

xhr.onreadystatechange = function() {

if (xhr.readyState !== STATE_LOADED) {

return;

}

if (xhr.status === STATUS_OK) {

// Execute the supplied callback function once a successful response has been

// received from the server

callback(xhr.responseText);

}

};

xhr.open("POST", url);

// Inform the server that we will be sending form-encoded data, where names and values

// are separated by the equals sign (=) character, and name/value pairs are separated by

// the ampersand (&) character

xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded");

// POST the data to the server

xhr.send(data);

}

// Subscribe to the global, custom "form-submit" event and, when this event is triggered by

// another module in the code base, make a Ajax POST request to the server using the

// supplied URL and data. Trigger the "ajax-response" event when complete, passing in the

// server's response from the Ajax call

observer.subscribe("form-submit", function(url, formData) {

ajaxPost(url, formData, function(response) {

// Trigger the global "ajax-response" event, passing along the data returned from

// the server during the Ajax POST

observer.publish("ajax-response", response);

});

});

}(observer));

// Define a module for handling submission of a simple form on the page containing text fields

// only with an ID of "my-form". Note that neither of the modules in this code listing reference

// each other, they only reference the observer object which handles all communication between

// modules in the system. Each module is said to be "loosely-coupled" as it has no hardcoded

// dependency on any other module

(function(observer) {

// Get a reference to a form on the current HTML page with ID "my-form"

var form = document.getElementById("my-form"),

// Get the "action" attribute value from the form, which will be the URL we perform an

// Ajax POST to

action = form.action,

data = [],

// Get a reference to all <input> fields within the form

fields = form.getElementsByTagName("input"),

index = 0,

length = fields.length,

field,

// Create a HTML <p> tag for use as a thank you message after form submission has

// taken place

thankYouMessage = document.createElement("p");

// Define a function to execute on submission of the form which uses the observer pattern to

// submit the form field data over Ajax

function onFormSubmit(e) {

// Prevent the default behavior of the submit event, meaning a normal in-page HTML form

// submission will not occur

e.preventDefault();

// Loop through all <input> tags on the page, creating an array of name/value pairs of

// the data entered into the form

for (; index < length; index++) {

field = fields[index];

data.push(escape(field.name) + "=" + escape(field.value));

}

// Trigger the global "form-submit" event on the observer object, passing it the URL to

// use for the Ajax POST and the form data to be sent. The Ajax communication module is

// listening for this event and will handle everything pertaining to the submission of

// that data to the server.

observer.publish("form-submit", action, data.join("&"));

}

// Wire up the onFormSubmit() function to the "submit" event of the form

form.addEventListener("submit", onFormSubmit, false);

// Subscribe to the global, custom "ajax-response" event, and use the server's response data

// sent along with the event to populate a Thank You message to display on the page beside

// the form

observer.subscribe("ajax-response", function(response) {

thankYouMessage.innerHTML = "Thank you for your form submission.<br>The server responded with: " + response;

form.parentNode.appendChild(thankYouMessage);

});

}(observer));

observer 模式允許您刪除代碼中模塊之間的硬編碼引用,以維護(hù)自定義的系統(tǒng)范圍的事件列表。隨著代碼庫的增長和模塊數(shù)量的增加,可以考慮使用這種模式來簡化代碼,并將模塊彼此分離。請(qǐng)注意,如果在您的模塊之一中發(fā)生錯(cuò)誤,并且沒有觸發(fā)應(yīng)該觸發(fā)的事件,錯(cuò)誤的來源可能不會(huì)立即顯現(xiàn)出來,并且可能需要額外的調(diào)試。我建議在開發(fā)期間將您自己的調(diào)試日志記錄添加到您的 observer 對(duì)象中,以允許您更容易地跟蹤代碼中的事件。

當(dāng)您希望將模塊松散地耦合在一起以減少雜亂無章的代碼時(shí),最好使用觀察者模式。要在線閱讀有關(guān)這種流行模式的更多信息,請(qǐng)查看以下資源:

  • “JavaScript 設(shè)計(jì)模式:觀察者”,作者 Joseph Zimmerman,Adobe Developer Connection(通過 http://bit.ly/observer_pattern )
  • 羅布·多德森的《JavaScript 設(shè)計(jì)模式:觀察者》(經(jīng)由 http://bit.ly/observer_js )

中介模式

中介模式是觀察者模式的一種變體,在一個(gè)關(guān)鍵方面有所不同。觀察者模式定義了一個(gè)全局對(duì)象,用于在整個(gè)系統(tǒng)中發(fā)布和訂閱事件,而中介模式定義了用于特定目的的本地化對(duì)象,每個(gè)對(duì)象都有相同的publish()、subscribe()unsubscribe()方法。隨著您的代碼庫變得越來越大,observer 模式被證明會(huì)產(chǎn)生大量難以管理的事件,因此可以使用 mediator 模式將這個(gè)較大的事件列表分成較小的組。觀察者模式是通過一個(gè)全局單例對(duì)象實(shí)現(xiàn)的,而中介者模式是通過使用一個(gè)“類”來實(shí)現(xiàn)的,因此可以根據(jù)需要?jiǎng)?chuàng)建盡可能多的對(duì)象實(shí)例來支持代碼的特性。清單 7-8 顯示了用來在你的代碼中實(shí)現(xiàn)中介模式的“類”。注意與清單 7-6 中為實(shí)現(xiàn)觀察者模式而創(chuàng)建的對(duì)象的相似之處。

清單 7-8。中介模式

// Define a "class" containing publish(), subscribe(), and unsubscribe() methods to implement

// the mediator pattern. Note the similarilty to the observer pattern, the only difference is

// that we are creating a "class" here for creating object instances from later, and that we

// initialize the events array afresh for each object instance to avoid all instances sharing

// the same array in memory.

function Mediator() {

this.events = {};

}

Mediator.prototype.subscribe = function(eventName, callback) {

if (!this.events.hasOwnProperty(eventName)) {

this.events[eventName] = [];

}

this.events[eventName].push(callback);

};

Mediator.prototype.unsubscribe = function(eventName, callback) {

var index = 0,

length = 0;

if (this.events.hasOwnProperty(eventName)) {

length = this.events[eventName].length;

for (; index < length; index++) {

if (this.events[eventName][index] === callback) {

this.events[eventName].splice(index, 1);

break;

}

}

}

};

Mediator.prototype.publish = function(eventName) {

var data = Array.prototype.slice.call(arguments, 1),

index = 0,

length = 0;

if (this.events.hasOwnProperty(eventName)) {

length = this.events[eventName].length;

for (; index < length; index++) {

this.events[eventName][index].apply(this, data);

}

}

};

清單 7-8 中的中介模式可以如清單 7-9 所示實(shí)現(xiàn),創(chuàng)建中介對(duì)象來表示代碼中的特定特性,并允許代碼庫中有模塊。假設(shè)它運(yùn)行在包含一個(gè)<form id= "my-form" >標(biāo)簽的 HTML 頁面的上下文中,該標(biāo)簽包含幾個(gè)表示表單字段的<input type= "text" >標(biāo)簽。

清單 7-9。正在使用的中介模式

// Define two mediators for our code base, one pertaining to code for a forms feature, and

// another to enable a message logging feature.

// The formsMediator will feature two events: "form-submit", and "ajax-response", whereas

// the loggingMediator will feature three events, "log", "retrieve-log", and "log-retrieved".

// Note how we're able to separate events for different features in our code using the

// mediator pattern

var formsMediator = new Mediator(),

loggingMediator = new Mediator();

// Define a module for Ajax communication which POSTs some supplied data to the server when a

// "form-submit" event is triggered within the formsMediator

(function(formsMediator) {

function ajaxPost(url, data, callback) {

var xhr = new XMLHttpRequest(),

STATE_LOADED = 4,

STATUS_OK = 200;

xhr.onreadystatechange = function() {

if (xhr.readyState !== STATE_LOADED) {

return;

}

if (xhr.status === STATUS_OK) {

callback(xhr.responseText);

}

};

xhr.open("POST", url);

xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded");

xhr.send(data);

}

formsMediator.subscribe("form-submit", function(url, formData) {

ajaxPost(url, formData, function(response) {

formsMediator.publish("ajax-response", response);

});

});

}(formsMediator));

// Define a module for handling submission of a simple form on the page containing text fields

// only with an ID of "my-form". When the form is submitted, the "form-submit" event is

// triggered within the formsMediator

(function(formsMediator) {

var form = document.getElementById("my-form"),

action = form.action,

data = [],

fields = form.getElementsByTagName("input"),

index = 0,

length = fields.length,

field,

thankYouMessage = document.createElement("p");

function onFormSubmit(e) {

e.preventDefault();

for (; index < length; index++) {

field = fields[index];

data.push(escape(field.name) + "=" + escape(field.value));

}

formsMediator.publish("form-submit", action, data.join("&"));

}

form.addEventListener("submit", onFormSubmit, false);

formsMediator.subscribe("ajax-response", function(response) {

thankYouMessage.innerHTML = "Thank you for your form submission.<br>The server responded with: " + response;

form.parentNode.appendChild(thankYouMessage);

});

}(formsMediator));

// Define a module for logging messages within the system to aid with debugging of issues that

// might occur. Uses the loggingMediator to separate the logging feature of the code base

// separate from that handling the form submission with the formsMediator

(function(loggingMediator) {

// Create an array to store the logs

var logs = [];

// When the "log" event is triggered on the loggingMediator, add an object to the logs

// containing a supplied message and the date / time that the message was received at

loggingMediator.subscribe("log", function(message) {

logs.push({

message: message,

date: new Date()

});

});

// When the "retrieve-log" event is triggered on the loggingMediator, trigger the

// "log-retrieved" event, passing along the current state of the stored logs

loggingMediator.subscribe("retrieve-log", function() {

loggingMediator.publish("log-retrieved", logs);

});

}(loggingMediator));

// Define a module which allows the stored logs in the loggingMediator to be displayed on screen

(function(loggingMediator) {

// Create a button which, when clicked, will display the current state of the log

var button = document.createElement("button");

button.innerHTML = "Show logs";

button.addEventListener("click", function() {

// Trigger the "retrieve-log" event within the loggingMediator. This triggers the

// "log-retrieved" event, passing along the current state of the logs

loggingMediator.publish("retrieve-log");

}, false);

// When the "log-retrieved" event occurs, display the logs on screen

loggingMediator.subscribe("log-retrieved", function(logs) {

var index = 0,

length = logs.length,

ulTag = document.createElement("ul"),

liTag = document.createElement("li"),

listItem;

// Loop through each log in the list of logs, rendering the date / time and message

// stored within a <li> tag

for (; index < length; index++) {

listItem = liTag.cloneNode(false);

listItem.innerHTML = logs[index].date.toUTCString() + ": " + logs[index].message;

ulTag.appendChild(listItem);

}

// Add the <ul> tag containing all the <li> tags representing the log data to the bottom

// of the page

document.body.appendChild(ulTag);

});

// Add the button to the bottom of the current page

document.body.appendChild(button);

}(loggingMediator));

// Define a module which logs events that occur within the formsMediator. This is the only

// module in this example to use more than one mediator

(function(formsMediator, loggingMediator) {

// Use the loggingMediator's "log" events to log the URL the form is submitted to when the

// "form-submit" event is triggered within the formsMediator

formsMediator.subscribe("form-submit", function(url) {

loggingMediator.publish("log", "Form submitted to " + url);

});

// Log the response from the server that is supplied when the "ajax-response" event is

// triggered within the formsMediator

formsMediator.subscribe("ajax-response", function(response) {

loggingMediator.publish("log", "The server responded to an Ajax call with: " + response);

});

}(formsMediator, loggingMediator));

隨著代碼庫的增長,您可能會(huì)發(fā)現(xiàn)從觀察者模式轉(zhuǎn)移到中介者模式是有意義的,這樣可以將系統(tǒng)的事件組織成更易于管理的特性。

當(dāng)您希望在一個(gè)非常大的代碼庫中將模塊松散地耦合在一起時(shí),最好使用 mediator 模式,如果使用 observer 模式,要處理的事件數(shù)量將會(huì)非常多。要了解有關(guān)中介模式的更多信息,請(qǐng)查看以下在線資源:

  • HB Stone 的《JavaScript 設(shè)計(jì)模式:中介者》(viabit . ly/Mediator _ pattern
  • Addy Osmani 的《大規(guī)模 JavaScript 應(yīng)用架構(gòu)的模式:中介者》(via http://bit.ly/mediator_js )

紀(jì)念品圖案

memento 模式定義了對(duì)象數(shù)據(jù)在存儲(chǔ)器中以靜態(tài)形式的存儲(chǔ),使得它可以在代碼執(zhí)行過程中的稍后時(shí)間被恢復(fù);這就好像您可以在任何時(shí)候拍攝一個(gè)對(duì)象的快照,然后您可以恢復(fù)它。清單 7-10 顯示了一個(gè)簡單的“類”,通過將對(duì)象的快照存儲(chǔ)為 JSON 格式的字符串表示,并提供存儲(chǔ)和恢復(fù)原始 JavaScript 對(duì)象的方法,可以用來實(shí)現(xiàn)這種模式。

清單 7-10。紀(jì)念品圖案

// Define a simple "class" to be used to implement the memento pattern. It can be used to

// provide the ability to save and restore a snapshot of an object in memory.

// Certain older browsers (e.g. Internet Explorer 7) do not support the JSON.stringify() and

// JSON.parse() methods natively. For these, you should include Doug Crockford's json2.js

// library found athttps://github.com/douglascrockford/JSON-js

function Memento() {

// Define an object in memory to store snapshots of other objects under a specified key

this.storage = {};

}

// Define a method to save the state of any object under a specified key

Memento.prototype.saveState = function(key, obj) {

// Convert the supplied object to a string representation in JSON format

this.storage[key] = JSON.stringify(obj);

};

// Define a method to restore and return the state of any object stored under a specified key

Memento.prototype.restoreState = function(key) {

var output = {};

// If the supplied key exists, locate the object stored there

if (this.storage.hasOwnProperty(key)) {

output = this.storage[key];

// Convert the stored value from a JSON string to a proper object

output = JSON.parse(output) ;

}

return output;

};

清單 7-11 展示了清單 7-10 中 memento“class”的應(yīng)用。

清單 7-11。正在使用的紀(jì)念品圖案

// Define an instance of a memento to allow us to save and restore the state of objects

var memento = new Memento(),

// Define an object whose state we wish to be able to save and restore

user = {

name: "Den Odell",

age: 35

};

// Save the current state of the user object using the memento

memento.saveState("user", user);

// Prove that the state of the object is save in JSON format by reading from the storage object

// of the memento directly

alert(memento.storage["user"]); // {"name":"Den Odell","age":35}

// Now change the values in the user object as you wish

user.name = "John Smith";

user.age = 21;

// Output the current state of the user object

alert(JSON.stringify(user)); // {"name":"John Smith","age":21}

// Whenever you wish to restore the last saved state of the user object, simply call the restoreState() method of the memento

user = memento.restoreState("user");

// Output the new value of the user object, which has been restored to its last saved state

alert(JSON.stringify(user)); // {"name":"Den Odell","age":35}

當(dāng)您需要在應(yīng)用執(zhí)行的特定時(shí)刻存儲(chǔ)和恢復(fù)應(yīng)用中對(duì)象的快照時(shí),最好使用 memento 模式。要在線閱讀有關(guān) memento 模式的更多信息,請(qǐng)查看以下資源:

  • DoFactory 上的“JavaScript Memento 設(shè)計(jì)模式”(通過 http://bit.ly/memento_pattern )
  • 維基百科上的“紀(jì)念模式”(通過bit . ly/Memento _ wiki)

承諾模式

當(dāng)處理異步函數(shù)時(shí),通常會(huì)將回調(diào)函數(shù)傳遞給這樣的函數(shù)。該函數(shù)在完成工作后,將代表我們執(zhí)行回調(diào)函數(shù)。這正如我們所希望的那樣工作;唯一的問題是,它可能會(huì)創(chuàng)建更難閱讀、更模糊的代碼——您必須知道調(diào)用的函數(shù)是異步函數(shù),傳遞給它的函數(shù)被用作回調(diào)函數(shù)。如果您希望在執(zhí)行回調(diào)之前等待幾個(gè)異步函數(shù)的結(jié)果完成,這會(huì)使結(jié)果代碼更加模糊,難以理解。進(jìn)入 promises 模式,這是一個(gè)創(chuàng)建于 20 世紀(jì) 70 年代的設(shè)計(jì)模式,但在 CommonJS 小組( http://bit.ly/common_js )的工作中針對(duì) JavaScript 進(jìn)行了更新。它定義了一種從異步調(diào)用返回承諾的方法,然后可以將該方法與對(duì)另一個(gè)函數(shù)的調(diào)用鏈接起來,該函數(shù)只有在承諾完成時(shí)才會(huì)執(zhí)行,這發(fā)生在異步調(diào)用完成時(shí)。這樣做的好處是確?;卣{(diào)與異步函數(shù)的調(diào)用充分分離,從而提高代碼的清晰度,使代碼更具可讀性,從而更易于理解和維護(hù)。

承諾在 JavaScript 中表現(xiàn)為包含一個(gè)then()方法的對(duì)象實(shí)例,一旦相關(guān)的異步函數(shù)完成執(zhí)行,就會(huì)執(zhí)行該方法??紤]一個(gè)簡單的 Ajax 調(diào)用,它需要一個(gè)回調(diào)函數(shù)作為第二個(gè)參數(shù),執(zhí)行如下:

ajaxGet("/my-url", function(response) {

// Do something with response

}).

使用 promises 模式,對(duì)同一個(gè) Ajax 調(diào)用生成的 JavaScript 如下所示:

ajaxGet("/my-url").then(function(response) {

// Do something with response

});

您可能認(rèn)為這兩者之間沒有什么區(qū)別,但事實(shí)上后者更加清晰:它清楚地告訴我們,一旦第一個(gè)函數(shù)完成,第二個(gè)函數(shù)將會(huì)執(zhí)行,而這僅僅是暗示了前者的情況。一旦與多個(gè)異步調(diào)用一起使用,promises 模式比使用回調(diào)的等價(jià)代碼更加清晰。例如,考慮下面的代碼,它對(duì)一個(gè) URL 進(jìn)行 Ajax 調(diào)用,然后對(duì)另一個(gè) URL 進(jìn)行第二次 Ajax 調(diào)用:

ajaxGet("/my-url", function() {

ajaxGet("/my-other-url", function() {

// Do something

});

});

使用 promises 模式,這段代碼被簡化為更容易理解的代碼,并且避免了嵌套代碼的層次,這種層次在鏈中的異步調(diào)用越多,就會(huì)變得越極端:

ajaxGet("/my-url").then(ajaxGet("/my-other-url")).then(function() {

// Do something

});

當(dāng)在標(biāo)準(zhǔn) JavaScript 中發(fā)生了大量同步異步調(diào)用之后,試圖執(zhí)行單個(gè)回調(diào)時(shí),事情會(huì)變得更加復(fù)雜。使用 promises 模式,您可以簡單地將一個(gè)承諾數(shù)組傳遞給它的all()方法,它將同時(shí)執(zhí)行每個(gè)承諾,當(dāng)數(shù)組中的每個(gè)方法都實(shí)現(xiàn)了它自己的承諾時(shí),返回一個(gè)承諾,如下所示:

Promise.all([ajaxGet("/my-url"), ajaxGet("/my-other-url")]).then(function() {

// Do something with the data returned from both calls

});

清單 7-12 顯示了 JavaScript 中表示承諾的“類”。我在 http://bit.ly/js_promises 的 GitHub 項(xiàng)目中管理這個(gè)代碼的獨(dú)立版本,你可以在你的項(xiàng)目中自由使用。

清單 7-12。承諾模式

// Define a "class" representing a promise, allowing readable and understandable code to be

// written to support asynchronous methods and their callbacks. Instances created from this

// "class" adhere to the Promises/A+ specification detailed athttp://promisesaplus.com

// pass all the official unit tests found athttps://github.com/promises-aplus/promises-tests

// which prove compliance of this specification.

var Promise = (function() {

// Define the three possible states a promise can take - "pending" - the default value

// meaning it has not resolved yet, "fulfilled" - meaning the promise has resolved

// successfully, and "rejected" - meaning the promise has failed and an error has occurred

var state = {

PENDING: "pending",

FULFILLED: "fulfilled",

REJECTED: "rejected"

};

// Define the "class" to represent a promise. If an asynchronous function is passed in at

// the point of instantiation, it will be executed immediately

function Promise(asyncFunction) {

var that = this;

// Define a property to represent the current state of the promise, set to "pending" by

// default

this.state = state.PENDING;

// Define a property to be used to store a list of callback functions to call once the

// asynchronous method has completed execution

this.callbacks = [];

// Define a property to store the value returned by the asynchronous method represented

// by this promise

this.value = null;

// Define a property to store the details of any error that occurs as a result of

// executing the asynchronous method

this.error = null;

// Define two functions which will be passed to the asynchronous function

// represented by this promise. The first will be executed if the asynchronous

// function executed successfully, the second will be executed if the execution

// failed in some way

function success(value) {

// Executes the resolve() method of this promise, which will ensure that any

// functions linked to this promise to be executed once its asynchronous method

// has executed successfully is executed at this point

that.resolve(value);

}

function failure(reason) {

// Executes the reject() method of this promise, which will execute any

// linked callback functions for displaying or handling errors. Any furthe r

// associated promises chained to this one will not be executed.

that.reject(reason);

}

// If an asynchronous function is passed to this promise at instantiation, it is

// executed immediately, and the success() and failure() functions defined above

// are passed in as function parameters. The asynchronous function must ensure it

// executes the most appropriate of these two functions depending on the outcome

// of the behaviour it is attempting to perform

if (typeof asyncFunction === "function") {

asyncFunction(success, failure);

}

}

// Define a then() method, the crux of the Promises/A+ spec, which allows callbacks to

// be associated to the result of the asynchronous function's execution depending on

// whether that function completed its task successfully or not. It allows chaining of

// promises to each other to allow further asynchronous functions to be executed at

// the point at which the current one is completed successfully

Promise.prototype.then = function(onFulfilled, onRejected) {

// Create a new promise (and return it at the end of this method) to allow for

// chaining of calls to then()

var promise = new Promise(),

// Define a callback object to be stored in this promise and associate the new

// promise instance to it to act as the context of any callback methods

callback = {

promise: promise

};

// If a function was provided to be executed on successful completion of the

// asynchronous function's action, store that function in the callback object

// together with its newly created promise as context

if (typeof onFulfilled === "function") {

callback.fulfill = onFulfilled;

}

// If a function was provided to be executed on unsuccessful completion of the

// asynchronous function's action, store that function in the callback object

// together with the new context promise

if (typeof onRejected === "function") {

callback.reject = onRejected;

}

// Add the callback object to the list of callbacks

this.callbacks.push(callback);

// Attempt to execute the stored callbacks (will only do this if the asynchronous

// function has completed execution by this point - if not, it will be called at

// such time as it has by other code in the "class")

this.executeCallbacks();

// Return the newly created promise, to allow for chaining of other asynchronous

// functions through repeated calls to the then() method

return promise;

};

// Define a method to execute any callbacks associated with this promise if the

// associated asynchronous function has completed execution

Promise.prototype.executeCallbacks = function() {

var that = this,

value,

callback;

// Define two functions to use as defaults to execute if an equivalent function has

// not been stored in the list of callbacks tied to this promise

function fulfill(value) {

return value;

}

function reject(reason) {

throw reason;

}

// Only execute the callbacks if the promise is not in its pending state, i.e. that

// the asynchronous function has completed execution

if (this.state !== state.PENDING) {

// Point 2.2.4 of the Promises/A+ spec dictates that callback functions should

// be executed asynchronously, outside of the flow of any other calls to then()

// which might take place. This ensures the whole chain of promises is in place

// before calls to the callbacks take place. Using a setTimeout with a delay of

// 0 milliseconds gives the JavaScript engine a split second to complete the

// process of going through the promise chain before any callbacks are run.

// Browsers have a minimum delay value possible for a setTimeout call so in

// reality the callbacks will be executed after, typically, 4 milliseconds

setTimeout(function() {

// Loop through all the callbacks associated with this promise and execute

// them each in turn, selecting the callback's fulfill method if the promise

// was fulfilled (by the asynchronous function completing execution

// successfully), or its reject method if the function returned an error

// during execution

while(that.callbacks.length) {

callback = that.callbacks.shift();

// Wrap the execution of the callback in a try/catch block, in case it

// throws an error. We don't want the promise chain to stop executing if

// an error is thrown, rather we want to reject the promise, allowing

// the calling code to handle the error itself

try {

// Execute the appropriate callback method based on the state of

// the promise. If no callback method has been associated, fall

// back to the default fulfill() and reject() functions defined at

// the top of the executeCallbacks() method, above

if (that.state === state.FULFILLED) {

value = (callback.fulfill || fulfill)(that.value);

} else {

value = (callback.reject || reject)(that.error);

}

// Pass the result of executing the callback function to the

// resolve() method, which will either mark the promise as fulfilled

// or continue to further execute chained calls to the then() method

callback.promise.resolve(value);

} catch (reason) {

// If an error is thrown by the callback

callback.promise.reject(reason);

}

}

}, 0);

}

};

// The fulfill() method will mark this promise as fulfilled provided it has not already

// been fulfilled or rejected before. Any associated callbacks will be executed at

// this point

Promise.prototype.fulfill = function(value) {

// Only transition the promise to the fulfilled state if it is still in the pending

// state, and a value is passed to this method when it is executed

if (this.state === state.PENDING && arguments.length) {

this.state = state.FULFILLED;

this.value = value;

this.executeCallbacks();

}

};

// The reject() method will mark this promise as rejected provided it has not already

// been fulfilled or rejected before. Any associated callbacks will be executed at

// this point

Promise.prototype.reject = function(reason) {

// Only transition the promise to the rejected state if it is still in the pending

// state, and a value is passed to this method when it is executed

if (this.state === state.PENDING && arguments.length) {

this.state = state.REJECTED;

this.error = reason;

this.executeCallbacks();

}

};

// The resolve() method takes the return value from a successfull call to a promise's

// fulfill() callback and uses it to fulfill the promise if it is the last promise in

// a chain of then() method calls. If it is not the last promise, it continues down

// the promise chain, recursively fulfilling and rejecting the linked promises as

// appropriate

Promise.prototype.resolve = function(value) {

var promise = this,

// Detect the type of the value returned from the fulfill() callback method. If

// this is the last promise in a chain, this should be the result of executing

// the asynchronous function itself. If this promise has other chained promises

// then the value passed to this method will contain another promise which will

// call the resolve() method again, recursively

valueIsThisPromise = promise === value,

valueIsAPromise = value && value.constructor === Promise,

// The term "thenable" refers to an object that looks like a promise in that it

// contains a then() method of its own, yet isn't an instance of this Promise

// "class" - useful for connecting promises created by other implementations of

// the Promises/A+ spec together

valueIsThenable = value && (typeof value === "object" || typeof value === "function"),

isExecuted = false,

then;

// Reject this promise if the value passed to this method represents the same

// promise represented here - otherwise we could potentially get stuck in a loop

if (valueIsThisPromise) {

// The Promises/A+ spec dictates that should this promise be the same as the

// one passed to this method, then a TypeError should be passed to the reject()

// method, effectively stopping execution of further promises in the chain

promise.reject(new TypeError());

// If the value passed to the resolve() method is another instance of this Promise

// "class", then either fulfill or reject the current promise based on the state of

// the provided promise

} else if (valueIsAPromise) {

// If the promise passed into this method has already been fulfilled or

// rejected, pass on the value or error contained within it to this promise

if (value.state === state.FULFILLED) {

promise.fulfill(value.value);

} else if (value.state === state.REJECTED) {

promise.reject(value.error);

// If the promise passed into this method hasn't yet been fulfilled or rejected,

// execute its then() method to ensure the current promise will get resolved

// or rejected along with that promise once it has completed execution of its

// asynchronous function

} else {

value.then(function(value) {

promise.resolve(value);

}, function(reason) {

promise.reject(reason);

});

}

// If the value passed to the resolve() method is not an instance of this Promise

// "class" but resembles a promise in that it is an object containing its own

// then() method, then execute its then() method, fulfilling or rejecting the

// current promise based on the state of this promise. This comes in useful when

// attempting to connect promises created with other implementations of the same

// spec together with this one

} else if (valueIsThenable) {

// Wrap execution in a try/catch block in case an error is thrown in the

// underlying code of the other promise implementation

try {

then = value.then;

// If the object stored in the value variable contains a then() method,

// execute it to ensure the current promise gets fulfilled or rejected when

// that promise does

if (typeof then === "function") {

then.call(value, function(successValue) {

if (!isExecuted) {

isExecuted = true;

promise.resolve(successValue);

}

}, function(reason) {

if (!isExecuted) {

isExecuted = true;

promise.reject(reason);

}

});

} else {

promise.fulfill(value);

}

} catch (reason) {

if (!isExecuted) {

isExecuted = true;

promise.reject(reason);

}

}

// If the value passed to the resolve() method is not a promise, then fulfill the

// current promise using its value. Any associated callbacks will then be executed

} else {

promise.fulfill(value);

}

};

// Add a bonus method, Promise.all(), which isn't part of the Promises/A+ spec, but is part

// of the spec for ECMAScript 6 Promises, which bring the benefits of promises straight into

// the JavaScript language itself.

//

// The method accepts an array of promises, each representing an asynchronous function,

// which are executed simultaneously, and returns a single promise, allowing a single

// then() method to be executed at such point all the supplied promsies are fulfilled. The

// value passed on fulfillment contains an array of all the returned values of the

// individual promises, in the same order as the promises in the original array passed to

// this method

Promise.all = function(promises) {

var index = 0,

promiseCount = promises.length;

// Return a single promise representing all the promises supplied to this method. It

// will be fulfilled as soon as every one of the supplied promises have been fulfilled.

return new Promise(function(fulfill, reject) {

var promise,

results = [],

resultsCount = 0;

// Execute an onSuccess() function each time one of the supplied promises is

// fulfilled, adding its resulting value to an array in the same index position as

// the promise was in the original array

function onSuccess(result, index) {

results[index] = result;

resultsCount++;

// If we have collected the results for all of the promises, then fulfill the

// current single promise, passing across the array of fulfilled values from

// the individual promises

if (resultsCount === promiseCount) {

fulfill(results);

}

}

// If any of the supplied promises are rejected, then reject the current promise

function onError(error) {

reject(error);

}

// Resolve a given promise, executing onSuccess() if fulfilled, or onError() if not

function resolvePromise(index, promise) {

promise.then(function(value) {

onSuccess(value, index);

}, onError);

}

// Loop through all the promises supplied to this method, resolving each in turn

for (; index < promiseCount; index++) {

promise = promises[index];

resolvePromise(index, promise);

}

});

};

return Promise;

}());

看一下清單 7-13,它展示了如何利用清單 7-12 中的Promise“class ”,在你的代碼中創(chuàng)建和使用承諾的例子。

清單 7-13。正在使用的承諾模式

// Define a variable to use as a counter further down in this code

var millisecondCount = 0;

// Define a method to get the data returned by a GET request to a given URL. Returns a promise

// to which callback functions can be hooked into using its then() method.

function ajaxGet(url) {

// Return a new promise, initializing it with the asynchronous function to perform the Ajax

// request. When the promise executes the function, it will pass in two function parameters,

// the first should be called by our code if and when the asynchronous request succeeds, and

// the second should be called if and when an error occurs in the execution of the

// asynchronous request.

return new Promise(function(fulfill, reject) {

var xhr = new XMLHttpRequest(),

STATE_LOADED = 4,

STATUS_OK = 200;

xhr.onreadystatechange = function() {

if (xhr.readyState !== STATE_LOADED) {

return;

}

// If the Ajax GET request returns data successfully, execute the fulfill method

if (xhr.status === STATUS_OK) {

fulfill(xhr.responseText);

// If the Ajax request does not return data successfully, execute the reject method

} else {

reject("For the URL '" + url + "', the server responded with: " + xhr.status);

}

};

// Perform the Ajax GET request

xhr.open("GET", url);

xhr.send();

});

}

// Define a method which waits a given number of milliseconds before continuing. Returns

// a promise.

function wait(milliseconds) {

return new Promise(function(fulfill, reject) {

// If the value provided for milliseconds is a number greater than 0, call the

// setTimeout method to wait that number of milliseconds before executing the fulfill

// method

if (milliseconds && typeof milliseconds === "number" && milliseconds > 0) {

setTimeout(function() {

fulfill(milliseconds);

}, milliseconds);

// If the value provided for milliseconds is not a number or is less than or equal to

// 0, then reject the promise immediately

} else {

reject("Not an acceptable value provided for milliseconds: " + milliseconds);

}

});

}

// Define two functions for use if a particular promise is fulfilled or rejected, respectively

function onSuccess(milliseconds) {

alert(milliseconds + "ms passed");

}

function onError(error) {

alert(error);

}

// EXAMPLE 1: Success

// Execute the wait() function with a value we know will cause it to succeed, and show that

// the first of the two supplied functions to the then() method is executed

wait(500).then(onSuccess, onError); // After 0.5 seconds, outputs: "500ms passed"

// EXAMPLE 2: Error

// Execute the wait() function with a value we know will cause it to error. Because this

// rejects immediately, this will alert the user before the result of example 1 is known

wait(0).then(onSuccess, onError); // "Not an acceptable value provided for milliseconds: 0"

// EXAMPLE 3: Chaining

// Multiple promises can be chained together using the then() method which allows operations to

// be executed in order once the result of the execution of the previous asynchronous function

// is known. This considerably simplifies the nesting of callbacks which would be necessary

// without the use of promises.

wait(1000)

.then(function(milliseconds) {

// After a delay of 1 second, increment the counter by the number of milliseconds

// passed into the function parameter (in this case, 1000)

millisecondCount += milliseconds;

// Returning a promise in this function means that the operation indicated by that

// promise will be executed once the previous operation is complete

return wait(1600);

})

.then(function(milliseconds) {

// By this point, 2600 milliseconds have passed, and this is stored in our counter

// variable

millisecondCount += milliseconds;

// Return another promise, indicating that a delay of 400 milliseconds should now

// take place before the function specified in the following then() statement is

// executed

return wait(400);

})

.then(function(milliseconds) {

// Increment the counter by the 400 milliseconds just passed, making its total 3000

millisecondCount += milliseconds;

// Finally, output the combined value of the counter, which indicates the number of

// milliseconds passed since the first operation in this chain began

alert(millisecondCount + "ms passed"); // After 3 seconds, outputs: "3000ms passed"

});

// EXAMPLE 4: Multiple Promises

// Different promises can be chained together, since as in this example, which gets a page by

// the URL /page1.html (assuming it exists on the server), then waits 3 seconds before getting

// another page by the URL /page2.html (again, assuming it exists).

ajaxGet("/page1.html")

.then(function() {

return wait(3000);

})

.then(function() {

return ajaxGet("/page2.html");

})

.then(function() {

// This alert will fire only if both /page1.html and /page2.html exist and can

// be accessed

alert("/page1.html and /page2.html received, with a 3s gap between requests");

});

// EXAMPLE 5: Simultaneous Promises

// The Promise.all() method accepts an array of promises which will be resolved simultaneously,

// passing the results as an array to the success function passed to its then() method. Get

// both /page1.html and /page2.html simultaneously, and when they are both complete, execute

// the success callback function with the contents of both files in the array parameter passed

// into this function, in the same order as in the array of promises. If any of the supplied

// promises fails, the error callback function will be executed, with the detail of the first

// error that occurred passed into this function parameter.

Promise.all([ajaxGet("/page1.html"), ajaxGet("/page2.html")])

.then(function(files) {

alert("/page1.html = " + files[0].length + " bytes. /page2.html = " + files[1].length + " bytes.");

}, function(error) {

alert(error);

});

當(dāng)代碼中出現(xiàn)許多異步操作,導(dǎo)致嵌套回調(diào)函數(shù)混亂時(shí),最好使用 promises 模式。它允許將回調(diào)函數(shù)鏈接到異步調(diào)用,使代碼更容易理解,因此更容易開發(fā)和維護(hù)。要在線閱讀有關(guān)承諾模式的更多信息,請(qǐng)查看以下資源:

  • “Promises/A+ Specification”由 Promises/A+組織(通過 http://bit.ly/promises_aplus )提供
  • 里斯·布雷特-鮑恩的《承諾模式》(via http://bit.ly/promises_js )

戰(zhàn)略模式

策略模式適用于這樣的情況:您有一個(gè)包含大型條件語句(ifelseswitch)的“類”,其中每個(gè)選項(xiàng)都會(huì)導(dǎo)致該“類”的特定行為以不同的方式改變。與其管理一個(gè)大的條件語句,不如將每個(gè)行為拆分成單獨(dú)的對(duì)象,每個(gè)對(duì)象稱為一個(gè)策略。在任何時(shí)候,只有其中一個(gè)應(yīng)用于原始對(duì)象,稱為客戶端。擁有多個(gè)策略對(duì)象也有助于提高代碼的質(zhì)量,因?yàn)椴呗詫?duì)象可以彼此獨(dú)立地進(jìn)行單元測試。

清單 7-14 顯示了一個(gè)應(yīng)用策略模式的“類”的例子——它包含了許多條件語句,這些語句改變了從它創(chuàng)建的對(duì)象的一個(gè)非常特殊的行為。

清單 7-14。將策略模式應(yīng)用于的代碼已經(jīng)成熟

// Define a "class" representing a form field in an HTML page

function FormField(type, displayText){

this.type = type || "text";

this.displayText = displayText || "";

// Create a new <input> tag, setting its field type to the value supplied upon instantiation

this.element = document.createElement("input");

this.element.setAttribute("type", this.type);

// Create a new <label> tag, setting its text to the value supplied upon instantiation

this.label = document.createElement("label");

this.label.innerHTML = this.displayText;

// Add the <label> and <input> tags to the current page

document.body.appendChild(this.label);

document.body.appendChild(this.element);

}

// Give each form field object instance three methods

FormField.prototype = {

// Return the current value stored in the form field

getValue: function() {

return this.element.value;

},

// Set a new value for the form field

setValue: function(value) {

this.element.value = value;

},

// Return a true / false value depending on whether the value in the form field is valid

isValid: function() {

var isValid = false,

value;

// If this is a <input type="text"> field, it is considered valid if its value is not

// an empty string

if (this.type === "text") {

isValid = this.getValue() !== "";

// If this is a <input type="email"> field, it is considered valid if its value is not

// an empty string, contains the "@" character and contains the "." character after "@"

} else if (this.type === "email") {

value = this.getValue();

isValid = value !== "" && value.indexOf("@") > 0 && value.indexOf(".",?????????????value.indexOf("@")) > 0;

// If this is a <input type="number"> field, it is considered valid if its value is

// a number

} else if (this.type === "number") {

value = this.getValue();

isValid = !isNaN(parseInt(value, 10));

// This could go on a while as there are 24 possible <input> types in HTML5\. We need a

// way to simplify this to make it easier to understand and extend in future - this is

// where the strategy pattern comes into play, as shown in Listing 7-14

} else {

// etc.

}

return isValid;

}

};

清單 7-15 中的代碼展示了我們?nèi)绾瓮ㄟ^應(yīng)用策略模式將清單 7-14 中的代碼重構(gòu)為一個(gè)更有效、更易于管理的結(jié)構(gòu)。

清單 7-15。戰(zhàn)略模式

// Define a "class" representing a form field in an HTML page. Note a new object is passed into

// the third parameter at instantiation, containing a strategy object. This object contains a

// specific implementation of the isValid() method pertaining to the specific type of form field

// we are creating - for example, a "text" field would require an isValid() method that checks

// to see if the stored value is not an empty string, so we create an object containing this

// method and pass it in through the strategy object at instantiation time

function FormField(type, displayText, strategy){

this.type = type || "text";

this.displayText = displayText || "";

this.element = document.createElement("input");

this.element.setAttribute("type", this.type);

this.label = document.createElement("label");

this.label.innerHTML = this.displayText;

// Check to see if the strategy object passed in contains the isValid() method to use and,

// if so, store the stragety object for use when the isValid() method of this object is

// executed. If no strategy object is supplied, use a default

if (strategy && typeof strategy.isValid === "function") {

this.strategy = strategy;

} else {

this.strategy = {

isValid: function() {

return false;

}

};

}

document.body.appendChild(this.label);

document.body.appendChild(this.element);

}

FormField.prototype = {

getValue: function() {

return this.element.value;

},

setValue: function(value) {

this.element.value = value;

},

// Replace the previous isValid() method with one that simply calls the isValid() method

// provided by the stored strategy object - no more extensive if..else statements, making

// the code for this "class" much smaller and easier to manage

isValid: function() {

return this.strategy.isValid.call(this);

}

};

// Define three strategy objects for three different types of form field to be used with the

// FormField "class" when it is instantiated. Here we provide specific implementations for the

// isValid() method, but we could have extended these to include more methods and/or properties

// to meet our needs. In cases like this, we would have created a strategy "class" and created

// these objects as instances of that "class". Here we have simple objects so it is smarter to

// keep the code short and to the point

var textFieldStrategy = {

// Specific functionality for validation of a <input type="text"> field

isValid: function() {

return this.getValue() !== "";

}

},

emailFieldStrategy = {

// Specific functionality for validation of a <input type="email"> field

isValid: function() {

var value = this.getValue();

return value !== "" && value.indexOf("@") > 0 && value.indexOf(".",?????????????value.indexOf("@")) > 0;

}

},

numberFieldStrategy = {

// Specific functionality for validation of a <input type="number"> field

isValid: function() {

var value = this.getValue();

return !isNaN(parseInt(value, 10));

}

};

清單 7-15 中的代碼可以如清單 7-16 所示使用。

清單 7-16。正在使用的策略模式

// Create three form fields for our HTML page, each with different types. We pass in the type,

// the text for the associated <label> tag, and the strategy object associated with this field

// type to provide the required behavior for field value validation

var textField = new FormField("text", "First Name", textFieldStrategy),

emailField = new FormField("email", "Email", emailFieldStrategy),

numberField = new FormField("number", "Age", numberFieldStrategy);

// Set values for each form field we know will validate

textField.setValue("Den Odell");

emailField.setValue("denodell@me.com");

numberField.setValue(35);

// Check to see if the values in the fields validate correctly

alert(textField.isValid());???// true

alert(emailField.isValid());??// true

alert(numberField.isValid()); // true

// Change the values in the fields to ones we know will fail validation

textField.setValue("");

emailField.setValue("denodell");

numberField.setValue("Den Odell");

// Check to ensure the isValid() method is working correctly, reflecting the new field values

alert(textField.isValid());???// false

alert(emailField.isValid());??// false

alert(numberField.isValid()); // false

當(dāng)您需要管理大量條件邏輯來實(shí)現(xiàn)“類”中方法的行為時(shí),最好使用策略模式要了解有關(guān)策略模式的更多信息,請(qǐng)查看以下在線資源:

  • 邁克爾·索科爾的《JavaScript 中的策略設(shè)計(jì)模式》(經(jīng)由 http://bit.ly/strategy_pattern )
  • 邁克·彭尼斯的《JavaScript 中的策略模式》(經(jīng)由 http://bit.ly/strategy_js )

摘要

在這一章中,我們已經(jīng)看到了行為設(shè)計(jì)模式,你可以在你自己的 JavaScript 應(yīng)用中使用這些模式來簡化不同對(duì)象之間的通信。這些是 JavaScript 開發(fā)的瑞士軍刀中的工具,但是像所有工具一樣,您需要知道何時(shí)何地最好地使用它們。熟悉本章中的模式及其用例,并確保在代碼中認(rèn)識(shí)到需要使用設(shè)計(jì)模式之前,不要使用它。

在下一章中,我們將著眼于架構(gòu)設(shè)計(jì)模式,它實(shí)際上是我們已經(jīng)討論過的現(xiàn)有設(shè)計(jì)模式的組合,用來解決大型 JavaScript 代碼庫中的特定問題。

http://aloenet.com.cn/news/40065.html

相關(guān)文章:

  • 網(wǎng)站系統(tǒng)的運(yùn)營和維護(hù)互聯(lián)網(wǎng)廣告營銷是什么
  • 商城建設(shè)網(wǎng)站的原因網(wǎng)站優(yōu)化課程
  • 蘭州微網(wǎng)站重慶網(wǎng)站seo費(fèi)用
  • 衡水建站公司seo用什么論壇引流
  • 谷歌優(yōu)化和谷歌競價(jià)的區(qū)別重慶可靠的關(guān)鍵詞優(yōu)化研發(fā)
  • 做網(wǎng)站編程用什么語言好seo怎么做最佳
  • 外國人學(xué)做中國菜的網(wǎng)站營銷網(wǎng)站做的好的公司
  • 網(wǎng)上做家教兼職哪個(gè)網(wǎng)站南昌百度快速排名提升
  • 網(wǎng)站開發(fā)經(jīng)常遇到的問題大一html網(wǎng)頁制作作業(yè)簡單
  • 南京模板建網(wǎng)站哪家好百度一下官網(wǎng)
  • 聊大 網(wǎng)站設(shè)計(jì)seo優(yōu)化主要做什么
  • 太倉建設(shè)銀行網(wǎng)站搜索指數(shù)查詢
  • 劉琪 找誰做網(wǎng)站靠譜東莞網(wǎng)絡(luò)營銷全網(wǎng)推廣
  • 做網(wǎng)站怎么做鼠標(biāo)跟隨2023很有可能再次封城嗎
  • 網(wǎng)站經(jīng)營與建設(shè)優(yōu)化大師軟件下載
  • 網(wǎng)上有做口譯的網(wǎng)站么官方進(jìn)一步優(yōu)化
  • 彩票代購網(wǎng)站建設(shè)百度怎么注冊自己的店鋪
  • 做電影平臺(tái)網(wǎng)站怎么賺錢的什么是網(wǎng)絡(luò)營銷平臺(tái)
  • wordpress主頁居中重慶seo俱樂部
  • 焦作專業(yè)做網(wǎng)站公司中國新聞發(fā)布
  • wordpress 個(gè)性化韶山百度seo
  • 桐鄉(xiāng)網(wǎng)站建設(shè)世界十大網(wǎng)站排名出爐
  • 高明專業(yè)網(wǎng)站建設(shè)報(bào)價(jià)青海百度關(guān)鍵詞seo
  • 網(wǎng)站制作需要平臺(tái)培訓(xùn)心得體會(huì)總結(jié)
  • 網(wǎng)站建設(shè)專企業(yè)站seo價(jià)格
  • 網(wǎng)站建設(shè)種類 優(yōu)幫云海外網(wǎng)站推廣優(yōu)化專員
  • 可信賴的南昌網(wǎng)站制作seo是付費(fèi)還是免費(fèi)推廣
  • 商城網(wǎng)站合同網(wǎng)站的優(yōu)化
  • wordpress 側(cè)邊懸浮塊鄭州谷歌優(yōu)化外包
  • web網(wǎng)站開發(fā)全過程網(wǎng)站內(nèi)鏈優(yōu)化