五、js手写函数第五批


21、防抖高级

<body>
  <div id="ni" class="hao"></div>
  <script>
    // 首先什么是防抖:先解释下什么是防抖,防抖就是当触发一个事件不会立即执行,会等待 n 秒后
    // 再执行该事件,如果在等待 n 秒期间你再次出发,则会重新计时,也就是说防抖不管你触发多少次这
    // 个事件,永远只有一次在执行,并且执行的是最后一次
    // 简易版防抖
    function debounce1(func, wait) {
      let timeout;// 这里还涉及到块级作用域
      debugger
      return function () {
        // 这里的this就是myDiv,args就是点击事件
        let context = this;
        //注意这里返回的函数没有形参,但是实际过程中,div的点击事件会自动给回调函数带入一个指针事件(指针事件的类型是click)
        let args = arguments;
        clearTimeout(timeout)
        timeout = setTimeout(function () {
          func.apply(context, args)
        }, wait);
      }
    }

    // 复杂版防抖
    // 支持立即执行;
    // 函数可能有返回值;
    // 支持取消功能;
    function debounce2(func, wait, immediate) {
      let timeout, result;

      let debounced = function () {
        var context = this;
        var args = arguments;
        clearTimeout(timeout);
        if (immediate) {
          // 立即执行完之后你再点是不会执行的,除非等过了wait时间,timeout置为null后才会再执行
          let callNow = !timeout;
          // 在设置的wait事件内,怎么点都不会执行,因为callnow为false
          timeout = setTimeout(function () {
            timeout = null;
          }, wait)
          if (callNow) result = func.apply(context, args)
        } else {
          timeout = setTimeout(function () {
            result = func.apply(context, args)
          }, wait);
        }
        return result;
      };

      debounced.cancel = function () {
        clearTimeout(timeout);
        timeout = null;
      };

      return debounced;
    }
    let count = 1;
    let myDiv = document.getElementById('ni')
    function getUserAction(e) {
      // 这里的e就是传进来的args
      console.log(this, e)
      myDiv.innerHTML = count++;
    };
    // 如何给一个DOM时间添加防抖操作
    myDiv.onclick = debounce1(getUserAction, 1000)//注意这里的等于应该是订阅
  </script>
</body>

22、节流高级

<body>
  <div id="ni" class="hao"></div>
  <script>
    // 节流:在触发任务的第一时间执行任务,并且设定定时器,如果在该定时器还未结束的时候还有触
    // 发任务的,也不执行,实现节流的核心是时间间隔,在设定的时间间隔内如果还有同样的任务进来,
    // 则不执行。

    // 简易版节流
    function throttle1(func, wait) {
      let context, args;
      let previous = 0;

      return function () {
        let now = new Date();
        context = this;
        args = arguments;
        debugger
        // 第一次点击会直接执行,后来N秒执行
        // 这里可以直接减是因为时间是秒级的,不是毫秒级的
        if (now - previous > wait) {
          func.apply(context, args);
          previous = now;
        }
      }
    }

    // 复杂版
    // 支持取消节流;另外通过传入第三个参数,options.leading 来表示是否可以立即执行一次,opitons.trailing 表示结束调用的时候是否还要执行一次,默认都是 true。
    //注意设置的时候不能同时将 leading 或 trailing 设置为 false。

    function throttle(func, wait, options) {
      let timeout, context, args, result;
      let previous = 0;
      if (!options) options = {};

      let later = function () {
        previous = options.leading === false ? 0 : new Date().getTime();
        timeout = null;
        func.apply(context, args);
        if (!timeout) context = args = null;
      };

      var throttled = function () {
        var now = new Date().getTime();
        if (!previous && options.leading === false) previous = now;
        var remaining = wait - (now - previous);
        context = this;
        args = arguments;
        if (remaining <= 0 || remaining > wait) {
          if (timeout) {
            clearTimeout(timeout);
            timeout = null;
          }
          previous = now;
          func.apply(context, args);
          if (!timeout) context = args = null;
        } else if (!timeout && options.trailing !== false) {
          timeout = setTimeout(later, remaining);
        }
      };

      throttled.cancel = function () {
        clearTimeout(timeout);
        previous = 0;
        timeout = null;
      }
      return throttled;
    }

    let count = 1;
    let myDiv = document.getElementById('ni')
    debugger
    function getUserAction(e) {
      // 这里的e就是传进来的args
      console.log(this, e)
      myDiv.innerHTML = count++;
    };
    myDiv.onclick = throttle1(getUserAction, 1000)//注意这里的等于应该是订阅
  </script>
</body>

23、函数柯里化

<body>
  <script>
    // 1、那么首先什么时函数柯里化呢?
    // 将一个有多个参数的函数转化为一连串的函数,它返回一个新的函数,等待下一个参数的内联。换句话说,
    // 一个函数不是一次接受所有的参数,而是接受第一个参数并返回一个新的函数,该函数接受第二个参数并返
    // 回一个新的函数,该函数接受第三个参数,以此类推,直到所有参数都得到满足。

    // 2、柯里化的特点?
    // 柯里化是一种函数的转换,它是指将一个函数从可调用的 f(a, b, c) 转换为可调用的 f(a)(b)(c);
    // 柯里化不会调用函数。它只是对函数进行转换。

    // 3、如何实现柯里化呢?
    function curry(fn) {
      // 注意这里的args不是形参,而是你调用时候传入的参数
      // 下面的例子解释了,箭头函数这样写是,args会自动是数组,数组里装着所有携带的参数
      let judge = (...args) => {
        // 有没有觉着很奇怪!!fn.length是什么鬼!!哦~看了才知道原来fn.length是指函数的形参个数!!!
        if (args.length == fn.length) {
          return fn(...args)
        } else {
          return (...arg) => judge(...args, ...arg)
        }
      }
      return judge;
    }

    function add(a, b, c) {
      return a + b + c
    }
    //add(1, 2, 3)

    let addCurry = curry(add)
    debugger
    addCurry(1)(2)(3)

    // 4、柯里化的应用?
    // 我们有一个用于格式化和输出信息的日志函数 log(date, importance, message),
    // 在实际项目中,此类函数具有很多有用的功能,例如通过网络发送日志,在这儿我们使用alert模拟一下;
    function log(date, importance, message) {
      alert(`[${date.getHours()}:${date.getMinutes()}] [${importance}] ${message}`);
    }
    // 让我们将它柯里化!
    let logCurry = curry(log);
    logCurry(new Date())('DEBUG')('There is some bugs!');
    // 现在,我们可以轻松地为当前日志创建便捷函数
    // 这样logNow就可以固定第一个参数了
    let logNow = logCurry(new Date());
    // 所以我们在使用他的时候如下,第一个参数就不需要动了
    logNow('INFO','Here is bug!');
    // 现在,logNow 是具有固定第一个参数的 log,换句话说,就是更简短的“部分应用函数”。
    // 那么接下来让我们继续更进一步,为当前的调试日志提供更便捷函数
    let debugNow = logNow('DEBUG');
    debugNow('There is some bugs!!!');

    //5、所以总结
    // 柯里化之后,我们没有丢失任何东西;log依然可以被正常调用。我们可以轻松地生成部分应用函数,例如用于生成今天的日志的部分应用函数。
  </script>
</body>

24、偏函数

<script>
  // 什么是偏函数呢,说白了就是柯里化的应用
  // 其实偏函数相当于提前设定好了某个函数需要的部分参数,并返回一个新的函数来等待接下来的参数传入
  function partial(fn, ...args) {
    // 这里的...arg是值返回的函数再次被调用时传入的参数
    return (...arg) => {
      return fn(...args, ...arg)
    }
  }

function add(a, b, c) {
  return a + b + c
}
debugger
let partialAdd = partial(add, 1)
partialAdd(2, 3)
  </script>

25、JSONP

<body>
  <script>
    // JSONP 核心原理:script 标签不受同源策略约束,所以可以用来进行跨域请求,优点是兼容性好,但是只能用于 GET 请求;
    const jsonp = ({ url, params, callbackName }) => {
      const generateUrl = () => {
        let dataSrc = ''
        for (let key in params) {
          // for in会遍历原型链上的属性,所以必须要用hasOwnProperty过滤一下
          if (params.hasOwnProperty(key)) {
            dataSrc += `${key}=${params[key]}&`
          }
        }
        dataSrc += `callback=${callbackName}`
        return `${url}?${dataSrc}`
      }
      return new Promise((resolve, reject) => {
        const scriptEle = document.createElement('script')
        scriptEle.src = generateUrl()
        document.body.appendChild(scriptEle)
        window[callbackName] = data => {
          resolve(data)
          document.removeChild(scriptEle)
        }
      })
    }
    // 如果没看懂的话去看看自己的博客,里面有对JsonP做详细解释的
  </script>
</body>

文章作者: 吴俊杰
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 吴俊杰 !
  目录