当前位置: 首页 > news >正文

网站建设优化工资高不有关做美食的网站有哪些

网站建设优化工资高不,有关做美食的网站有哪些,eclipse sdk做网站,汤阴县seo快速排名有哪家好原文#xff1a;Pro JavaScript Development 协议#xff1a;CC BY-NC-SA 4.0 六、设计模式#xff1a;结构型 在这一章中#xff0c;我们将继续关注设计模式#xff0c;重点是结构设计模式。我们在前一章中看到的创造性设计模式集中在对象创建上#xff0c;而结构化设计… 原文Pro JavaScript Development 协议CC BY-NC-SA 4.0 六、设计模式结构型 在这一章中我们将继续关注设计模式重点是结构设计模式。我们在前一章中看到的创造性设计模式集中在对象创建上而结构化设计模式帮助你将对象组合成一个更大、更结构化的代码库。它们是灵活的、可维护的、可扩展的并且确保如果系统的一部分发生变化您不需要完全重写其余部分来适应。结构化设计模式还可以用来帮助与其他代码结构进行交互您需要在应用中轻松地使用这些代码结构。让我们一起来看看你可能会发现在你的代码中有用的八种结构设计模式以及一些例子。 适配器模式 adapterpattern 是一种有用的设计模式当您需要将两个或更多通常不会连接在一起的代码组件连接在一起时可以使用它类似地当您开发的 API 被更新不再以相同的方式调用时它就变得有用了——提供了一个适配器来连接新旧版本有助于 API 用户的迁移他们可以利用您代码中的其他改进而不会破坏他们的代码。清单 6-1 中的例子展示了如何使用这种模式为你的代码创建一个适配器来映射一个新的 API 接口到一个旧的接口。 清单 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); }, companyAKQAnameDen%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, companyAKQAnameDen%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); }, companyAKQAnameDen%20Odell); 适配器模式最适合在需要将本来不能组合在一起的代码连接在一起时使用例如当外部 API 被更新时——您创建一个适配器来将新方法映射到旧方法以避免需要对依赖于这些方法的代码的其余部分进行更改。 要在线阅读有关适配器模式的更多信息请查看以下资源: “JavaScript 设计模式:适配器”作者 Joseph ZimmermanAdobe Developer Connection(通过 http://bit.ly/adapter_pattern )维基百科上的“适配器设计模式”(via http://bit.ly/adapter_wiki ) 复合模式 复合模式为一个或多个对象创建了一个界面而最终用户不需要知道他们正在处理多少个对象。当你想简化其他人访问你的函数的方式时它就派上用场了无论是将单个对象还是一组对象传递给同一个方法都没有区别。清单 6-2 显示了复合模式的一个简单例子允许用户向一个或多个 DOM 节点添加类名而不需要知道他们是否需要向方法传递一个或多个 DOM 节点。 清单 6-2。复合模式 // 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); 当您不希望与您的方法交互的开发人员担心有多少对象作为参数传递给它们从而简化方法调用时最好使用复合模式。 要在线阅读关于复合模式的更多信息请查阅以下资源: Joseph Zimmerman 在 Adobe Developer Connection 上发表的“JavaScript 设计模式:复合”(通过 http://bit.ly/composite_pattern )维基百科上的“复合模式”(via http://bit.ly/composite_wiki ) 装饰图案 装饰模式是一种扩展和定制从“类”创建的对象的方法和属性的方式而不需要创建大量可能变得难以管理的子类。这是通过有效地将对象包装在另一个实现相同公共方法的对象中来实现的其中相关方法根据我们试图增强的行为被覆盖。清单 6-3 中的代码展示了一个创建几个装饰器的例子每个装饰器都用额外的属性和行为来扩充一个现有的对象。 清单 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 中创建的装饰器可以如清单 6-4 所示用于生成一个表示表单中表单字段的对象使用这些装饰器而不是通过子类来扩充它的属性和行为。 清单 6-4。正在使用的装饰模式 // Create an empty form tag and a new FormField object to represent // a input typesearch 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 tags 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 doesnt, 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); 当您需要快速简单地增加从一个“类”创建的对象实例的行为而不必求助于从它创建一长串继承的子类时最好使用装饰模式。要在线阅读关于装饰模式的更多信息请查阅以下资源: Addy Osmani 的《探索 JavaScript 中的装饰模式》(via http://bit.ly/decorator_pattern )斯托扬·斯特凡诺夫关于多布斯博士的“JavaScript 中的装饰模式”(通过 http://bit.ly/decorator_js ) 立面图案 外立面图案很常见它只是编写一个函数来简化对一个或多个更大、可能更复杂的函数的访问。可能有人会说任何简单地调用另一个函数的函数都是这种模式的一个例子但是我发现最好是从简化一些原本需要多个步骤的事情的角度来考虑或者提供一个访问更大系统的单点这将使其他开发人员访问该系统更加容易。清单 6-5 中的代码演示了一个简单的外观它提供了一个包装器来简化跨浏览器 Ajax 调用。 清单 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 // users 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 browsers 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 操作背后的复杂性。 清单 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); }, companyAKQAnameDen%20Odell); 当您希望通过单个函数或方法来提供对一系列函数或方法调用的访问以便简化代码库的其余部分使其更容易遵循因此将来更易于维护和扩展时最好使用外观模式。要在线阅读有关外观模式的更多信息请查看以下资源: Joseph Zimmerman 在 Adobe Developer Connection 上的“JavaScript 设计模式:外观”(通过 http://bit.ly/facade_pattern )卡尔·丹利的“正面图案”(经由 http://bit.ly/facade_js ) 轻量级模式 flyweight 模式是一种优化模式这对于创建大量相似对象的代码非常有用否则这些对象会消耗大量内存。它用一些共享对象代替了大量的相似对象使得代码更轻性能更好因此得名它来自拳击界指的是最轻重量级的运动员那些最敏捷的运动员。清单 6-7 显示了一个例子这个例子说明了 flyweight 模式旨在解决的一个问题即对象的低效存储。 清单 6-7。低效的对象实例 // Create a class to store data to related to employees working for one or more different // companies function Employee(data) { // Represent an employees ID within an organisation this.employeeId data.employeeId || 0; // Represent an employees social security number this.ssId data.ssId || 0000-000-0000; // Represent an employees name this.name data.name || ; // Represent an employees occupation this.occupation data.occupation || ; // Represent an employees company name, address and country this.companyName data.companyName || ; this.companyAddress data.companyAddress || ; this.companyCountry data.companyCountry || ; } // Create three methods to get the employees 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. Johns 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 模式是通过尝试解构一个现有的“类”来应用的这样可以最小化对象实例之间可能重复的任何数据。这是通过研究重复数据的任何当前对象实例并创建单独的“类”来表示该数据来实现的。然后单个对象实例可以表示重复的数据这些数据可以从原始“类”的多个对象实例中引用从而减少存储的数据从而减少应用的内存占用。 每个当前对象实例的任何数据核心都称为该“类”的内部数据任何可以从对象中提取、单独存储和引用的数据都称为其外部数据。在清单 6-7 中与雇员相关的内在数据——本质上是唯一的——是它的employeeId和occupation值。目前复制在多个Employee对象上的公司数据可以单独提取和存储每个人的数据也是如此比如他们的name和ssId值。因此一个雇员可以用四个属性来表示:employeeId、occupation、company、person。最后两个属性引用其他对象实例。 flyweight 模式分三个阶段应用如清单 6-8 所示:首先创建新的“类”来表示外部数据第二通过应用工厂模式来确保先前创建的对象不会被重新创建最后通过编写代码以与最初相同的方式创建对象允许所有 flyweight 的繁重工作在幕后进行。 清单 6-8。轻量级模式 // 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. Lets create two classes to represent those types of data // // A Person object represents an individuals social security number and their name function Person(data) { this.ssId data.ssId || ; this.name data.name || ; } // A Company object represents a companys 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 users 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 所示使用它复制了清单 6-7 的行为。应用 flyweight 模式的原始内存消耗对象中的重复数据越多共享的对象就越多因此减少了应用的内存占用证明了这种设计模式的有用性。 清单 6-9。正在使用的轻量级模式 // 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. Johns 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 }); // Weve created three objects representing people by ssId and name - Den Odell, Steve Ballmer // and Bill Gates alert(personFactory.getPersonCount()); // 3 // Weve created three objects representing companies by name, address and country - AKQA, // Microsoft and the Gates Foundation alert(companyFactory.getCompanyCount()); // 3 // Weve 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 were storing in our application // and the more effective the flyweight pattern becomes alert(employee.getTotalCount()); // 4 当您有大量具有相似共享属性名称-值对的对象时最好使用 flyweight 模式这些对象可以被分成更小的对象这些对象之间通过引用共享数据以便减少代码的内存占用提高代码的效率。要在线关于 flyweight 模式的内容请查阅以下资源: Addy Osmani 在《MSDN》杂志上发表的“用 Flyweight 模式管理应用资源”(via http://bit.ly/flyweight_pattern )Gurpreet Singh 的“轻量级模式”(via http://bit.ly/flyweight_js ) 混合模式 mixin 模式通过快速方便地将一组方法和属性从一个对象直接应用到另一个对象或者直接应用到“类”的原型使得所有对象实例都可以访问这些属性和方法从而避免了对大量子类化和继承链的需求。尽管这听起来像是“黑客”特别是对于那些从传统的面向对象背景开始接触 JavaScript 的开发人员来说这种模式直接利用了 JavaScript 语言的优势及其对原型的使用而不是其他语言所应用的严格的经典继承并且如果小心使用可以简化开发和代码维护。清单 6-10 中的代码展示了如何使用 mixin 模式简单快速地将一组通用方法应用到多个对象上。 清单 6-10。混合模式 // 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 well 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 中的代码您可能会注意到一些意想不到的事情:尽管将 mixin 独立地应用于 singleton 和“class”但是所有记录的数据都存储在一起。对任何包含该方法的对象调用readLog()方法都会输出相同的结果。发生这种情况是因为当extendObj()函数将 objectlike 属性从一个对象复制到另一个对象时比如本例中的logs数组(记住数组是 JavaScript 中的一种对象类型)这些是通过引用复制的而不是实际的数据副本。每次从任何对象访问该属性时都使用相同的属性最初来自loggingMixin对象。在这个例子中我们希望看到所有的日志所以这是有用的然而在您自己的代码中使用这种模式时这可能不是您需要的结果。如果你想为复制的属性创建单独的副本更新extendObj()函数如清单 6-11 所示。 清单 6-11。更新了 extendObj()函数以复制属性而不是通过引用复制 // 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; } 当您希望快速地将一组属性和方法直接从一个对象应用到另一个对象或者应用到一个“类”以供其所有对象实例使用时mixin 模式是最好的选择而不需要求助于复杂的子类化和继承。要在线关于 mixin 模式的内容请参考以下资源: 安格斯·克罗尔的《JavaScript Mixins 的新观点》(via http://bit.ly/mixin_pattern )《JavaScript Mixins:超越简单对象扩展》作者吴镇男·贝利(via http://bit.ly/mixins_beyond ) 模块模式 模块模式可能是专业 JavaScript 开发人员最常用的模式。事实上我们已经在前面的章节中两次讨论了模式的基础:第一次是在第一章中讨论公共、私有和受保护变量时第二次是在第四章中讨论改进 JavaScript 压缩的方法时。这一切都基于自执行函数闭包它允许我们创建一个沙盒代码区域可以访问全局变量和函数但不会将其中声明的变量或函数暴露给周围的作用域除非使用return语句显式声明。自执行函数的最简单示例如下所示: (function() { // Any variables or functions declared within this function arent accessible outside it }()); 我们可以使用这种模式将我们的代码库划分成更小的、相关的代码块我们称之为模块这就是该模式的名字。这些模块中的每一个都应该清楚地说明它们对代码的其他部分的依赖性如果有的话这些部分应该作为参数传递给函数如下所示: (function($) { // We very clearly define jQuery as a dependency for this module, making it available // internally through the $ variable }(jQuery)); Tip 在函数内访问 JavaScript 参数比在函数外访问全局变量更快因为语言解释器不必执行离开当前函数范围来搜索变量的额外步骤。 模块模式的基本形式是通过使用函数闭包内的return语句来传递回任何可能对其他模块或主应用本身有用的声明代码来完成的。清单 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 browsers 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)); 在通过单例对象结构利用命名空间的大型代码库中模块模式的使用方式与我们看到的略有不同在这种情况下我们传入一个依赖项然后在函数闭包结束时返回使用该模块用新的属性和方法来增加单例。清单 6-13 显示了模块模式应用于名称空间的扩充这是它最常见的用途之一。 清单 6-13。使用模块模式扩充名称空间 // 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 youre 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 当您希望将大型代码库分解成更小的、可管理的、自包含的部分时最好使用模块模式每个部分都有一组清晰的依赖项和定义明确的目的。由于它们的沙箱特性它们的自执行功能块也是通过混淆和编译创建较小文件的主要领域我们在第四章中讨论过这些主题。在第九章中我们将会看到一种使用异步模块定义(AMD) API 来定义模块并将模块加载到 JavaScript 代码中的替代方法但是现在如果你想在线关于模块模式的内容请查阅以下资源: 本·切瑞的《JavaScript 模块模式:深入研究》(via http://bit.ly/module_pattern )雷蒙德·卡姆登的《JavaScript 设计模式——揭示模块模式》(via http://bit.ly/revealing_module ) 代理模式 代理模式是一种定义代理或替代对象或方法的模式用于替换或增强现有的对象或方法以提高其性能或添加额外的功能而不会影响已经使用该对象或方法的代码的其他部分。我和许多其他专业 JavaScript 开发人员使用这种模式的最常见方式是在不改变方法或函数名的情况下包装现有的方法或函数如清单 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; }; 代理模式的一个变种叫做虚拟代理它可以通过延迟对象实例化从而延迟构造函数的执行直到来自对象实例的方法被实际调用来提高性能和内存使用如清单 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 // wont be recreated and the stored instance will be used instead alert(emailField.isValid()); // false 对于可能同时进行多个调用的对象可以通过延迟或分组调用(如 Ajax 请求或其他与网络相关的调用)来进一步扩展代理模式以提高性能和减少内存。 当您需要覆盖一个对象或“类”上的特定方法的行为时最好使用代理模式或者应用代理模式来提高现有“类”的性能以便在调用它的一个方法之前它不会被实际实例化。要在线阅读有关代理模式的更多信息请查看以下资源: 关于使用 jQuery 的“JavaScript 中的代理模式”(通过 http://bit.ly/proxy_pattern )“JavaScript 设计模式:代理”作者 Joseph ZimmermanAdobe Developer Connection(通过 http://bit.ly/proxy_js ) 摘要 在这一章中我们研究了结构化设计模式你可以在适当的时候使用这些模式来帮助你构建大型的 JavaScript 应用并提高它们的性能。这些是 JavaScript 开发的瑞士军刀中的工具但是像所有工具一样您需要知道何时何地最好地使用它们。记住那句古老的格言:“当你有一把锤子时一切看起来都像钉子。”熟悉本章中的模式及其用例并确保在代码中认识到需要使用设计模式之前不要使用它。 在下一章中我们将会看到一些行为设计模式它们可以用来简化 JavaScript 应用代码库中不同对象之间的通信。 七、设计模式行为型 在这一章中我们将继续关注设计模式重点是行为设计模式。我们在第五章中看到的创造性设计模式侧重于对象创建而我们在前一章中看到的结构性设计模式侧重于对象结构行为设计模式侧重于帮助代码库中多个对象之间的通信。这里的要点是让你更容易理解你的代码是如何作为一个整体运作的而不是仅仅关注单个对象的构造和结构。让我们一起来看看八种行为设计模式你可能会发现它们在你的代码中很有用还有一些例子。 责任链模式 当基于同一个“类”的多个对象中的任何一个可以处理一个请求或方法调用时就使用责任链模式。请求被发送给一个对象如果它不是处理请求的最合适的对象它就把请求传递给另一个对象来处理。所以它会一直继续下去直到一个对象处理了请求并通过对象链将操作的结果传递回原始请求或方法调用。链中的每个对象都知道另一个对象如果链中的下一个对象不能完成请求那么它可以处理请求。这种模式最适用于共同形成某种层次结构的对象如清单 7-1 所示你不希望将的实现暴露给代码的其他部分。 清单 7-1。责任链模式 // 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 well store in the logger variable logger errorLogger; // Set the chain of responsibility hierarchy using the setNextInChain() method on each // object instance - were 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 */ 当您有一个对象层次结构并且希望在整个代码中访问它而不暴露这个结构时最好使用责任链模式。要了解有关责任链模式的更多信息请查看以下在线资源: 约瑟夫·齐默曼的《JavaScript 设计模式:责任链》(via http://bit.ly/chain_pattern )维基百科上的“责任链模式”(via http://bit.ly/chain_wiki ) 命令模式 command 模式用于在调用代码和对象的特定方法之间提供一个抽象层确保所有调用都是通过该对象上的一个公共方法进行的通常命名为run()或execute()。使用这种模式提供了在不影响调用代码的情况下更改底层代码和 API 的能力。清单 7-2 中的例子显示了一个简单的命令模式的例子它把要执行的方法名和参数传递给一个单独的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) ; expiresThu, 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 命令模式也可以在需要“撤消”功能的应用的上下文中使用其中执行的语句可能需要在将来的某个时间点被撤销例如在文字处理 web 应用的上下文中。在这种情况下命令是通过一个命令执行对象来传递的该对象使用这个抽象来存储适当的函数以反转传递给它的方法调用如清单 7-3 所示它显示了一个简单的命令执行对象和一个基于清单 7-2 中的代码使用 cookies 的例子。 清单 7-3。支持 web 应用中多级撤消的命令执行对象 // 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)); // 当您需要从代码的其余部分中抽象出特定的方法名时最好使用 command 模式。通过按名称引用方法就像存储在字符串中一样底层代码可以随时更改而不会影响代码的其余部分。要在线阅读有关命令模式的更多信息请查阅以下资源: 《JavaScript 设计模式:命令》作者 Joseph ZimmermanAdobe Developer Connection(通过 http://bit.ly/command_pattern )彼得·米肖的《JavaScript 中的命令模式》(经由 http://bit.ly/command_js ) 迭代器模式 顾名思义交互模式允许应用中的代码在一组数据上迭代或循环而不需要知道数据是如何在内部存储或构造的。迭代器通常提供一组标准方法用于移动到集合中的下一项并检查当前项是集合中的第一项还是最后一项。 清单 7-4 显示了一个通用“类”的例子它可以迭代Array类型和Object类型的数据。这个迭代器的实例可以使用提供的方法rewind()、current()、next()、hasNext()和first()手动操作和查询或者可以使用其each()方法提供自动自迭代其中函数回调参数为数据集中的每一项执行一次提供了一个有用的for循环的等效形式。 清单 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”对存储的数据进行迭代和循环的不同方式 清单 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 lets use a for loop to iterate over the stored data – // note how we dont need to supply the first argument to the for loop as the data is already // reset and initialized in its start position, and we dont 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 objects 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 Iterators 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 当您需要为代码的其余部分提供一种标准方式来遍历复杂的数据结构而又不暴露数据最终是如何存储或表示的时候最好使用迭代器模式。要在线了解迭代器模式的更多信息请查阅以下在线资源: DoFactory 上的“JavaScript 迭代器设计模式”(通过 http://bit.ly/iterator_pattern )维基百科上的“迭代器模式”(via http://bit.ly/iterator_wiki ) 观察者模式 观察者模式用于由许多独立的代码模块组成的大型代码库中这些代码模块相互依赖或者必须相互通信。在这样的代码库中从一个模块到其他模块的硬编码引用提供了所谓的紧耦合即需要明确了解系统中的每个其他模块以便整个代码能够一起正确运行。然而理想情况下大型代码库中的模块应该是松散耦合的。没有明确地引用其他模块相反整个代码库都会触发和监听系统范围的事件就像标准 DOM 事件处理的定制版本一样。 例如如果一个模块负责通过 Ajax 进行的所有客户端-服务器通信而另一个模块负责在将表单传输到服务器之前对其进行渲染和验证那么当用户成功提交和验证表单时代码库可以触发一个全局的“表单已提交”事件以及表单中的数据通信模块将监听这些数据。然后通信模块将执行其任务向服务器发送数据并在自身触发“接收到响应”事件之前接收其响应表单模块将监听该事件。收到该事件后表单模块可以显示一条消息表明表单已成功提交所有这些都不需要任何一个模块相互了解——每个模块唯一知道的是一组全局配置的事件名系统中的任何模块都可以触发或响应这些事件名。 实现观察者模式的系统必须有三个对系统代码库可用的全局方法:publish()它通过名称触发事件传递任何可选数据subscribe()它允许模块分配一个函数当特定的命名事件被触发时执行和unsubscribe()它取消了函数的设计这样当指定的事件被触发时它将不再被执行。清单 7-6 中的代码演示了一个简单的对象它可以在您的应用中全局使用以实现 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()方法。假设它运行在一个 HTML 页面的上下文中该页面包含一个具有有效action属性的form id my-form 标签并且包含几个表示表单字段的input type text 标签。 清单 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 // servers 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 servers 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.brThe server responded with: response; form.parentNode.appendChild(thankYouMessage); }); }(observer)); observer 模式允许您删除代码中模块之间的硬编码引用以维护自定义的系统范围的事件列表。随着代码库的增长和模块数量的增加可以考虑使用这种模式来简化代码并将模块彼此分离。请注意如果在您的模块之一中发生错误并且没有触发应该触发的事件错误的来源可能不会立即显现出来并且可能需要额外的调试。我建议在开发期间将您自己的调试日志记录添加到您的 observer 对象中以允许您更容易地跟踪代码中的事件。 当您希望将模块松散地耦合在一起以减少杂乱无章的代码时最好使用观察者模式。要在线阅读有关这种流行模式的更多信息请查看以下资源: “JavaScript 设计模式:观察者”作者 Joseph ZimmermanAdobe Developer Connection(通过 http://bit.ly/observer_pattern )罗布·多德森的《JavaScript 设计模式:观察者》(经由 http://bit.ly/observer_js ) 中介模式 中介模式是观察者模式的一种变体在一个关键方面有所不同。观察者模式定义了一个全局对象用于在整个系统中发布和订阅事件而中介模式定义了用于特定目的的本地化对象每个对象都有相同的publish()、subscribe()和unsubscribe()方法。随着您的代码库变得越来越大observer 模式被证明会产生大量难以管理的事件因此可以使用 mediator 模式将这个较大的事件列表分成较小的组。观察者模式是通过一个全局单例对象实现的而中介者模式是通过使用一个“类”来实现的因此可以根据需要创建尽可能多的对象实例来支持代码的特性。清单 7-8 显示了用来在你的代码中实现中介模式的“类”。注意与清单 7-6 中为实现观察者模式而创建的对象的相似之处。 清单 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 所示实现创建中介对象来表示代码中的特定特性并允许代码库中有模块。假设它运行在包含一个form id my-form 标签的 HTML 页面的上下文中该标签包含几个表示表单字段的input type text 标签。 清单 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 were 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.brThe 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 loggingMediators 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)); 随着代码库的增长您可能会发现从观察者模式转移到中介者模式是有意义的这样可以将系统的事件组织成更易于管理的特性。 当您希望在一个非常大的代码库中将模块松散地耦合在一起时最好使用 mediator 模式如果使用 observer 模式要处理的事件数量将会非常多。要了解有关中介模式的更多信息请查看以下在线资源: HB Stone 的《JavaScript 设计模式:中介者》(viabit . ly/Mediator _ patternAddy Osmani 的《大规模 JavaScript 应用架构的模式:中介者》(via http://bit.ly/mediator_js ) 纪念品图案 memento 模式定义了对象数据在存储器中以静态形式的存储使得它可以在代码执行过程中的稍后时间被恢复这就好像您可以在任何时候拍摄一个对象的快照然后您可以恢复它。清单 7-10 显示了一个简单的“类”,通过将对象的快照存储为 JSON 格式的字符串表示并提供存储和恢复原始 JavaScript 对象的方法可以用来实现这种模式。 清单 7-10。纪念品图案 // 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 Crockfords 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”的应用。 清单 7-11。正在使用的纪念品图案 // 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} 当您需要在应用执行的特定时刻存储和恢复应用中对象的快照时最好使用 memento 模式。要在线阅读有关 memento 模式的更多信息请查看以下资源: DoFactory 上的“JavaScript Memento 设计模式”(通过 http://bit.ly/memento_pattern )维基百科上的“纪念模式”(通过bit . ly/Memento _ wiki) 承诺模式 当处理异步函数时通常会将回调函数传递给这样的函数。该函数在完成工作后将代表我们执行回调函数。这正如我们所希望的那样工作唯一的问题是它可能会创建更难阅读、更模糊的代码——您必须知道调用的函数是异步函数传递给它的函数被用作回调函数。如果您希望在执行回调之前等待几个异步函数的结果完成这会使结果代码更加模糊难以理解。进入 promises 模式这是一个创建于 20 世纪 70 年代的设计模式但在 CommonJS 小组( http://bit.ly/common_js )的工作中针对 JavaScript 进行了更新。它定义了一种从异步调用返回承诺的方法然后可以将该方法与对另一个函数的调用链接起来该函数只有在承诺完成时才会执行这发生在异步调用完成时。这样做的好处是确保回调与异步函数的调用充分分离从而提高代码的清晰度使代码更具可读性从而更易于理解和维护。 承诺在 JavaScript 中表现为包含一个then()方法的对象实例一旦相关的异步函数完成执行就会执行该方法。考虑一个简单的 Ajax 调用它需要一个回调函数作为第二个参数执行如下: ajaxGet(/my-url, function(response) { // Do something with response }). 使用 promises 模式对同一个 Ajax 调用生成的 JavaScript 如下所示: ajaxGet(/my-url).then(function(response) { // Do something with response }); 您可能认为这两者之间没有什么区别但事实上后者更加清晰:它清楚地告诉我们一旦第一个函数完成第二个函数将会执行而这仅仅是暗示了前者的情况。一旦与多个异步调用一起使用promises 模式比使用回调的等价代码更加清晰。例如考虑下面的代码它对一个 URL 进行 Ajax 调用然后对另一个 URL 进行第二次 Ajax 调用: ajaxGet(/my-url, function() { ajaxGet(/my-other-url, function() { // Do something }); }); 使用 promises 模式这段代码被简化为更容易理解的代码并且避免了嵌套代码的层次这种层次在链中的异步调用越多就会变得越极端: ajaxGet(/my-url).then(ajaxGet(/my-other-url)).then(function() { // Do something }); 当在标准 JavaScript 中发生了大量同步异步调用之后试图执行单个回调时事情会变得更加复杂。使用 promises 模式您可以简单地将一个承诺数组传递给它的all()方法它将同时执行每个承诺当数组中的每个方法都实现了它自己的承诺时返回一个承诺如下所示: 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 项目中管理这个代码的独立版本你可以在你的项目中自由使用。 清单 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 functions 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 functions 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 functions 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 callbacks 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 dont 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 promises // 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 isnt 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 hasnt 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 isnt 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 ”,在你的代码中创建和使用承诺的例子。 清单 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); }); 当代码中出现许多异步操作导致嵌套回调函数混乱时最好使用 promises 模式。它允许将回调函数链接到异步调用使代码更容易理解因此更容易开发和维护。要在线阅读有关承诺模式的更多信息请查看以下资源: “Promises/A Specification”由 Promises/A组织(通过 http://bit.ly/promises_aplus )提供里斯·布雷特-鲍恩的《承诺模式》(via http://bit.ly/promises_js ) 战略模式 策略模式适用于这样的情况:您有一个包含大型条件语句(if … else或switch)的“类”其中每个选项都会导致该“类”的特定行为以不同的方式改变。与其管理一个大的条件语句不如将每个行为拆分成单独的对象每个对象称为一个策略。在任何时候只有其中一个应用于原始对象称为客户端。拥有多个策略对象也有助于提高代码的质量因为策略对象可以彼此独立地进行单元测试。 清单 7-14 显示了一个应用策略模式的“类”的例子——它包含了许多条件语句这些语句改变了从它创建的对象的一个非常特殊的行为。 清单 7-14。将策略模式应用于的代码已经成熟 // 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 typetext 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 typeemail 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 typenumber 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 中的代码展示了我们如何通过应用策略模式将清单 7-14 中的代码重构为一个更有效、更易于管理的结构。 清单 7-15。战略模式 // 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 typetext field isValid: function() { return this.getValue() ! ; } }, emailFieldStrategy { // Specific functionality for validation of a input typeemail 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 typenumber 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(denodellme.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 当您需要管理大量条件逻辑来实现“类”中方法的行为时最好使用策略模式要了解有关策略模式的更多信息请查看以下在线资源: 迈克尔·索科尔的《JavaScript 中的策略设计模式》(经由 http://bit.ly/strategy_pattern )迈克·彭尼斯的《JavaScript 中的策略模式》(经由 http://bit.ly/strategy_js ) 摘要 在这一章中我们已经看到了行为设计模式你可以在你自己的 JavaScript 应用中使用这些模式来简化不同对象之间的通信。这些是 JavaScript 开发的瑞士军刀中的工具但是像所有工具一样您需要知道何时何地最好地使用它们。熟悉本章中的模式及其用例并确保在代码中认识到需要使用设计模式之前不要使用它。 在下一章中我们将着眼于架构设计模式它实际上是我们已经讨论过的现有设计模式的组合用来解决大型 JavaScript 代码库中的特定问题。
http://www.w-s-a.com/news/671121/

相关文章:

  • 蔡甸网站建设烟台网站建设yt
  • 最流行的网站开发新开的网页游戏平台
  • 暴富建站wordpress 标签分类
  • 搞笑网站源码百度快照替代
  • 重庆网站建设哪家公司哪家好关键词是怎么排名的
  • 青县网站建设今天国际大事新闻
  • 深圳正规网站制作哪里好怎样优化网络
  • 米拓网站建设教程dw成品网站成品视频教学
  • 用jsp做的网站源代码天门网站网站建设
  • 百度如何把网站做链接地址有没有资源可以在线观看
  • 淮安做网站找哪家好电子商务网站建设规划书的内容
  • 开发网站建设用什么框架php黄页系统
  • 聊城制作网站全球十大电商平台排名
  • 用什么来网站开发好mega menu wordpress
  • 深圳制作网站有用吗wordpress的主题
  • 网站的规划与创建天津市南开区网站开发有限公司
  • 免备案网站主机建站哪个平台好
  • python做网站 不适合单页营销分享网站
  • 珠海市研发网站建设建设网站挣钱
  • 阿里巴巴国际站特点做wps的网站赚钱
  • wordpress更换域名后网站打不开宜昌建设银行网站
  • 写出网站开发的基本流程百度网页电脑版入口
  • 网站设计有限公司怎么样网站建设西班牙语
  • 网站安全解决方案宁波seo网络推广优化价格
  • 做网站带来好处wordpress可以做oa系统吗
  • 建筑设计人才招聘网站h5营销型网站suteng
  • 做app和网站怎样如何做html网站
  • php开发手机端网站开发更换网站标题
  • 提供网站建设报价延津县建设局网站
  • 江苏网站建设流程土巴兔全包装修怎么样