PureComponent的趣事

源码

废话不多说,在这里先公布一下小姐姐的照片

function ComponentDummy() {}
ComponentDummy.prototype = Component.prototype;

/**
 * Convenience component with default shallow equality check for sCU.
 */
function PureComponent(props, context, updater) {
  this.props = props;
  this.context = context;
  // If a component has string refs, we will assign a different object later.
  this.refs = emptyObject;
  this.updater = updater || ReactNoopUpdateQueue;
}

const pureComponentPrototype = (PureComponent.prototype = new ComponentDummy());
pureComponentPrototype.constructor = PureComponent;
// Avoid an extra prototype jump for these methods.
Object.assign(pureComponentPrototype, Component.prototype);
pureComponentPrototype.isPureReactComponent = true;

export {Component, PureComponent};
 
  1. ComponentDummy函数使用的意义

     

  2. pureComponent继承的方式和注意点

     

  3. 这里为什么要拷贝一份Component.protytype

当你想清楚这三个问题,追到PureComponent小姐姐就绝对不是问题啦!

JS中的原型链

每次说到JavaScript的时候,不得不提的就是其中的原型链,这个可谓是js中最有自己特色的东西,但是我把原型链学了这么多遍,却很难说把原型链弄懂了,这玩意似乎就和this一样,每学一次就感觉又懂了一点,这次为了学习原型链,我查阅了众多资料,希望能带给大家一点启示,当你理解原型链的时候,PureComponent源码的原理你也差不多也能读懂了。

我们先来看看MDN里对于原型链这一块的描述

JavaScript 只有一种结构:对象。每个实例对象( object )都有一个私有属性(称之为__proto__)指向它的构造函数的原型对象(prototype )。该原型对象也有一个自己的原型对象( __proto__ ) ,层层向上直到一个对象的原型对象为 null。根据定义,null 没有原型,并作为这个原型链中的最后一个环节。

这段话意思有点绕,其实意思很简单,总结一下就是以下

  1. 每个js对象都有一个属性__proto__,其值为构造这个函数的原型对象(prototype),例如:

function tempFunc() {} //创建了一个名为tempFunc的函数
tempFunc.__proto__
//打印:ƒ () { [native code] } 
//这里其实打印出来的东西就是Function的原型,即Function.prototype


const obj = new Object() //创建一个名为obj的Object实例对象
obj.__proto__
//打印:{constructor: ƒ, __defineGetter__: ƒ, …}
//相信大家一眼就看出来了,这里打印出来是Object.prototype


const str = new String() //创建一个名为str的String实例对象
str.__proto__
//打印:String {"", constructor: ƒ, anchor: ƒ, big: ƒ, blink: ƒ, …}
//这里打印出来是String.prototype
  • 这里能非常明显的看出来,每个对象都有这么一个属性指向自己的原型对象。

  • 原型对象在往上的__proto__也是他的构造函数,就比如上文中str的原型对象String.prototype.__proto__指向也是他的原型对象,至于这个原型对象是什么,在描述的开头有讲到:JavaScript 只有一种结构:对象。所以大家可以在浏览器里输入String.prototype.__proto__,就可以找到答案了。

  • 每个原型对象最后都会回到Object.prototypeObject.prototype.__proto__的值就是null,原型链到此为止。

说到这里大家应该对原型链都有一定的了解了吧,但是原型链究竟是用来干啥的呢,他在js中的作用到底是什么,接下来我们就要引出一个概念:继承。

继承

在其他很多OOP语言中的继承很容易理解也很容易实现,但是在JS中这一点变得异常困难,而且在MDN中关于继承的说明也是极其含糊:

JavaScript 对象是动态的属性“包”(指其自己的属性)。JavaScript 对象有一个指向一个原型对象的链。当试图访问一个对象的属性时,它不仅仅在该对象上搜寻,还会搜寻该对象的原型,以及该对象的原型的原型,依次层层向上搜索,直到找到一个名字匹配的属性或到达原型链的末尾。

其实看不懂没关系,因为这一段话在我看来并没说清楚,在我查阅资料后,了解到所谓的继承就是要实现Child.prototype.__proto__ = Parent.prototype。因为在JS中,当要访问或者调用对象中的方法时,JS会从对象上开始搜索,当没有找到时将会去对象的prototype中寻找,当找不到的时候将会继续从prototypeprototype中去寻找,直到达到原型链尾部(Object.prototype.__proto__),而连接各个prototype的正是 __proto__,通过Child.prototype.__proto__ = Parent.prototype,这种方式创建继承的形式。

到这里我们需要了解的都差不多了,接下来就让我们一起进入PureComponent小姐姐的世界。

PureComponent中原型链继承的应用

我们一块一块的来解析PureComponent源码

//这里创建了一个名为ComponentDummy的空函数,具体的作用我们后面再说
function ComponentDummy() {}
//将Component的原型属性赋值给ComponentDummy的原型
ComponentDummy.prototype = Component.prototype;

//这一块无需多了解,可以理解为PureComponent的函数原型
function PureComponent(props, context, updater) {
  this.props = props;
  this.context = context;
  // If a component has string refs, we will assign a different object later.
  this.refs = emptyObject;
  this.updater = updater || ReactNoopUpdateQueue;
}

到此为止,大部分的数据初始化都已经结束了,接下来就是闪亮亮的继承及原型链登场了

//应该有小伙伴和我一样,第一次看到这时是一脸懵逼的,这一块到底是什么意思?
//其实这一块就是模拟继承的ES5实现方式
const pureComponentPrototype = (PureComponent.prototype = new ComponentDummy());

我们一步步分析,首先执行的是这个实例操作

PureComponent.prototype = new ComponentDummy()

对于new的执行步骤你可以这么理解

  1. 创建一个空对象o
  2. o__proto__指向实例对象的原型ComponentDummy.prototype(这一步很关键,创造了一个原型链)。
  3. 执行ComponentDummy函数,执行时函数内的this指向o,大致为ComponentDummy.call(o,...args)
  4. 返回对象o

执行完这一步,我们就得到了PureComponent.prototype === o,又因为o.__proto__ === ComponentDummy.prototype === Component.prototype,所以PureComponent.prototype.__proto__ === Component.prototype,因此PureComponent的原型链创建完成,他的父级就是Component

pureComponentPrototype.constructor = PureComponent;

这一步其实在对代码的执行上来说没有实际的意义,constructor实际上是对于原型对象一个标识,对于原型链的检测或继承没有影响,但是为了严谨性还是将constructor改为他本身。

OK啦!!如果你看到这里都能看懂,说明对于原型链和继承你已经理解的差不多了,PureComponent对你来说变得轻而易举了啦!

最后两行代码:

//将Component.prototype的属性浅拷贝到pureComponentPrototype上
Object.assign(pureComponentPrototype, Component.prototype);
pureComponentPrototype.isPureReactComponent = true; // 这一步大家应该都看得懂吧QAQ

在MDN中有一段说

在原型链上查找属性比较耗时,对性能有副作用,这在性能要求苛刻的情况下很重要。

所以将Compoent.prototype拷贝到pureComponentPrototype这一层是为了优化原型链查找耗费的空间,用户在使用PureComponent时需要的方法将会直接从最外层开始搜索。另外最后一行使用过React的小伙伴应该都能理解,这个标识主要是为了渲染时能够识别PureComponent触发shallowEqual对比函数。

到此为止都结束了,希望大家都能从中收获到新知识。

最后还有一个点需要注意的,可能有小伙伴会有疑问,为什么非要提供一个ComponentDummy空函数,直接执行PureComponent.prototype = new Component()不行吗?其实这样还是能够实现的,但是这样会有一定的副作用,在new实例化函数的时候,我们将会执行一遍Component函数,其中带有的私有属性或方法都会赋值给PureComponent.prototype,这样就导致了一定程度上内存的浪费。

 

最后附上我的博客地址:https://www.amikara.com

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值