1 前言
大家好,我是心锁,一枚23届准毕业生。
如果读者阅读过我其他几篇文章,就知道我近期在阅读React源码。
而阅读源码的终极目的还是应用,在这个想法下,我盘点了一些可以快速在工程中应用的( 或许冷门 )知识,希望读者可以get到
2 盘点
2.1 多层循环中跳出最外层
我曾在部分场景中,遇到多层循环需要一次性跳出的场景,虽然我已经找不到原场景了,但是还好并不妨碍我们给出一个demo。
代码语言:javascript复制function test() {
let baseCount = 5;
while (baseCount--) {
let count = 10;
while (count--) {
if (count === 5) {
// 如何在这里直接跳到最外层
}
}
}
console.log("test", baseCount);
}
test();
// test -1
那么如果我们要求将上述代码改成在count===5
时直接跳出循环应该怎么做?
改成这样?
这样并不能成功跳出。
代码语言:javascript复制function test() {
let baseCount = 5;
while (baseCount--) {
let count = 10;
while (count--) {
if (count === 5) {
break;
}
}
}
console.log("test", baseCount);
}
test();
// test -1
我们会发现,我们好像并不能实现在第二层whilte循环中直接跳出最外层,我之前一般会通过抽离函数的形式来实现。
代码语言:javascript复制function test() {
let baseCount = 5;
const whileFn = () => {
while (baseCount--) {
let count = 10;
while (count--) {
if (count === 5) {
return;
}
}
}
};
whileFn();
console.log("test", baseCount);
}
test();
// test 4
而React源码中,给了我一个非常眼前一亮的操作,而这其实是一个基础的JavaScript知识,被称为label
语法。
function test() {
let baseCount = 5;
baseWhile:while (baseCount--) {
let count = 10;
while (count--) {
if (count === 5) {
break baseWhile;
}
}
}
console.log("test", baseCount);
}
test();
// test 4
除了上述在while循环中使用,对于continue
、for
等语法也适用
function test() {
let baseCount = 5;
baseWhile: for (baseCount; baseCount > 0; baseCount--) {
let count = 10;
while (count--) {
if (count === 5) {
continue baseWhile;
}
}
}
console.log("test", baseCount);
}
test();
2.2 void 0
在阅读React的编译后代码时,发现代码中使用undefined
的地方统一都使用了void 0
而不是undefined
。
为什么呢,我当时就懵了,虽然我知道使用void 0===undefined
,但是不曾使用过void
这个关键字。
所以,我进行了一定的探索。最终发现了一个惊人的事件。
undefined
不是一个关键字,这玩意儿是全局变量的一个属性,在低版本浏览器中全局undefined可以被改写,在现代浏览器的局部作用域中同样可以被改写。
(function(){
var undefined = 10
console.log(undefined)
})() // 10
我的天啊,JavaScript你要不要看看你自己在做什么!!!
所以,建议使用babel将undefined
编译成void 0
亦或者统一使用void 0
2.3 判断异步返回/判断Promise对象
我们如何判断一个对象是异步(Promise)对象?
我们或许会使用instanceof
,一般情况下是没有问题的。
async function back(){
return 1;
}
const res=back();
console.log(res instanceof Promise); //true
但是问题来了,Promise是一个规范而不只是一个类。
遵循Promise规范的库包含了ES6默认Promise、bluebird Promise、Q Promise等,那么我们使用bluebird Promise生成的Promise去instanceof
ES6的默认Promise会不会有问题呢?
显然,要出错。
所以这引出了React官方使用的方式是通过判断条件typeof destroy.then === 'function'
来判断一个对象是否是异步返回对象。
这样子的好处是,对于所有实现了Promise规范的异步库,这样的判断方式都是有效的。虽然这有产生误报的风险,但这是所有Promise库都必须遵循的规范。
同样的Promise判断方式并不只是React在使用,可以试试在F12运行这行代码,这将不会有任何输出
代码语言:javascript复制await {then:()=>1};
原因无他,await
的语法糖里判断Promise对象也是通过promise.then==='funtion'
,这源于Promise A 最基本的定义:
- "promise"是具有
then
方法的对象或函数
当然,除了这方法,还有
- Promise.resolve(res) === res // 这方法现在是比较规范的判断方式,不过早些版本的Safari浏览器跑不了
2.4 获取变量类型
在Js中类型判断其实有一定的心智负担。
常用的两个类型判断关键字都有一定的缺陷
2.4.1 两个缺陷
#1 typeof
typeof是用来判断变量基本类型的关键字,但是我们也知道typeof null==='object'
,这是Js的老BUG了。
typeof Symbol() // 'symbol'
typeof BigInt(1) // 'bigint'
typeof 1 // 'number'
typeof '' // 'string'
typeof undefined // 'undefined'
typeof null // 'object'
typeof {} // 'object'
#2 instanceof
instanceof无法判断数组、日期类型也是老问题了。。。
代码语言:javascript复制[] instanceof Array // true
[] instanceof Object // true
上述问题的原因是,Array的原型是Object,而instanceof的实现原理是在原型链上遍历
代码语言:javascript复制//类似于
function myInstanceof(target, origin) {
const proto = target.__proto__;
if (proto) {
if (origin.prototype === proto) {
return true;
} else {
return myInstanceof(proto, origin)
}
} else {
return false;
}
}
2.4.2 React的做法
社区中常常教我们通过这一行代码来获取变量的实际类型,我也常这样使用
代码语言:javascript复制const getType = (a) => Object.prototype.toString.call(a).split(" ")
.slice(1)
.join(" ")
.split("]")[0];
而我看到了React获取变量实际类型的方式,这里实质上一次为我们展现了两种获得变量实际类型的方式。
(相比之下,React的代码运行更快)
代码语言:javascript复制function typeName(value) {
{
// toStringTag is needed for namespaced types like Temporal.Instant
var hasToStringTag = typeof Symbol === 'function' && Symbol.toStringTag;
var type = hasToStringTag && value[Symbol.toStringTag] || value.constructor.name || 'Object';
return type;
}
}
- value[Symbol.toStringTag]
ES6中对于Symbol的定义,非常考验功底
代码语言:javascript复制JSON[Symbol.toStringTag] // 'JSON'
Promise.prototype[Symbol.toStringTag] // 'Promise'
- value.constructor.name:
const arr=[];
console.log(arr.constructor.name); // 'Array'
2.5 合理使用Map减轻心智负担
我们知道,Object对象天生可以进行索引,所以大部分同学会忽视Map
和WeakMap
这两个真正的Map对象。
而Object对象索引的特点是,会默认调用key.toString()
作为索引。以1
作为key举例子,那么当我们再次获取key的时候,就成了string类型'1'
。
const obj={a:'qqq',1:'www'};
Object.keys(obj); // ['1','a']
而相比之下,使用Map
就不存在隐式转换这种麻烦。React中,会使用Map
用于flag的映射
const map=new Map();
map.set(1,2);
map.keys();
2.6 特定场景使用二进制来替代列表
我们上一章有讲到,React通过flags来保存操作依据。
这个思想我认为是非常赞的,使用二进制可以减少运行时间,二进制的运算级别是O(1),这是列表无法比较的。
- 通过
unknownFlags & Placement
判断unknownFlags
是否包含Placement
- 通过
unknownFlags |= Placement
将Placement
合并进unknownFlags
中 - 通过
unknownFlags &= ~Placement
将Placement
从unknownFlags
中删去
3 总结
阅读源码最重要的是把收获应用在自己的生活、工作中
本篇文章总结了一些React源码中对于一些冷门/规范知识的应用,我们总结一下收获:
- 多层循环中跳出最外层可以使用
label
语法 - 使用
void 0
替代直接使用undefined
,因为undefined
不是关键字 - 使用
type(xx.then)==='function'
来判断对象是否是Promise - 使用
Symbol.toStringTag
和value.constructor.name
都可以获取到对象的真实Class - 在合适的场景使用Map来减轻心智负担