晴明的博客园 GitHub      CodePen      CodeWars     

[js] 函数

(高阶)函数

高阶函数

高阶函数(higher-order function)指操作函数的函数

  1. 函数可以作为参数被传递
  2. 函数可以作为返回值输出

常见的 sort,reduce 等函数也算。

AOP

AOP(面向切面编程)的主要作用是把一些跟核心业务逻辑模块无关的功能抽离出来,这些跟业务逻辑无关的功能通常包括日志统计、安全控制、异常处理等。
把这些功能抽离出来之后,再通过“动态织入”的方式掺入业务逻辑模块中。这样做的好处首先是可以保持业务逻辑模块的纯净和高内聚性,其次是可以很方便地复用日志统计等功能模块
通常,在javascript中实现AOP,都是指把一个函数“动态织入”到另外一个函数之中。

      Function.prototype.before = function (beforefn) {
        var _this = this;    // 保存原函数的引用
        return function () {    // 返回包含了原函数和新函数的"代理"函数 
          beforefn.apply(this, arguments);    // 先执行新函数,修正this 
          return _this.apply(this, arguments);    // 再执行原函数
        }
      };
      Function.prototype.after = function (afterfn) {
        var _this = this;
        return function () {
          var ret = _this.apply(this, arguments); //先执行原函数
          afterfn.apply(this, arguments); //再执行新函数
          return ret;
        }
      };

      var func = function () {
        console.log(2);
      };

      func = func.before(function () {
        console.log(1);
      }).after(function () {
        console.log(3);
      });

      func();
      //1
      //2
      //3

not

      function not(f) {
        return function () {
          return !(f.apply(this, arguments));
        };
      }
      //偶数时,返回true;奇数时,返回false
      var even = function (x) {
        return x % 2 === 0;
      }
      //偶数时,返回false;奇数时,返回true
      var odd = not(even);
      console.log([1, 1, 3, 5, 5].every(odd));//true

柯里化

函数柯里化(currying)又称部分求值。一个currying的函数首先会接受一些参数,接受了这些参数之后,该函数并不会立即求值,而是继续返回另外一个函数,刚才传入的参数在函数形成的闭包中被保存起来。待到函数被真正需要求值的时候,之前传入的所有参数都会被一次性用于求值。

把接受多个参数的函数变换成接受一个单一参数的函数,并且返回(接受余下的参数而且返回结果的)新函数的技术.

      var cost = (function () {
        var args = [];
        return function () {
          //如果没有参数,则计算args数组中的和
          if (arguments.length === 0) {
            var money = 0;
            for (var i = 0, l = args.length; i < l; i++) {
              money += args[i];
            }
            return money;
            //如果有参数,则只能是将数据传到args数组中
          } else {
            [].push.apply(args, arguments);
          }
        }
      })();
      cost(100); // 未真正求值 
      cost(200); // 未真正求值 
      cost(300); // 未真正求值
      console.log(cost()); // 求值并输出:600

通用函数

      var currying = function (fn) {
        var args = [];
        return function () {
          if (arguments.length === 0) {
            return fn.apply(this, args);
          } else {
            [].push.apply(args, arguments);
            return arguments.callee;
          }
        }
      };
      var cost = (function () {
        var money = 0;
        return function () {
          for (var i = 0, l = arguments.length; i < l; i++) {
            money += arguments[i];
          }
          return money;
        }
      })();
      cost = currying(cost); // 转化成 currying 函数
      cost(100); // 未真正求值 
      cost(200); // 未真正求值 
      cost(300);   // 未真正求值
      console.log(cost());  // 求值并输出:600

可传(多个)参函数

      var currying = function (fn) {
        var args = [];
        //储存传到curring函数中的除了fn之外的其他参数,并储存到args函数中
        args = args.concat([].slice.call(arguments, 1));//0为function
        return function () {
          if (arguments.length === 0) {
            return fn.apply(this, args);
          } else {
            //将fn中的参数展开,然后再储存到args数组中
            [].push.apply(args, arguments);
          }
        }
      };
      var cost = (function () {
        var money = 0;
        return function () {
          for (var i = 0, l = arguments.length; i < l; i++) {
            money += arguments[i];
          }
          return money;
        }
      })();
      cost = currying(cost, 100, 200); // 转化成 currying 函数
      cost(100, 200); // 未真正求值 
      cost(300);   // 未真正求值
      console.log((cost()));  // 求值并输出:900

求值柯里化

      var currying = function (fn) {
        //获取除了fn之外的其他参数
        var args = [].slice.call(arguments, 1);
        return function () {
          //获取fn里的所有参数
          var innerArgs = [].slice.call(arguments);
          //最终的参数列表为args和innerArgs的结合
          var finalArgs = args.concat(innerArgs);
          //将finalArgs里的参数展开,传到fn中执行
          return fn.apply(null, finalArgs);
        };
      };
      var cost = (function () {
        var money = 0;
        return function () {
          for (var i = 0, l = arguments.length; i < l; i++) {
            money += arguments[i];
          }
          return money;
        }
      })();
      cost = currying(cost, 100, 200); // 转化成 currying 函数
      console.log(cost(300));//100+200+300=600
      console.log(cost(100, 100));//(100+200+300)+(100+200+100+100)=1100
function add(a) {
    return function(b) {
        return a + b
    }
}

var add3 = add(3)
add3(4) === 3 + 4 //true


//add 函数 在 es6 里的写法等价为
let add = a => b => a + b

redux-thunk源码
连续箭头函数是多次柯里化函数的 es6 写法

function createThunkMiddleware(extraArgument) {
  return ({ dispatch, getState }) => next => action => {
    if (typeof action === 'function') {
      return action(dispatch, getState, extraArgument);
    }

    return next(action);
  };
}

const thunk = createThunkMiddleware();
thunk.withExtraArgument = createThunkMiddleware;

export default thunk;

反柯里化(uncurrying)?

Function.prototype.uncurrying = function () { 
  var _this = this;
  return function() {
    var obj = Array.prototype.shift.call( arguments );
    return _this.apply( obj, arguments );
  };
};

//或
Function.prototype.uncurrying = function() {
    var _this = this;
    return function() {
        return Function.prototype.call.apply(_this, arguments);
    }
}

使用uncurrying

      var push = Array.prototype.push.uncurrying();
      var obj = {};
      push(obj, 'first', 'two');
      console.log(obj);//{0: "first", 1: "two", length: 2}

通过uncurrying的方式,Array.prototype.push.call变成了一个通用的push函数。
这样一来,push函数的作用就跟Array.prototype.push一样了,同样不仅仅局限于只能操作array对象。(但实际生产中似乎很少见人这么用?)

      var toUpperCase = String.prototype.toUpperCase.uncurrying();
      console.log(toUpperCase('avd')); // AVD
      function AryUpper(ary) {
        return ary.map(toUpperCase);
      }
      console.log(AryUpper(['a', 'b', 'c'])); // ["A", "B", "C"]

debounce(函数去抖)

函数防抖的原理是将即将被执行的函数用setTimeout延迟一段时间执行。对于正在执行的函数和新触发的函数冲突问题有两种处理,也分别对应了定时器管理的两种机制

只要当前函数没有执行完成,任何新触发的函数都会被忽略

function debounce(method, context) {
  //忽略新函数
  if(method.tId){
    return false;
  }
  method.tId = setTimeout(function() {
    method.call(context);
  }, 1000);
}

只要有新触发的函数,就立即停止执行当前函数,转而执行新函数

function debounce(method, context) {
 //停止当前函数
  clearTimeout(method.tId);
  method.tId = setTimeout(function() {
    method.call(context);
  }, 1000);
}
      var debounce = function (fn, interval) {
        //闭包
        var _self = fn,    // 保存需要被延迟执行的函数引用
          timer,    // 定时器
          firstTime = true;    // 是否是第一次调用

        return function () {
          var args = arguments,
            _me = this;

          if (firstTime) {    // 如果是第一次调用,不需延迟执行
            _self.apply(_me, args);
            return firstTime = false;
          }
          if (timer) {    // 如果定时器还在,说明前一次延迟执行还没有完成
            return false;
          }
          timer = setTimeout(function () { // 延迟一段时间执行
            clearTimeout(timer);
            timer = null;
            _self.apply(_me, args);
          }, interval || 500);
        };
      };

      window.onresize = debounce(function () {
        console.log(1);
      }, 500);

throttle(函数节流)

函数节流使得连续的函数执行,变为固定时间段间断地执行。
节流的实现,有两种主流的实现方式.

一种是使用时间戳:
触发事件时,取出当前的时间戳,然后减去之前的时间戳(最一开始值设为 0 ),如果大于设置的时间周期,就执行函数,然后更新时间戳为当前的时间戳,如果小于,就不执行

      function throttle(func, wait) {
        var context, args;
        var previous = 0;
        return function () {
          var now = +new Date();
          context = this;
          args = arguments;
          if (now - previous > wait) {
            func.apply(context, args);
            previous = now;
          }
        }
      }

一种是设置定时器:
触发事件时,设置一个定时器,再触发事件的时候,如果定时器存在,就不执行,直到定时器执行,然后执行函数,清空定时器,这样就可以设置下个定时器

      function throttle(func, wait) {
        var timeout, args, context;
        var previous = 0;
        return function () {
          context = this;
          args = arguments;
          if (!timeout) {
            timeout = setTimeout(function () {
              timeout = null;
              func.apply(context, args)
            }, wait)
          }
        }
      }
  // Produce an array that contains every item shared between all the
  // passed-in arrays.
  _.intersection = function (array) {
    var result = [];
    var argsLength = arguments.length;
    for (var i = 0, length = getLength(array); i < length; i++) {
      var item = array[i];
      if (_.contains(result, item)) { continue; }
      for (var j = 1; j < argsLength; j++) {
        if (!_.contains(arguments[j], item)) { break; }
      }
      if (j === argsLength) { result.push(item); }
    }
    return result;
  };

数组分块

数组分块是一种使用定时器分割循环的技术,为要处理的项目创建一个队列,然后使用定时器取出下一个要处理的项目进行处理,接着再设置另一个定时器.

在数组分块模式中,array变量本质上就是一个“待办事宜”列表,它包含了要处理的项目。使用shift()方法可以获取队列中下一个要处理的项目,然后将其传递给某个函数。如果在队列中还有其他项目,则设置另一个定时器,并通过arguments.callee调用同一个匿名函数.

数组分块的重要性在于它可以将多个项目的处理在执行队列上分开,在每个项目处理之后,给予其他的浏览器处理机会运行,这样就可能避免长时间运行脚本的错误。一旦某个函数需要花50ms以上的时间完成,那么最好看看能否将任务分割为一系列可以使用定时器的小任务.

      function chunk(array, process, context) {
        setTimeout(function () {
          //取出下一个条目并处理
          var item = array.shift();
          process.call(context, item);
          //若还有条目,再设置另一个定时器
          if (array.length > 0) {
            setTimeout(arguments.callee, 300);
          }
        }, 300);
      }

      var data = [1, 2, 3, 4, 5, 6, 7, 8, 9, 0];
      function printValue(item) {
        document.body.innerHTML += item + '<br>';
      }
      chunk(data.concat(), printValue);

第1个参数是创建节点时需要用到的数据,第2个参数是封装了创建节点逻辑的函数,第3个参数表示每一批创建的节点数量

var timeChunk = function (ary, fn, count) {
        var obj, t;
        var len = ary.length;
        var start = function () {
          for (var i = 0; i < Math.min(count || 1, ary.length); i++) {
            var obj = ary.shift();
            fn(obj);
          }
        };
        return function () {
          t = setInterval(function () {
            if (ary.length === 0) { // 如果全部节点都已经被创建好
              return clearInterval(t);
            }
            start();
          }, 200);    // 分批执行的时间间隔,也可以用参数的形式传入
        };
      };

      var ary = [];
      for (var i = 1; i <= 1000; i++) {
        ary.push(i);
      }
      var renderFriendList = timeChunk(ary, function (n) {
        var div = document.createElement('div');
        div.innerHTML = n;
        document.body.appendChild(div);
      }, 8);//每次创建8个
      renderFriendList();

惰性函数

惰性函数表示函数执行的分支只会在函数第一次调用的时候执行,在第一次调用过程中,该函数会被覆盖为另一个按照合适方式执行的函数,这样任何对原函数的调用就不用再经过执行的分支了。

产生原因,并不是每次都需要检查。

      function addEvent(type, element, fun) {
        if (element.addEventListener) {
          element.addEventListener(type, fun, false);
        }
        else if (element.attachEvent) {
          element.attachEvent('on' + type, fun);
        }
        else {
          element['on' + type] = fun;
        }
      }

函数重写

由于一个函数可以返回另一个函数,因此可以用新的函数来覆盖旧的函数。

这样一来,第一次调用该函数时console.log('a')会被执行;全局变量a被重定义,并被赋予新的函数。当该函数再次被调用时, console.log('b')会被执行

function a(){
    console.log('a');
    a = function(){
        console.log('b');
    }
}

惰性函数的本质就是函数重写。所谓惰性载入,指函数执行的分支只会发生一次,有两种实现惰性载入的方式.

第一种是在函数被调用时,再处理函数。函数在第一次调用时,该函数会被覆盖为另外一个按合适方式执行的函数,这样任何对原函数的调用都不用再经过执行的分支了。

在这个惰性载入的addEvent()中,if语句的每个分支都会为addEvent变量赋值,有效覆盖了原函数。最后一步便是调用了新赋函数。下一次调用addEvent()时,便会直接调用新赋值的函数,这样就不用再执行if语句了.
这种方法有个缺点,如果函数名称有所改变,修改起来比较麻烦.

      function addEvent(type, element, fun) {
        if (element.addEventListener) {
          addEvent = function (type, element, fun) {
            element.addEventListener(type, fun, false);
          }
        } else if (element.attachEvent) {
          addEvent = function (type, element, fun) {
            element.attachEvent('on' + type, fun);
          }
        } else {
          addEvent = function (type, element, fun) {
            element['on' + type] = fun;
          }
        }
        return addEvent(type, element, fun);
      }

第二种是声明函数时就指定适当的函数。把嗅探浏览器的操作提前到代码加载的时候,在代码加载的时候就立刻进行一次判断,以便让addEvent返回一个包裹了正确逻辑的函数.

      var addEvent = (function () {
        if (document.addEventListener) {
          return function (type, element, fun) {
            element.addEventListener(type, fun, false);
          }
        }
        else if (document.attachEvent) {
          return function (type, element, fun) {
            element.attachEvent('on' + type, fun);
          }
        }
        else {
          return function (type, element, fun) {
            element['on' + type] = fun;
          }
        }
      })();
posted @ 2018-07-20 00:44  晴明桑  阅读(115)  评论(0编辑  收藏  举报