UNPKG

chimee-helper-utils

Version:
367 lines (346 loc) 11.8 kB
// @flow import { isArray, isFunction } from 'toxic-predicate-functions'; // ********************** judgement ************************ /** * check if the code running in browser environment (not include worker env) * @returns {Boolean} */ export const inBrowser = typeof window !== 'undefined' && Object.prototype.toString.call(window) !== '[object Object]'; // ********************** 对象操作 ************************ /** * 转变一个类数组对象为数组 */ export function makeArray(obj: any): Array<any> { return Array.from(obj); } /** * sort Object attributes by function * and transfer them into array * @param {Object} obj Object form from numric * @param {Function} fn sort function * @return {Array} the sorted attirbutes array */ export function transObjectAttrIntoArray(obj: Object, fn: Function = (a, b) => +a - +b): Array<string> { return Object.keys(obj) .sort(fn) .reduce((order, key) => { return order.concat(obj[key]); }, []); } /** * run a queue one by one.If include function reject or return false it will stop * @param {Array} queue the queue which we want to run one by one * @return {Promise} tell us whether a queue run finished */ export function runRejectableQueue(queue: Array<any>, ...args: any): Promise<*> { return new Promise((resolve, reject) => { const step = index => { if (index >= queue.length) { resolve(); return; } const result = isFunction(queue[index]) ? queue[index](...args) : queue[index]; if (result === false) return reject('stop'); return Promise.resolve(result) .then(() => step(index + 1)) .catch(err => reject(err || 'stop')); }; step(0); }); } /** * run a queue one by one.If include function return false it will stop * @param {Array} queue the queue which we want to run one by one * @return {boolean} tell the user if the queue run finished */ export function runStoppableQueue(queue: Array<any>, ...args: any): boolean { const step = index => { if (index >= queue.length) { return true; } const result = isFunction(queue[index]) ? queue[index](...args) : queue[index]; if (result === false) return false; return step(++index); }; return step(0); } function checkContinuation(uint8array, start, checkLength) { const array = uint8array; if (start + checkLength < array.length) { while (checkLength--) { if ((array[++start] & 0xC0) !== 0x80) { return false; } } return true; } return false; } // decodeUTF8 export function decodeUTF8(uint8array: any) { const out = []; const input = uint8array; let i = 0; const length = uint8array.length; while (i < length) { if (input[i] < 0x80) { out.push(String.fromCharCode(input[i])); ++i; continue; } else if (input[i] < 0xC0) { // fallthrough } else if (input[i] < 0xE0) { if (checkContinuation(input, i, 1)) { const ucs4 = (input[i] & 0x1F) << 6 | (input[i + 1] & 0x3F); if (ucs4 >= 0x80) { out.push(String.fromCharCode(ucs4 & 0xFFFF)); i += 2; continue; } } } else if (input[i] < 0xF0) { if (checkContinuation(input, i, 2)) { const ucs4 = (input[i] & 0xF) << 12 | (input[i + 1] & 0x3F) << 6 | input[i + 2] & 0x3F; if (ucs4 >= 0x800 && (ucs4 & 0xF800) !== 0xD800) { out.push(String.fromCharCode(ucs4 & 0xFFFF)); i += 3; continue; } } } else if (input[i] < 0xF8) { if (checkContinuation(input, i, 3)) { let ucs4 = (input[i] & 0x7) << 18 | (input[i + 1] & 0x3F) << 12 | (input[i + 2] & 0x3F) << 6 | (input[i + 3] & 0x3F); if (ucs4 > 0x10000 && ucs4 < 0x110000) { ucs4 -= 0x10000; out.push(String.fromCharCode((ucs4 >>> 10) | 0xD800)); out.push(String.fromCharCode((ucs4 & 0x3FF) | 0xDC00)); i += 4; continue; } } } out.push(String.fromCharCode(0xFFFD)); ++i; } return out.join(''); } export function debounce(func: Function, wait: number, immediate: boolean) { // immediate默认为false let timeout, args: any, context, timestamp, result; const later = function() { // 当wait指定的时间间隔期间多次调用_.debounce返回的函数,则会不断更新timestamp的值,导致last < wait && last >= 0一直为true,从而不断启动新的计时器延时执行func const last = new Date() - timestamp; if (last < wait && last >= 0) { timeout = setTimeout(later, wait - last); } else { timeout = null; if (!immediate) { result = func.apply(context, args); if (!timeout) context = args = null; } } }; return function() { context = this; args = arguments; timestamp = new Date(); // 第一次调用该方法时,且immediate为true,则调用func函数 const callNow = immediate && !timeout; // 在wait指定的时间间隔内首次调用该方法,则启动计时器定时调用func函数 if (!timeout) timeout = setTimeout(later, wait); if (callNow) { result = func.apply(context, args); context = args = null; } return result; }; } /** * 函数节流(控制函数执行频率) * @param {Function} func 要节流控制的函数,必填 * @return {Number} wait 等待时长 * @return {Object} options { * leading<是否首次调用立即执行,否:则按wait设定等待到期后调用才执行>:false, * trailing<是否在调用并未到期时启用定时器,以保证一定执行>:true * } * @return {Object} cxt 上下文对象 * @return {Function} */ export function throttle(func: Function, wait: number, options: any, cxt: any) { /* options的默认值 * 表示首次调用返回值方法时,会马上调用func;否则仅会记录当前时刻,当第二次调用的时间间隔超过wait时,才调用func。 * options.leading = true; * 表示当调用方法时,未到达wait指定的时间间隔,则启动计时器延迟调用func函数,若后续在既未达到wait指定的时间间隔和func函数又未被调用的情况下调用返回值方法,则被调用请求将被丢弃。 * options.trailing = true; * 注意:当options.trailing = false时,效果与上面的简单实现效果相同 */ let context, args: any, result; let timeout = null; let previous = 0; if (!options) options = {}; const later = function() { previous = options.leading === false ? 0 : new Date() - 0; timeout = null; result = func.apply(context, args); if (!timeout) context = args = null; }; wait = wait || 0; return function() { const now = new Date(); if (!previous && options.leading === false) previous = now; // 计算剩余时间 const remaining = wait - (now - previous); if (cxt) { context = cxt; } else { context = this; } args = arguments; // 当到达wait指定的时间间隔,则调用func函数 // 精彩之处:按理来说remaining <= 0已经足够证明已经到达wait的时间间隔,但这里还考虑到假如客户端修改了系统时间则马上执行func函数。 if (remaining <= 0 || remaining > wait) { // 由于setTimeout存在最小时间精度问题,因此会存在到达wait的时间间隔,但之前设置的setTimeout操作还没被执行,因此为保险起见,这里先清理setTimeout操作 if (timeout) { clearTimeout(timeout); timeout = null; } previous = now; result = func.apply(context, args); if (!timeout) context = args = null; } else if (!timeout && options.trailing !== false) { // options.trailing=true时,延时执行func函数 timeout = setTimeout(later, remaining); } return result; }; } // requestAnimationFrame export const raf = ( inBrowser && ( window.requestAnimationFrame || window.mozRequestAnimationFrame || window.webkitRequestAnimationFrame || window.msRequestAnimationFrame || window.oRequestAnimationFrame ) ) || function(cb: Function) { return setTimeout(cb, 17); }; // cancelAnimationFrame export const caf = ( inBrowser && ( window.cancelAnimationFrame || window.mozCancelAnimationFrame || window.webkitCancelAnimationFrame || window.webkitCancelRequestAnimationFrame || window.msCancelAnimationFrame || window.oCancelAnimationFrame ) ) || function(id: number) { clearTimeout(id); }; // 根据要求的位数,将9格式化为 09\009\0009... export function strRepeat(num: any, bit: number) { const pBit = bit; num = `${num || ''}`; const numLen = num.length; bit = (bit || numLen) - numLen; const paddingStr = bit > 0 ? (num.repeat ? '0'.repeat(bit) : new Array(bit + 1).join('0')) : ''; return (paddingStr + num).slice(0, pBit); } // video 时间格式化 export function formatTime(time: number) { const hh = Math.floor(time / 3600); time = Math.floor(time % 3600); const mm = strRepeat(Math.floor(time / 60), 2); time = Math.floor(time % 60); const ss = strRepeat(time, 2); return hh >= 1 ? `${hh}:${mm}:${ss}` : `${mm}:${ss}`; } /** * 追加样式代码到head的style标签,不存在则创建 * @param {String} cssText 样式文本 * @return {HTMLElement} */ export function appendCSS(cssText: string) { const doc = document; let styleEl = doc.querySelector('style'); if (!styleEl) { styleEl = doc.createElement('style'); const header = doc.querySelector('head'); header && header.appendChild(styleEl); } styleEl.appendChild(doc.createTextNode(cssText)); return styleEl; } /** * 格式化日期对象为:年-月-日 时:分:秒.毫秒 * @param {Date} date Date日期对象 * @param {String} pattern 要输出的日期格式,默认:`yyyy-MM-dd hh:mm:ss.i` * @return {String} */ export function formatDate(date: Date = new Date(), pattern: string = 'yyyy-MM-dd hh:mm:ss.i'): string { const year = date.getFullYear().toString(); const fields = { M: date.getMonth() + 1, d: date.getDate(), h: date.getHours(), m: date.getMinutes(), s: date.getSeconds(), i: date.getMilliseconds(), }; pattern = pattern.replace(/(y+)/ig, (_, yearPattern) => year.substr(4 - Math.min(4, yearPattern.length))); for (const i in fields) { pattern = pattern.replace( new RegExp('(' + i + '+)', 'g'), (_, pattStr) => ((fields[i] < 10 && pattStr.length > 1) ? '0' : '') + fields[i] ); } return pattern; } /** * 读取本地存储的值(不支持localStorage则降级到cookie) * @param {String} key 目标数据key * @return {String} */ export function getLocalStorage(key: string): string | null | void { try { return window.localStorage.getItem(key); } catch (e) { try { const regRt = document.cookie.match(new RegExp('(^| )' + key + '=([^;]*)(;|$)')); return isArray(regRt) ? unescape(regRt[2]) : ''; } catch (e) { return ''; } } } /** * 将指定key对应值写入本地存储(不支持localStorage则降级到cookie) * @param {String} key * @param {String} val * @return {String} */ export function setLocalStorage(key: string, val: string) { try { window.localStorage.setItem(key, val); } catch (e) { const expires = new Date(); // 默认存储300天 expires.setTime(expires.getTime() + 24 * 3600 * 1000 * 300); try { document.cookie = key + '=' + escape(val) + ';expires=' + expires.toUTCString() + ';path=/;'; } catch (e) { console.error(e); } } }