# 常用的一些知识点
# 1、网页关闭前阻塞提示
如下面的效果
window.addEventListener('beforeunload', event => {
// Cancel the event as stated by the standard.
event.preventDefault();
// Chrome requires returnValue to be set.
event.returnValue = '222';
});
# 2、动态加载js脚本
function loadScript(src, done) {
var js = document.createElement('script');
js.src = src;
js.onload = function() {
done();
};
js.onerror = function() {
done(new Error('Failed to load script ' + src));
};
document.head.appendChild(js);
}
# 3、Object.keys() Object.getOwnPropertyNames() 区别
共同点: 都是返回对象自身的属性, 不包括原型链上的
区别:Object.keys()
返回自身可枚举的属性,Object.getOwnPropertyNames()
返回所有的属性, 包括不可枚举的。
展开:for...in
循环遍历自身和原型链上可枚举的属性。 JSON.stringify
只序列化对象自身的可枚举属性。 Object.assign()
只拷贝对象自身的可枚举的属性。
总结:想要原型链上的属性可以采用for...in
。想要所有的属性采用Object.getOwnPropertyNames()
, 比如用在对象深拷贝的递归中时。
# 4、乱序一个数组
// 自己随便写的
function easyShuffle(arr) {
const source = [...arr];
const target = [];
while (source.length > 0) {
const index = Math.floor(Math.random() * source.length);
const value = source.splice(index, 1);
target.push(value[0]);
}
return target;
}
// Fisher–Yates
function shuffle(a = []) {
const arr = [...a];
var j, x, i;
for (i = arr.length; i; i--) {
j = Math.floor(Math.random() * i);
x = arr[i - 1];
arr[i - 1] = arr[j];
arr[j] = x;
}
return arr;
}
# 5、字符串分割支持多种字符
const text = '131,13123,13123、13、1321;add; adsad adasd asdasd'
const a = text.split(/,|,|、|(\s+)|;|;/).filter((_) => _);
console.log(a);
const text = '131,13123,13123、13、1321;add; adsad adasd asdasd';
const a = text.split(/[,,、(\s+);;]/)
console.log(a);
可以根据各种中文逗号、顿号、分号、空格等分割
# 6、html字符串的转义和逆转义
转义
function escapeHTML(str) {
const charMap = {
'&': '&',
'<': '<',
'>': '>',
"'": ''',
'"': '"'
};
return str.replace(/[&<>'"]/g, tag => {
return charMap[tag] || tag;
});
}
逆转义
function unescapeHTML(str) {
const charMap = {
'&': '&',
'<': '<',
'>': '>',
''': "'",
''': "'",
'"': '"'
};
return str.replace(/&|<|>|'|'|"/g, tag => {
return charMap[tag] || tag;
});
}
# 7、全屏操作
// 全屏元素
function handleRequestFullScreen(domElement) {
if (domElement.requestFullscreen) {
domElement.requestFullscreen()
} else if (domElement.webkitRequestFullScreen) {
domElement.webkitRequestFullScreen()
} else if (domElement.mozRequestFullScreen) {
domElement.mozRequestFullScreen()
} else {
domElement.msRequestFullscreen()
}
}
// 退出全屏
function handleExitFullscreen() {
if (document.exitFullscreen) {
document.exitFullscreen()
} else if (document.mozCancelFullScreen) {
document.mozCancelFullScreen()
} else if (document.msExitFullscreen) {
document.msExiFullscreen()
} else if (document.webkitCancelFullScreen) {
document.webkitCancelFullScreen()
}
}
全屏事件, 可以通过 document.fullscreenElement
获取当前被全屏展示的元素
document.addEventListener('fullscreenchange', () => {
// document.fullscreenElement
})
# 8、获取URL参数query params
通过字符串截断获取
function getAllUrlParams(
url,
options = {
needDecode: true,
ignoreCase: false
}
) {
const { needDecode, ignoreCase } = options;
var queryString = url ? url.split('?')[1] : window.location.search.slice(1);
var obj = {};
if (queryString) {
queryString = queryString.split('#')[0];
if (needDecode) {
queryString = decodeURIComponent(queryString);
}
var arr = queryString.split('&');
for (var i = 0; i < arr.length; i++) {
var a = arr[i].split('=');
var paramName = a[0];
var paramValue = typeof a[1] === 'undefined' ? true : a[1];
if (ignoreCase) {
paramName = paramName.toLowerCase();
}
//
if (typeof obj[paramName] === 'string') {
obj[paramName] = [obj[paramName]];
obj[paramName].push(paramValue);
} else if (typeof obj[paramName] === 'object') {
obj[paramName].push(paramValue);
} else {
obj[paramName] = paramValue;
}
}
}
return obj;
}
使用api-URLSearchParams获取
function getAllUrlParamsByURLSearchParams(
url,
options = {
needDecode: false,
ignoreCase: false
}
) {
let urlIns;
const { needDecode, ignoreCase } = options;
try {
if (!url) {
urlIns = new URL(window.location.href);
} else {
// 考虑URL参数格式不对
urlIns = new URL(url);
}
} catch (error) {
console.error(error);
return {};
}
let params = new URLSearchParams(urlIns.search);
const obj = {};
for (let param of params) {
let [key, value] = param;
// 使用 URLSearchParams 其实可以不用decodeURIComponent
if (needDecode) {
value = decodeURIComponent(value);
}
if (ignoreCase) {
key = key.toLowerCase();
}
if (typeof obj[key] === 'string') {
obj[key] = [obj[key]];
obj[key].push(value);
} else if (typeof obj[key] === 'object') {
obj[key].push(value);
} else {
obj[key] = value;
}
}
return obj;
}
# 9、使用object
标签嵌入PDF
宽度设置为800*1200看起来更为合理
<object data="./1.pdf" type="application/pdf" width="800" height="1200">
<p>
You don't have a PDF plugin, but you can
<a href="1.pdf">download the PDF file. </a>
</p>
</object>
# 10、判断是否点击元素外部
clickoutside.js
let seed = 0;
const ctx = '@@clickoutsideContext';
const nodeList = [];
let inited = false;
function initDocumentMouseHandler() {
let startClickEvent = null;
document.addEventListener('mousedown', e => {
// console.log(e.target);
startClickEvent = e;
});
document.addEventListener('mouseup', e => {
// console.log(e.target);
nodeList.forEach(node => {
node[ctx].documentHandler(e, startClickEvent);
});
});
}
function createDocumentHandler(el, callback) {
return function (mouseUpEvent, mouseDownEvent) {
if (
el.contains(mouseUpEvent.target) ||
el.contains(mouseDownEvent.target) ||
el === mouseUpEvent.target
) {
return;
}
callback.apply(mouseUpEvent.target, [mouseUpEvent]);
};
}
function bindClickOutside(el, callback) {
if (!inited) {
initDocumentMouseHandler();
inited = true;
}
nodeList.push(el);
const id = seed++;
el[ctx] = {
id,
documentHandler: createDocumentHandler(el, callback)
};
}
function unbindClickOutside(el) {
for (let i = 0, len = nodeList.length; i < len; i++) {
if (nodeList[i][ctx].id === el[ctx].id) {
nodeList.splice(i, 1);
break;
}
}
delete el[ctx];
}
export { bindClickOutside, unbindClickOutside };
用法
import { bindClickOutside, unbindClickOutside } from 'clickoutside.js';
const helloEl = document.querySelector('.hello');
bindClickOutside(helloEl, function (e) {
console.log(this, e);
console.log('clickoutside');
// unbindClickOutside(helloEl);
});
# 11、获取客户端滚动条宽度
let scrollBarWidth;
export default function () {
if (scrollBarWidth !== undefined) return scrollBarWidth;
const outer = document.createElement('div');
outer.className = 'xd-scrollbar__wrap';
outer.style.visibility = 'hidden';
outer.style.width = '100px';
outer.style.position = 'absolute';
outer.style.top = '-9999px';
document.body.appendChild(outer);
const widthNoScroll = outer.offsetWidth;
outer.style.overflow = 'scroll';
const inner = document.createElement('div');
inner.style.width = '100%';
outer.appendChild(inner);
const widthWithScroll = inner.offsetWidth;
outer.parentNode.removeChild(outer);
scrollBarWidth = widthNoScroll - widthWithScroll;
return scrollBarWidth;
}
# 12、轻量的并不高效的UUID生成函数
function generateUUID() {
var d = new Date().getTime(); //Timestamp
var d2 = (performance && performance.now && performance.now() * 1000) || 0; //Time in microseconds since page-load or 0 if unsupported
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
var r = Math.random() * 16; //random number between 0 and 16
if (d > 0) {
//Use timestamp until depleted
r = (d + r) % 16 | 0;
d = Math.floor(d / 16);
} else {
//Use microseconds since page-load if supported
r = (d2 + r) % 16 | 0;
d2 = Math.floor(d2 / 16);
}
return (c === 'x' ? r : (r & 0x3) | 0x8).toString(16);
});
}
# 13、判断两个对象是否相等
简易版本: 判断两个字面量对象的值是否相等,而且只判断第一层
function eqPlainObject(objA, objB, fields) {
if (!objA || !objB) {
return false;
}
let fieldsA = [];
let fieldsB = [];
if (!fields) {
fieldsA = Object.keys(objA);
fieldsB = Object.keys(objB);
fields = fieldsA;
} else {
}
if (fieldsA.length !== fieldsB.length) {
return false;
}
let flag = true;
for (let key of fields) {
if (objA[key] !== objB[key]) {
flag = false;
break;
}
}
return flag;
}
全功能版本
/*
NaN 和 NaN 是相等
[1] 和 [1] 是相等
{value: 1} 和 {value: 1} 是相等
不仅仅是这些长得一样的,还有
1 和 new Number(1) 是相等
'Curly' 和 new String('Curly') 是相等
true 和 new Boolean(true) 是相等
*/
var _toString = Object.prototype.toString;
function isFunction(obj) {
return _toString.call(obj) === '[object Function]';
}
function eq(a, b, aStack, bStack) {
// === 结果为 true 的区别出 +0 和 -0
if (a === b) return a !== 0 || 1 / a === 1 / b;
// typeof null 的结果为 object ,这里做判断,是为了让有 null 的情况尽早退出函数
if (a == null || b == null) return false;
// 判断 NaN
if (a !== a) return b !== b;
// 判断参数 a 类型,如果是基本类型,在这里可以直接返回 false
var type = typeof a;
if (type !== 'function' && type !== 'object' && typeof b != 'object') return false;
// 更复杂的对象使用 deepEq 函数进行深度比较
return deepEq(a, b, aStack, bStack);
}
function deepEq(a, b, aStack, bStack) {
// a 和 b 的内部属性 [[class]] 相同时 返回 true
var className = _toString.call(a);
if (className !== _toString.call(b)) return false;
switch (className) {
case '[object RegExp]':
case '[object String]':
return '' + a === '' + b;
case '[object Number]':
if (+a !== +a) return +b !== +b;
return +a === 0 ? 1 / +a === 1 / b : +a === +b;
case '[object Date]':
case '[object Boolean]':
return +a === +b;
}
var areArrays = className === '[object Array]';
// 不是数组
if (!areArrays) {
// 过滤掉两个函数的情况
if (typeof a != 'object' || typeof b != 'object') return false;
var aCtor = a.constructor,
bCtor = b.constructor;
// aCtor 和 bCtor 必须都存在并且都不是 Object 构造函数的情况下,aCtor 不等于 bCtor, 那这两个对象就真的不相等啦
if (
aCtor !== bCtor &&
!(
isFunction(aCtor) &&
aCtor instanceof aCtor &&
isFunction(bCtor) &&
bCtor instanceof bCtor
) &&
'constructor' in a &&
'constructor' in b
) {
return false;
}
}
aStack = aStack || [];
bStack = bStack || [];
var length = aStack.length;
// 检查是否有循环引用的部分
while (length--) {
if (aStack[length] === a) {
return bStack[length] === b;
}
}
aStack.push(a);
bStack.push(b);
// 数组判断
if (areArrays) {
length = a.length;
if (length !== b.length) return false;
while (length--) {
if (!eq(a[length], b[length], aStack, bStack)) return false;
}
}
// 对象判断
else {
var keys = Object.keys(a),
key;
length = keys.length;
if (Object.keys(b).length !== length) return false;
while (length--) {
key = keys[length];
if (!(b.hasOwnProperty(key) && eq(a[key], b[key], aStack, bStack))) return false;
}
}
aStack.pop();
bStack.pop();
return true;
}
export default eq;
# 14、Vue2 父子组件通信
/* 一般子组件的mounted会先执行, 所以子组件先$on 绑定一个事件,然后父组件在mounted钩子 broadcast */
/* 父组件会先created, 然后子组件再created, 所以父组件的$on 可以在created执行,子组件可以在其声明周期钩 dispatch */
function broadcast(componentName, eventName, params) {
this.$children.forEach(child => {
var name = child.$options.componentName;
if (name === componentName) {
child.$emit.apply(child, [eventName].concat(params));
} else {
broadcast.apply(child, [componentName, eventName].concat([params]));
}
});
}
export default {
methods: {
dispatch(componentName, eventName, params) {
var parent = this.$parent || this.$root;
var name = parent.$options.componentName;
while (parent && (!name || name !== componentName)) {
parent = parent.$parent;
if (parent) {
name = parent.$options.componentName;
}
}
if (parent) {
parent.$emit.apply(parent, [eventName].concat(params));
}
},
broadcast(componentName, eventName, params) {
broadcast.call(this, componentName, eventName, params);
}
}
};
# 15、数字截取和补零
// 保留两位小数,不进行4舍5入
function numFixed(num, floatLen = 2) {
num = String(num);
const str = num.split('.');
let insStr = str[0];
let floatStr = str[1];
if (floatStr) {
floatStr = floatStr.substring(0, floatLen);
if (!insStr) {
insStr = '0';
}
}
return [insStr, floatStr].filter(Boolean).join('.');
}
// 前置补零
function pad(val, len = 2) {
val = String(val);
len = len || 2;
while (val.length < len) {
val = '0' + val;
}
return val;
}
// 后置补0
function suffixPad(val, len = 2) {
val = String(val);
len = len || 2;
while (val.length < len) {
val = val + '0';
}
return val;
}