js基础知识-高阶函数

高阶函数

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

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

应用:

  1. Array.sort
  2. 解决var i的作用域问题
  3. 单例模式
  4. 各种钩子函数,修饰器。
  5. 节流,拉缩窗口大小,加购商品。页面滚动事件。
  6. 分时加载列表数据。
函数可以作为参数被传递
  1. 回调函数
  2. 生成节点,样式设置
  3. 数组排序Array.prototype.sort
let arr = [1, 4, 3]
arr.sort(function(a, b) { // 从小到大
    return a - b
})
arr.sort(function(a, b) { // 从大到小
    return b -a
})

函数可以作为返回值输出
  1. 判断数据的类型
var isString = function( obj ){
 return Object.prototype.toString.call( obj ) === '[object String]';
};
var isArray = function( obj ){
 return Object.prototype.toString.call( obj ) === '[object Array]';
};
var isNumber = function( obj ){
 return Object.prototype.toString.call( obj ) === '[object Number]';
}; 
var isType = function( type ){
 return function( obj ){
 	return Object.prototype.toString.call( obj ) === '[object '+ type +']';
 }
}; 
var isString = isType( 'String' );
var isArray = isType( 'Array' );
var isNumber = isType( 'Number' );
console.log( isArray( [ 1, 2, 3 ] ) ); // 输出:true

// 提取公共部分,使用高阶函数
var Type = {};
for ( var i = 0, type; type = [ 'String', 'Array', 'Number' ][ i++ ]; ){
 (function( type ){
 Type[ 'is' + type ] = function( obj ){
 	return Object.prototype.toString.call( obj ) === '[object '+ type +']';
 }
 })( type )
};
Type.isArray( [] ); // 输出:true
Type.isString( "str" ); // 输出:true
  1. 单例模式
var getSingle = function ( fn ) {
 var ret;
 return function () {
 	return ret || ( ret = fn.apply( this, arguments ) );
 };
};
var getScript = getSingle(function(){
 return document.createElement( 'script' );
});
var script1 = getScript();
var script2 = getScript();
alert ( script1 === script2 ); // 输出:true 

高阶函数实现AOP

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

实现技术有很多,这里我们通过扩展 Function.prototype 来做到这一点。

设计模式-装饰者模式

	// 动态织入
    Function.prototype.before = function(beforeFn) {
      let _self = this // 这里的this指向调用before的函数,此处是func
      return function() {
        beforeFn.apply(this, arguments) // this指向调用闭包的函数,这里的this指window,2.执行before
        return _self.apply(this, arguments) // 3. 执行func函数
      }
    }
    Function.prototype.after = function(afterFn) {
      let _self = this // this指向before
      return function() {
        let ret = _self.apply(this.arguments) // 1.指向before函数。
        afterFn.apply(this, arguments)// 4,执行after函数
        return ret
      }
    }

    let func = function(){
      console.log(2)
    }
    // 链式调用,先调用after,然后_self指向before,这时候调用before函数,里面则先执行before,然后func,
    let func2 = func.before(function(){
      console.log(1)
    }).after(function(){
      console.log(3)
    })
    func2()
应用
  1. 柯里化: 通过闭包来存储临时数据,最后一次解决问题
  2. 非柯里化:通过call和apply改变this指向,泛化this
  3. 节流:通过定时器,解决频繁调用
  4. 分时函数:解决大数据
  5. 惰性加载函数:通过重定义函数,解决条件分支
柯里化

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

说白了就是暂存计算的值,直到需要的时候,扣动扳机,直接计算结果。

一般都是设计成两个部分,第一部分是传参存入数组,第二部分是不传参,通过参数个数判断,来执行数组循环输出,执行对应的数据操作.

// 计算每个月花销
  // let monthlyCost = 0
  // let cost = function(money) {
  //   monthlyCost += money
  // }
  // cost(100)
  // cost(200)
  // cost(300)
  // console.log(monthlyCost)
  // 每天记录并结算花了多少钱,而我们不关心每天花了多少钱,而是关心这个月共花了多少钱。
  // 优化,使用闭包,如果传参则存入数组,没有参数则计算总和。
  // let cost = (function() {
  //   let args = []
  //   return function() {
  //     if(arguments.length === 0) {
  //       let money = 0
  //       // 一次调用计算整个月花销
  //       for(let i = 0, l = args.length; i < l; i++) {
  //         money += args[i]
  //       }
  //       return money
  //     } else {
  //       [].push.apply(args, arguments) // 存入数组中
  //     }
  //   }
  // })()
  // cost(100)
  // cost(200)
  // cost(300)
  // console.log(cost())
  // 柯里化
  let currying = function(fn) {
    let args = []
    return function() { // 返回的闭包1
      if(arguments.length === 0) {
        console.log(args)
        return fn.apply(this, args) // 回调cost函数,返回闭包2
      } else {
        [].push.apply(args, arguments) // 存入数组
      }
    }
  }
  let cost = (function(){
    let money = 0
    return function() { // 返回的闭包2
      for(let i = 0, l = arguments.length; i < l; i++){
        money += arguments[i]
      }
      return money
    }
  })()
  let costFn = currying(cost) // cost传入后暂时不调用,而是存了起来,返回了闭包1给costFn
  costFn(100)
  costFn(200)
  costFn(300)
  console.log(costFn()) // 执行cost函数返回的闭包2
非柯里化

非柯里化要解决的问题就是把泛化 this 的过程提取出来,让调用更加简洁和意图明了

  • 一个对象怎么调用另一个对象的方法

call和apply都可以实现

var obj1 = {
 name: 'sven'
};
var obj2 = {
 getName: function(){
 return this.name;
 }
};
console.log( obj2.getName.call( obj1 ) ); // 输出:sven

我们常常让类数组对象去借用 Array.prototype 的方法,这是 call 和 apply 最常见的应用场
景之一:

 Array.prototype.push.call( arguments, 4 ); // arguments 借用 Array.prototype.push 方法
 console.log( arguments ); // 输出:[1, 2, 3, 4]
})( 1, 2, 3 );

在我们的预期中,Array.prototype 上的方法原本只能用来操作 array 对象。但用 call 和 apply
可以把任意对象当作 this 传入某个方法,这样一来,方法中用到 this 的地方就不再局限于原来
规定的对象,而是加以泛化并得到更广的适用性。

  • 怎么把泛化 this 的过程提取出来
	Function.prototype.uncurrying = function() { 
      let self = this // this =>Array.prototype.push 指向调用者
      return function(){
        let obj = Array.prototype.shift.call(arguments) // 截取push函数的第一个参数,obj 下面闭包的arguments [1 ,2, 3]
        // console.log(obj) // 截取后的剩下 [4]
        return self.apply(obj, arguments)
        // => Array.prototype.push.apply(obj, arguments) 
        // 代入 Array.prototype.push.apply([1, 2, 3], 4) 输出[1, 2, 3, 4]
      }
    }
    // push
    let push = Array.prototype.push.uncurrying();
    (function(){
      push(arguments, 4)
      console.log(arguments) // [1,2,3,4]
    })(1, 2, 3);

另一种实现

    // 另一种实现
    Function.prototype.uncurrying = function () {
      let self = this;
      console.log(self)
      return function () {
        // 解绑定包装器
        // Function.prototype.call.apply(self, arguments) => 
        // 先执行apply, 那么call的this指向self, arguments作为参数传入
        // 转换后得:self.call(arguments)
        // 代入则是self.call(obj, 4), 
        // 相当于let obj = Array.prototype.shift.call(arguments) 然后 self.apply(obj, arguments)
        return Function.prototype.call.apply(self, arguments);
      };
    };
    for (let i = 0, fn, ary = ["push", "shift", "forEach"]; (fn = ary[i++]); ) {
      Array[fn] = Array.prototype[fn].uncurrying();
    }
    let obj = {
      0: 1,
      1: 2,
      length: 2,
    };
    Array.push(obj, 4);
    console.log(obj); // {0: 1, 1: 2, 2: 4, length: 3}
    let first = Array.shift(obj, 4);
    console.log(first); // 1
    console.log(obj); // {0: 2, 1: 4, length: 2}
    Array.forEach(obj, function (v, k) {
      console.log(v); // 2, 4
    });

节流

函数有可能被非常频繁地调用,而造成大的性能问题.

作用:限制函数被频繁调用

  • 场景:
  1. window.onresize
  2. mousemove
  3. 上传进度
  • 原理:setTimeout按时间段来忽略一些事件请求。
  • 实现
let throttle = function(fn, interval) {
    let _self = fn // 存储回调
    let timer = null
    let firstTime = true
    return function() {
        let args = arguments
        let _me = this // 这里是window
        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 = throttle(function(){
    console.log(1)
}, 500)

分时函数

解决一次加载大数据问题,使用setInterval分时执行函数,直到清空数据.

 // console.time('1')
  // // qq列表加载
  // let arr = []
  // for(let i = 0; i <= 1000; i++) {
  //   arr.push(i)
  // }
  // let renderFriendList = function(data) {
  //   for(let i = 0; i <= data.length; i++) {
  //     let span = document.createElement('span')
  //     span.innerHTML = data[i]
  //     document.body.appendChild(span)
  //   }
  // }
  // renderFriendList(arr)
  // console.timeEnd('1') // 1: 3.242919921875 ms

  // 分批进行
  console.time('2')
  let timeChunk = function(ary, fn, count){
    let obj
    let t;
    let len = ary.length
    let start = function(){
      for(let i = 0; i < Math.min( count | 1, ary.length); i++) {
        var obj = ary.shift()
        fn(obj)
      }
    }
    return function(){
      t = setInterval(() => {
        if(ary.length == 0) {
          // console.timeEnd('2') // 2: 23001.0849609375 ms
          return clearTimeout(t)
        }
        start()
      }, 200);
    }
  }
  let arr = []
  for(let i = 0; i <= 1000; i++) {
    arr.push(i)
  }
  let renderFriendList = timeChunk(arr, function(data) {
      let span = document.createElement('span')
      span.innerHTML = data
      document.body.appendChild(span)
  }, 8)
  
  renderFriendList()

惰性加载函数

嗅探浏览器之间的实现差异的工作

第一种,分支判断,每次都需要判断一下if

var addEvent = function( elem, type, handler ){
 if ( window.addEventListener ){
	 return elem.addEventListener( type, handler, false );
     }
 if ( window.attachEvent ){
	 return elem.attachEvent( 'on' + type, handler );
 }
}; 

第二种,闭包回调,这里肯定会执行一次嗅探,即会走一次判断,并返回addEvent函数,但后面我们可能不会调用,而且还会延迟网页加载.

var addEvent = (function(){
 if ( window.addEventListener ){
 	return function( elem, type, handler ){
		elem.addEventListener( type, handler, false );
 	}
 }
 if ( window.attachEvent ){
 	return function( elem, type, handler ){
 		elem.attachEvent( 'on' + type, handler );
 	}
 }
})(); 

第三种,惰性载入
此时 addEvent 依然被声明为一个普通函
数,在函数里依然有一些分支判断。但是在第一次进入条件分支之后,在函数内部会重写这个函
数,重写之后的函数就是我们期望的 addEvent 函数,在下一次进入 addEvent 函数的时候,addEvent
函数里不再存在条件分支语句

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <div id="div1">绑定事件</div>
</body>
<script>
  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)
      }
    }
    addEvent(elem, type, handler)
  }
  let div = document.getElementById('div1')
  addEvent(div, 'click', function(){
    console.log(1)
  })
  
  addEvent(div, 'click', function(){
    console.log(2)
  })

</script>
</html>
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值