引言
移动端 H5 页面开发过程中会用到非常多的辅助函数,合理使用这些函数能大大提高研发效率和程序的健壮性。这里总结了一些出现频次较高的函数,包括 JS 类型判断、浏览器环境判断、获取 URL 参数、DOM处理等等。俗话说好记性不如烂笔头,把常用的代码片段记下来也可以做到温故而知新。
基础函数
创建一个纯函数的缓存版本
export function cached(fn: Function): Function {
const cache = Object.create(null);
return function(str: string) {
const hit = cache[str];
// eslint-disable-next-line no-return-assign
return hit || (cache[str] = fn(str));
};
}
新对象覆盖旧对象
export function copy(dest: Object, source: Object) {
Object.keys(source).forEach(key => {
dest[key] = source[key];
});
}
是否为空对象
export function isEmptyObject(obj: Object) {
try {
return Object.getOwnPropertyNames(obj).length === 0;
} catch (error) {
// 在安卓 5 上此处报错,导致页面白屏
// 此处直接抛出异常可以重现问题,但安卓 4 真机,安卓 5 真机,chrome 老版本,IE 均无法重现此问题
// 修复方式:捕获异常后,当做参数非空返回,保障后续业务逻辑可以继续
return false;
}
}
是否空值
export function isEmpty(val: any) {
return val === null || val === undefined || val === '' || isEmptyObject(val);
}
限制函数的执行频率
export function throttle({ func = () => {}, wait = 200, frequency = 200 }) {
let timeout = null;
let lastExecuteTime = Date.now();
return function(...rest: any) {
const self = this;
const currentTime = Date.now();
if (currentTime - lastExecuteTime > frequency) {
lastExecuteTime = Date.now();
func.apply(self, rest);
} else {
timeout = setTimeout(function() {
lastExecuteTime = Date.now();
clearTimeout(timeout);
timeout = null;
}, wait);
}
};
}
获取随机数(包含首尾临界值)
export function getRandomInt(min: number, max: number) {
return Math.floor(Math.random() * (max - min + 1) + min);
}
浏览器环境判断
是否 iOS 终端
export function isiOS() {
const ua = navigator.userAgent.toLowerCase();
return /iphone|ipad|ipod|ios/.test(ua);
}
是否 Safari 浏览器
export function isSafari() {
const ua = navigator.userAgent.toLowerCase();
const result = ua.match(/version\/([\d.]+).*safari/gi) || ua.match(/(ipad).*os\s([\d_]+)/) || ua.match(/(iphone\sos)\s([\d_]+)/);
// qq3.7 和海豚浏览器等部分浏览器 UA 中包含 safari,需排除
if (result && ua.match(/.\*android/gi)) {
return false;
}
return result;
}
是否 Android 浏览器
export function isAndroid() {
const u = navigator.userAgent;
return u.includes('Android') || u.includes('Adr');
}
是否微信环境
export function isWeChat() {
return !!navigator.userAgent.match(/MicroMessenger\/([\d.]+)/i);
}
是否PC端微信环境
export function isWeChatPc() {
return !!navigator.userAgent.match(/WindowsWechat/i);
}
是否微信小程序环境
export function isWechatMp() {
return (
isWeChat() &&
(navigator.userAgent.match(/miniprogram/i) ||
window.__wxjs_environment === 'miniprogram')
);
};
是否QQ环境
export function isQQ() {
return !!navigator.userAgent.match(/QQ\/([\d.]+)/);
}
是否微博环境
export function isWeibo() {
return !!navigator.userAgent.match(/WeiBo/i);
}
是否支持webp格式
export function isSupportWebp() {
try {
return (
document
.createElement('canvas')
.toDataURL('image/webp', 0.5)
.indexOf('data:image/webp') === 0
);
} catch (err) {
return false;
}
}
URL 解析
获取查询字符串
export const getQueryString = cached((name: string) => {
const reg = new RegExp(`(^|&)${name}=([^&]*)(&|$)`, 'i');
const r = window.location.search.substr(1).match(reg);
if (r != null) return decodeURIComponent(r[2]);
return null;
});
还原查询字符串
例如:parseUrlSearchParams({name: 'xiaowang', age: 25}) 输出 name=xiaowang&age=25
export function parseUrlSearchParams(map: Record<string, string>) {
try {
if (typeof URLSearchParams === 'function') {
return new URLSearchParams(map).toString();
}
return Object.entries(map)
.reduce((initial, [key, value]) => {
return `${initial}&${key}=${value}`;
}, '')
.substring(1);
} catch (err) {
console.error(err);
return '';
}
}
获取url上的参数params
const channelUrlReg = /channel(?:v2)?\/(\w+)/;
// url约定 /channel/{id}
function getIdByChannel(pattern: RegExp = channelUrlReg) {
const result = window.location.href.match(pattern);
if (result) {
return result[1];
}
return null;
}
// 例如url是:https://www.baidu.com/channel/abefd7206249e561
// getIdByChannel()输出:abefd7206249e561
获取url上的多个参数params
const singleRingUrlReg = /\/(?:sr|ring)\/(\w+)\/(\w+)/i;
// url约定 ring/{cid}/{ringid}
export function getCidAndRingIdFromUrl(pattern: RegExp = singleRingUrlReg) {
const result = window.location.href.match(pattern);
if (result) {
return {
cid: result[1],
ringId: result[2],
};
}
return null;
}
// 例如url是:https://www.baidu.com/ring/abefd7206249e561/541564145111111
// getCidAndRingIdFromUrl()输出:{cid: 'abefd7206249e561', ringId: '541564145111111'}
字符串处理
将文本中所有的数字两边添加空格
export function addSpaceBetweenNumber(htmlStr: string) {
if (!htmlStr) {
return htmlStr;
}
const htmlTagReg = /(<\s*\w+[^>]*\s*>|<\s*\/\s*\w+\s*>)/gi;
const segements = htmlStr.split(htmlTagReg);
return segements
.filter(item => item.trim() !== '')
.map(item => {
if (htmlTagReg.test(item) || /<.*>/.test(item)) {
return item;
}
return item.replace(/(\d[-\d/\\]*)/g, ' $1 ');
})
.join('');
}
DOM 处理
加载脚本文件
export function loadScript(url: string, callback?: () => void) {
// 兼容服务端渲染模式
if (!window) return;
const scriptElem = document.createElement('script');
if (callback) {
scriptElem.onload = callback;
}
scriptElem.async = true;
scriptElem.src = url;
document.head.appendChild(scriptElem);
}
检测某个元素是否在 viewport 中
export function isInViewport(el: Element) {
if (!el) {
return true;
}
const { top, left, bottom, right, width, height } = el.getBoundingClientRect();
const viewportWidth = window.innerWidth || document.documentElement.clientWidth;
const viewportHeight = window.innerHeight || document.documentElement.clientHeight;
return top + height > 0 && left + width > 0 && bottom - width < viewportWidth && right - height < viewportHeight;
}
图片预加载
一般设计元素重的H5页面,我们经常看到一进页面就是带有进度条的loading页,此时其实就是在加载图片等资源,到真正渲染时能做到拿来就用不卡顿,提升用户体验。
export function preload(resArr: Array<string>, notifyProgress: Function) {
if (!resArr || !Array.isArray(resArr) || !notifyProgress) {
return;
}
let loaded = 0;
const resNum = resArr.length;
let isReplace = false;
function loadEvents() {
if (isReplace) {
return;
}
loaded++;
if (loaded === resNum) {
isReplace = true;
}
// eslint-disable-next-line consistent-return
return notifyProgress(loaded / resNum);
}
resArr.forEach(item => {
const tempImg = new Image();
tempImg.src = item;
tempImg.onload = loadEvents;
tempImg.onerror = loadEvents;
});
// 10s还没加载完时 直接结束
setTimeout(() => {
if (loaded < resNum) {
isReplace = true;
notifyProgress(1);
}
}, 10 * 1000);
}
使用案例
// 使用webpack内置的require.content API获取所有图片url
const req = (require as any).context('../images', true, /\.png$/);
const res = req.keys().map(req);
const resArr = res.filter(item => /images\/first_.*\.png$/.test(item));
// 传入图片url数组,和进度变化函数
preload(resArr, this.progressChange);
// 全部加载完毕进入首页
progressChange(progress: number) {
this.progress = +(progress * 100).toFixed(0);
if (progress === 1) {
this.$router.replace({ name: 'home' });
}
}
其他
获取网络类型
export function getNetworkType() {
return ((navigator as any).connection || {}).type || 'unknown';
}
判断是否网络错误
export function isNetworkError(e: Error) {
return /network error/i.test(e.message);
}
判断是否网络超时
export function isNetworkTimeout(e: Error) {
return /timeout/i.test(e.message);
}