JavaScript 模式(四)

函数模式


1.回调模式

【基本模式】

  var func = function(callback){
  
      //TODO
      
      //检查回调函数是否可用
      if(typeof callback !== "function"){
         callback = false;
      }

      //TODO
      //运行回调函数
      if(callback){
         callback(value);
      }
  }

回调模式下,回调函数作为参数可以是一个已有的函数,也可以是匿名函数

【作用域】

在一般方法下,回调函数可以正常运行,但对于对象方法,很可能出现对象方法内调用自身属性,造成this指向不明,其原因是作用域改变:

  //创建对象和对象方法,对象方法调用其属性值
  var objectName = {
      propName: value,
      methodName: function(){
         var other = this.propName;
      }
  }

  //使用对象方法作为回调函数
  func(objectName.methodName);//函数调用时出现问题,因为this.propName在相应的作用域内不存在

解决方法一:回调函数参数不仅包含对象方法,也包含该对象,通过绑定限定作用域:

    //更改外层函数的使用方式
    var func = function(callback,obj_callback){
  
      //TODO
      
      //检查回调函数是否可用
      if(typeof callback !== "function"){
         callback = false;
      }

      //TODO
      //运行回调函数
      if(callback){
         callback.call(obj_callback,value);
      }
  }

解决方法二:将对象方法的方法名作为参数,按照方法一的模式进行

      var func = function(callback,obj_callback){
  
      //TODO
      
      //检查回调函数赋值参数是否为函数名
      if(typeof callback === "string"){
         callback = obj_callback[callback];//回调函数从对象方法中获得
      }

      //TODO
      //运行回调函数
      if(typeof callback === "function"){
         callback.call(obj_callback,value);
      }

【超时用法】

   setTimeout(callback,timeout);

2.返回函数

将函数作为一个返回值,函数获得返回值后,可以继续调用:

   var outer = function(){
       //在函数声明的作用域中,创建变量,这些变量只能在该域内使用
       var params = value;
      
       //返回一个函数
       return function(){
          //TODO 可以在函数中处理内部参数,对外保持隐蔽
       }
   }

   //调用
   var First = outer(); //此时Fisrt即为一个可调用的函数
   var Second = First(); //继续调用

3.自定义函数(惰性函数定义)

当函数需要初始化工作,且只需要进行一次时,可以使用该模式:

   var selFunc = function(){
      
        //TODO 做一些需要初始化的工作
        
        //注意名称不能改变,因为需要对函数进行重定义
        selFunc = function(){
           //TODO 该函数真正要实现的功能
        };
   }
   
   //使用时
   selFunc(); //第一次进行初始化工作
   selFunc(); //执行真正的功能

【说明】

  • 重定义时,必须要使用相同名称,否则真实功能无法发生
  • 在重定义后,该函数之前添加的属性和方法全部丢失

4.即时函数

函数在定义后立即执行的一种模式,包括以下部分:

  • 需要使用函数表达式,不能使用函数声明
  • 在函数主体后添加“()”,表示立即执行
  • 将整个函数包装在括号中
  (function(){
     //TODO
  })();

【使用】
对于在一些场景中(如页面加载),一些操作只需要进行一次不需要重复使用,没有必要声明函数,如果直接操作,会污染全局域,此时使用即时函数创建沙箱

【参数和返回值】

对于含有参数的函数,直接将参数赋值即可,一般可以将全局域本身赋值给函数,就可以调用其中的属性

  (function(region){
     //TODO 使用this中的属性,如DOM元素
  })(this);

返回值可以直接从即时函数获取,返回值或者返回函数均可:

  var result = (function(){
     //TODO
  })();

【预设值】
可以利用即时函数的特点,直接返回一些预设值,且用户只读不可更改

  var getPreValue = (function(){
     //获取或计算预设值
     var preValue = {
            propName = value
         };
     //通过闭包的方式返回预设值 
     return function(){
        return preValue;
     };

  })();

  //使用
  var preValue = getPreValue();
  preValue.propName //可以使用预设值

【属性不变量】
可以利用即时函数给对象的属性赋值,在对象创建时进行赋值后,该属性保持不变

  //定义字面量对象
  var objectName = {
     //定义属性不变量
     constProp: (function(){
         //利用即时函数,直接返回处理结果
         var privateField = value;
         return privateField; 
    })()
  }; 

  //使用
  objectName.constProp //直接使用属性不变量

【变种:即时对象初始化】

    //创建匿名对象
   ({
      //这里定义的属性为对象内的属性,不会污染全局域
       propName: value,
       methodName: function(){
       },
       //定义初始化任务
       init: function(){
         //TODO 初始化任务
       }
    }).init(); //直接调用初始化方法

【说明】

  • 该模式可以更有效的组织代码结构
  • 如果要获取该对象,可以在init()中返回this
  • 该模式,在js压缩器工具中,一般不会被压缩

【变种:初始化分支(加载时分支)】

初始化时,确定一个条件,该条件在整个程序运行中不会改变,则只需确定一次即可(常见的浏览器嗅探功能,平台确认,函数是否兼容等)

   var mainObj = ({
  
      //需要确认的属性和方法
      propName: null,
      methodName: null,
      
      init: function(){
         //TODO 根据条件给相关属性和方法进行赋值或者定义
         this.propName = value;
         methodName = function(){
         };
      }

   }).init();

   //使用
   mainObj.propName;
   mainObj.methodName();

6.备忘模式(memoization)

给函数添加自定义属性,利用该属性缓存函数的返回结果,在下一次相同计算时,无需再做大量计算,直接返回缓存中的结果

   var funcMem = function(param){
       //一般需要将输入参数进行转换,生成一个键值
       var key = param;
       if(! funcMem.cache[key]){
          var result = {};
          //TODO 计算result值
         funcMem.cache[key] = result;
       }
       return funcMem.cache[key]; //返回结果
   };
   funcMem.cache = {};//为函数创建自定义属性

【说明】

  • 创建键值时,一般使用序列化,将输入参数转换为json字符串key = JSON.stringfy(Array.prototype.slice.call(arguments))

7.配置模式

函数在需要传递大量参数的情况下,不要直接列在函数参数中,而是传入一个配置对象

  //创建配置对象
  var conf = {
     param_1:value_1,
     param_2:value_2
  };
  
  //传入参数
  func(conf);

【优势】

  • 无需记住所有参数和顺序
  • 可以安全忽略可选参数
  • 易于阅读和维护,易于添加和删除参数
    【劣势】
  • 需要记住参数名称
  • 参数属性无法被压缩器压缩

8.柯里化(Curry)

【函数的apply和call】

函数不仅可以描述为调用,还可以描述为应用

   //原型:Function.prototype.apply()
   //使用: 
   func.apply(region,["param1","param2"]);

【说明】

  • region:表示函数中this所指向的作用域,如果赋值为null则为全局作用域,对于对象方法,必须要赋对象值
  • 参数数组:输入的参数数组,即生成函数的arguments对象
  • call函数为apply的语法糖,即只有一个输入参数时,效率更高:
   func.call(region,"param");

【柯里化过程】

使函数理解并处理部分应用的过程
通用实现方法:

   function currify(fn){
      var slice = Array.prototype.slice, //获取数组的原型方法slice()
          stored_args = slice(arguments, 1); //获取参数中第二个至最后的参数
        //返回利用剩余参数构建的新函数
        return function(){
           var new_args = slice.call(arguments), //获取新函数中所有参数
               args = stored_args.concat(new_args); //并将新参数和原有剩余参数合并在一起,组成新的参数数组
           return fn.apply(null,args); //返回原函数调用
        }
   }

【使用场景】
如果调用同一个函数,且传递的参数绝大多数都是相同的,则该函数可以用于柯里化

推荐阅读更多精彩内容