【源码之下】PureComponent的趣事
Opened this issue · 1 comments
最近在逛React商场的时候,突然遇到了PureComponet,本来想扫一眼就过去的,但是看了她一眼就把我迷住了,瞬间对她产生了极大的兴趣,但是哥哥又没胆子在大庭广众之下找她要vx,于是通过各方关系,终于加到了PC小姐姐的微信。在深入交流♂后,我发现这个小姐姐不仅很好看,而且还非常有内涵,所以在这里就忍不住拿出来与大家分享。
源码
废话不多说,在这里先公布一下小姐姐的照片
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};
欣赏不来的小伙伴也别急,后面我会给大家一一分析,这里只需要注意三个点
-
ComponentDummy
函数使用的意义 -
pureComponent
继承的方式和注意点 -
这里为什么要拷贝一份
Component.protytype
当你想清楚这三个问题,追到PureComponent小姐姐就绝对不是问题啦!
JS中的原型链
每次说到JavaScript的时候,不得不提的就是其中的原型链,这个可谓是js中最有自己特色的东西,但是我把原型链学了这么多遍,却很难说把原型链弄懂了,这玩意似乎就和this一样,每学一次就感觉又懂了一点,这次为了学习原型链,我查阅了众多资料,希望能带给大家一点启示,当你理解原型链的时候,PureComponent源码的原理你也差不多也能读懂了。
我们先来看看MDN里对于原型链这一块的描述
JavaScript 只有一种结构:对象。每个实例对象( object )都有一个私有属性(称之为**__proto__)指向它的构造函数的原型对象(prototype )。该原型对象也有一个自己的原型对象( __proto__ ) ,层层向上直到一个对象的原型对象为
null
。根据定义,null
没有原型,并作为这个原型链**中的最后一个环节。
这段话意思有点绕,其实意思很简单,总结一下就是以下
-
每个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.prototype
,Object.prototype.__proto__
的值就是null
,原型链到此为止。
说到这里大家应该对原型链都有一定的了解了吧,但是原型链究竟是用来干啥的呢,他在js中的作用到底是什么,接下来我们就要引出一个概念:继承。
继承
在其他很多OOP语言中的继承很容易理解也很容易实现,但是在JS中这一点变得异常困难,而且在MDN中关于继承的说明也是极其含糊:
JavaScript 对象是动态的属性“包”(指其自己的属性)。JavaScript 对象有一个指向一个原型对象的链。当试图访问一个对象的属性时,它不仅仅在该对象上搜寻,还会搜寻该对象的原型,以及该对象的原型的原型,依次层层向上搜索,直到找到一个名字匹配的属性或到达原型链的末尾。
其实看不懂没关系,因为这一段话在我看来并没说清楚,在我查阅资料后,了解到所谓的继承就是要实现Child.prototype.__proto__ = Parent.prototype
。因为在JS中,当要访问或者调用对象中的方法时,JS会从对象上开始搜索,当没有找到时将会去对象的prototype
中寻找,当找不到的时候将会继续从prototype
的prototype
中去寻找,直到达到原型链尾部(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
的执行步骤你可以这么理解
-
创建一个空对象
o
。 -
将
o
的__proto__
指向实例对象的原型ComponentDummy.prototype
(这一步很关键,创造了一个原型链)。 -
执行
ComponentDummy
函数,执行时函数内的this指向o
,大致为ComponentDummy.call(o,...args)
-
返回对象
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
,这样就导致了一定程度上内存的浪费。
下方有我的邮箱,如果有任何问题请及时与我联系。
在阅读React源码时,偶然发现了PureComponent的实现方式,它在实现方式上稍微有点绕,但是可以学习的JS细节非常多,所以我在这里为大家粗略的介绍一下,希望看完的小伙伴能有所启发