I remember I said earlier that I wanted to share "javascript design patterns" with you. The reason why I haven't written it yet is not because I'm lazy. I've really been too busy recently, busy with work and traveling (oh?). I finally have some free time these days. Let's continue. It's time to make good on the empty talk.
Before discussing design patterns, please make sure that you already have a certain foundation in script programming. If you don’t know much about it, it is recommended that you check out this article I wrote a long time ago, "A Brief Talk on JavaScript Object-Oriented Programming". Please read the next article. .
When it comes to design patterns, we have to focus on "interface design" first, because interface design is of great significance in design patterns, greater than the pattern itself. For the sake of intuition, let’s first introduce the form of the interface definition:
var interface = new Interface("interface",[["getName",1],["getAge",1]]);
It can be seen that the interface function must contain two parameters, the interface method Defined in a two-dimensional array. In the above example, two interface methods are defined: getName and getAge. Both methods take a parameter. Let's take a detailed look at the implementation code of the Interface function to deepen everyone's understanding of the interface.
function Interface(name,methods){
if (arguments.length !=2){
console.log("The parameters must be two");
}
this.name = name;
this.methods = [];
if(methods.length<1){
console.log("The second parameter cannot be an empty array");
}
for(var i=0;len=methods.length,i< ;len;i ){
if(typeof methods[i][0] !== 'string'){
console.log("The first parameter data type must be string");
}
if(methods[i][1] && typeof methods[i][1] !== 'number'){
console.log("The data type of the second parameter must be integer" );
}
if(methods[i].length == 1){
methods[i][1] = 0;
}
this.methods.push(methods[ i]);
}
}
It is not difficult to see from the code that the definition rules of interface functions: [1] The Interface function can only contain two parameters, the first parameter is the interface name, the second parameter is a two-dimensional array [2] The second parameter is not allowed to be an empty array [3] The first parameter in the methods parameter must be a string type, used to define the method name, the second parameter The parameters must be of integer type, used to define the number of parameters of the method [4] When the number of parameters of the method in methods is 0, it can be omitted.
Next, we need to build a class and let it inherit the interface defined earlier. So what should we do? Don’t worry, we need to add a new method. See the following code:
var ioldfish = function(name,age){
this.name = name;
this.age = age;
Interface.regImplement(this,interface);
}
ioldfish.prototype.getName = function(){
alert(this.name);
};
ioldfish.prototype.getAge = function(){
alert(this.age);
};
var fishwl = new ioldfish("老鱼",27);
fishwl. getName();
Interface.regImplement is the method we want to add. Its function is to make the ioldfish class code according to the specifications of the interface, otherwise an exception will be thrown in the firebug console.
Look at the specific implementation code of this method:
Interface.regImplement = function(object){
if(arguments.length<2){
console.log("Interface inheritance parameters cannot be less than two");
}
for(var i=1;len = arguments.length,ivar interface = arguments[i];
if(interface.constructor !== Interface){
console.log(" The third parameter must start with an interface instance");
}
for(var j=0;len=interface.methods.length,jvar method = interface.methods [j][0];
if(!object[method] || typeof object[method] !=="function" || object[method].getParameters().length !== interface.methods[j ][1]){
console.log("" method "Method interface does not match");
}
}
}
}
Interpreting this code, you can easily find: [1] The parameters of the Interface.regImplement inherited interface function must have at least two parameters. If there is a third parameter, then the parameter must be an instance of the Interface interface [2] We Traverse the methods in the interface interface and match them one by one with the methods in the new class. If it is found that the class that inherits the interface specification is missing a method, an error message will be thrown. [3] The interface also matches the number of parameters. If the number of parameters in the interface method does not match the number of methods in the new class, an error message will be thrown.
In order to match the number of parameters in the method, a getParameters() method is used here. We make an extension based on Function. The code is implemented as follows:
Function.prototype.getParameters = function(){
var str = this.toString();
var paramStr = str.slice (str.indexOf("(") 1,str.indexOf(")")).replace(/s*/g,'');
try{
return (paramStr.length ==0 ? [] : paramStr.split(","));
}
catch(err){
console.log("Illegal function");
}
}
Next, you can integrate the Interface function, Interface.regImplement function, and Function.prototype.getParameters function into an interface.js file to debug the newly created ioldfish class. See what happens when the getAge method is missing from the class? It is recommended for novices to simulate various situations to enhance their understanding! If you are sure that you have fully understood interface design, then follow me further.
Javascript design pattern Singleton mode
Single mode Singleton: This is the most basic design pattern. Strictly speaking, there is no pattern at all, but it is easy to use and easy to use. Many components of Alipay are Designed through the singleton pattern. In fact, this pattern has already been used when explaining prototypal inheritance in "A Brief Talk on JavaScript Object-Oriented Programming". I will briefly mention it here, focusing on the lazy monomer. This is not necessary for all users and is only needed in specific situations. The component that will be used has a very good optimization effect. It can defer the instantiation of the component until the user triggers it.
var ioldfish = {
name:'老鱼',
age:27,
getName:function(){
alert(name);
},
getAge:function(){
alert(age);
}
}
The above example is the simplest single mode, integrating all my data into the ioldfish object literal to form a module and at the same time function as a namespace role.
var ioldfish =(function(){
var name = '老鱼';
var age = 27;
return{
getName:function(){
alert(name);
},
getAge:function() {
alert(age);
}
}
})();
Make a simple modification to the first single body and use closure to change the name , age becomes a static private variable, ensuring that there is always only one copy in the memory when instantiated, which is more in line with the definition of the single mode.
The following focuses on the lazy monomer. Without further ado, let’s take a look at how we can implement the lazy monomer:
var ioldfish = (function(){
var uniqueInstance;
var name = '老鱼';
var age = 27;
function constructor(){
return{
getName:function(){
alert(name);
},
getAge:function(){
alert(age);
}
}
}
return{
isInstance:function(){
if(uniqueInstance == null){
uniqueInstance = constructor();
}
return uniqueInstance;
}
}
})();
ioldfish.isInstance().getName();
The above structure is clearly public and private. The private variable uniqueInstance (identifies whether the class has been instantiated) and the private method constructor return a public method isInstance (through which the method defined in the private method constructor can be called), in the shape of: ioldfish .isInstance().getName(); First determine whether it has been instantiated through the isInstance() method, and then obtain the private variable name in the closure through the getName() method. There are still many application scenarios for this mode. Have you ever encountered a large calendar control that needs to be loaded on the page, but not all users can use it? Is it...
Javascript Design Pattern Factory Mode Factory
Factory Mode Factory: First create an abstract class, then derive a subclass based on this abstract class, and create a factory method in the subclass to postpone instantiation Go to the corresponding subclass. To be honest, the application of factory pattern in javascript is a bit far-fetched. After all, javascript does not have the difficulties caused by hard coding like java. What you need to learn is just the idea of patterns, and avoid patterns because of patterns.
As an extreme example, add positioning, fading, delay and other effects to components such as tab switching and drop-down lists. We can first define an interface for these components:
var Iwidget = new Interface("iwidget ",[["addEffect"]]);
Define this interface so that subsequent derived subclasses can inherit it. The interface defines an addEffect method. After the interface method is implemented, the calling students do not need to pay attention to each subclass. For the code implementation of the addEffect method.
var Widget = function(){};
Widget.prototype={
fire:function(model){
var widget = this.createWidget(model);
//Some students asked why subclasses must define interface methods, because they need to be called below
widget.addEffect();
return widget;
},
show:function(){
//show code implementation
},
hide:function( ){
//Hide code specific implementation
},
createWidget:function(model){
alert('Abstract class, cannot be instantiated')
}
};
The above example first defines an abstract class Widget as the parent class of the derived subclass. Considering that both types of components involve hiding and displaying a container, it is predefined in the parent class. Good show and hide methods for subclass inheritance.
var xTab = function(){};
extend(xTab,Widget);
xTab.prototype.createWidget = function(model){
var widget;
switch(model){
case 'position':
widget = new xTabPosition ();
break;
case 'anim':
widget = new xTabAnim();
break;
case 'delay':
default:
widget = new xTabDelay();
}
};
var dropDown = function(){};
extend(dropDown,Widget);
dropDown.prototype.createWidget = function(model){
var widget;
switch(model){
case 'position':
widget = new dropDownPosition();
break;
case 'anim':
widget = new dropDownAnim();
break;
case 'delay':
default:
widget = new dropDownDelay();
}
};
The subclasses xTab and dropDown inherit the parent class and rewrite the createWidget method. Different subclasses create different instances according to positioning, fading, and delay effects. As long as the classes that create these instances implement the addEffect method agreed in the interface, As for how to implement the method code, it's all the same, just adjust it as you like.
var xTabPosition = function(){};
xTabPosition.prototype ={
addEffect:function(){
//Specific implementation code
}
};
var dropDownPosition = function(){};
dropDownPosition.prototype = {
addEffect:function(){
//Specific implementation code
}
};
var dropDownInstance = new dropDown();
dropDownInstance.fire('position');
By analogy, if you need to add these effects to the bubble component, just follow the same pattern. At this point, you can clearly see that this design pattern greatly reduces the coupling between classes. , and can implement different auxiliary actions according to specific interaction requirements, but it also inevitably increases the complexity of code implementation. In fact, this mode is not suitable for JavaScript. After all, it is different from Java and does not have The purpose of hard-coding class names is to learn his design ideas, so the above examples are for reference only. Children should not follow suit unless an adult is around.
For JavaScript enthusiasts, the more valuable thing should be the "caching (memoization) mechanism" mentioned in the factory pattern. The book gives an example of creating an XHR object to illustrate this feature, but the effect is obviously not enough. Obviously...
memoization noun explanation: Put the result of each execution of the function into a key-value pair (an array can also be used, depending on the situation). In the subsequent execution, check whether the key-value pair is There is already a corresponding executed value. If there is, the value is returned directly. If not, the evaluation part of the function body is actually executed. Obviously, finding values, especially in key-value pairs, is much faster than executing functions.
The power of memoization can be better demonstrated during recursive calls. The following is a classic Fibonacci sequence. fib(20) will execute the fib method 21891 times. If it is fib(40), this will be executed 331160281 times.
function fib(n) {
if (n < 2) {
return n;
}
return fib(n - 1) fib(n - 2);
}
Let’s see how to use it again To implement memoization:
var iterMemoFib = (function() {
var cache = [1, 1];
var fib = function(n) {
if (n >= cache.length) {
//Convert a recursion into a
for (var i = cache.length; i <= n; i ) {
cache[i] = cache[i - 2] cache[i - 1];
}
}
return cache[n-1];
}
return fib;
})();
Extend the Function prototype with the memoize and unmemoize methods so that you can Implement memoize and unmemoize any function. Of course, this method should be used with caution. For some functions that are not executed frequently, there is no need to cache:
Function.prototype.memoize = function() {
var pad = {};
var self = this;
var obj = arguments .length > 0 ? arguments[i] : null;
var memoizedFn = function() {
// Save the parameters as an array as a key, and cache the result of function execution as a value
var args = [];
for (var i = 0; i < arguments.length; i ) {
args[i] = arguments[i];
}
if (!(args in pad)) {
pad[args] = self.apply(obj, arguments);
}
return pad[args];
}
memoizedFn.unmemoize = function() {
return self;
}
return memoizedFn;
}
Function.prototype.unmemoize = function() {
alert("Attempt to unmemoize an unmemoized function.");
return null;
}
Usage: fib.memoize();
Composition mode of Javascript design pattern
Composition mode: Use this design pattern to combine objects Add properties and methods, and recursively batch the leaf objects to obtain the properties and methods of the combined object. For example, we now want to dynamically create a bank list, which is divided into online banking and cartoon banking by bank type, and can configure whether they are displayed. How to achieve this using combination mode?
The first step is to define the interface first, because in order to make it configurable whether a certain type of bank or even a certain bank can be displayed, we first agree on two interfaces, showBank and hideBank.
var IcardItem = new Interface("icardItem",[["showBank"],["hideBank"]]);
Next, define the combination object of the card and set the basic methods add,remove of the combination object , getChild, since this class inherits the IcardItem interface class, it also defines two interface methods, showBank and hideBank.
var cardMain = function(id){
this.cards = [];
this.element = document.createElement("div");
this.element.id = id;
Interface.regImplement(this,IcardItem);
};
cardMain.prototype = {
add:function(card){
this.cards.push(card);
this.element.appendChild(card.getElement());
},
remove:function(card){
for(i=0;len=this.cards.length,iif(cards[i] == card){
this.cards.splice(i,1);
break;
}
this.element.removeChild(card.getElement());
}
},
getChild:function(i){
return this.cards[i];
},
getElement:function(){
return this.element;
},
showBank:function(){
this.element.style.display ="block";
for(i=0;len=this.cards.length,ithis.cards[i].showBank();
}
},
hideBank:function(){
this.element.style.display ="none";
for(i=0;len=this.cards.length,ithis.cards[i].hideBank();
}
}
};
然后定义叶子对象类bankLogo用以创建银行logo,这里银行logo都以带class的a标签标识:
var bankLogo = function(bankClassName){
this.element = document.createElement("a");
this.element.className = bankClassName;
Interface.regImplement(this,IcardItem);
};
bankLogo.prototype ={
showBank:function(){
this.element.style.display ="block";
},
hideBank:function(){
this.element.style.display ="none";
},
getElement:function(){
return this.element;
}
};
最后设置一个单体对象,将操作银行的相关信息形成一个模块,方便调用:
var BankAction ={
bankList:[],
addBank:function(card){
this.bankList.push(card);
},
innerBank:function(conId){
for(i=0;len=this.bankList.length,ivar cardObj =this.bankList[i].getElement();
}
document.getElementById(conId).appendChild(cardObj);
}
};
到了实现环节了,实例化生成一个包含所有卡的最外层容器,然后根据卡类,分别生成一个放置银行卡和卡通卡的容器,最后生成各银行卡的实例,并按层级关系形成DOM结构:
var bankDivT = new cardMain("PayCard");//创建最外层容器
var ebankCard = new cardMain("ebankCard");//创建网银类银行卡容器
var ktCard = new cardMain("ktCard");//创建卡通类银行卡容器
var ccbBank = new bankLogo('Ebank-CMB');//创建招行银行卡
var abcBank = new bankLogo('Ebank-ABC');//创建农行银行卡
var abcKtBank = new bankLogo('Kt-ABC');//创建卡通农行卡
ebankCard.add(ccbBank);
ebankCard.add(abcBank);
ktCard.add(abcKtBank);
bankDivT.add(ebankCard);
bankDivT.add(ktCard);
BankAction.addBank(bankDivT);
BankAction.innerBank("bankList");
将动态生成的银行列表,DOM结构形如:
The combination mode application is a very good choice when dynamically generating user interfaces. It can greatly simplify the cohesive code and improve maintainability. However, it should be used with caution. After all, when there are many leaf objects, recursion still has performance problems.
Decorator Pattern of Javascript Design Patterns
Decorator Pattern: You can create new functions for objects without creating new subclasses. For example: the application scenario of Alipay checkout red envelope combined with balance payment.
var Ieconomics = new Interface("ieconomics",[["getPrice"]]);
First create a component class, and the object instantiated based on the component will be passed to the decorator class as a parameter. So that the decorator can call various methods in the component.
var economic = function(){
Interface. regImplement(this,Ieconomics);
};
economic.prototype={
getPrice:function(){
//Code implementation
}
};
Then create a decorator abstract class as the parent class of the derived decorator option class:
var economicsDecorator = function(economic){
this.economic = economic;
this.regImplement(economic,Ieconomics);
};
economicsDecorator. prototype={
getPrice:function(){
return this.economic.getPrice();
}
};
Finally, based on the above abstract class, derive Create a decorator option class:
//Red envelope decoration Or option class
var coupon = function(economic){
//Call the constructor decorated with abstract class
economicsDecorator.call(this,economic);
};
extend( coupon, couponDecorator);
coupon.prototype=function(){
//Rewrite the getPrice method
getPrice:function(){
return this.economic.getPrice() - this.getCoupon() ;
},
getCoupon:function(){
//The specific implementation of getting the total price of the red envelope
}
};
var myCoupon = new economic();
myCoupon = new coupon(myCoupon);
It is so simple to implement the decorator pattern. First create an instance of the component myCoupon, and then pass the object as a parameter to the decorator option class coupon. You will find that in both lines of code, I assigned the value to the variable myCoupon. This is because they both implement the same interface class and can be used interchangeably.
Careful students who see this may find that we have added a getCoupon method in the coupon class. There will be no problems at present, but if we continue to create a shopping coupon decorator option class, and then combine How about using red envelopes together?
//Shopping Voucher Decorator Option Class
var voucher = function(economic){
economicsDecorator.call(this,economic);
};
extend(voucher,couponDecorator);
voucher.prototype=function(){
getPrice :function(){
return this.getPrice() - this.getVoucher();
},
getVoucher:function(){
//The specific implementation of getting the total price of the coupon
}
};
var myCoupon = new economic();
myCoupon = new coupon(myCoupon);
myCoupon = new voucher(myCoupon);
here The getCoupon method in this scenario can no longer be found. This is because when the voucher decorates myCoupon, its parent class economicsDecorator does not contain the getCoupon method, so naturally it cannot be obtained. So what should I do?
Analyzing the decorator abstract class economicsDecorator, we pass a reference to myCoupon as a parameter. We can use this parameter to do some small actions and obtain newly added methods.
var economicsDecorator = function(economic){
this.economic = economic;
this.interface = Ieconomics;
for(var k in this.economic){
if( typeof this.economic[key] !== "function"){
continue;
var i;
for(i = 0;len = this.interface.methods.length,i < len; i ) {
//Compare whether this method is included in the interface class by traversing, and if it is included, return the next one
if(key == this.interface.methods[i][0]) {
break ;
}
}
if(i < this.interface.methods.length)
continue;
var decorator = this;
//Defined using anonymous function calling New method
(function(methodName) {
decorator[methodName] = function() {
return decorator.economic[methodName]();
};
})(key);
}
}
}
this.regImplement(economic,Ieconomics);
};
economicsDecorator.prototype={
getPrice:function(){
return this.economic.getPrice();
}
};
Looking at the above code, we have made some modifications to the decorator abstract class. This is done to ensure that the Once a new method is defined in the option class, it can be dynamically defined in the decorator abstract class. Here is just an idea for using the decorator pattern. The specific implementation code is far more complicated than this. Since the project is still under development, the demo is not available yet. After the new version of Alipay's cashier is released, we will share the detailed design with you.
Bridge mode of Javascript design pattern
Bridge mode: Separate abstraction and its implementation so that they can change independently. In fact, it is very simple. It just adds a bridge between the API and specific events, thereby reducing the coupling between the API and the classes and objects that use it.
In fact, the bridge mode is not unfamiliar to most students. The following this.getName is a bridge method. It is an interface for external access. Its internal implementation is achieved by accessing internal private variables. This method serves as a bridge between external and internal communication.
var ioldfish = function(){
var name = 'Old Fish';
this.getName = function(){
alert(name);
}
}
The most commonly used bridge mode is Event listener callback function. The following is the API interface function to obtain user information:
function getUserInfo(userid,callback){
asyncRequest('GET','userInfo?userid=' userid,function(resp){
callback(resp.responseText);
});
}
What we need to do next is to establish a bridge relationship between this API and the triggering of an event
addEvent(element,'click',bridgeMethod);
function bridgeMethod(e) {
getUserInfo(this.userid,function(){
//Callback function implementation code
});
}
The function that is triggered here when the element object is clicked is not getIserInfo, but A new bridge method, bridgeMethod, is created. Through this layer of bridging, the API interface function and click event are relatively independent, which greatly broadens the scope of application of the API.
Javascript Design Pattern Adapter Pattern
Adapter Pattern: For example, you maintain a system. You have always used the prototype framework before, but now you plan to introduce the YUI framework. How to make the transition between the two frameworks smooth?
, for example, how to convert the $ method in prototype to the get method in YUI:
function $(){};
function YAHOO.util.Dom.get=function(el){};
function prototypeToYuiAdapter(){
return YAHOO .util.Dom.get(arguments);
}
If you want to use yui's get method in prototype, you only need to make the following statement:
$ = prototypeToYuiAdapter;
In this case, you can use the get method in YUI in prototype. I don’t really admire this model, so I won’t elaborate on it. In fact, I don’t think it’s necessary. We don’t need to use this model at all. As a responsible designer, I would rather refactor the code. If you do not want to use this mode, it can only be used as a transitional solution in desperation.
Javascript design pattern facade mode, observer mode
Facade mode: This should be used in all script frameworks. It is the most basic design pattern. Just look for any method defined in the framework and take a look. , such as the setStyle method in YUI and so on. I won’t elaborate much here.
Observer pattern: The application of this design pattern in JavaScript seems to be more far-fetched. I don’t understand it well, so I won’t go into it here to avoid misleading others. If you have any experience, please give me some advice.
I have spent a whole day on this blog post, and it seems that there is still a lot I want to write. It seems that it is not easy to write down what is on my mind clearly. I will sort it out later, so stay tuned!