一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第7天,点击查看活动详情。
1. 随机数/随机 id
function getUuid(len = 32) {
const S4 = () => Math.random().toString(16).substring(2);
let res = "";
while (res.length < len) {
res += S4().substring(0, len - res.length);
}
return res;
}
代码分析
Math.random().toString(16)
获取随机数,转成 16 进制substring(2)
前两位是0.
所以从第二位之后开始取while (res.length < len)
循环,直到取到符合长度要求的字符串substring(0, len - res.length)
,这一步也很关键,特别是最后一次循环,确保不会多截取
2. 获取文本确切宽度
function getActualWidthOfChars(text, options = {}) {
const { size = 14, family = "Microsoft YaHei" } = options;
const canvas = document.createElement("canvas");
const ctx = canvas.getContext("2d");
ctx.font = `${size}px ${family}`;
const metrics = ctx.measureText(text);
return Math.abs(metrics.actualBoundingBoxLeft) + Math.abs(metrics.actualBoundingBoxRight);
}
代码分析
将文本绘制到 canvas 上,再使用 canvas 的measureText
api 获取物理宽度。具体可参考我的上篇文章面试官:你是如何获取文本宽度的?
3. 判断某个对象是否发生变化
-
可以通过序列化的方式
JSON.stringify(objA) === JSON.stringify(_objA)
在页面未保存触发拦截的文章中有使用到这个方法,判断表单内容是否有修改未保存 可以参考这篇文章如何实现页面跳转拦截?
-
更多情况下我们不需要判断整个对象是否变化,而是对象里面的某个属性
这里直接再复杂一丢丢,判断数组里面每个属性是否发生变化
equalProps(newV, oldV, props) {
if (!newV || !oldV || newV.length !== oldV.length) return false;
return newV.every((e, i) => JSON.stringify(e[props]) === JSON.stringify(oldV[i][props]));
}
代码分析
- 首先如果
newV, oldV
有一个为 null 或 undefined 或空,或者二者长度不一致,直接返回 false,props
一定发生了变化 - 循环遍历
newV, oldV
,只需要遍历一遍,通过i
下标访问另一个,序列化方式比较是否相等 - every 确保只要找到一个不满足判断条件的情况,立即终止。(也可以选择用 some)
我这里有个实际的例子,帮助大家体会下:
watch: {
cardList: {
handler: function (newV, oldV) {
const equal = this.equalProps(newV, oldV, 'operators');
// 如果operators发生了变化,重绘缩略图
!equal && this.initImagesMap();
},
immediate: true,
deep: true,
}
}
功能描述: 监听cardList
的变化更新,重绘card 上的缩略图。
但是缩略图的数据存在于operators
上,当我们修改cardList
上的其他属性,因为是 deep,所以也会触发缩略图的重绘。
我们现在加了个收藏的属性,用户点击收藏图标,发现这个 所有card上的缩略图都重刷了一遍,体验极差。
用上equalProps
方法之后,就可以避免其他属性变化对缩略图的影响~
还有其他好办法吗?? 我本寄希望于 Lodash,文档翻了一遍没找到对应的方法。 大家如果有更好的方式,可以在评论区交流。
4. 巧用sort()调取目标元素到队首/位
先看一下我们常用的数组排序:
[4,3,9,5,2].sort(), ['b', 'a', 'c'].sort()
数字根据大小排序,字符串根据 unicode 码排序。
这些最基础的用法相信大家都比较熟悉了,不做赘述。
根据 MDN 的描述,其实 sort 是接收一个回调函数(a, b) => {}
,
回调返回值 等于 0: 顺序不变; 小于零:a 在前 b 在后, 大于 0:b 在前 a 在后;。
【这里可以记住 sort()不传参数等同于
(a,b) => a - b
;是从小到大排序的,帮助记忆】
[4,3,9,5,2].sort((a,b) => a - b)
,a, b
分别代表迭代的前一项和后一项。
基于以上,如何实现下面的需求:
这样的一个组件,需要向后端传递一个数组,当前选中的属性在前,折叠在下面的属性在后,并且每一项上包含该属性是正序还是逆序排列的属性。
数据结构长这样:
const ORDERLIST = [
{
label: "热度",
value: "HEAT",
order: "DESC",
},
{
label: "更新时间",
value: "UPDATE_TIME",
order: "DESC",
},
];
没错,把当前展示的属性移动到上面就可以了。
具体要怎么做呢?
从两项中查找目标并删除,然后再插入到数组前面? 能实现但显然不够优雅~
我们试着用 sort 来实现下:
ORDERLIST.map(e => ...).sort((a, b) => a.value === currentProp ? -1 : 1)
当 当前项等于目标值时,返回 -1, 小于零, a 会排在 b 的前面。
在每次两两比较的时候,目标对象都会排在另一个前面,也就意味着它一定会排在数组的第一个。(可以想下冒泡排序的原理)
所以即使数组存在很多项, 此方法也同样适用哦~
比如:
我们在下拉项目列表里面将当前项目提到第一个,刚好就可以用上面的排序方式!美滋滋~
5. 巧用 reduce 减少遍历次数
首先我们有这样的一份数据,要求从中筛选出包含 id 的菜单项并组合成数组返回。
const modules = {
modulesA: {
name: "modelA_name",
menus: [
{
id: "menu-A1",
name: "菜单项A-1",
},
{
name: "菜单项A-2",
},
],
},
modulesB: {
name: "modelB_name",
menus: [
{
id: "menu-B1",
name: "菜单项B-1",
},
],
},
};
同事的原始代码:
function allTasks() {
const _modules = Object.keys(modules).filter(
key => modules[key].menus && modules[key].menus.some(i => i.id)
);
let tasks = [];
_modules.forEach(key => {
tasks = tasks.concat(
modules[key].menus
.filter(e => !!e.id)
.map(e => {
return {
...e,
moduleCode: key,
};
})
);
});
return tasks;
}
reduce 优化后的代码:
function allTasks() {
return Object.keys(modules).reduce((res, key) => {
let item = [];
if (modules[key].menus && modules[key].menus.some(i => i.id)) {
item.push(
...modules[key].menus
.filter(e => !!e.id)
.map(e => {
return {
...e,
moduleCode: key,
};
})
);
}
return res.concat(item);
}, []);
}
我们成功从两次遍历缩减为了一次遍历,具体的细节就不展开了,仔细看下,还是比较好理解的。
总结
以上就是笔者在项目中经常使用的一些 javascript 小技巧,掌握这些技巧,确实能够在一定程度上提高开发效率。助力前端开发正常下班~
一抬头快十二点了,抓紧收尾睡觉了~
更文不易,如果对你有帮助还请点个赞支持下,帮助笔者输出更多优质文章哦~