盘点六个阅读React源码后get到的基础知识

2022-09-21 14:49:41 浏览数 (1)

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语法。

代码语言:javascript复制
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循环中使用,对于continuefor等语法也适用

代码语言:javascript复制
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可以被改写,在现代浏览器的局部作用域中同样可以被改写

代码语言:javascript复制
(function(){
    var undefined = 10
    console.log(undefined)
})() // 10

我的天啊,JavaScript你要不要看看你自己在做什么!!!

所以,建议使用babel将undefined编译成void 0亦或者统一使用void 0

2.3 判断异步返回/判断Promise对象

我们如何判断一个对象是异步(Promise)对象?

我们或许会使用instanceof,一般情况下是没有问题的。

代码语言:javascript复制
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去instanceofES6的默认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了。

代码语言:javascript复制
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:
代码语言:javascript复制
const arr=[];
console.log(arr.constructor.name); // 'Array'

2.5 合理使用Map减轻心智负担

我们知道,Object对象天生可以进行索引,所以大部分同学会忽视MapWeakMap这两个真正的Map对象。

而Object对象索引的特点是,会默认调用key.toString()作为索引。以1作为key举例子,那么当我们再次获取key的时候,就成了string类型'1'

代码语言:javascript复制
const obj={a:'qqq',1:'www'};

Object.keys(obj); // ['1','a']

而相比之下,使用Map就不存在隐式转换这种麻烦。React中,会使用Map用于flag的映射

代码语言:javascript复制
const map=new Map();
map.set(1,2);
map.keys();

2.6 特定场景使用二进制来替代列表

我们上一章有讲到,React通过flags来保存操作依据。

这个思想我认为是非常赞的,使用二进制可以减少运行时间,二进制的运算级别是O(1),这是列表无法比较的。

  • 通过unknownFlags & Placement判断unknownFlags是否包含Placement
  • 通过unknownFlags |= PlacementPlacement合并进unknownFlags
  • 通过unknownFlags &= ~PlacementPlacementunknownFlags中删去

3 总结

阅读源码最重要的是把收获应用在自己的生活、工作中

本篇文章总结了一些React源码中对于一些冷门/规范知识的应用,我们总结一下收获:

  • 多层循环中跳出最外层可以使用label语法
  • 使用void 0替代直接使用undefined,因为undefined不是关键字
  • 使用type(xx.then)==='function'来判断对象是否是Promise
  • 使用Symbol.toStringTagvalue.constructor.name都可以获取到对象的真实Class
  • 在合适的场景使用Map来减轻心智负担

0 人点赞