前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >盘点六个阅读React源码后get到的基础知识

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

作者头像
源心锁
发布2022-09-21 14:49:41
5640
发布2022-09-21 14:49:41
举报
文章被收录于专栏:前端魔法指南前端魔法指南

1 前言

大家好,我是心锁,一枚23届准毕业生。

如果读者阅读过我其他几篇文章,就知道我近期在阅读React源码。

27F141F8A438C8E0D0EC9A0C760678FB
27F141F8A438C8E0D0EC9A0C760678FB

而阅读源码的终极目的还是应用,在这个想法下,我盘点了一些可以快速在工程中应用的( 或许冷门 )知识,希望读者可以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时直接跳出循环应该怎么做?

43885F5E1F8C7FF2B3392D297C855609
43885F5E1F8C7FF2B3392D297C855609

改成这样?

这样并不能成功跳出。

代码语言: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
383C8600B9482E7EF711D15F6D955F07
383C8600B9482E7EF711D15F6D955F07

除了上述在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

image-20220919210435592
image-20220919210435592

为什么呢,我当时就懵了,虽然我知道使用void 0===undefined,但是不曾使用过void这个关键字。

12EFA2E671764C72AB5B0DAA793117A8
12EFA2E671764C72AB5B0DAA793117A8

所以,我进行了一定的探索。最终发现了一个惊人的事件。

984B6CA817BBC4F431BABB977CDA2815
984B6CA817BBC4F431BABB977CDA2815

undefined不是一个关键字,这玩意儿是全局变量的一个属性,在低版本浏览器中全局undefined可以被改写,在现代浏览器的局部作用域中同样可以被改写

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

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

C47E807B51873D09AB5EE960B7F76BAC
C47E807B51873D09AB5EE960B7F76BAC

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

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

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

244392FC225E2177F8435874B3A49BE3
244392FC225E2177F8435874B3A49BE3

我们或许会使用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会不会有问题呢?

1020055674B596C2B83948ADE0679D33
1020055674B596C2B83948ADE0679D33

显然,要出错。

所以这引出了React官方使用的方式是通过判断条件typeof destroy.then === 'function'来判断一个对象是否是异步返回对象。

image-20220919224516758
image-20220919224516758

这样子的好处是,对于所有实现了Promise规范的异步库,这样的判断方式都是有效的。虽然这有产生误报的风险,但这是所有Promise库都必须遵循的规范。

8E0B48BD4AA1E478A961D2C5EC0ECDDB
8E0B48BD4AA1E478A961D2C5EC0ECDDB

同样的Promise判断方式并不只是React在使用,可以试试在F12运行这行代码,这将不会有任何输出

代码语言:javascript
复制
await {then:()=>1};

原因无他,await的语法糖里判断Promise对象也是通过promise.then==='funtion',这源于Promise A+最基本的定义:

  • "promise"是具有then方法的对象或函数

当然,除了这方法,还有

  • Promise.resolve(res) === res // 这方法现在是比较规范的判断方式,不过早些版本的Safari浏览器跑不了

2.4 获取变量类型

在Js中类型判断其实有一定的心智负担。

A5A6BD4B8BB02E9E932AA7164F2E36B9
A5A6BD4B8BB02E9E932AA7164F2E36B9

常用的两个类型判断关键字都有一定的缺陷

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对象。

AF2353D75A0A1495FD056E15AAF75A09
AF2353D75A0A1495FD056E15AAF75A09

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

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

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

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

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

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

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

F632D101460BE0F7F6206294CB3624A2
F632D101460BE0F7F6206294CB3624A2

这个思想我认为是非常赞的,使用二进制可以减少运行时间,二进制的运算级别是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来减轻心智负担
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2022-09-20,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1 前言
  • 2 盘点
    • 2.1 多层循环中跳出最外层
      • 2.2 void 0
        • 2.3 判断异步返回/判断Promise对象
          • 2.4 获取变量类型
            • 2.4.1 两个缺陷
            • 2.4.2 React的做法
          • 2.5 合理使用Map减轻心智负担
            • 2.6 特定场景使用二进制来替代列表
            • 3 总结
            领券
            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档