使用JavaScript Function.prototype进行代码重构的一些例子

时间:2022-07-22
本文章向大家介绍使用JavaScript Function.prototype进行代码重构的一些例子,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

Example1 – how to enhance a big / complex function with your own logic

Suppose there is already a big function with huge logic, and you have the task to enhance it by appending your own logic to its end.

var  bigFunction  =  function()  {  // big logic console.log("big logic");  }

Of course if the big function is owned by you, you could directly append the enhancement, like below:

var  bigFunction  =  function()  {  // big logic console.log("big logic");  // append our own enhancement directly console.log("our own enhancement");  }

Another approach, if you would not like to touch the existing one, is to use a temporary variable to store the old bigFunction:

var _old = bigFunction;  bigFunction  =  function()  {  if  ( _old )  {  _old();  } console.log("our own enhancement");  }  bigFunction();  // After it is executed, console will print out

A Better Solution

We add a new function in Function.prototype:

Function.prototype.after  =  function( func ){  var _self =  this;  return  function()  {  var ret =  _self.apply(  this, arguments );  if  ( ret ===  false  )  {  return  false;  }  func.apply(  this, arguments);  return ret;  }  }

This after function returns a new function, which will first call the original function, and then call the subsequent function passed in via variable “func” if the original function execution is successfully ( ret != false ).

Then just construct a new function using after function:

bigFunction = bigFunction.after(  function()  { console.log("our own logic");  });

Now when bigFunction() is executed, we get the same printout. With this approach, the temporary variable is avoided.

Example 2 – Write performance measurement code without polluting your productive code

Suppose in your application you have to create mass div node. You need to measure the creation performance. The most straightforward way is to get timestamp before and after creation. In order to get timestamp you cannot avoid to pollute your productive code with two “new Date()” ( or any other way for time measurement in JavaScript ) as below.

var  append_doms  =  function()  {  var d =  new  Date();  // dirty code - nothing to do with application logic!!!  for(  var i =  0; i <  100000; i++)  {  var div = document.createElement(  "div"); document.body.appendChild(div);  }  // dirty code - nothing to do with application logic!!! console.log(" time consumed: "  +  (  new  Date()  - d));  };

A Better Solution

Using the idea of the first example, we create another method before in Function.prototype, which has similar design as Function.prototype.after:

Function.prototype.before  =  function( func)  {  var _self =  this;  return  function()  {  if  (  func.apply(  this, arguments )  ===  false  )  {  return  false;  }  return  _self.apply(  this.arguments);  }  }

With this approach, our productive code is clean – not any time measurement code there.

var  append_doms  =  function()  {  for(  var i =  0; i <  100000; i++)  {  var div = document.createElement(  "div"); document.body.appendChild(div);  }  };

And we wrap the original function with before and after function we defined in Function.prototype:

var  log_time  =  function( func, log_name)  {  return func =  (  function()  {  var d;  return func.before(  function(){ d =  new  Date();  }).after(  function(){ console.log( log_name +  (  new  Date()  - d));  });  })();  };

Now we get a new function log_time which is dedicatedly used for performance measurement. This new function actually consists of three parts:

(1) an anonymous function with body “d = new Date();”, chained by Function.prototype.before.

(2) the original append_doms

(3) an anonymous function with body “console.log( log_name + ( new Date() – d)); “, chained by Function.prototype.after.

We can elegantly call it via one line of code below to achieve the performance measurement.

log_time(append_doms, "consumed time: ")();

AOP in Java

Update on 2016-07-29: In Java it could be done elegantly via Spring framework. Please find more detail from this blog: [An example to explain why we need AOP – Aspect Oriented Programming](https://blogs.sap.com/?p=145576) .

Example 3 – Replace lots of IF-ELSE with Design Pattern “Chain of Responsibility”

Suppose I am responsible for developing a file upload function and the upload could be finished by various approach if each feature is supported by client side. The priority is listed below:

// Priority: ActiveX > HTML5 > Flash > Form(default)

If means for example, if ActiveX is supported by client’s browser, it should be used. The default is upload via form, if all previous tries have failed.

function  isActiveXSupported(){  //...  return  false;  }  function  isHTML5Supported(){  //...  return  true;  }  function  isFlashSupported(){  //...  return  false;  }

The codes above just simulate the situation that HTML5 upload should be used, since its preceding attempt, isActiveXSupported, returns false. In order to get the proper upload service, we have to code lots of tedious IF-ELSE evaluation:

var uploadAPI;  if  (  isActiveXSupported())  {  // lots of initialization work uploadAPI =  {  "name":  "ActiveX"};  }  else  if(  isHTML5Supported())  {  // lots of initialization work uploadAPI =  {  "name":  "HTML5"};  }  else  if(  isFlashSupported())  {  // lots of initialization work uploadAPI =  {  "name":  "Flash"};  }  else  {  // lots of initialization work uploadAPI =  {  "name":  "Form"};  } console.log(uploadAPI);  // HTML5 service is got

A Better Solution

We do some minor change on Function.prototype.after:

Function.prototype.after  =  function( func ){  var _self =  this;  return  function()  {  var ret =  _self.apply(  this, arguments );  if  ( ret )  {  return ret;  }  return  func.apply(  this, arguments);  }  }

Now if the execution of original function is successfully ( returns true ), we terminate the function chain, that is, we don’t pass the responsibility chain to its subsequent function passed via func.

With this approach, there is no more IF-ELSE evaluation. In fact, now we spread the evaluation into the dedicated initialization function of each API:

var  getActiveX  =  function()  {  try  {  // lots of initialization work  return  {  "name":  "ActiveX"};  }  catch  (e)  {  // user broswer does not support ActiveX  return  null;  }  }  var  getHTML5  =  function()  {  try  {  // lots of initialization work  return  {  "name":  "HTML5"};  }  catch  (e)  {  // user broswer does not support HTML5  return  null;  }  }  var  getFlash  =  function()  {  try  {  // lots of initialization work  return  {  "name":  "Flash"};  }  catch  (e)  {  // user broswer does not support Flash  return  null;  }  }  var  getForm  =  function()  {  return  {  "name":  "Form"};  } ```

Now in order to get appropriate API, we just use single line:

> var uploadAPI = getActiveX.after(getHTML5).after(getFlash).after(getForm)();

This design idea is actually the so called “Chain of Responsibility”. Simply speaking, the function in the beginning of chain ( in my example, it is getActiveX ) will analyze whether it is able to resolve the task. If yes, the whole statement returns, task is over. Otherwise, it simply delegate the task to the next node in the chain.

# Example 4 – eliminate lots of IF-ELSE in validity check via Strategy Design Pattern

For example, before we assemble the request payload to send OData request via OData API, we must perform various validity check on the user input. If you have a page with lots of UI elements, usually it will lead to lots of IF-ELSEIF-ELSEIF validity check code spread in your application.

```javascript

var  send  =  function()  {  var value = input.value;  if( value.length ===  ''  )  {  return  false;  }  else  if( value.length >  MAX_LENGTH)  {  return  false;  }  ...  // LOTS OF other rules to be checked  else  {  // all check passes, now ready to call OData API  }  }

A Better Solution

Instead of directly coding all those validity checks in the application, we can first put the rules in a JavaScript object (so called “Strategy” in Design Pattern ):

var valid_rules =  {  not_empty:  function( value )  {  return value.length !==  '';  },  max_length:  function( value )  {  return value.length <=  MAX_LENGTH  ;  }  }

With strategy ready, now we can simply write the validity check function in a generic way. It doesn’t know any detail about validity rules, but simply scan rules configured in strategy object one by one. If any check fails, the whole function will return false – check not pass.

var  valid_check  =  function()  {  for(  var i in valid_rules )  {  if  ( vali_rules[i].apply(  this, arguments)  ===  false  )  {  return  false;  }  }  }

Finally the send function could be reduced as:

var  send  =  function( value )  {  if  (  valid_check( value )  ===  false  )  {  return;  }  // all check passes, now ready to call OData API  }