从React源码中看到的一些JS写法技巧
目录
小技巧
将值转化为字符串 使用 $$ 来标记特殊属性名 使用 /* */ 来让代码右侧对齐
大智慧
使用 Symbol.for() 来验证对象是否合法 使用 二进制数字 标记状态,便于汇总最终状态
小技巧
1、将值转化为字符串
key = '' + config.key2、使用 $$ 来标记特殊属性名
$$typeof: REACT_ELEMENT_TYPE并不是什么特殊语法,使用纯粹是为了在内部用来凸显、标记某个属性名 在调用该属性时和普通属性无任何区别:object.$$typeof
3、使用 /* */ 来让代码右侧对齐
export const NoFlags = 0b0000000000000000000;export const PerformedWork = 0b0000000000000000001;export const Placement = 0b0000000000000000010;export const Update = 0b0000000000000000100;export const PlacementAndUpdate = 0b0000000000000000110;为了让二进制值右侧对齐,可以通过添加 /* */ ,使用不同数量的空格占位,以便右侧对齐,修改后如下:
export const NoFlags = /* */ 0b0000000000000000000;export const PerformedWork = /* */ 0b0000000000000000001;export const Placement = /* */ 0b0000000000000000010;export const Update = /* */ 0b0000000000000000100;export const PlacementAndUpdate = /* */ 0b0000000000000000110;大智慧
1、使用 Symbol.for() 验证对象是否合法
是否合法?这里说的 “是否合法” 是指该对象是由 JS 创建的,而非 JSON 创建的。
为什么?因为 JSON 创建的对象可能内嵌不安全的代码
实现原理?利用 JSON 无法存在 Symbol 值的特性,来验证某属性是否值是否是 Symbol
知识点:通过 Symbol.for(str) 创建的 Symbol 实例,则可通过引用来实现多次对比。
具体如何实现?
第1步:创建一个内部的常量,例如 REACT_ELEMENT_TYPE
//在外部,声明一个 REACT_ELEMENT_TYPE 并具有默认值export let REACT_ELEMENT_TYPE = 0xeac7;//若当前 JS 支持 Symbol 则使用 Symbol.for() 修改 REACT_ELEMENT_TYPE 的值if (typeof Symbol === 'function') { REACT_ELEMENT_TYPE = Symbol.for('react.element');}//经过以上操作,我们对外导出 REACT_ELEMENT_TYPE //事实上目前绝大多数JS运行环境都支持 Symbol,所以实际中可以直接使用 Symbol.for() 来创建常亮,无需再做过多判断第2步:创建一个对象,并为其添加 特殊属性
const element = { $$typeof: REACT_ELEMENT_TYPE, ...}上述代码中,创建一个对象 element,并添加一个 $$typeof 的属性名,属性值为 REACT_ELEMENT_TYPE
第3步:当需要验证 某对象是否为内置合法对象时,则通过对比 $$typeof 来判断
function isValidElement(object) { return ( typeof object === 'object' && object !== null && object.$$typeof === REACT_ELEMENT_TYPE );}为什么需要验证?答:担心对象并不是由 React 创建的,而是别人通过 JSON 动态创建的,因为使用 JSON 可以内嵌恶意代码(XSS 攻击)。
2、使用 二进制数字 标记状态,便于汇总最终状态
假设某个对象(React中的节点),需要有以下几种状态:
NoFlags:不做任何操作 PerformedWork:已经完成操作 Placement:需要插入 Update:需要更新 PlacementAndUpdate:需要插入的同时,还需要更新 Deletion:需要删除 ContentReset:需要重置
按照一般的习惯,我们可能会使用不同的常量值来指不同的状态。例如:
NoFlags = 0、PerformedWork = 1、Placement = 2、Update = 3、PlacementAndUpdate = 4
当然可能不是数字,而是字符串。
模拟一个使用场景
假设这个对象的状态,是由 2 个处理函数 计算得出的:
A 函数计算结果为 a,表明是否需要插入 B 函数计算结果为 b,表明是否需要更新
那么这 2 个函数计算的结果 a、b 可能存在以下组合结果:
不需要插入,不需要更新 不需要插入,需要更新 需要插入,不需要更新 需要插入,需要更新
假设最终处理状态的函数为 do(),那么需要分别将 a 和 b 的值汇总在一起 传递给 do()。
至于如何 "汇总",可以采用:
do() 有多个参数 将 a b 组成一个组数,将该数组传递给 do()
有没有简单的汇总方式?
答:我们可以将 不同状态 对应的常量值类型,修改为 二进制数字。所谓 汇总 只不过是 进行 a + b,最终将计算结果(也是二进制值)传递给 do() 即可。
具体实施方式:
第1步:将不同状态的值 设置成 二进制数字
export const NoFlags = 0b0000000000000000000;export const PerformedWork = 0b0000000000000000001;export const Placement = 0b0000000000000000010;export const Update = 0b0000000000000000100;export const PlacementAndUpdate = 0b0000000000000000110;export const Deletion = 0b0000000000000001000;export const ContentReset = 0b0000000000000010000;第2步:汇总状态
假设 分别计算出需要更新的状态为:
那么汇总 a 和 b 就可以通过 二进制运算来得出最终状态值:
let result = NoFlags //先得到一个初始值 0 , NoFlags 即 什么也不操作//使用二进制 按位或操作,先汇总 a 的值result |= a//这行代码相当于:result = result | a,对于二进制来说相当于相加操作//再使用二进制 按位或操作,汇总 b 的值result |= b最终,result 的值即最终汇总的 状态对应的值。
假设同时需要插入和更新,那么上面最终汇总计算结果的值,就是 PlacementAndUpdate 的值 0b0000000000000000110
在 do() 函数中可以通过 switch 该值 得到最终所需的执行操作状态。
补充:二进制的按位操作
假设有两个二进制数字,他们分别是 B1,B2,其中某相同位置对应的值分别是 b1,b2。
b1、b2 的值只能是 0 或 1
那么对应的各种 按位操作符 的含义如下:
&:相当于 b1 & b2,只有 b1、b2 都为 1 时,最终计算值才会是 1 |:相当于 b1 | b2,只要 b1 或 b2 其中一个为 1,最终计算结果就为 1 ^:相当于 b1 !== b2,若 b1 b2 的值相同,则最终结算结果为 0,若不同 则最终计算结果为 1
以上操作都是针对 两个二进制数字 对比计算的,还有另外一个按位计算符 ~,但这个计算法是针对 一个二进制数据操作的。
把 二进制数字 转化成 32 位二进制数字 把得到的这个二进制数字进行 按位取反 把取反后的二进制数字转化为 浮点数
整个过程操作完成后,其实相当于目标数字 转化为负的32位数字再减1。
例如:~12 的结果为 -(12) -1 ,即 -13
同理,~-13 的结果为 -(-13) -1,即 12
夜雨聆风