js设计模式-高阶函数

90 阅读3分钟

定义

高阶函数是指至少满足下列条件之一的函数。

  • 函数可以作为参数被传递
  • 函数可以作为返回值输出

应用场景

作为参数传递
  • 回调函数
const appendDiv = function(callback: Function) {
  for (let i = 0; i < 100; i++) {
    const div = document.createElement('div');
    div.innerHTML = i + '';
    document.body.appendChild(div)
    if (typeof callback === 'function') {
      callback(div)
    }
  }
}
appendDiv(function(node: HTMLElement) {
  node.style.display="none"
})
  • Array.prototype.sort
const res = [2,6,4].sort((a, b) => {
  return a - b
})
console.log(res);
作为返回值输出
  • 判断数据的类型
const type = {};
['String', 'Array', 'Number'].map((v) => {
  (function(v) {
    type[`is${v}`] = (obj: any) => {
      return Object.prototype.toString.call(obj) === `[object ${v}]`
    }
  })(v)
})
console.log(type.isString('222'));
console.log(type.isString(2));
  • 单例模式 getSingle
const getSingle = (fn: Function) => {
  let ret: any;
  return function() {
    return ret || (ret = fn.apply(this, arguments))
  }
}
const getScript = getSingle(() => {
  return document.createElement('script')
})
const script1 = getScript();
const script2 = getScript();
console.log(script1 === script2)

高阶函数实现AOP

就是在现有代码程序中,在程序的生命周期或横向流程中,加入或减去一个或多个功能,使原本功能不受影响。

AOP即面向切面编程,主要是把一些跟核心业务逻辑模块无关的功能抽离出来,这些跟业务逻辑无关的功能通常包括日志统计、安全控制、异常处理等。一般是把这些功能抽离出来之后再通过动态织入的方式掺入业务逻辑模块中。

Function.prototype.before = function(beforeFn){
  var _Self = this;
  return function() {
    beforeFn.apply(this, arguments);
    return _Self.apply(this, arguments)
  }
}
Function.prototype.after = function(afterFn){
  var _Self = this;
  return function() {
    var ret = _Self.apply(this, arguments)
    afterFn.apply(this, arguments);
    return ret
  }
}
var func = function() {
  console.log(2)
}
func = func.before(() => {
  console.log(1)
}).after(() => {
  console.log(3)
})
func()

其他应用

函数柯里化 currying

柯里化(Currying)是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数且返回结果的新函数的技术。

var carry = 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 = carry(cost)
cost(100);
cost(200);
cost(300);
console.log(cost())
uncarrying

反柯里化的作用在于扩大函数的适用性,使本来作为特定对象所拥有的功能的函数可以被任意对象所用,即把函数obj.func(arg1, arg2)转化成function(obj, arg1, arg2)

Function.prototype.uncurrying = function() {
  var self = this;
  return function() {
    var obj = Array.prototype.shift.call(arguments);
    return self.apply(obj, arguments)
  }
}
var push = Array.prototype.push.uncurrying();
(function() {
  push(arguments, 4)
  console.log(arguments)
})(1,2,3)
函数节流
  • 函数被频繁调用的场景
    1. window.resize事件,浏览器窗口大小被拖动而改变时,这个事件触发频率非常高,如果在这个函数里面进行了dom操作们就会造成卡顿现象
    2. mousemove事件,拖拽事件被拖动的过程中也会被频繁触发
    3. 上传进度,当使用浏览器插件上传文件时,获取上传进度的回调函数一般会被频繁触发
  • 原理
    为了要解决函数在短时间内被频繁调用的问题,我们可以设置固定时间段来忽略掉一些时间请求,比如确保在500ms内只执行一次
  • 代码实现
const throttle = 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(() => { // 延迟执行
      clearTimeout(timer);
      timer = null; // 执行完后将延时器清除 并且重置timer变量
      __self.apply(__me, args)
    }, interval, 500); 
  }
}
window.onmousemove = throttle(() => {
  console.log(1)
}, 1000)
分时函数

一秒钟创建1000个节点改为每隔200毫秒创建8个节点

var timeChunk = function(ary, fn, count) {
  var t;
  var start = function() {
    for (let i = 0; i < Math.min(count || 1, ary.length); i++) {
      fn(ary.shift())
    }
  }
  return function() {
    t = setInterval(() => {
      if (ary.length === 0) { // 如果全部节点都已经被创建好
        return clearInterval(t)
      }
      start()
    }, 200) // 分批执行的时间间隔,也可以用参数的形式传入
  }
}
var ary = [];
for (let i = 0; i < 1000; i++) {
  ary.push(i)
}
var renderFriendlyList = timeChunk(ary, function(n) {
  var div = document.createElement('div');
  div.innerHTML = n;
  document.body.appendChild(div)
}, 8)
renderFriendlyList()
惰性加载函数

实现一个在各个浏览器通用的事件绑定函数addEvent

var addEvent = function(elem, type, handler) {
  // 第一次执行的时候会触发判断分支,后续直接调用重新赋值的函数
  if (window.addEventListener) {
    addEvent = function(elem, type, handler) {
      elem.addEventListener(type, handler, false)
    }
  } else if (window.attachEvent) {
    addEvent = function(elem, type, handler) {
      elem.attachEvent('on'+ type, handler)
    }
  }
  addEvent(elem, type, handler)
}