- 将值转化为字符串
- 使用 $$ 来标记特殊属性名
- 使用 /* */ 来让代码右侧对齐
- 使用 Symbol.for() 来验证对象是否合法
- 使用 二进制数字 标记状态,便于汇总最终状态
key = '' + config.key
$$typeof: REACT_ELEMENT_TYPE
$$ 并不是什么特殊语法,使用 $$ 纯粹是为了在内部用来凸显、标记某个属性名
在调用该属性时和普通属性无任何区别:object.$$typeof
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;
是否合法?
这里说的 “是否合法” 是指该对象是由 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
);
}
请注意上述代码中的
object.$$typeof === REACT_ELEMENT_TYPE
,通过判断对象的 $$typeof 属性值与内置的 REACT_ELEMENT_TYPE 是否相同,以此来验证对象是否合法。
为什么需要验证?
答:担心对象并不是由 React 创建的,而是别人通过 JSON 动态创建的,因为使用 JSON 可以内嵌恶意代码(XSS 攻击)。
假设某个对象(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:Placement > 0b0000000000000000010
- b:Update > 0b0000000000000000100
那么汇总 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