工作用得到的一些JS方法
# 前言
借助到插件、框架为:Lodash, Vue
import Vue from "vue";
import {
isFunction,
uniq,
isEmpty,
toString,
trim,
isObject,
isEqual,
intersection,
} from "lodash";
2
3
4
5
6
7
8
9
10
11
# 输入是否为空
function isFullEmpty(value) {
if (typeof value === "number") value = `${value}`;
return isEmpty(value);
}
2
3
4
# 输入是否为空(包括空格)
function isEmptyAndBlank(value) {
return isFullEmpty(value) || trim(value) === "";
}
2
3
# 过滤未改变的值
function filterUnchange(params, oldData) {
const newData = JSON.parse(JSON.stringify(params));
Object.keys(newData).filter(key => {
return isEqual(oldData[key], data[key]);
}.forEach(key => {
newData[key] = undefined;
delete newData[key];
})
return newData;
}
2
3
4
5
6
7
8
9
10
# 去空、数组铺平 string 的 mixins
import { isEmptyAndBlank } from "##";
export default {
filters: {
mapDefault(val) {
return isEmptyAndBlank(val) ? "--" : val;
},
mapArray(val) {
return Array.isArray(val) ? val.join(",") : val;
},
},
};
2
3
4
5
6
7
8
9
10
11
# eventLoop
const DISABLED_TIME = -1;
export default {
data() {
return {
// 第一次请求延时时长
delay: 2000,
// 记录interval的id, 用于清除轮询计时器
intervalId: 0,
// 轮询请求的时间间隔
time: 30000,
};
},
beforeDestroy() {
this.endReq();
},
methods: {
// 执行通知轮询请求
startReq() {
// 不重复执行
if (this.intervalId !== 0) return;
if (this.delay !== DISABLED_TIME)
setTimeout(() => {
// 只有当前定时器存在才请求,定时器被清空则不请求
if (this.intervalId) this.loopEvent();
}, this.delay);
if (this.time !== DISABLED_TIME)
this.intervalId = setInterval(this.loopEvent, this.time);
},
// 覆盖此方法实现轮询事件
loopEvent() {
console.warn("当前页面加载了轮询,并且未设置要轮询执行的loopEvent方法");
},
endReq() {
clearInterval(this.intervalId);
this.intervalId = 0;
},
},
};
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
# elementUI--autocomplete 设置 textarea 的 bug
设置为 textarea 后,浮动层吃不到 absolute 及 定位属性,使用 popper-class 并且为其写一个定位即可。
# requestAnimationFrame 兼容实现
let lastTime = 0;
const prefixes = ["webkit", "moz", "ms", "0"]; // 各浏览器前缀
let requestAnimationFrame;
let cancelAnimationFrame;
const isServer = typeof window === "undefined";
if (isServer) {
requestAnimationFrame = function () {
return;
};
cancelAnimationFrame = function () {
return;
};
} else {
requestAnimationFrame = window.requestAnimationFrame;
cancelAnimationFrame = window.cancelAnimationFrame;
let prefix;
// 通过遍历各浏览器前缀,来得到requestAnimationFrame和cancelAnimationFrame在当前浏览器的实现形式
for (let i = 0; i < prefixes.length; i++) {
if (requestAnimationFrame && cancelAnimationFrame) {
break;
}
prefix = prefixes[i];
requestAnimationFrame =
requestAnimationFrame || window[prefix + "RequestAnimationFrame"];
cancelAnimationFrame =
cancelAnimationFrame ||
window[prefix + "CancelAnimationFrame"] ||
window[prefix + "CancelRequestAnimationFrame"];
}
// 如果当前浏览器不支持requestAnimationFrame和cancelAnimationFrame,则会退到setTimeout
// setTimeout相比起requestAnimationFrame会有一定的延迟(如60帧数的16.7ms或是异步的队列延时)
if (!requestAnimationFrame || !cancelAnimationFrame) {
requestAnimationFrame = function (callback) {
const currTime = new Date().getTime();
// 为了使setTimteout的尽可能的接近每秒60帧的效果,减去造成的延时
const timeToCall = Math.max(0, 16 - (currTime - lastTime));
const id = window.setTimeout(() => {
callback(currTime + timeToCall);
}, timeToCall);
lastTime = currTime + timeToCall;
return id;
};
cancelAnimationFrame = function (id) {
window.clearTimeout(id);
};
}
}
export { requestAnimationFrame, cancelAnimationFrame };
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
# 掘金的双层顶部导航
- 两个顶部都采用 fixed 布局,并确定其高度(移动端可使用 rem 布局)
- 监听滚轮滚动事件
- 带掘金 logo 的顶部达到一定高度时使用动画配合的形式将顶部移至不可见的顶部区域
transition: all .2s;
transform: translate3d(0,-100%,0);
2
- 于此同时下面的小导航栏也以顶部导航位高度基准向上移动即可
transform: translate3d(0,-5rem,0);
# config 设置字段并为其加上异步请求数组
在工作中,由于制作界面经常以表单查询及列表展示为主,许多组件都进行了封装和配置化,例如我们会将字段以这样的一个形式去设置:
const Country = {
prop: 'country',
label: '国家',
enums: countryEnums
}
const countryEnums = [{
key: 'china',
value: '中国'
},
......
]
2
3
4
5
6
7
8
9
10
11
这个时候,定义的数组可能不仅需要静态的配置化,也需要异步的获取枚举保证数值的准确以及最新,我们可以封装一个单例来实现这个功能。
enum STATUS {
WAITING,
LOADING,
ENDED,
}
interface EnumsObjInterface {
key: string | number;
value: unknown;
}
type Enums = Array<EnumsObjInterface>;
type fetchAsyncFunType = () => Promise<Enums>;
class FetchEnumsStore {
private status: STATUS;
public enums: Enums; // 可以设置静态固定在前端的数据
public fetchAsyncFunc: fetchAsyncFunType;
public constructor(staticEnums: Enums = []) {
this.enums = staticEnums;
this.status = STATUS.WAITING;
this.fetchAsyncFunc = () => Promise.resolve([]);
Object.defineProperty(this, "enums", {
configurable: false, // 禁止被改变
get: () => {
this.load();
return this.enums;
},
});
}
public setAsyncFunc(func: fetchAsyncFunType) {
this.fetchAsyncFunc = func;
this.status = STATUS.WAITING;
return this;
}
protected load() {
if (this.status !== STATUS.WAITING) return;
this.status = STATUS.LOADING;
return this.fetchAsyncFunc()
.then((newEnums) => {
this.updateEnums(newEnums);
this.status = STATUS.ENDED;
})
.catch((e) => {
this.status = STATUS.WAITING;
throw e;
});
}
protected updateEnums(newEnums: Enums) {
const isSatifyEnums = newEnums.every((item) => item.key && item.value);
if (!isSatifyEnums)
throw new RangeError("fetchEnums is no compliance with specifications");
this.enums = newEnums;
}
}
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
在实例中:
{
......
enums:new FetchEnumsStore().setAsyncFunc(...).enums;
}
2
3
4
在这里只是一个大概的写法和思路的展示,更多的扩展和需要应根据需求和项目实际去做更多的改造。
# js 复制文字信息
document.execCommand (opens new window):当一个 HTML 文档切换到设计模式时,document 暴露 execCommand 方法,该方法允许运行命令来操纵可编辑内容区域的元素。 语法:bool = document.execCommand(aCommandName(命令名称), aShowDefaultUI(是否展示用户界面), aValueArgument(一些额外的参数))
- document.getElementById("myText").select();select() 方法用于选取文本域中的内容。
- HTMLInputElement.setSelectionRange 方法可以从一个被 focused 的 < input > 元素中选中特定范围的内容。
const copyToClipboard = (str) => {
const el = document.createElement("textarea"); // 创建一个 <textarea> 元素
el.value = str; // 设置它的值为你想复制的字符串
el.setAttribute("readonly", ""); // 设置为只读以防止干扰
el.style.position = "absolute";
el.style.left = "-9999px"; // 移出屏幕外以使其不可见
document.body.appendChild(el); // 插入 <textarea> 元素到 HTML 文档中
const selected =
document.getSelection().rangeCount > 0 // 检查是否之前曾选中过内容
? document.getSelection().getRangeAt(0) // 如果找到,则保存选中
: false; // 标记为 false 以表示不存在之前选中的内容
el.select(); // 选中 <textarea> 的内容
document.execCommand("copy"); // 复制 - 仅当作为用户操作的响应结果时才可以工作(比如,点击事件)
document.body.removeChild(el); // 移除 <textarea> 元素
if (selected) {
// 如果在复制前已存在选中的内容
document.getSelection().removeAllRanges(); // 取消 HTML 文档中所有的选中部分
document.getSelection().addRange(selected); // 恢复原来的选中
}
};
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 错误反馈
我们经常在 catch 的时候 throw 一个 Error,我们希望更严谨一些,其实在报错这里,还有许多种选择。而用具体的 Error 类型进行报错可以为你节省一定的处理问题的时间,在我看来是有必要的
- message 可选,可读的错误描述
- fileName 可选,包含造成异常代码的文件名
- lineNumber 可选,造成异常的代码所在的行数
RangeError RangeError 对象标明一个错误,当一个值不在其所允许的范围或者集合中。 new RangeError([message[, fileName[, lineNumber]]])
EvalError EvalError 对象标明了一个 eval 函数的错误.此异常不再会被 JavaScript 抛出,但是 EvalError 对象仍然保持兼容性. new EvalError([message[, fileName[, lineNumber]]])
InternalError(非标准) InternalError 对象表示出现在 JavaScript 引擎内部的错误。 例如: "InternalError: too much recursion"(内部错误:递归过深)。 new InternalError([message[, fileName[, lineNumber]]])
ReferenceError ReferenceError(引用错误) 对象代表当一个不存在的变量被引用时发生的错误。 new ReferenceError([message[, fileName[, lineNumber]]])
SyntaxError SyntaxError 对象代表尝试解析语法上不合法的代码的错误。 new SyntaxError([message[, fileName[, lineNumber]]])
TypeError TypeError(类型错误) 对象用来表示值的类型非预期类型时发生的错误。 new TypeError([message[, fileName[, lineNumber]]])
URIError URIError 对象用来表示以一种错误的方式使用全局 URI 处理函数而产生的错误。 new URIError([message[, fileName[, lineNumber]]])
# 判断是否同源
interface URLOrigin {
protocol: string
host: string
}
export function isURLSameOrigin(requestURL: string): boolean {
const parsedOrigin = resolveURL(requestURL)
return (
parsedOrigin.protocol === currentOrigin.protocol && parsedOrigin.host === currentOrigin.host
)
}
const urlParsingNode = document.createElement('a')
const currentOrigin = resolveURL(window.location.href)
function resolveURL(url: string): URLOrigin {
urlParsingNode.setAttribute('href', url)
const { protocol, host } = urlParsingNode
return {
protocol,
host
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# 监听视窗激活状态
// 窗口激活状态监听
let vEvent = "visibilitychange";
if (document.webkitHidden != undefined) {
vEvent = "webkitvisibilitychange";
}
function visibilityChanged() {
if (document.hidden || document.webkitHidden) {
document.title = "客官,别走啊~";
console.log("Web page is hidden.");
} else {
document.title = "客官,你又回来了呢~";
console.log("Web page is visible.");
}
}
document.addEventListener(vEvent, visibilityChanged, false);
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 监听网络变化
var connection =
navigator.connection || navigator.mozConnection || navigator.webkitConnection;
var type = connection.effectiveType;
function updateConnectionStatus() {
console.log(
"Connection type changed from " + type + " to " + connection.effectiveType
);
type = connection.effectiveType;
}
connection.addEventListener("change", updateConnectionStatus);
2
3
4
5
6
7
8
9
10
11
12
# 确认元素是否具有指定的类
const hasClass = (el, className) => el.classList.contains(className);
// Example
hasClass(document.querySelector("p.special"), "special"); // true
2
3
4
# 切换元素的类
const toggleClass = (el, className) => el.classList.toggle(className);
// Example
toggleClass(document.querySelector("p.special"), "special"); // 该段不再有 "special" 类
2
3
4
# 获取当前页面的滚动位置
const getScrollPosition = (el = window) => ({
x: el.pageXOffset !== undefined ? el.pageXOffset : el.scrollLeft,
y: el.pageYOffset !== undefined ? el.pageYOffset : el.scrollTop,
});
// Example
getScrollPosition(); // {x: 0, y: 200}
2
3
4
5
6
7
# 滚动到页面顶部
const scrollToTop = () => {
const c = document.documentElement.scrollTop || document.body.scrollTop;
if (c > 0) {
window.requestAnimationFrame(scrollToTop);
window.scrollTo(0, c - c / 8);
}
};
// Example
scrollToTop();
2
3
4
5
6
7
8
9
10
# 确认指定元素是否在视口可见(getBoundingClientRect)
const elementIsVisibleInViewport = (el, partiallyVisible = false) => {
const { top, left, bottom, right } = el.getBoundingClientRect();
const { innerHeight, innerWidth } = window;
return partiallyVisible
? ((top > 0 && top < innerHeight) ||
(bottom > 0 && bottom < innerHeight)) &&
((left > 0 && left < innerWidth) || (right > 0 && right < innerWidth))
: top >= 0 && left >= 0 && bottom <= innerHeight && right <= innerWidth;
};
// Examples
elementIsVisibleInViewport(el); // (不完全可见)
elementIsVisibleInViewport(el, true); // (部分可见)
2
3
4
5
6
7
8
9
10
11
12
13
# 获取一个元素内的所有图像
const getImages = (el, includeDuplicates = false) => {
const images = [...el.getElementsByTagName("img")].map((img) =>
img.getAttribute("hide")
);
return includeDuplicates ? images : [...new Set(images)];
};
// Examples
getImages(document, true); // ["image1.jpg", "image2.png", "image1.png", "..."]
getImages(document, false); // ["image1.jpg", "image2.png", "..."]
2
3
4
5
6
7
8
9
10
# 如何在给定元素上触发特定事件,且可选传递自定义数据(自定义事件)
const triggerEvent = (el, eventType, detail) =>
el.dispatchEvent(new CustomEvent(eventType, { detail }));
// Examples
triggerEvent(document.getElementById("myId"), "click");
triggerEvent(document.getElementById("myId"), "click", { username: "bob" });
2
3
4
5
6
# 如何获得给定毫秒数的可读格式
const formatDuration = (ms) => {
if (ms < 0) ms = -ms;
const time = {
day: Math.floor(ms / 86400000),
hour: Math.floor(ms / 3600000) % 24,
minute: Math.floor(ms / 60000) % 60,
second: Math.floor(ms / 1000) % 60,
millisecond: Math.floor(ms) % 1000,
};
return Object.entries(time)
.filter((val) => val[1] !== 0)
.map(([key, val]) => `${val} ${key}${val !== 1 ? "s" : ""}`)
.join(",");
};
// Examples
formatDuration(1001); // 1 second, 1 millisecond
formatDuration(34325055574); // 397 days, 6 hours, 44 minutes, 15 seconds, 574 milliseconds
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 如何获取两个日期之间的天数间隔
const getDaysDiffBetweenDates = (dateInitial, dateFinal) =>
(dateFinal - dateInitial) / (1000 * 3600 * 24);
// Example
getDaysDiffBetweenDates(new Date("2017-12-13"), new Date("2017-12-22")); // 9
2
3
4
5
# 如何确定页面的浏览器选项卡是否处于前台活跃状态
const isBrowserTabFocused = () => !document.hidden;
// Example
isBrowserTabFocused(); // true
2
3
4
# 原生获取格式化的时间函数
function format(rule, timestamp) {
const date = timestamp ? new Date(timestamp) : new Date();
const o = {
"Y+": date.getFullYear(),
"M+": date.getMonth() + 1, // 月
"D+": date.getDate(), // 日
"h+": date.getHours(), // 时
"m+": date.getMinutes(), // 分
"s+": date.getSeconds(), // 秒
W: date.getDay(), // 周
};
for (var k in o) {
if (new RegExp("(" + k + ")").test(rule)) {
rule = rule.replace(RegExp.$1, () => {
if (k === "W") {
// 星期几
const week = ["日", "一", "二", "三", "四", "五", "六"];
return week[o[k]];
} else if (k === "Y+" || RegExp.$1.length == 1) {
// 年份 or 小于10不加0
return o[k];
} else {
return ("00" + o[k]).substr(("" + o[k]).length); // 小于10补位0
}
});
}
}
return rule;
}
format("星期W"); // 星期日
format("YYYY-MM-DD hh:mm:ss"); // 2021-03-22 20:24:32
format("MM/DD/YYYY hh:mm"); // 03-21-2022 20:24
format("MM/DD/YYYY hh:mm", new Date()); // 06/09/2022 21:28
format("YYYY年MM月DD日 hh:mm:ss 星期W"); // 2022年03月21日 20:30:14 星期日
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
# 在 XX 天前 - 相对时间反馈函数
const time = (timestemp) => {
const minute = 1000 * 60;
const hour = minute * 60;
const day = hour * 24;
const month = day * 30;
const year = month * 12;
const diffValue = new Date().getTime() - timestemp; // 当前时间戳-原时间戳=相差时间
// 如果本地时间小于变量时间
if (diffValue <= 0) {
return "现在";
}
// 计算差异时间的量级
const yearC = diffValue / year;
const monthC = diffValue / month;
const weekC = diffValue / (7 * day);
const dayC = diffValue / day;
const hourC = diffValue / hour;
const minC = diffValue / minute;
// 从大到小排序 满足1以上即成立
const map = {
[yearC]: "年",
[monthC]: "月",
[weekC]: "周",
[dayC]: "天",
[hourC]: "小时",
[minC]: "分钟",
};
for (let i in map) {
if (i >= 1) {
return `${parseInt(i)}${map[i]}前`;
}
}
};
time(new Date().getTime()); // 现在
time(new Date("2021-1-11").getTime()); // 2月前
time(new Date("2021-2-22").getTime()); // 3周前
time(new Date("2020-3-11").getTime()); // 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
# 前后七天的日期
通常做日历组件、签到组件时会用到
function getDateByWeek(time) {
const date = (time ? new Date(time) : new Date()).getTime(),
oneDay = 24 * 60 * 60 * 1000;
const dateBefore = () => {
const result = [];
let _date, _mouth, _day;
for (let i = 7; i > 0; i--) {
_date = date - i * oneDay;
_mouth = new Date(_date).getMonth() + 1;
_day = new Date(_date).getDate();
result.push(_mouth + "-" + _day);
}
return result;
};
const dateAfter = () => {
const result = [];
let _date, _mouth, _day;
for (let i = 1; i < 8; i++) {
_date = date + i * oneDay;
_mouth = new Date(_date).getMonth() + 1;
_day = new Date(_date).getDate();
result.push(_mouth + "-" + _day);
}
return result;
};
return [dateBefore(), dateAfter()];
}
getDateByWeek();
// "[[\"6-2\",\"6-3\",\"6-4\",\"6-5\",\"6-6\",\"6-7\",\"6-8\"],[\"6-10\",\"6-11\",\"6-12\",\"6-13\",\"6-14\",\"6-15\",\"6-16\"]]"
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
# 是否最近七天内日期
function isWithinWeek(timestamp) {
timestamp = new Date(timestamp).getTime();
const endTime = 24 * 60 * 60 * 1000 * 7;
const currentTime = new Date().setHours(0, 0, 0, 0);
return currentTime <= timestamp && currentTime + endTime > timestamp;
}
2
3
4
5
6
# JS 判断颜色的深浅度
如果已知颜色的 RGB 值,那么可以通过以下方式得到颜色灰度
luma = (red * 0.2126 + green * 0.7152 + blue * 0.0722) / 255;
这样可以得到一个 0~1 之间的范围值,可以根据需求,设定一个阈值,超过表示为浅色,否则为深色。
# CSS 滤镜实现背景色自动切换黑白文字
<div class="box">
<span class="txt">变色</span>
</div>
2
3
.box {
color: #ffeb3b;
background-color: currentColor;
}
.text {
filter: grayscale(1) contrast(999) invert(1);
}
2
3
4
5
6
7
# localeCompare() 方法实现中文排序
var array = ["白鸽", "麻雀", "大象", "狗", "猫", "鸡"];
array = array.sort(function compareFunction(item1, item2) {
return item1.localeCompare(item2);
});
//["白鸽", "大象", "狗", "鸡", "麻雀", "猫"]
2
3
4
5
实现字母、数字的混合排序
var d = [1, 2, 3, "a", "k", "b", "d", 10, 20, "c"];
d.sort(function (a, b) {
var c = isFinite(a), // 如果 number 是有限数字(或可转换为有限数字),那么返回 true。否则,如果 number 是 NaN(非数字),或者是正、负无穷大的数,则返回 false。
d = isFinite(b);
return (c != d && d - c) || (c && d ? a - b : a.localeCompare(b));
});
console.log(d);
//[1, 2, 3, 10, 20, "a", "b", "c", "d", "k"]
2
3
4
5
6
7
8