高阶函数

高阶函数

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

  • 函数可以作为参数被传递

    实际应用场景:回调函数(callback)、常见的数组方法,如sort filter map reduce

  • 函数可以作为返回值输出

    实际应用场景:闭包等各种场景

相关应用

实现AOP

AOP(面向切面编程)的主要作用是把一些跟核心业务逻辑无关的功能抽离出来,通过“动态织入”的方式掺入业务逻辑模块中。这样做的好处首先是可以保持业务逻辑模块的纯净和高内聚性(一个模块只负责一种功能),其次是可以很方便的复用代码。

代码如下:

Function.prototype.before = function (beforefn) {
  const __self = this // 保存原函数的引用
  return function () { // 返回包含了原函数和新函数的“代理”函数
    beforefn.apply(this, arguments) // 执行新函数,修正this
    return __self.apply(this, arguments) // 执行原函数
  }
}

Function.prototype.after = function (afterfn) {
  const __self = this
  return function () {
    const ret = __self.apply(this, arguments) // 执行原函数
    afterfn.apply(this, arguments)
    return ret
  }
}

let func = function () {
  console.log(2)
}

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

func() // 1 2 3

函数柯里化

柯里化又称部分求值,柯里化函数会接受一些参数,然后不会立即求值,而是继续返回一个新函数,将传入的参数通过闭包的形式保存,等到被真正求值的时候,再一次性把所有传入的参数进行求值。

代码如下:

var monthTime = 0;

function overtime(time) {
 return monthTime += time;
}

overtime(3.5);    // 第一天
overtime(4.5);    // 第二天
overtime(2.1);    // 第三天
//...

console.log(monthTime);    // 10.1


延迟执行:利用闭包的特点,缓存积累传入的参数,等到需要的时候再执行函数。举个例子:

// 柯里化
var overtime = (function() {
  var args = [];

  return function() {
    if(arguments.length === 0) { // 当不传入参数时,调用原函数
      var time = 0;
      for (var i = 0, l = args.length; i < l; i++) {
        time += args[i];
      }
      return time;
    }else {
      [].push.apply(args, arguments); // 存储每次传入的参数
    }
  }
})();

overtime(3.5);    // 第一天
overtime(4.5);    // 第二天
overtime(2.1);    // 第三天
//...

console.log( overtime() );    // 10.1

反柯里化

从字面意义上讲,反柯里化的意义和用法和柯里化正好相反,反柯里化的主要作用是使本来只有特定对象才适用的方法,扩展到更多的对象。例如我们经常使用call和apply去借用Array.prototype上的方法,使其适用的范围从数组扩大到类数组对象。

(function () {
  Array.prototype.push.call(arguments, 4) // arguments为类数组对象,借用Array.prototype.push方法
  console.log(arguments)
})(1, 2, 3)

反柯里化的函数实现:

Function.prototype.uncurrying = function () {
    var self = this; return function () {
      var obj = Array.prototype.shift.call(arguments);
      return self.apply(obj, arguments);
    };
  };
//case1
var obj1 = { name: 'sven' };
var obj2 = { getName: function () { return this.name; } };
console.log(obj2.getName.call(obj1)); // 输出:sven
//case2 类数组使用push
(function(){     
    Array.prototype.push.call( arguments, 4 ); // arguments 借用 Array.prototype.push 方法     
    console.log( arguments ); // 输出:[1, 2, 3, 4] 
  })( 1, 2, 3 );
//case3
var push = Array.prototype.push.uncurrying(); 
  (function(){     
    push( arguments, 4 );     
    console.log( arguments ); // 输出:[1, 2, 3, 4] 
  })( 1, 2, 3 ); 

节流与防抖

节流函数:让原先频繁触发的函数在间隔某一段时间内只执行一次。

常见的使用场景:

  • window.onresize 事件
  • mousemove 事件
  • 上传进度

函数实现:

const throttle = function (fn, interval) {
  const __self = fn // 保存需要被延迟执行的函数引用
  let timer, // 定时器
      firstTime = true // 是否第一次调用
  return function () {
    const args = arguments,
          __me = this
    if (firstTime) { // 如果是第一次调用,不需要延迟执行
      __self.apply(__me, args)
      return firstTime = false
    }
    if (timer) {
      return false // 如果定时器还在,说明前一次延迟执行还没有完成
    }
    timer = setTimeout(() => {
      clearTimeout(timer)
      timer = null
      __self.apply(__me, args)
    }, interval || 500)
  }
}

防抖函数:指在事件被触发的某个时间间隔后再执行相应的回调,如果再这个时间间隔内事件再次被触发,则重新计时。

常见的使用场景:

  • input 输入事件所触发的ajax请求

函数实现:

const debounce = function (fn, interval) {
  const __self = fn
  let timer
  return function () {
    const args = arguments,
          __me = this
    if(timer !== null) {  // 在间隔内又被触发,则清空定时器,重新计时
      clearTimeout(timer)
      timer = null
    }
    timer = setTimeout(() => {
      __self.apply(__me, args)
    }, interval || 500)
  }
}

分时函数

分时函数的主要作用就是避免在短时间内执行太多任务,通常用于优化大量数据渲染的场景。

函数实现:

const timeChunk = function (ary, fn, count, duration) {
  let timer
  const start = function () {
    for (let i = 0; i < Math.min(count || 1, ary.length); i ++) {
      const obj = ary.shift()
      fn(obj)
    }
  }
  return function () {
    timer = setInterval(() => {
      if (ary.length = 0) {
        return clearInterval(timer)
      }
      start()
    }, duration || 500)
  }
}

惰性加载函数

由于浏览器之间的行为差异,经常会在函数中包含大量的if语句,以检查浏览器的特性,解决不同浏览器的兼容问题。比如,最常见的为dom节点添加事件的函数:

function addEvent (elem, type, handler) {
  if (window.addEventListener) {
    elem.addEventListener(type, handler, false)
  } else if (window.attachEvent) {
    elem.attachEvent('on' + type, handler)
  } else {
    elem['on' + type] = handler
  }
}

每次调用addEvent函数的时候,它都要对浏览器所支持的能力进行检查,但其实,当页面加载完毕时,浏览器已经是一个固定的浏览器了,检查只需要做一次。我们可以使用立即执行函数来优化这个方法:

const addEvent = (function () {
  if (window.addEventListener) {
    return function (elem, type, handler) {
      elem.addEventListener(type, handler, false)
    }
  } else if (window.attachEvent) {
    return function (elem, type, handler) {
      elem.attachEvent('on' + type, handler)
    }
  } else {
    return function (elem, type, handler) {
      elem['on' + type] = handler
    }
  }
})()

以上就是惰性加载的一种方式。所谓的惰性加载,就是函数的if分支只会执行一次

不过上述函数也存在一个缺点,也许我们从头到尾都没有使用过addEvent函数,那么执行这个函数就是一个多余的操作,并且也会延长页面ready的时间。

优化:

let 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)
    }
  } else {
    addEvent = function (elem, type, handler) {
      elem['on' + type] = handler
    }
  }
}

你可能感兴趣的:(高阶函数)