在上一篇文章中咱们经过create-react-app
脚手架快速搭建了一个简单的示例,并基于该示例讲解了在类组件中React.Component
和React.PureComponent
背后的实现原理。同时咱们也了解到,经过使用Babel预置工具包@babel/preset-react
能够将类组件中render
方法的返回值和函数定义组件中的返回值转换成使用React.createElement
方法包装而成的多层嵌套结构,并基于源码逐行分析了React.createElement
方法背后的实现过程和ReactElement
构造函数的成员结构,最后根据分析结果总结出了几道面试中可能会碰到或者本身之前遇到过的面试考点。上篇文章中的内容相对而言仍是比较简单基础,主要是为本文以及后续的任务调度相关内容打下基础,帮助咱们更好地理解源码的用意。本文就结合上篇文章的基础内容,从组件渲染的入口点ReactDOM.render
方法开始,一步一步深刻源码,揭秘ReactDOM.render
方法背后的实现原理,若有错误,还请指出。javascript
源码中有不少判断相似__DEV__变量的控制语句,用于区分开发环境和生产环境,笔者在阅读源码的过程当中不太关心这些内容,就直接略过了,有兴趣的小伙伴儿能够本身研究研究。css
本系列的源码分析是基于Reactv16.10.2
版本的,为了保证源码一致仍是建议你选择相同的版本,下载该版本的地址和笔者选择该版本的具体缘由能够在上篇文章的准备阶段小节中查看,这里就不作过多讲解了。项目示例自己也比较简单,能够按照准备阶段的步骤自行使用create-react-app
快速将一个简单的示例搭建起来,而后咱们定位到src/index.js
文件下,能够看到以下代码:html
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
...
ReactDOM.render(<App />, document.getElementById('root')); ... 复制代码
该文件即为项目的主入口文件,App
组件即为根组件,ReactDOM.render
就是咱们要开始分析源码的入口点。咱们经过如下路径能够找到ReactDOM
对象的完整代码:前端
packages -> react-dom -> src -> client -> ReactDOM.js
复制代码
而后咱们将代码定位到第632
行,能够看到ReactDOM
对象包含了不少咱们可能使用过的方法,例如render
、createPortal
、findDOMNode
,hydrate
和unmountComponentAtNode
等。本文中咱们暂且只关心render
方法,但为了方便对比,也能够简单看下hydrate
方法:java
const ReactDOM: Object = {
...
/** * 服务端渲染 * @param element 表示一个ReactNode,能够是一个ReactElement对象 * @param container 须要将组件挂载到页面中的DOM容器 * @param callback 渲染完成后须要执行的回调函数 */
hydrate(element: React$Node, container: DOMContainer, callback: ?Function) {
invariant(
isValidContainer(container),
'Target container is not a DOM element.',
);
...
// TODO: throw or warn if we couldn't hydrate?
// 注意第一个参数为null,第四个参数为true
return legacyRenderSubtreeIntoContainer(
null,
element,
container,
true,
callback,
);
},
/** * 客户端渲染 * @param element 表示一个ReactElement对象 * @param container 须要将组件挂载到页面中的DOM容器 * @param callback 渲染完成后须要执行的回调函数 */
render(
element: React$Element<any>,
container: DOMContainer,
callback: ?Function,
) {
invariant(
isValidContainer(container),
'Target container is not a DOM element.',
);
...
// 注意第一个参数为null,第四个参数为false
return legacyRenderSubtreeIntoContainer(
null,
element,
container,
false,
callback,
);
},
...
};
复制代码
发现没,render
方法的第一个参数就是咱们在上篇文章中讲过的ReactElement
对象,因此说上篇文章的内容就是为了在这里打下基础的,便于咱们对参数的理解。事实上,在源码中几乎全部方法参数中的element
字段都可以传入一个ReactElement
实例,这个实例就是经过Babel编译器在编译过程当中使用React.createElement
方法获得的。接下来在render
方法中调用legacyRenderSubtreeIntoContainer
来正式进入渲染流程,不过这里须要留意一下的是,render
方法和hydrate
方法在执行legacyRenderSubtreeIntoContainer
时,第一个参数的值均为null
,第四个参数的值刚好相反。node
而后将代码定位到第570
行,进入legacyRenderSubtreeIntoContainer
方法的具体实现:react
/** * 开始构建FiberRoot和RootFiber,以后开始执行更新任务 * @param parentComponent 父组件,能够把它当成null值来处理 * @param children ReactDOM.render()或者ReactDOM.hydrate()中的第一个参数,能够理解为根组件 * @param container ReactDOM.render()或者ReactDOM.hydrate()中的第二个参数,组件须要挂载的DOM容器 * @param forceHydrate 表示是否融合,用于区分客户端渲染和服务端渲染,render方法传false,hydrate方法传true * @param callback ReactDOM.render()或者ReactDOM.hydrate()中的第三个参数,组件渲染完成后须要执行的回调函数 * @returns {*} */
function legacyRenderSubtreeIntoContainer( parentComponent: ?React$Component<any, any>, children: ReactNodeList, container: DOMContainer, forceHydrate: boolean, callback: ?Function, ) {
...
// TODO: Without `any` type, Flow says "Property cannot be accessed on any
// member of intersection type." Whyyyyyy.
// 在第一次执行的时候,container上是确定没有_reactRootContainer属性的
// 因此第一次执行时,root确定为undefined
let root: _ReactSyncRoot = (container._reactRootContainer: any);
let fiberRoot;
if (!root) {
// Initial mount
// 首次挂载,进入当前流程控制中,container._reactRootContainer指向一个ReactSyncRoot实例
root = container._reactRootContainer = legacyCreateRootFromDOMContainer(
container,
forceHydrate,
);
// root表示一个ReactSyncRoot实例,实例中有一个_internalRoot方法指向一个fiberRoot实例
fiberRoot = root._internalRoot;
// callback表示ReactDOM.render()或者ReactDOM.hydrate()中的第三个参数
// 重写callback,经过fiberRoot去找到其对应的rootFiber,而后将rootFiber的第一个child的stateNode做为callback中的this指向
// 通常状况下咱们不多去写第三个参数,因此能够没必要关心这里的内容
if (typeof callback === 'function') {
const originalCallback = callback;
callback = function() {
const instance = getPublicRootInstance(fiberRoot);
originalCallback.call(instance);
};
}
// Initial mount should not be batched.
// 对于首次挂载来讲,更新操做不该该是批量的,因此会先执行unbatchedUpdates方法
// 该方法中会将executionContext(执行上下文)切换成LegacyUnbatchedContext(非批量上下文)
// 切换上下文以后再调用updateContainer执行更新操做
// 执行完updateContainer以后再将executionContext恢复到以前的状态
unbatchedUpdates(() => {
updateContainer(children, fiberRoot, parentComponent, callback);
});
} else {
// 不是首次挂载,即container._reactRootContainer上已经存在一个ReactSyncRoot实例
fiberRoot = root._internalRoot;
// 下面的控制语句和上面的逻辑保持一致
if (typeof callback === 'function') {
const originalCallback = callback;
callback = function() {
const instance = getPublicRootInstance(fiberRoot);
originalCallback.call(instance);
};
}
// Update
// 对于非首次挂载来讲,是不须要再调用unbatchedUpdates方法的
// 即再也不须要将executionContext(执行上下文)切换成LegacyUnbatchedContext(非批量上下文)
// 而是直接调用updateContainer执行更新操做
updateContainer(children, fiberRoot, parentComponent, callback);
}
return getPublicRootInstance(fiberRoot);
}
复制代码
上面代码的内容稍微有些多,咋一看可能不太好理解,咱们暂且能够不用着急看完整个函数内容。试想当咱们第一次启动运行项目的时候,也就是第一次执行ReactDOM.render
方法的时候,这时去获取container._reactRootContainer
确定是没有值的,因此咱们先关心第一个if
语句中的内容:git
if (!root) {
// Initial mount
// 首次挂载,进入当前流程控制中,container._reactRootContainer指向一个ReactSyncRoot实例
root = container._reactRootContainer = legacyCreateRootFromDOMContainer(
container,
forceHydrate,
);
...
}
复制代码
这里经过调用legacyCreateRootFromDOMContainer
方法将其返回值赋值给container._reactRootContainer
,咱们将代码定位到同文件下的第517
行,去看看legacyCreateRootFromDOMContainer
的具体实现:github
/** * 建立并返回一个ReactSyncRoot实例 * @param container ReactDOM.render()或者ReactDOM.hydrate()中的第二个参数,组件须要挂载的DOM容器 * @param forceHydrate 是否须要强制融合,render方法传false,hydrate方法传true * @returns {ReactSyncRoot} */
function legacyCreateRootFromDOMContainer( container: DOMContainer, forceHydrate: boolean, ): _ReactSyncRoot {
// 判断是否须要融合
const shouldHydrate =
forceHydrate || shouldHydrateDueToLegacyHeuristic(container);
// First clear any existing content.
// 针对客户端渲染的状况,须要将container容器中的全部元素移除
if (!shouldHydrate) {
let warned = false;
let rootSibling;
// 循环遍历每一个子节点进行删除
while ((rootSibling = container.lastChild)) {
...
container.removeChild(rootSibling);
}
}
...
// Legacy roots are not batched.
// 返回一个ReactSyncRoot实例
// 该实例具备一个_internalRoot属性指向fiberRoot
return new ReactSyncRoot(
container,
LegacyRoot,
shouldHydrate
? {
hydrate: true,
}
: undefined,
);
}
/** * 根据nodeType和attribute判断是否须要融合 * @param container DOM容器 * @returns {boolean} */
function shouldHydrateDueToLegacyHeuristic(container) {
const rootElement = getReactRootElementInContainer(container);
return !!(
rootElement &&
rootElement.nodeType === ELEMENT_NODE &&
rootElement.hasAttribute(ROOT_ATTRIBUTE_NAME)
);
}
/** * 根据container来获取DOM容器中的第一个子节点 * @param container DOM容器 * @returns {*} */
function getReactRootElementInContainer(container: any) {
if (!container) {
return null;
}
if (container.nodeType === DOCUMENT_NODE) {
return container.documentElement;
} else {
return container.firstChild;
}
}
复制代码
其中在shouldHydrateDueToLegacyHeuristic
方法中,首先根据container
来获取DOM容器中的第一个子节点,获取该子节点的目的在于经过节点的nodeType
和是否具备ROOT_ATTRIBUTE_NAME
属性来区分是客户端渲染仍是服务端渲染,ROOT_ATTRIBUTE_NAME
位于packages/react-dom/src/shared/DOMProperty.js
文件中,表示data-reactroot
属性。咱们知道,在服务端渲染中有别于客户端渲染的是,node
服务会在后台先根据匹配到的路由生成完整的HTML
字符串,而后再将HTML
字符串发送到浏览器端,最终生成的HTML
结构简化后以下:面试
<body>
<div id="root">
<div data-reactroot=""></div>
</div>
</body>
复制代码
在客户端渲染中是没有data-reactroot
属性的,所以就能够区分出客户端渲染和服务端渲染。在React中的nodeType
主要包含了五种,其对应的值和W3C
中的nodeType
标准是保持一致的,位于与DOMProperty.js
同级的HTMLNodeType.js
文件中:
// 表明元素节点
export const ELEMENT_NODE = 1;
// 表明文本节点
export const TEXT_NODE = 3;
// 表明注释节点
export const COMMENT_NODE = 8;
// 表明整个文档,即document
export const DOCUMENT_NODE = 9;
// 表明文档片断节点
export const DOCUMENT_FRAGMENT_NODE = 11;
复制代码
通过以上分析,如今咱们就能够很容易地区分出客户端渲染和服务端渲染,而且在面试中若是被问到两种渲染模式的区别,咱们就能够很轻松地在源码级别上说出二者的实现差别,让面试官眼前一亮。怎么样,到目前为止,其实仍是以为挺简单的吧?
在这一小节中,咱们将尝试去理解两个比较容易混淆的概念:FiberRoot
和RootFiber
。这两个概念在React的整个任务调度过程当中起着关键性的做用,若是不理解这两个概念,后续的任务调度过程就是空谈,因此这里也是咱们必需要去理解的部分。接下来接着上一小节的内容,继续分析legacyCreateRootFromDOMContainer
方法中的剩余内容,在函数体的结尾返回了一个ReactSyncRoot
实例,咱们从新回到ReactDOM.js
文件能够很容易找到ReactSyncRoot
构造函数的具体内容:
/** * ReactSyncRoot构造函数 * @param container DOM容器 * @param tag fiberRoot节点的标记(LegacyRoot、BatchedRoot、ConcurrentRoot) * @param options 配置信息,只有在hydrate时才有值,不然为undefined * @constructor */
function ReactSyncRoot( container: DOMContainer, tag: RootTag, options: void | RootOptions, ) {
this._internalRoot = createRootImpl(container, tag, options);
}
/** * 建立并返回一个fiberRoot * @param container DOM容器 * @param tag fiberRoot节点的标记(LegacyRoot、BatchedRoot、ConcurrentRoot) * @param options 配置信息,只有在hydrate时才有值,不然为undefined * @returns {*} */
function createRootImpl( container: DOMContainer, tag: RootTag, options: void | RootOptions, ) {
// Tag is either LegacyRoot or Concurrent Root
// 判断是不是hydrate模式
const hydrate = options != null && options.hydrate === true;
const hydrationCallbacks =
(options != null && options.hydrationOptions) || null;
// 建立一个fiberRoot
const root = createContainer(container, tag, hydrate, hydrationCallbacks);
// 给container附加一个内部属性用于指向fiberRoot的current属性对应的rootFiber节点
markContainerAsRoot(root.current, container);
if (hydrate && tag !== LegacyRoot) {
const doc =
container.nodeType === DOCUMENT_NODE
? container
: container.ownerDocument;
eagerlyTrapReplayableEvents(doc);
}
return root;
}
复制代码
从上述源码中,咱们能够看到createRootImpl
方法经过调用createContainer
方法来建立一个fiberRoot
实例,并将该实例返回并赋值到ReactSyncRoot
构造函数的内部成员_internalRoot
属性上。咱们继续深刻createContainer
方法去探究一下fiberRoot
完整的建立过程,该方法被抽取到与react-dom
包同级的另外一个相关的依赖包react-reconciler
包中,而后定位到react-reconciler/src/ReactFiberReconciler.js
的第299
行:
/** * 内部调用createFiberRoot方法返回一个fiberRoot实例 * @param containerInfo DOM容器 * @param tag fiberRoot节点的标记(LegacyRoot、BatchedRoot、ConcurrentRoot) * @param hydrate 判断是不是hydrate模式 * @param hydrationCallbacks 只有在hydrate模式时才可能有值,该对象包含两个可选的方法:onHydrated和onDeleted * @returns {FiberRoot} */
export function createContainer( containerInfo: Container, tag: RootTag, hydrate: boolean, hydrationCallbacks: null | SuspenseHydrationCallbacks, ): OpaqueRoot {
return createFiberRoot(containerInfo, tag, hydrate, hydrationCallbacks);
}
/** * 建立fiberRoot和rootFiber并相互引用 * @param containerInfo DOM容器 * @param tag fiberRoot节点的标记(LegacyRoot、BatchedRoot、ConcurrentRoot) * @param hydrate 判断是不是hydrate模式 * @param hydrationCallbacks 只有在hydrate模式时才可能有值,该对象包含两个可选的方法:onHydrated和onDeleted * @returns {FiberRoot} */
export function createFiberRoot( containerInfo: any, tag: RootTag, hydrate: boolean, hydrationCallbacks: null | SuspenseHydrationCallbacks, ): FiberRoot {
// 经过FiberRootNode构造函数建立一个fiberRoot实例
const root: FiberRoot = (new FiberRootNode(containerInfo, tag, hydrate): any);
if (enableSuspenseCallback) {
root.hydrationCallbacks = hydrationCallbacks;
}
// Cyclic construction. This cheats the type system right now because
// stateNode is any.
// 经过createHostRootFiber方法建立fiber tree的根节点,即rootFiber
// 须要留意的是,fiber节点也会像DOM树结构同样造成一个fiber tree单链表树结构
// 每一个DOM节点或者组件都会生成一个与之对应的fiber节点(生成的过程会在后续的文章中进行解读)
// 在后续的调和(reconciliation)阶段起着相当重要的做用
const uninitializedFiber = createHostRootFiber(tag);
// 建立完rootFiber以后,会将fiberRoot实例的current属性指向刚建立的rootFiber
root.current = uninitializedFiber;
// 同时rootFiber的stateNode属性会指向fiberRoot实例,造成相互引用
uninitializedFiber.stateNode = root;
// 最后将建立的fiberRoot实例返回
return root;
}
复制代码
一个完整的FiberRootNode
实例包含了不少有用的属性,这些属性在任务调度阶段都发挥着各自的做用,能够在ReactFiberRoot.js
文件中看到完整的FiberRootNode
构造函数的实现(这里只列举部分属性):
/** * FiberRootNode构造函数 * @param containerInfo DOM容器 * @param tag fiberRoot节点的标记(LegacyRoot、BatchedRoot、ConcurrentRoot) * @param hydrate 判断是不是hydrate模式 * @constructor */
function FiberRootNode(containerInfo, tag, hydrate) {
// 用于标记fiberRoot的类型
this.tag = tag;
// 指向当前激活的与之对应的rootFiber节点
this.current = null;
// 和fiberRoot关联的DOM容器的相关信息
this.containerInfo = containerInfo;
...
// 当前的fiberRoot是否处于hydrate模式
this.hydrate = hydrate;
...
// 每一个fiberRoot实例上都只会维护一个任务,该任务保存在callbackNode属性中
this.callbackNode = null;
// 当前任务的优先级
this.callbackPriority = NoPriority;
...
}
复制代码
部分属性信息如上所示,因为属性过多而且在本文中暂时还用不到,这里就先不一一列举出来了,剩余的属性及其注释信息已经上传至Github,感兴趣的朋友能够自行查看。在了解完了fiberRoot
的属性结构以后,接下来继续探究createFiberRoot
方法的后半部份内容:
// 如下代码来自上文中的createFiberRoot方法
// 经过createHostRootFiber方法建立fiber tree的根节点,即rootFiber
const uninitializedFiber = createHostRootFiber(tag);
// 建立完rootFiber以后,会将fiberRoot实例的current属性指向刚建立的rootFiber
root.current = uninitializedFiber;
// 同时rootFiber的stateNode属性会指向fiberRoot实例,造成相互引用
uninitializedFiber.stateNode = root;
// 如下代码来自ReactFiber.js文件
/** * 内部调用createFiber方法建立一个FiberNode实例 * @param tag fiberRoot节点的标记(LegacyRoot、BatchedRoot、ConcurrentRoot) * @returns {Fiber} */
export function createHostRootFiber(tag: RootTag): Fiber {
let mode;
// 如下代码根据fiberRoot的标记类型来动态设置rootFiber的mode属性
// export const NoMode = 0b0000; => 0
// export const StrictMode = 0b0001; => 1
// export const BatchedMode = 0b0010; => 2
// export const ConcurrentMode = 0b0100; => 4
// export const ProfileMode = 0b1000; => 8
if (tag === ConcurrentRoot) {
mode = ConcurrentMode | BatchedMode | StrictMode;
} else if (tag === BatchedRoot) {
mode = BatchedMode | StrictMode;
} else {
mode = NoMode;
}
...
// 调用createFiber方法建立并返回一个FiberNode实例
// HostRoot表示fiber tree的根节点
// 其余标记类型能够在shared/ReactWorkTags.js文件中找到
return createFiber(HostRoot, null, null, mode);
}
/** * 建立并返回一个FiberNode实例 * @param tag 用于标记fiber节点的类型(全部的类型存放在shared/ReactWorkTags.js文件中) * @param pendingProps 表示待处理的props数据 * @param key 用于惟一标识一个fiber节点(特别在一些列表数据结构中,通常会要求为每一个DOM节点或组件加上额外的key属性,在后续的调和阶段会派上用场) * @param mode 表示fiber节点的模式 * @returns {FiberNode} */
const createFiber = function( tag: WorkTag, pendingProps: mixed, key: null | string, mode: TypeOfMode, ): Fiber {
// $FlowFixMe: the shapes are exact here but Flow doesn't like constructors
// FiberNode构造函数用于建立一个FiberNode实例,即一个fiber节点
return new FiberNode(tag, pendingProps, key, mode);
};
复制代码
至此咱们就成功地建立了一个fiber
节点,上文中咱们提到过,和DOM树结构相似,fiber
节点也会造成一个与DOM树结构对应的fiber tree
,而且是基于单链表的树结构,咱们在上面刚建立的fiber
节点可做为整个fiber tree
的根节点,即RootFiber
节点。在目前阶段,咱们暂时不用关心一个fiber
节点所包含的全部属性,但能够稍微留意一下如下相关属性:
/** * FiberNode构造函数 * @param tag 用于标记fiber节点的类型 * @param pendingProps 表示待处理的props数据 * @param key 用于惟一标识一个fiber节点(特别在一些列表数据结构中,通常会要求为每一个DOM节点或组件加上额外的key属性,在后续的调和阶段会派上用场) * @param mode 表示fiber节点的模式 * @constructor */
function FiberNode( tag: WorkTag, pendingProps: mixed, key: null | string, mode: TypeOfMode, ) {
// Instance
// 用于标记fiber节点的类型
this.tag = tag;
// 用于惟一标识一个fiber节点
this.key = key;
...
// 对于rootFiber节点而言,stateNode属性指向对应的fiberRoot节点
// 对于child fiber节点而言,stateNode属性指向对应的组件实例
this.stateNode = null;
// Fiber
// 如下属性建立单链表树结构
// return属性始终指向父节点
// child属性始终指向第一个子节点
// sibling属性始终指向第一个兄弟节点
this.return = null;
this.child = null;
this.sibling = null;
// index属性表示当前fiber节点的索引
this.index = 0;
...
// 表示待处理的props数据
this.pendingProps = pendingProps;
// 表示以前已经存储的props数据
this.memoizedProps = null;
// 表示更新队列
// 例如在常见的setState操做中
// 其实会先将须要更新的数据存放到这里的updateQueue队列中用于后续调度
this.updateQueue = null;
// 表示以前已经存储的state数据
this.memoizedState = null;
...
// 表示fiber节点的模式
this.mode = mode;
// 表示当前更新任务的过时时间,即在该时间以后更新任务将会被完成
this.expirationTime = NoWork;
// 表示当前fiber节点的子fiber节点中具备最高优先级的任务的过时时间
// 该属性的值会根据子fiber节点中的任务优先级进行动态调整
this.childExpirationTime = NoWork;
// 用于指向另外一个fiber节点
// 这两个fiber节点使用alternate属性相互引用,造成双缓冲
// alternate属性指向的fiber节点在任务调度中又称为workInProgress节点
this.alternate = null;
...
}
复制代码
其余有用的属性笔者已经在源码中写好相关注释,感兴趣的朋友能够在Github上查看完整的注释信息帮助理解。固然在现阶段,其中的一些属性还暂时难以理解,不过没有关系,在后续的内容和系列文章中将会逐个击破。在本小节中咱们主要是为了理解FiberRoot
和RootFiber
这两个容易混淆的概念以及二者之间的联系。同时在这里咱们须要特别注意的是,多个fiber
节点可造成基于单链表的树形结构,经过自身的return
,child
和sibling
属性能够在多个fiber
节点之间创建联系。为了更加容易理解多个fiber
节点及其属性之间的关系,这里先回顾一下在上一篇文章中的简单示例,咱们在src/App.js
文件中将create-react-app
脚手架生成的默认根组件App
修改成以下形式:
import React, {Component} from 'react';
function List({data}) {
return (
<ul className="data-list"> { data.map(item => { return <li className="data-item" key={item}>{item}</li> }) } </ul>
);
}
export default class App extends Component {
constructor(props) {
super(props);
this.state = {
data: [1, 2, 3]
};
}
render() {
return (
<div className="container"> <h1 className="title">React learning</h1> <List data={this.state.data} /> </div> ); } } 复制代码
最终生成的DOM结构以下所示:
<div class="container">
<h1 class="title">React learning</h1>
<ul class="data-list">
<li class="data-item">1</li>
<li class="data-item">2</li>
<li class="data-item">3</li>
</ul>
</div>
复制代码
基于该DOM结构再结合上文中对源码的分析过程,最后咱们能够尝试得出一张关系图来加深印象:
本文主要是在上一篇文章内容的基础之上从零开始逐行分析ReactDOM.render
方法的实现原理,其背后的实现过程和调用栈仍是很是复杂的,本身也是处于不断的摸索过程当中。在本文中主要是介绍两个核心概念:FiberRoot
和RootFiber
,只有理解并区分这两个概念以后才能更好地理解React的Fiber
架构和任务调度阶段中任务的执行过程。阅读源码的过程是痛苦的,但与此同时本身所得到的收益也是巨大的,为了不文章过于枯燥,仍是打算将源码内容划分到系列文章中来单独解读,期间的间隔时间可用于对以前内容进行回顾,避免一口吃个胖子反而效果很差。
若是你以为这篇文章的内容对你有帮助,可否帮个忙关注一下笔者的公众号[前端之境],每周都会努力原创一些前端技术干货,关注公众号后能够邀你加入前端技术交流群,咱们能够一块儿互相交流,共同进步。
文章已同步更新至Github博客,若觉文章尚可,欢迎前往star!
你的一个点赞,值得让我付出更多的努力!
逆境中成长,只有不断地学习,才能成为更好的本身,与君共勉!