Underscore.js 学习

375 阅读5分钟

常见用法

  • 类型判断typeof
  • 获取undefined原始值: void 0void(0)
  • Array(3)
  • 数据类型分类处理
    • target == null判断null/undefined
    • string number boolean Symbol
    • Object
  • call扩展使用内置api
  • js精确整数最大值: Math.pow(2,53)-1 =9007199254740991
  • 快转Number: +new Date / +"100" ...
  • Math.pow()的简写 : x**y
  • 遍历元素,分为两类处理
    • Array & Array-like很多时候统一处理
    • Object.keys() : Object properties
  • 不对外暴露的方法使用 _APIName 格式命名
  • 闭包closure
// 常用于定义不随函数的调用而被重新定义或初始化的变量,同时不污染全局作用域
function outer(){
    let _count = 0;
    return function inner(){
        let count = 0;
        console.log(++_count,++count);
    }
}
const closureFn = outer();
closureFn(); // 1 1
closureFn(); // 2 1
closureFn(); // 3 1
  • 在函数式编程中传递函数时this / arguments尤为重要

optimizeCb: 转化callback函数

function optimizeCb(func, context, argCount) {
    if (context === void 0) return func;
    switch (argCount == null ? 3 : argCount) {
      case 1: return function (value) {
        return func.call(context, value);
      };
      // 忽略用不到的两个参数的情况
      case 3: return function (value, index, collection) {
        return func.call(context, value, index, collection);
      };
      case 4: return function (accumulator, value, index, collection) {
        return func.call(context, accumulator, value, index, collection);
      };
    }
    return function () {
      return func.apply(context, arguments);
    };
  }
// 根据参数 argCount 重新定制 func 为不同参数列表形式的回调函数以适用各种情况的函数式编程
// 类似以下几种调用方式:
// 1.[].forEach(function(value){})
// 2.[].forEach(function(value,index,array){})
// 3.[].forEach(function(accumulator,value,idx,array){})

模拟ES6restArgs

// 主要思路: 切割参数列表以及利用apply的降维特性
function restArguments(func, startIndex) {
    // startIndex默认为 func 的参数个数
    startIndex = startIndex == null ? func.length - 1 : +startIndex;
    return function () {
    // 若 arguments.length < startIndex 则 rest 数组为空
      var length = Math.max(arguments.length - startIndex, 0),
        rest = Array(length),
        index = 0;
      for (; index < length; index++) {
        rest[index] = arguments[index + startIndex];
      }
      // 直接提供最为常见的三种调用方式
      switch (startIndex) {
        case 0: return func.call(this, rest);
        case 1: return func.call(this, arguments[0], rest);
        case 2: return func.call(this, arguments[0], arguments[1], rest);
      }
      // startIndex > 2 多参数的情况
      var args = Array(startIndex + 1);
      for (index = 0; index < startIndex; index++) {
        args[index] = arguments[index];
      }
      args[startIndex] = rest;
      // args 格式: [1,2,3,[4,5,6]] , 利用apply能扁平化数组一个维度的特点
      return func.apply(this, args); 
    };
  }
//usage demo
const test = restArguments(function(a,b,c){
    console.log(a,b,c); // 1,2,[3,4,5]
},2);
test(1,2,3,4,5);

shuffle

精简地限定变量的取值范围

// length最小值为0
// 传入 n < 0, n 最终取值为 0
// n >=0 && n < length , n 取值 n
// n >=0 && n > length , n 取值 length
// 即 n 的取值范围: [0~length]
n = Math.max(Math.min(n, length), 0);

// 判断一个值是否超出指定范围,超出则纠正为对应的最值
function range(min=0,max=10,target){
    if(min>max) return;
    target = Math.max(Math.min(target, max), min);
    return target;
}
function shuffle(obj) {
    // Infinity 保证在调用 sample 时其中的 n 取值为 obj 的 length
    return sample(obj, Infinity);
}
function sample(obj, n, guard) {
    if (n == null || guard) {
      if (!isArrayLike(obj)) obj = values(obj);
      return obj[random(obj.length - 1)];
    }
    var sample = isArrayLike(obj) ? clone(obj) : values(obj);
    var length = getLength(sample);
    n = Math.max(Math.min(n, length), 0);
    var last = length - 1;
    for (var index = 0; index < n; index++) {
      var rand = random(index, last); // random a index of collection
      var temp = sample[index];
      sample[index] = sample[rand];
      sample[rand] = temp;
    }
    return sample.slice(0, n);
}

throttle

何为节流 ?

  • 短时间内连续多次调用同个函数,如:用户连续点击按钮触发事件处理函数.
  • 为避免不必要的频繁操作,在调用时将目标函数在特定延迟时间段的前或后执行且该时间段内其它调用是无效的.
  • 特点 : 延迟时间段内多余的点击视为无效,类似按钮禁用后的点击效果
function throttle(func, wait, options) {
    var timeout, context, args, result;
    var previous = 0;
    if (!options) options = {};
    // wait 后执行目标函数 func 并重置定时器id,context以及args
    var later = function () {
      previous = options.leading === false ? 0 : now();
      timeout = null;
      result = func.apply(context, args);
      if (!timeout) context = args = null;
    };
    
    var throttled = function () {
      var _now = now();
      if (!previous && options.leading === false) previous = _now;
      // 首次触发throttled时 remaining 等于 wait
      // 二次触发 remaining 值为距离执行目标函数的余下时间
      var remaining = wait - (_now - previous);
      // 保存 throttled 被调用时接收的参数以及上下文this,后面需要原样传入func 
      context = this;
      args = arguments;
      /*
        假设: wait = 10s
        1.options.leading 为 false
        每次调用throttled都会延迟10s后再执行目标函数,
        当下一次调用的时间间隔 >= 10s,进入下一轮执行,若 < 10s则为无效
            
        2.options.leading 为 true (默认值)
        每次调用throttle就直接执行目标函数,
        二次调用,时间间隔上一次调用 >= 10s 则进入下一轮,反之无效.
        
        即 options.leading 用于定义目标函数是在延迟时间段之前还是之后执行
      */
      if (remaining <= 0 || remaining > wait) {
        if (timeout) {
          clearTimeout(timeout);
          timeout = null;
        }
        previous = _now;
        result = func.apply(context, args);
        if (!timeout) context = args = null;
      } else if (!timeout && options.trailing !== false) {
        // 首次触发,开启定时器并记录定时器id
        timeout = setTimeout(later, remaining);
      }
      return result;
    };
    return throttled;
  }

debounce

何为防抖?

  • 同样是为了避免用户短时间内频繁操作.
  • 让目标函数在特定延迟时间段的前或后执行,若该时间段内再次触发则重置延迟时间
  • immedidate: 定义目标函数是在延迟时间段之前还是之后执行,同throttleoptions.leading
  • 特点 : 每次点击都会重新计算延迟时间
function debounce(func, wait, immediate) {
    var timeout, result;
    var later = function (context, args) {
      timeout = null;
      if (args) result = func.apply(context, args);
    };
    var debounced = restArguments(function (args) {
      if (timeout) clearTimeout(timeout);
      if (immediate) { 
        var callNow = !timeout;
        timeout = setTimeout(later, wait);
        if (callNow) result = func.apply(this, args);
      } else {
        timeout = delay(later, wait, this, args);
      }
      return result;
    });
    return debounced;
 }

once

// 简单实现 _.once
function once(targetFn) {
    let once;
    return function (...args) {
        let result = !once ? targetFn(...args) : "not once!";
        if (once === void 0) once = !once;
        return result;
    }
}

const hello = once(function (...args) {
    console.log("run innner Fn", args);
});

hello(1, 2, 3, 'ohuo'); // "run innner Fn" [1,2,3,'ohuo']
hello("now"); // not once