util.js

/**
 * 提供常用辅助函数
 * @module $ui/utils/util
 */


const _isEqual = require('lodash/isEqual')
const _get = require('lodash/get')
const _set = require('lodash/set')
const _cloneDeep = require('lodash/cloneDeep')

// requestAnimationFrame 适配函数
const raFrame = window.requestAnimationFrame || window.webkitRequestAnimationFrame || function (callback) {
  return window.setTimeout(callback, 1000 / 60);
};

/**
 * 动画延时函数
 * @function
 * @param {function} callback 动画回调函数
 * @return {number} id 标识
 *
 * @example
 *
 * import {requestAnimationFrame} from '$ui/utils/util'
 * requestAnimationFrame(() => {
 *   // do sth ....
 *
 * })
 */
export const requestAnimationFrame = raFrame

/**
 * 清除动画延时
 * @function
 * @param {number} id 标识
 *
 * @example
 *
 * import {requestAnimationFrame, cancelAnimationFrame} from '$ui/utils/util'
 * const id = requestAnimationFrame(()= > {
 *   // do sth
 * })
 * cancelAnimationFrame(id)
 */
export const cancelAnimationFrame = window.cancelAnimationFrame || window.webkitRequestAnimationFrame || function (id) {
  window.clearTimeout(id);
};


/**
 * 判断两个对象是否相等
 * @param {*} object 对象1
 * @param {*} other 对象2
 * @return {boolean}
 */
export function isEqual(object, other) {
  return _isEqual(object, other)
}

/**
 * 防抖函数
 * @param {function} fn 事件处理函数
 * @param {number} [delay=20] 延迟时间
 * @param {boolean} [isImmediate=false] 是否立刻执行
 * @param {object} [context=this] 上下文对象
 * @returns {Function} 事件处理函数
 */
export function debounce(fn, delay = 20, isImmediate = false, context = this) {
  // 使用闭包,保存执行状态,控制函数调用顺序
  let timer;
  
  return function () {
    const _args = [].slice.call(arguments)
    
    clearTimeout(timer);
    
    const _fn = function () {
      timer = null;
      if (!isImmediate) fn.apply(context, _args);
    };
    
    // 是否滚动时立刻执行
    const callNow = !timer && isImmediate;
    
    timer = setTimeout(_fn, delay);
    
    if (callNow) fn.apply(context, _args);
  }
}

/**
 * 节流函数
 * @param {function} fn 事件处理函数
 * @param {object} [context=this] 上下文对象
 * @param {boolean} [isImmediate=false] 是否立刻执行
 * @returns {Function} 事件处理函数
 */
export function throttle(fn, context = this, isImmediate = false) {
  let isLocked;
  return function () {
    const _args = arguments
    
    if (isLocked) return
    
    isLocked = true
    raFrame(function () {
      isLocked = false;
      fn.apply(context, _args)
    })
    
    isImmediate && fn.apply(context, _args)
  }
}


/**
 * 生成随机GUID
 * @return {string}
 */
export function guid() {
  return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
    const r = Math.random() * 16 | 0, v = c === 'x' ? r : (r & 0x3 | 0x8);
    return v.toString(16);
  }).toUpperCase();
}

/**
 * 生成唯一id
 * @return {string}
 */
export function uid() {
  const rnd = Math.floor(Math.random() * 1000)
  const timestamp = new Date().getTime()
  return [timestamp, rnd].join('')
}

/**
 * 根据path查找对象或数组中的某个属性
 * @param {object|array} object 要检索的对象
 * @param {string|array} path 要获取属性的路径
 * @param {string} [defaultValue] 如果解析值是 undefined ,这值会被返回。
 * @return {undefined|*}
 *
 * @example
 *
 *  import {get} from '$ui/utils/util'
 *  const object = { 'a': [{ 'b': { 'c': 3 } }] };
 *  get(object, 'a[0].b.c') // -> 3
 */
export function get(object, path, defaultValue) {
  return _get(object, path, defaultValue)
}

/**
 * 设置 object对象中对应 path 属性路径上的值,如果path不存在,则创建。 缺少的索引属性会创建为数组,而缺少的属性会创建为对象
 * @param {object} object 要修改的对象
 * @param {string|array} path 要设置的对象路径
 * @param {*} value 要设置的值
 */
export function set(object, path, value) {
  return _set(object, path, value)
}

/**
 * 深拷贝
 * @param {*} value  要深拷贝的值
 * @return {*} 返回拷贝后的值
 */
export function cloneDeep(value) {
  return _cloneDeep(value)
}

/**
 * 对数组按制定字段名称进行分组
 * @param {Array} data 数组数组
 * @param {string} [field=group] 分组字段名称
 * @returns {object} 结果
 *
 * @example
 * [{name:1, group:'a'},{name:2, group:'a'}, {name:3, group:'b'}]  ->
 * {
 *  'a':[{name:1, group:'a'}, {name:2, group:'a'}]
 *  'b': [{name:3, group:'b'}]
 * }
 */
export function grouping(data = [], field = 'group') {
  const result = {
    default: []
  }
  data.forEach(item => {
    const group = item[field]
    if (group) {
      if (!result[group]) {
        result[group] = []
      }
      result[group].push(item)
    } else {
      result.default.push(item)
    }
  })
  return result
}



/**
 * 分页获取数据
 * @param {Array} data 源数据
 * @param {number} [page=1] 当前页面,1开始
 * @param {number} [size=10] 页大小,默认10
 * @return {Array}
 */
export function pager(data, page = 1, size = 10) {
  if (!data) return []
  const start = (page - 1) * size
  const end = start + size
  return data.length > start ? data.slice(start, end) : []
}