0
点赞
收藏
分享

微信扫一扫

前端枚举这样玩,效率超高!就是容易被打

偶然间,我发现了一种

缓过神来,直呼“精妙!牛哔!效率贼高!”

尝试在公司的项目里用了用,确实感觉程序的执行效率变快了(当然,肯定有心理暗示)。

当周 ​​Code Review​​ 时,同事们惊了:

前端枚举这样玩,效率超高!就是容易被打_JavaScript

让我们看看就是怎样的一种玩法!

一、枚举定义

/**
* SKILLS:面试者的技能枚举
**/
const SKILLS = {
CSS: 1 ,
JS: 1 << 1,
HTML: 1 << 2,
WEB_GL: 1 << 3
}
复制代码

枚举的定义看起来平平无奇,但又有些奇怪,是吗?

解释下:

​<<​​ 符号是 ​​js​​ 中的位操作符,​​1 << N​​ 的意思是:将数字 ​​1​​​ 的位向左移动 ​​N​​ 位

1 << 1 // 2
// 二进制:1 => 10
1 << 2 // 4
// 二进制:10 => 100
...
1 << N // 2的N次方
复制代码

二、枚举使用

我们按照 “步骤一” 里的方式定义了枚举,那应该怎么使用呢?

假设一个场景:当我们面试一个前端同学时,我们会不断标记他有哪些技能,这个添加标记的过程如下:

let skills = 0
// 增加一项他会的技能
function addSkill(skill) {
skills = skills | skill // 加上
}

addSkill(SKILLS.CSS) // 1
addSkill(SKILLS.HTML) // 5
addSkill(SKILLS.WEB_GL) // 13
复制代码

完成了技能的添加,我们就需要在其他地方开销这个人具备什么技能了,开销过程如下:

// 判断他是否会 CSS
SKILLS.CSS & skills // 1 (0 代表不会,非0代表会)

// 判断他是否会 JS
SKILLS.JS & skills // 0 (0 代表不会,非0代表会)


// 判断他是否会 HTML 且会 WebGl
SKILLS.HTML & skills && SKILLS.WEB_GL & skills // 8 (0 代表不会,非0代表会)

// 判断他是否会 JS 或会 HTML
SKILLS.JS & skills || SKILLS.HTML & skills
复制代码

OK!基本能力具备了。

有同学要问了:“它有什么优点?可读性这么差!”

当然有优点,优点就在于:它效率 贼高

三、效率对比

为了验证它的“效率有多高”,我写了两个常见的“对照组”写法来进行验证。

常见写法一:数组式玩法

let skills = []
function addSkill(skill) {
if (!skills.includes(skill)) { // 判断技能术里是否有该技能
skills.push(skill)
}
}

addSkill(SKILLS.CSS) // 1
addSkill(SKILLS.HTML) // 5
addSkill(SKILLS.WEB_GL) // 13

skills.includes(SKILLS.CSS)
skills.includes(SKILLS.JS)
skills.includes(SKILLS.HTML) && skills.includes(SKILLS.WEB_GL)
skills.includes(SKILLS.JS) || skills.includes(SKILLS.HTML)
复制代码

这种写法,使用一个数组来存储技能枚举,再通过 ​​arr.includes()​​ 方法来判断枚举是否已被存储。

常见写法二:​​Map​​ 式玩法

let skills = {}
function addSkill(skill) {
if (!(skills[skill])) { // 判断技能术里是否有该技能
skills[skill] = true
}
}
addSkill(SKILLS.CSS) // 1
addSkill(SKILLS.HTML) // 5
addSkill(SKILLS.WEB_GL) // 13

skills[SKILLS.CSS]
skills[SKILLS.JS]
skills[SKILLS.HTML] && skills[SKILLS.WEB_GL]
skills[SKILLS.JS] || skills[SKILLS.HTML]
复制代码

这种方法则是通过 ​​{ [value]: true }​​ 的方式来存储枚举,然后再通过 ​​map[value]​​ 的方式进行取值,来判断枚举是否已被存储。

对比结果

猜猜看,三种方式的效率排名会怎样?

结果如下:

最快组:位运算组(本文推荐的玩法)


每秒执行 11 亿次


第二名:数组式玩法


每秒执行 3124 万次


最慢组:​​Map​​ 式玩法


每秒执行 952 万次


用例地址: ​​jsbench.me/4okyr97qi9/…​​

感兴趣的朋友可访问上面的链接进行自行验证。

按结果分析,​​最快​​ 与 ​​最慢​​ 之间甚至有 ​​2-3​​ 个数据级的差别。

四、原理分析(为啥这么快!)

本文推荐的“位运算式”为什么效率这么高?它又是怎么通过位运算完成上述内容的功能的?

4.1 定义原理

这得从 Javascript 中整型的数据格式说起,有符号整数使用 31 位表示整数的数值,用第 32 位表示整数的符号,0 表示正数,1 表示负数。数值范围为 [-2^31,2^31-1] ,即[ -2147483648, 2147483647]。
前端枚举这样玩,效率超高!就是容易被打_css_02
当我们定义枚举时,每个枚举都占领着不同位上的一个二进制值。

因此,这种方式仅仅适合少于等于31个枚举项 的场景。

4.2 存值原理

为什么 skills = skills | skill 这串代码能表示 skills​ 中增加了一个枚举项

|Javascript 中的位运算符:

当两个数值进行 | 运算时,会对每一位进行比较,任何一位上,如果两个数都为 0,则结果为 0;其余情况结果为 1;

例如:

1 << 1 | 1 << 2 // 010 | 100
// 结果为 110,即数字:6

1 | 1 << 3 // 0001 | 1000
// 结果为 1001,即数字:9
复制代码

因此,只要执行过 ​​skills = skills | skill ​​,其结果中,​​skill​​ 对应的那一位上一定存在一个 ​​1​​。

4.3 取值原理

为什么 SKILLS.CSS & skills 返回 非0 值时,可以代表 skills 上包含 SKILLS.CSS?反之则不包含?

&Javascript 中的运算符:
当两个数值进行 & 运算时,会对每一位进行比较,任何一位上如果两个数都为 1,则结果为 1;其余情况结果为 0;

例如:

1 & 9 // 0001 & 1001 = 0001 (1)

2 & 8 // 0010 & 1000 = 0000 (0)
复制代码

因此,​​SKILLS.CSS & skills​​ 返回 ​​非0​​ 值时,就能判定 ​​skills​​ 中一定存在 ​​SKILLS.CSS​​。

五、谁在用?(尤雨溪:正是在下)

以上用法我是从哪里看到的呢?

没错,正是从 vue3 源码中看到的。
源码地址:​​github1s.com/vuejs/core/…​​

在该源码中,vue3 通过此种方式定义枚举,开销枚举,非常值得我们学习。

ShapeFlags 的枚举定义方式:

export const enum ShapeFlags {
ELEMENT = 1,
FUNCTIONAL_COMPONENT = 1 << 1,
STATEFUL_COMPONENT = 1 << 2,
TEXT_CHILDREN = 1 << 3,
ARRAY_CHILDREN = 1 << 4,
SLOTS_CHILDREN = 1 << 5,
TELEPORT = 1 << 6,
SUSPENSE = 1 << 7,
COMPONENT_SHOULD_KEEP_ALIVE = 1 << 8,
COMPONENT_KEPT_ALIVE = 1 << 9,
COMPONENT = ShapeFlags.STATEFUL_COMPONENT | ShapeFlags.FUNCTIONAL_COMPONENT
}

结束

我是春哥

我热爱 vue.js , ElementUI , Element Plus 相关技术栈,我的目标是给大家分享最实用、最有用的知识点,希望大家都可以早早下班,并可以飞速完成工作,淡定摸鱼????。

可以在公众号里找到我:​​前端要摸鱼​​。

希望大家在 2022 变得更强。


举报

相关推荐

0 条评论