最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • JavaScript高阶函数 | 小册免费学

    正文概述 掘金(奶茶不够甜)   2021-04-06   462

    前言

    在JavaScript中,函数不仅可以被调用,还可以像普通变量一样被赋值、传参、返回,所以我们说JavaScript函数是JavaScript语言中的一等公民。如果一个函数可以作为另一个函数的参数传入,或者该函数反回一个函数,那么这个函数就被称为高阶函数(High Order Function)。

    JavaScript中的高阶函数

    其实JavaScript就定义了很多的高阶函数让我们,比如:

    • Array.prototype.map()
    • Array.prototype.reduce()
    • Array.prototype.every()
    • Array.prototype.some()
    • Array.prototype.filter()
    • ...

    这些函数都可以接收一个函数作为参数

    //计算数组和
    function countSum(a, b) {
        return a + b;
    }
    const arr = [1, 2, 3, 4, 5];
    const sum = arr.reduce(countSum);
    console.log(sum);  //15
    

    这些高阶函数的使用方法可以自行百度/谷歌,在这里我还想介绍一个常用的JavaScript函数Array.prototype.sort(),它是一个对数组进行原地排序的函数,我们试着将一个Number类型的数组进行排序:

    const arr = [5, -1, 3, -6, 7, -1];
    arr.sort(); 
    console.log(arr);  //[-1, -1, -6, 3, 5, 7]
    

    咦,这个排序结果既不是升序也不是降序,究竟是怎么回事呢? 这是因为使用sort()排序时,默认排序顺序是在将元素转换为字符串后,再比较它们的UTF-16代码单元值序列。所以我们比较的其实是Number类型转换为字符串后的UTF-16代码单元值序列呐。

    其实sort()还支持传入一个函数作为参数,让它支持特定类型的排序,这个传入函数的返回值和JAVA的java.lang.Comparable接口类似:

    //升序排序
    const arr = [5, -1, 3, -6, 7, -1];
    arr.sort((a, b) => a - b);
    console.log(arr);  //[-6, -1, -1, 3, 5, 7]
    
    //降序排序
    const arr = [5, -1, 3, -6, 7, -1];
    arr.sort((a, b) => b - a);
    console.log(arr);  //[7, 5, 3, -1, -1, -6]
    
    //多维数组排序
    const arr = [[1,2], [-1, 1], [1, 0], [-1, 0]];
    arr.sort((a, b) => a[0] === b[0] ? a[1] - b[1] : a[0] - b[0]);
    console.log(arr);  //[[-1, 0], [-1, 1], [1, 0], [1, 2]]
    

    高阶函数基础

    说了这么多,我们该怎么定义一个高阶函数呢?再次回顾一下高阶函数的概念:如果一个函数可以作为另一个函数的参数传入,或者该函数反回一个函数,那么这个函数就被称为高阶函数。

    一个简单的forEach函数

    我们假设一个情景,一款浏览器中对Array.prototype.forEach()方法不支持...

    如果要考虑兼容这个浏览器,我们可以试着实现这个函数的polyfill。一个常见的polyfill模板如下:

    (function () {
    if (!Array.prototype.forEach) {
      Array.prototype.forEach = function (func) {
        //在这里实现
      }
    }
    })()
    

    forEach函数接收一个函数作为参数,这个函数的形参分别为当前遍历的值、当前遍历的数组下标以及当前的数组。当然forEach还支持传入第二个参数作为传入函数的一个上下文,因为我们实现的是一个简单的forEach,所以我们忽略第二个参数。比较完整的forEach的polyfil实现在文后。

    对上面的细节进行补充,首先形参func不能为空且它是一个函数:

    if(typeof func !== 'function') {throw new TypeError(func.toString() + `is not a function`)}
    

    对数组进行遍历,并将相应的参数传入func中,注意forEach函数的没有返回值。

    const arr = this;
    const length = arr.length >>> 0;   //无符号右移0位,保证length为正整数
    let k = 0;
    while(k < length) {
      if(k in arr) {
        func(arr[k], k, arr);
      }
      k++;
    }
    

    于是,一个简单的forEach函数就完成啦。

    (function () {
    if (!Array.prototype.forEach) {
      Array.prototype.forEach = function (func) {
        if (typeof func !== 'function') {
          throw new TypeError(func.toString() + `is not a function`)
        }
        const arr = this;
        const length = arr.length >>> 0;   //无符号右移0位,保证length为正整数
        let k = 0;
        while (k < length) {
          if (k in arr) {
            func(arr[k], k, arr);
          }
          k++;
        }
      }
    }
    })()
    

    总结一下,我们实现的这个函数有一个函数作为形参,我们并不关心这个形参的具体内容、具体实现,我们只是在forEach函数中直接调用了这个函数,也就是让这个函数“生效”。

    HOF0

    再来看一个例子:

    function HOF0(func) {
      const resFunc =  function (...args) {
        return func.apply(null, args);
      }
      return resFunc;
    }
    

    这个可以称为高阶函数的一个范式,首先HOF0函数传入一个函数func,然后HOF0返回一个匿名函数,匿名函数又返回func函数的调用。在忽略上下文的情况下,下面两者的调用是一致的:

    function sum(a, b) {
      return a + b;
    }
    let countSum = HOF0(sum);   //返回的是一个函数
    countSum(1, 2) === sum(1, 2);
    

    那为什么要将它复杂化呢?别急,我们很快就要基于这个HOF0函数来进行应用了。

    高阶函数进阶

    在面试过程中,面试官经常会让我们手写节流、防抖这些函数,这些函数都是高阶函数。如果你掌握了上面的知识,那么手写这些函数简直是轻轻松松!

    手写函数节流

    首先要保证一段时间内只执行一次,所以可以使用一个定时器来控制“次数”,其次在这段时间内还要触发一次事件处理函数,这个不就是要在函数内调用一次事件处理函数嘛!所以我们将上面的HOF0函数拿过来修改一下:

    //delay为需要延迟的时间
    function throttle(func, delay) {
      const resFunc =  function (...args) {
        func.apply(null, args);
      }
      return resFunc;
    }
    

    事件调用的功能已经完成,接下来就是控制“间隔一段时间”再执行事件处理函数: 把func.apply(null, args)放到setTimeout中,delay作为延迟的时间

    //delay为需要延迟的时间
    function throttle(func, delay) {
      const resFunc =  function (...args) {
        setTimeout(() => {
          func.apply(null, args);
        }, delay)
      }
      return resFunc;
    }
    

    我们试着执行一下这个函数,发现它每一次调用都会执行,没有实现“间隔”。这是因为调用resFunc函数时每一次都会生成一个新的定时器,所以还需要阻止该函数在delay的时间内不再生成新的定时器,这里我们使用到了闭包。

    //delay为需要延迟的时间
    function throttle(func, delay) {
      let timer = null;
      const resFunc =  function (...args) {
        if(timer == null) {
          timer = setTimeout(() => {
            func.apply(null, args);
            timer = null;
          }, delay)
        }
      }
      return resFunc;
    }
    

    这样子我们就实现一个高阶函数啦! 示例代码:codepen.io/hengistchan…

    如果你对为什么返回的函数还能引用throttle函数的timer变量有疑问的话,可以去看一下关于闭包的知识。

    手写函数防抖

    按照上面的分析,只需要改一下节流的代码即可。如果触发了事件,就把定时器清空,再创建一个新的定时器。

    //delay为需要延迟的时间
    function debounce(func, delay) {
      let timer = null;
      const resFunc =  function (...args) {
        if(timer != null) clearTimeout(timer);
        timer = setTimeout(() => {
          func.apply(null, args);
        }, delay)
      }
      return resFunc;
    }
    

    示例代码:codepen.io/hengistchan…

    总结

    如果一个函数可以作为另一个函数的参数传入,或者该函数反回一个函数,那么这个函数就被称为高阶函数(High Order Function)。不只是函数防抖和函数节流,高阶函数在很多方面都有应用,比如说限制函数的执行次数、函数柯里化等。待我找到实习后再慢慢补上?。

    附:一个较为完备的forEach的polyfill实现,在此基础上,也可以通过添加或删除一些代码实现更多的polyfill,比如map、reduce、some等。

    Array.prototype.forEach = function (func, thisArg = window) {
      if (this == null) throw new Error("");
    
      if (typeof func !== 'function') throw new Error("is not a function");
    
      //这里为什么要使用Object(this)呢?可以参考:
      //https://stackoverflow.com/questions/66941001/in-the-array-prototype-finds-implementation-why-do-we-need-to-use-objetctthis
      const O = Object(this);
      const length = O.length >>> 0;
      let k = 0;
      while(k < length) {
        if (k in O) {
          func.call(thisArg, O[k], k, O);
        }
        k++;
      }
    };
    

    本文正在参与「掘金小册免费学啦!」活动, 点击查看活动详情


    起源地下载网 » JavaScript高阶函数 | 小册免费学

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元