# 树形格式化处理
/**
* @description 树形格式化处理
* @author fengchunqi
* @date 2021-04-14 17:37:47
* @param {object[]} data 原数据数组
* @param {object} [config={}] 配置信息
* @param {string} [config.idKey=id] 树节点id取值的key,默认id
* @param {string} [config.parentKey=parent] 树节点parentId取值的key,默认parent
* @param {string} [config.childrenKey=children] 树节点子级属性名称,默认children
* @param {*} [config.emptyChildren=[]] 末节点的子级表现方式,默认[]
* @returns {object[]} tree 处理好的树形数据
*/
function listToTree(data, config = {}) {
const {
idKey = 'id',
parentKey = 'parent',
childrenKey = 'children',
emptyChildren,
} = config;
const tree = [];
// 核心:每个节点的children都用id+children存起来
const childrenOf = {};
const lastNode = new Map();
const removeNode = new Set();
let id;
let parentId;
data.forEach((item) => {
// TIPS:如果要禁止修改源数据,则需要拷贝每个item
// item = { ...item };
id = item[idKey];
parentId = item[parentKey] || null;
// 每个节点默认有children为[]
// 判断是不是存过的原因是有可能当前节点在前面的节点循环的时候作为父级已经有值了
childrenOf[id] = childrenOf[id] || [];
// 每个节点都可能是末尾节点
if (!removeNode.has(id)) {
lastNode.set(id, item);
}
// init its children
item[childrenKey] = childrenOf[id];
if (parentId !== null) {
// 如果当前节点有父级 只需要在childrenOf里面找parentId的值并push进去
childrenOf[parentId] = childrenOf[parentId] || [];
childrenOf[parentId].push(item);
// 此时父节点肯定不是末尾节点
lastNode.delete(parentId);
removeNode.add(parentId);
} else {
// is parent
tree.push(item);
}
});
if (emptyChildren !== undefined) {
// eslint-disable-next-line no-restricted-syntax
for (const [key, value] of lastNode.entries()) {
value[childrenKey] = emptyChildren;
}
}
return tree;
}
/**
* @description 利用对象引用值的特性来实现树形结构
* @description 注意:源数据的内容会被修改
* @param {object[]} data
* @returns
*/
function listToTree1(data, config = {}) {
const {
idKey = 'id',
parentKey = 'parent',
childrenKey = 'children',
} = config;
const cache = {};
const res = [];
data.forEach((item) => {
cache[item[idKey]] = Object.assign(item, cache[item[idKey]] || {});
cache[item[parentKey]] = cache[item[parentKey]] || {};
if (!item[parentKey]) {
// root
res.push(item);
} else {
cache[item[parentKey]][childrenKey] = cache[item[parentKey]][childrenKey] || [];
cache[item[parentKey]][childrenKey].push(item);
}
});
return res;
} 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
# 获取select框选择并赋其他值
/**
* @description 获取select框选择并赋其他值
* @author fengchunqi
* @date 2021-04-14 17:37:47
* @param {object} config 参数配置
* @param {object} config.targetData 需要添加属性的对象 一般为form表单
* @param {object[]} config.options select框绑定的options
* @param {*} config.checkAttr 当前选中的值绑定的属性
* @param {*} config.checkValue 当前选中的值
* @param {*} config.addAttr 需要添加的属性名
* @param {*} config.addValueAttr 添加的新属性在options中取值的属性名
* @param {function} [config.callback]
* @returns {object} item 选中选项
*/
function getSelectItem(config) {
const {
targetData,
options,
checkAttr,
checkValue,
addAttr,
addValueAttr,
callback,
} = config;
const item = options.find((el) => el[checkAttr] === checkValue);
if (item) {
if (Object.prototype.toString.call(targetData) === '[object Object]') {
targetData[addAttr] = item[addValueAttr];
}
if (callback && typeof callback === 'function') {
callback(item);
}
}
return item;
} 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
# 计算字符串的物理长度
/**
* @description 计算字符串的物理长度
* @author fengchunqi
* @date 2021-05-31 10:29:11
* @param {string} str 待计算的字符串
* @param {string} [fontSize='14px'] 字体大小,默认14px
* @returns {number}
*/
function getTextWidth(str, fontSize = '14px') {
const ele = document.createElement('div');
ele.style.position = 'absolute';
ele.style.whiteSpace = 'nowrap';
ele.style.fontSize = fontSize;
ele.style.opacity = 0;
ele.innerText = str;
document.body.append(ele);
const result = ele.getBoundingClientRect().width;
document.body.removeChild(ele);
return result;
} 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 获取JS数据类型
TIPS
typeof可以正确识别:number、string、boolean、undefined、symbol、bigint、function的类型的数据,但是对于其他的都会认为是object,比如null、Date等,所以通过typeof来判断数据类型会不准确。但是可以使用Object.prototype.toString实现。
/**
* @description 获取JS数据类型
* @author fengchunqi
* @date 2021-06-16 09:54:42
* @param {*} value
* @returns {string} 数据类型(number|string|boolean|...)
*/
function getDataType(value) {
return (Object.prototype.toString.call(value).slice(8, -1) || '').toLowerCase();
} 1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
# JS数组去重
/**
* @description 数组去重
* @author fengchunqi
* @date 2021-06-16 14:52:32
* @param {array} arr
* @returns {array} unique arr
*/
function arrUnique(arr) {
return arr.filter((el, index) => arr.indexOf(el) === index);
}
// ES6的Set
// function arrUnique(arr) {
// return Array.from(new Set(arr));
// }
// 其他的还有各种下标查询、遍历记录次数、排序后相邻比较等等
// 如果是对象数组,分两种:有唯一Key、没有唯一Key
// const arrData2 = [
// { key: '01', value: '乐乐' },
// { key: '02', value: '博博' },
// { key: '03', value: '淘淘' },
// { key: '01', value: '乐乐' },
// { key: '02', value: '明明' },
// { value: '明明', key: '02' },
// ];
// 有唯一Key标识 Key 重复就算是重复
function objArrUnique(arr, key) {
const set = new Set();
return arr.filter((el) => !set.has(el[key]) && set.add(el[key]));
}
// 如果没有唯一的Key标识
function objArrUniqueWithoutKey(arr) {
const set = new Set();
// 很明显的这种方法遇到对象属性顺序不同也不行
// 实在要处理只能手动比对每个对象的属性了
return arr.filter((el) => {
const elStr = JSON.stringify(el);
if (set.has(elStr)) return false;
set.add(elStr);
return true;
});
} 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
# 观察者模式
// 被观察者
class Subject {
constructor(name) {
this.name = name;
this.observers = [];
this.state = 'XXXX';
}
// 被观察者要提供一个接受观察者的方法
attach(observer) {
this.observers.push(observer);
}
// 改变被观察着的状态
setState(newState) {
this.state = newState;
this.observers.forEach((o) => {
o.update(newState);
});
}
}
// 观察者
class Observer {
constructor(name) {
this.name = name;
}
update(newState) {
console.log(`${this.name}say:${newState}`);
}
} 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// 被观察者 灯
const sub = new Subject('灯');
const mm = new Observer('小明');
// 订阅 观察者
sub.attach(mm);
sub.setState('灯亮了来电了');
// 小明say:灯亮了来电了
1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
# 发布订阅模式
class EventEmitter {
constructor() {
this.store = new Map();
}
on(name, handler) {
if (!name || !handler) {
throw new Error('事件绑定错误');
}
if (this.store.has(name)) {
this.store.set(name, [...this.store.get(name), handler]);
} else {
this.store.set(name, [handler]);
}
}
off(name, handler) {
if (!this.store.has(name)) {
throw new Error(`事件:${name}未找到`);
}
if (!handler) {
this.store.delete(name);
} else {
const handlers = this.store.get(name);
const idx = handlers.findIndex((fn) => fn === handler);
if (idx === -1) {
throw new Error('事件执行函数未找到');
}
handlers.splice(idx, 1);
if (handlers.length === 0) {
this.store.delete(name);
} else {
this.store.set(name, handlers);
}
}
}
emit(name, ...args) {
if (!this.store.has(name)) {
throw new Error(`事件:${name}未找到`);
}
const handlers = this.store.get(name).slice();
handlers.forEach((fn) => fn(...args));
}
} 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
# 单例模式
// 方案一
const SingletonTester = (function () {
// 单例方法
function Singleton(data) {
this.name = 'SingletonTester';
this.data = data;
}
// 单例实例
let instance;
// 返回对象
return {
name: 'SingletonTester',
getInstance(data) {
if (instance === undefined) {
instance = new Singleton(data);
}
return instance;
},
};
}());
// const aa = SingletonTester.getInstance({aa: 'aa'})
// const bb = SingletonTester.getInstance({bb: 'bb'})
// aa === bb // true
// 方案二
function Singleton2(data) {
// 判断是否存在实例
if (typeof Singleton2.instance === 'object') {
return Singleton2.instance;
}
// 其它内容
this.data = data;
// 缓存
Singleton2.instance = this;
// new隐式返回this
}
// const cc = new Singleton2({cc: 'cc'})
// const dd = new Singleton2({dd: 'dd'})
// cc === dd // true 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
# 解析URL参数为对象
/**
* @description 解析URL参数为对象
* @author fengchunqi
* @date 2021-06-17 16:00:47
* @param {string} url
* @returns {object}
*/
function parseUrlParams(url) {
const params = {};
const paramsStr = /.+\?(.+)$/.exec(url);
if (paramsStr && paramsStr[1]) {
const paramsArr = paramsStr[1].split('&');
paramsArr.forEach((el) => {
const [key, value] = el.split('=');
params[key] = decodeURIComponent(value);
});
}
return params;
} 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 填充模板字符串
/**
* @description 填充模板字符串
* @author fengchunqi
* @date 2021-06-17 16:59:42
* @param {string} template
* @param {object} data
* @returns {string} result
*/
function renderTemplate(template, data) {
const reg = /\{\{(\w+)\}\}/g; // 匹配 {{key}} 其他格式只需修改正则
const result = template.replace(reg, (match, p1) => data[p1] || '');
return result;
} 1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
const template = '我是{{name}},年龄{{age}},性别{{sex}}';
const person = {
name: '卡布达',
age: 2,
};
renderTemplate(template, person);
// "我是卡布达,年龄2,性别"
1
2
3
4
5
6
7
2
3
4
5
6
7
# 图片懒加载
// <img src="" alt="" data-src="src"></img>
let imgList = [...document.querySelectorAll('img')];
const { length } = imgList;
const imgLazyLoad = function () {
let count = 0;
return (function () {
const deleteIndexList = []; // 已经加载了的
imgList.forEach((img, index) => {
const rect = img.getBoundingClientRect();
// 图片没有滚动到可视区域时,top是大于innerHeight的
if (rect.top < window.innerHeight) {
// 滚到可视区域内时把data-src的值赋给src 加载图片
img.src = img.dataset.src;
deleteIndexList.push(index);
count++;
if (count === length) {
document.removeEventListener('scroll', imgLazyLoad);
}
}
});
imgList = imgList.filter((img, index) => !deleteIndexList.includes(index));
}());
};
document.addEventListener('scroll', imgLazyLoad); 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# 函数柯里化
/**
* @description 函数柯里化
* @author fengchunqi
* @date 2021-06-18 15:22:51
* @param {function} fn
* @returns {function} curryFn
*/
function curry(fn) {
const curryFn = (...args) => {
// 返回的新函数参数个数大于等于fn参数个数就可以执行
if (args.length >= fn.length) return fn(...args);
// 否则再返回一个新函数,拼接前面传进来的参数
return (...arg) => curryFn(...args, ...arg);
};
return curryFn;
} 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 偏函数
/**
* @description 偏函数
* @author fengchunqi
* @date 2021-06-18 15:41:40
* @param {function} fn
* @param {*} args
* @returns {function} partialFn
*/
function partial(fn, ...args) {
return (...arg) => fn(...args, ...arg);
}
// 有点类似于 bind 的调用 1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
# jsonp的实现
/**
* @description jsonp的实现
* @author fengchunqi
* @date 2021-06-18 15:58:16
* @param {string} url
* @param {object} params 请求参数
* @returns {promise}
*/
function jsonp(url, params) {
const funcName = `cb_${Date.now()}`;
const formatQuery = () => {
let dataSrc = '';
Object.keys(params).forEach((key) => {
dataSrc += `${encodeURIComponent(key)}=${encodeURIComponent(params[key])}&`;
});
dataSrc += `callback=${funcName}`;
return `${url}?${dataSrc}`;
};
return new Promise((resolve, reject) => {
const script = document.createElement('script');
script.src = formatQuery();
script.type = 'text/javascript';
document.body.appendChild(script);
window[funcName] = (data) => {
document.body.removeChild(script);
delete window[funcName];
resolve(data);
};
script.onerror = () => {
document.body.removeChild(script);
delete window[funcName];
reject(new Error('request error'));
};
});
} 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
TIPS
如何解决window上函数重名为题?
通过时间戳生成callback函数名称,回调执行后从window上移除该属性
# instanceof的实现
function myInstanceof(instance, Constructor) {
// Object.getPrototypeOf(instance) === instance.__proto__
let proto = instance;
while (proto && Constructor) {
if (Object.getPrototypeOf(proto) === Constructor.prototype) return true;
proto = Object.getPrototypeOf(proto);
}
return false;
} 1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
# Object.create的实现
Object.create2 = function (proto, propertyObject = undefined) {
if (typeof proto !== 'object' && typeof proto !== 'function') {
throw new TypeError('Object prototype may only be an Object or null.');
}
// if (propertyObject == null) {
// throw new TypeError('Cannot convert undefined or null to object');
// }
function F() { }
F.prototype = proto;
const obj = new F();
if (propertyObject !== undefined) {
Object.defineProperties(obj, propertyObject);
}
if (proto === null) {
// 创建一个没有原型对象的对象,Object.create(null)
// eslint-disable-next-line no-proto
obj.__proto__ = null;
}
return obj;
}; 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 获取DOM第n级的父元素
/**
* @description 获取DOM第n级的父元素
* @author fengchunqi
* @date 2021-07-20 16:31:26
* @param {Element} ele
* @param {number} n
* @returns {Element}
*/
function getElementParent(ele, n) {
while (ele && n) {
ele = ele.parentElement ? ele.parentElement : ele.parentNode;
n--;
}
return ele;
} 1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
# 返回元素的第n个兄弟节点
/**
* @description 返回元素的第n个兄弟节点
* @author fengchunqi
* @date 2021-07-20 16:37:11
* @param {Element} ele
* @param {number} n 大于0往后找 小于0往前找
* @returns {Element}
*/
function getElementSibling(ele, n) {
// ele 不存在或 n === 0 结束
while (ele && n) {
if (n > 0) {
// nextElementSibling 返回当前元素在其父元素的子元素节点中的后一个元素节点,
// 如果该元素已经是最后一个元素节点,则返回null
if (ele.nextElementSibling) {
ele = ele.nextElementSibling;
} else {
// nextSibling 返回其父节点的 childNodes 列表中紧跟在其后面的节点,
// 如果指定的节点为最后一个节点,则返回 null。ele.nodeType===1为元素节点
for (ele = ele.nextSibling; ele && ele.nodeType !== 1; ele = ele.nextSibling);
}
n--;
} else {
// previousElementSibling 返回当前元素在其父元素的子元素节点中的前一个元素节点,
// 如果该元素已经是第一个元素节点,则返回null
if (ele.previousElementSibling) {
ele = ele.previousElementSibling;
} else {
// previousSibling 返回当前节点的前一个兄弟节点,没有则返回null. ele.nodeType===1为元素节点
for (ele = ele.previousSibling; ele && ele.nodeType !== 1; ele = ele.previousSibling);
}
n++;
}
}
return ele;
} 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
节点类型常量 Node.nodeType
2, 5, 6, 12 弃用 (opens new window)
| 常量 | 值 | 描述 |
|---|---|---|
| Node.ELEMENT_NODE | 1 | 一个元素节点,例如 <p> 和 <div>。 |
| Node.TEXT_NODE | 3 | Element 或者 Attr 中实际的文字 |
| Node.CDATA_SECTION_NODE | 4 | 一个 CDATASection,例如<!CDATA[[ … ]]>。 |
| Node.PROCESSING_INSTRUCTION_NODE | 7 | 一个用于XML文档的ProcessingInstruction(en-US),例如<?xml-stylesheet ... ?>声明 |
| Node.COMMENT_NODE | 8 | 一个 Comment(注释) 节点。 |
| Node.DOCUMENT_NODE | 9 | 一个 Document 节点。 |
| Node.DOCUMENT_TYPE_NODE | 10 | 描述文档类型的 DocumentType 节点。例如 <!DOCTYPE html> 就是用于 HTML5 的。 |
| Node.DOCUMENT_FRAGMENT_NODE | 11 | 一个 DocumentFragment 节点 |
# 获得滚动条的滚动距离
/**
* @description 获得滚动条的滚动距离
* @author fengchunqi
* @date 2021-07-21 15:00:11
* @returns {object}
*/
function getScrollOffset() {
if (window.pageXOffset) {
return {
x: window.pageXOffset,
y: window.pageYOffset,
};
}
return {
x: document.body.scrollLeft + document.documentElement.scrollLeft,
y: document.body.scrollTop + document.documentElement.scrollTop,
};
} 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 获得视口的尺寸
/**
* @description 获得视口的尺寸
* @author fengchunqi
* @date 2021-07-21 15:04:50
* @returns {object}
*/
function getViewportSize() {
if (window.innerWidth) {
return {
width: window.innerWidth,
height: window.innerHeight,
};
}
// ie8及其以下
if (document.compatMode === 'BackCompat') {
// 怪异模式
return {
width: document.body.clientWidth,
height: document.body.clientHeight,
};
}
// 标准模式
return {
width: document.documentElement.clientWidth,
height: document.documentElement.clientHeight,
};
} 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# 获取任一元素的任意属性
/**
* @description 获取任一元素的任意属性
* @author fengchunqi
* @date 2021-07-21 15:17:25
* @param {Element} ele
* @param {string} prop
* @returns {string}
*/
function getStyle(ele, prop) {
// getComputedStyle第二个参数是查询伪元素,默认为null,为了兼容旧版本浏览器也传null
// getComputedStyle(ele, '::after')
// style: 能设置样式和获取样式,但是获取不了外部样式,如果写了行内没有的样式,返回的是空值
// getComputedStyle和currentStyle可以获取到外部样式,返回一个只读的样式对象
return window.getComputedStyle ? window.getComputedStyle(ele, null)[prop] : ele.currentStyle[prop];
} 1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
# 绑定事件与解除绑定的兼容
/**
* @description 绑定事件
* @author fengchunqi
* @date 2021-07-21 15:21:16
* @param {Element} elem
* @param {string} type
* @param {function} handle
*/
function addEvent(elem, type, handle) {
if (elem.addEventListener) { // 非ie和非ie9以下
elem.addEventListener(type, handle, false);
} else if (elem.attachEvent) { // ie6到ie8
elem.attachEvent(`on${type}`, () => {
// attachEvent里面的this指向window 在这里用call处理一下
handle.call(elem);
});
} else {
elem[`on${type}`] = handle;
}
} 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
* @description 解绑事件
* @author fengchunqi
* @date 2021-07-21 15:21:16
* @param {Element} elem
* @param {string} type
* @param {function} handle
*/
function removeEvent(elem, type, handle) {
if (elem.removeEventListener) { // 非ie和非ie9以下
elem.removeEventListener(type, handle, false);
} else if (elem.detachEvent) { // ie6到ie8
elem.detachEvent(`on${type}`, handle);
} else {
elem[`on${type}`] = null;
}
} 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 取消冒泡兼容
/**
* @description 取消冒泡兼容
* @author fengchunqi
* @date 2021-07-21 15:23:20
* @param {Event} e
*/
function stopBubble(e) {
if (e && e.stopPropagation) {
e.stopPropagation();
} else {
// IE
window.event.cancelBubble = true;
}
} 1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
# JavaScript中One-Liner技巧
// 获取字符串中的字符数
const characterCount = (str, char) => str.split(char).length - 1;
// 检查对象是否为空 Reflect.ownKeys返回所有类型的键名,包括常规键名和Symbol键名
const isEmpty = (obj) => Reflect.ownKeys(obj).length === 0 && obj.constructor === Object;
// 等待一定时间后执行
const wait = async (milliseconds) => new Promise((resolve) => setTimeout(resolve, milliseconds));
// 获取两个日期之间的天差
const daysBetween = (date1, date2) => Math.ceil(Math.abs(date1 - date2) / (1000 * 60 * 60 * 24));
// 在元素后插入一串 HTML
const insertHTMLAfter = (html, el) => el.insertAdjacentHTML('afterend', html);
// 乱序数组
const shuffle = (arr) => arr.sort(() => 0.5 - Math.random());
// 在网页上获取选定的文本
const getSelectedText = () => window.getSelection().toString();
// 获取一个随机布尔值
const getRandomBoolean = () => Math.random() >= 0.5;
// 50/50的机会返回true或false。因为生成的随机数大于0.5的概率等于较小的概率
// 计算数组的平均值
const average = (arr) => arr.reduce((a, b) => a + b) / arr.length; 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# 生成随机字符串
function uuid() {
return Math.random().toString(36).slice(2);
// return Number(Math.random().toString().substr(2, length) + Date.now()).toString(36);
} 1
2
3
2
3
# 数字格式化千分位
/**
* @description 数字格式化千分位
* @author fengchunqi
* @date 2021-09-10 09:34:04
* @param {number} num
* @param {boolean} [decimal=false] 是否处理小数部分,num为整数时无效
*/
function toThousands(num, decimal = false) {
// 如果只处理整数(不含小数点),正向断言则是后面数字个数为3的倍数并且字符串结尾
if (String(num).indexOf('.') === -1) {
const reg1 = /(\d)(?=(\d{3})+$)/g;
return String(num).replace(reg1, '$1,');
}
// 整数部分正向先行断言,指针从前往后,只有当后面数字个数是3的倍数加上小数点是匹配,在数字后面加逗号
const reg = /(\d)(?=(\d{3})+\.)/g;
if (decimal) {
// 需要正向先行断言处理整数部分,正向后行断言处理小数部分
// 匹配前面是小数点且数字个数是3的倍数的数字,在前面加逗号
const reg2 = /(?<=\.(\d{3})+)(\d)/g;
return String(num).replace(reg, '$1,').replace(reg2, ',$2');
}
return String(num).replace(reg, '$1,');
} 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
new Intl.NumberFormat().format(num)。(只会保留3位小数)Number(num).toLocaleString('en-us')。(只会保留3位小数)Number(num).toFixed(3).replace(/(\d)(?=(\d{3})+\.)/g, '$1,')。(只会保留3位小数且整数会变成浮点数)String(num).replace(/(\d)(?=(\d{3})+$)/g, '$1,')。(只处理整数)String(num).replace(/(\d)(?=(\d{3})+\.)/g, '$1,').replace(/(?<=\.(\d{3})+)(\d)/g, ',$2')。(可以处理整数和浮点数,且精度不会丢失)
# 光标所在位置插入字符
/**
* @description 光标所在位置插入字符
* @author fengchunqi
* @date 2021-09-14 14:47:59
* @param {HTMLElement} dom
* @param {string} val
*/
function insertAtCursor(dom, val) {
if (document.selection) {
dom.focus();
const sel = document.selection.createRange();
sel.text = val;
sel.select();
} else if (dom.selectionStart || dom.selectionStart === '0') {
const startPos = dom.selectionStart;
const endPos = dom.selectionEnd;
const restoreTop = dom.scrollTop;
dom.value = dom.value.substring(0, startPos) + val + dom.value.substring(endPos, dom.value.length);
if (restoreTop > 0) {
dom.scrollTop = restoreTop;
}
dom.focus();
dom.selectionStart = startPos + val.length;
dom.selectionEnd = startPos + val.length;
} else {
dom.value += val;
dom.focus();
}
} 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# 求数组的最大子数组和
/**
* @description 求数组的最大子数组和
* @author fengchunqi
* @date 2021-09-17 11:06:06
* @param {number[]} arr
* @returns {number}
*/
function maxSequence(arr) {
let sum = 0;
return arr.reduce((prev, cur) => {
sum = Math.max(sum + cur, 0);
return Math.max(sum, prev);
}, 0);
// 有个问题就是全是负数得到的0
} 1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
# 数字金额大写转换
/**
* @description 数字金额大写转换
* @author fengchunqi
* @date 2021-09-23 10:00:55
* @param {number|string} value
* @returns {string}
*/
function transformMoney(value) {
let str = '';
try {
const dw = ['零', '壹', '贰', '叁', '肆', '伍', '陆', '柒', '捌', '玖']; // 整数部分用
const dw1 = ['拾', '佰', '仟']; // 小单位
const dw2 = ['', '万', '亿']; // 大单位 前四位没有单位 5-8位表示万 再往前就是亿
// 分离整数与小数
const source = String(value).split('.');
const num = source[0];
const dig = source[1];
// 转换整数部分
let k1 = 0; // 计小单位
let k2 = 0; // 计大单位
let sum = 0;
const len = num.length; // 整数的长度
let i = 1;
for (i = 1; i <= len; i++) {
// 从后往前取得某个位数上的数字
const xxx = num.charAt(len - i);
let bn = 0;
// 如果当前位前面还有数字
if (len - i - 1 >= 0) {
// 取得某个位前一位上的数字
bn = Number(num.charAt(len - i - 1));
}
sum += Number(xxx);
if (sum !== 0) {
// 取得该数字对应的大写数字,并插入到str字符串的前面
str = dw[Number(xxx)].concat(str);
// 如果当前位已经是0了 sum应该置零 防止再次进入这个判断,在大写时出现两个零 如果 4001
if (xxx === '0') sum = 0;
}
// 当前位前面还有数字
if (len - i - 1 >= 0) {
if (k1 !== 3) { // 加小单位
if (bn !== 0) { // 如果前面那位不是0
// 加上的是前面那一位的位权
str = dw1[k1].concat(str);
}
k1++;
} else { // 不加小单位,加大单位
// 每四位为一组 等于3就进位 并且小单位复位
k1 = 0;
const temp = str.charAt(0);
if (temp === '万' || temp === '亿') {
// 若大单位前没有数字则舍去大单位
str = str.substr(1, str.length - 1);
}
str = dw2[k2].concat(str);
sum = 0;
}
}
if (k1 === 3) {
// 小单位到千则大单位进一
k2++;
}
}
// 转换小数部分
let strdig = '';
if (dig) {
const jiao = dig.charAt(0);
if (jiao !== 0) {
strdig += `${dw[Number(jiao)]}角`; // 加数字
}
const fen = dig.charAt(1);
if (fen !== 0) {
strdig += `${dw[Number(fen)]}分`; // 加数字
}
}
str += `元${strdig}`;
} catch (e) {
throw new Error(`transformMoney fail: ${e}`);
}
return str;
} 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
# 日期转换
/**
* @param {string} fmt yyyy-MM-dd HH:mm:ss
* @returns {string}
*/
// eslint-disable-next-line no-extend-native
Date.prototype.format = function (fmt = 'yyyy-MM-dd HH:mm:ss') {
const year = this.getFullYear();
const month = this.getMonth() + 1;
const day = this.getDate();
const hour = this.getHours();
const minutes = this.getMinutes();
const seconds = this.getSeconds();
const milliseconds = this.getMilliseconds();
const week = this.getDay();
const weekData = ['日', '一', '二', '三', '四', '五', '六'];
const data = {
yyyy: year, // 年:四位
yy: `${year}`.slice(-2), // 年:两位
MM: month, // 月:补零
M: month,
dd: day, // 日:补零
d: day,
HH: hour, // 24小时制:补零
H: hour,
hh: hour, // 12小时制:补零
h: hour,
mm: minutes, // 分:补零
m: minutes,
ss: seconds, // 秒:补零
s: seconds,
S: milliseconds, // 毫秒
a: 'am', // 上午/下午
A: 'AM',
W: `星期${weekData[week]}`, // 星期
};
if (month < 10) data.MM = `0${month}`;
if (day < 10) data.dd = `0${day}`;
if (hour < 10) data.HH = `0${hour}`;
if (minutes < 10) data.mm = `0${minutes}`;
if (seconds < 10) data.ss = `0${seconds}`;
if (hour > 12) {
data.h = hour % 12;
data.a = 'pm';
data.A = 'PM';
}
if (data.h < 10) data.hh = `0${data.h}`;
const reg = /(y*)(M*)(d*)(H*)(h*)(m*)(s*)(S?)(a?)(A?)(W?)/g;
return fmt.replace(reg, (match) => data[match] || match);
}; 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
# 检测是否IOS
/**
* @description 检测设备是否IOS
* @author fengchunqi
* @date 2022-02-18 11:51:29
* @returns {boolean}
*/
function isIOS() {
const u = navigator.userAgent;
return !!u.match(/\(i[^;]+;( U;)? CPU.+Mac OS X/); // ios终端
// navigator.platform 已经废弃
// return (/iPad|iPhone|iPod/.test(navigator.platform) // IOS 12及以前
// || (navigator.platform === 'MacIntel' && navigator.maxTouchPoints > 1)) // iPad IOS 13
// && !window.MSStream;
// !window.MSStream是为了不错误地检测到 IE11
} 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 检测是否Android
/**
* @description 检测设备是否Android
* @author fengchunqi
* @date 2022-02-18 11:51:29
* @returns {boolean}
*/
function isAndroid() {
const u = navigator.userAgent;
return u.indexOf('Android') > -1 || u.indexOf('Adr') > -1; // android终端
} 1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
# 使用instranceof判断基本类型
class PrimitiveNumber {
static [Symbol.hasInstance](x) {
return typeof x === 'number';
}
}
console.log(111 instanceof PrimitiveNumber); // true 1
2
3
4
5
2
3
4
5
# awaitWrap捕获async/await错误
/**
* @description
* @author fengchunqi
* @date 2024-08-26 11:50:56
* @param {Promise<any>} promise
* @returns {Promise<[null, any] | [any, null]>}
*/
async function awaitwrap(promise) {
try {
const res = await promise;
return [null, res];
} catch (err) {
return [err, null];
}
}
const fetchData = () => new Promise((resolve, reject) => {
setTimeout(() => {
resolve('fetch data is me');
}, 1000);
});
const resp = async () => {
const [err, res] = await awaitwrap(fetchData());
};
// ts版本
/*
async function awaitwrap<T>(promise: Promise<T>): Promise<[Error | null, T | null]> {
try {
const res = await promise;
return [null, res];
} catch (err) {
return [err, null];
}
}
*/ 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35