从Javascript角度出发
经典面试题目,面向对象三大特性是什么?
答曰:继承、多态、封装
确实,在很多老牌语言中,面向对象都代表着Class这一写法,比如个人之前做的PHP,比如Java等等,其使得代码变得更加模块化、可维护、可扩展和易理解,确实还是蛮舒服的。但是JS却只在es6才出现Class,还只是个语法糖,这主要是因为JS是通过原型委托去进行代码复用的,在出现Class之前,JS可以通过构造函数、原型及原型链来实现面向对象的,所以Class的底层其实还是原型继承,只是提供了这一语法糖来使得JS的面向对象更符合大众。

原型的话,写的再好可能还不如多去看看红宝书~
其实Class这块,也是多看红宝书就完事,我这里只是简要说一下:
类可以包含构造方法、实例方法、获取函数、设置函数和静态类方法,但这些都不是必须的。空的类定义照样有效。默认情况下,类定义中的代码都在严格模式下执行。
且与函数不同的是,函数受函数作用域限制,类受块作用域限制。
其实new一个类就是在调用构造函数。
从React角度出发
从React角度去思考这个问题的话,便是从组件的角度去开始摸索:
js
<Demo />
对于React组件而言,其定义方式主要分为两种:
1.Hooks
js
function Demo() {
return <div>Hello World</div>
}
2.Class
js
class Demo extends React.Component {
render() {
return <div>Hello World</div>
}
}
是的,对于用户而言,这两种的调用方式是一样的,但是React要做的远不止这些。 对比这两种方法: Class拥有非常清晰的生命周期,以及内部状态,使得一个组件的代码更为聚合,但是与之而来的缺点便是:
- 面向对象写法中复杂的this指向问题;
- 组件状态难以复用;
- 代码复杂度高,逻辑过于混合;
所以,为什么Hooks的优势便是:
- 函数式本来就是js的本命,函数式写法简单明了,且易于解耦合,易于复用;
- 不需要再在各个生命周期中放置大量不相关的逻辑,或者同一逻辑需要分散在各个生命周期中;
- 使用Hooks(内置/包/自定义)可以极大的简化很多逻辑处理以及组件层级,例如HOC(当然,HOC也可以作为优化方式)等等;
但是,Hooks也是有缺点的:
- 闭包问题,且不正确使用Hooks会造成性能问题;
- 对于副作用等操作,没有Class较为详细的生命周期,需要换成Hooks的方式去解决;
- 过分的原子化组件,可能会造成代码理解上的困难等等。
关于Hooks和Class的性能问题,其实官网是有相应的FQ,会有人说函数闭包的方法,会在每次重渲染更新的时候重新构建函数上下文,但是经过很多人在正常环境下的对比,其实二者差别不大,并且,下面的源码分析中也可以看出Hooks方法相较于Class少了很多例如创建实例等等的操作,且在核心方法上例如diff,都是调用相同的函数,且在Hooks中,很多避免重渲染的手段都开放给了开发者来决定。
常见的Class转换Hooks的方式有: 1.shouldComponentUpdate可以用React.memo代替进行浅比较; 2.componentDidMount和componentDidUpdate可以用useLayoutEffect或者useEffect代替 3.componentWillUnmount相当于useEffect的return等等。
ok,知道二者区别后,我们就要企图搞清楚React是怎么处理他们的。
当然大家都知道的答案是,React对其采用了两种不同的方式,有人会讲,为什么不统一使用new呢,确实,从js的角度出发,当然可以:

但是,new并不能处理箭头函数(没有this),并且如图所示,new调用函数,无法返回原始值(new的特性)。 所以在React源码中有如下方法:
js
function isReactClass(type: any) {
return type.prototype && type.prototype.isReactComponent;
}
当Class extends React.Component时会在其原型上找isReactComponent来判断是否为Class~
Class
在Hooks出现之前,react就像是原始js,也是两种写法:函数式和class,函数组件就是一个函数,没有this,并不能去组件实例化,只能单纯去渲染UI;而class拥有state、生命周期,可以使react组件具有UI的同时也拥有具体的事件处理能力。
class组件是以面向对象的方式进行编程,以类的方式拥有属性和方法,在组件开发过程中通过this去获取相应的数据和方法,同时对于class组件,react还内置了多种周期以供调用。
我们从render开始分析react是如何处理Class:
js
function renderRootSync(root: FiberRoot, lanes: Lanes) {
// ...
outer: do {
try {
if (
workInProgressSuspendedReason !== NotSuspended &&
workInProgress !== null
) { // ... }
// 调用workLoopSync循环update
workLoopSync();
break;
} catch (thrownValue) {
handleThrow(root, thrownValue);
}
} while (true)
// ...
// 循环更新结束,进入commit阶段
workInProgressRoot = null;
workInProgressRootRenderLanes = NoLanes;
// ...
}
js
function workLoopSync() {
// Perform work without checking if we need to yield between fiber.
// workInProgress指向当前正在处理的 fiber 节点
while (workInProgress !== null) {
performUnitOfWork(workInProgress);
}
}
js
function performUnitOfWork(unitOfWork: Fiber): void {
const current = unitOfWork.alternate;
setCurrentDebugFiberInDEV(unitOfWork);
let next;
if (enableProfilerTimer && (unitOfWork.mode & ProfileMode) !== NoMode) {
startProfilerTimer(unitOfWork);
// 处理当前节点
next = beginWork(current, unitOfWork, renderLanes);
stopProfilerTimerIfRunningAndRecordDelta(unitOfWork, true);
} else {
next = beginWork(current, unitOfWork, renderLanes);
}
resetCurrentDebugFiberInDEV();
unitOfWork.memoizedProps = unitOfWork.pendingProps;
if (next === null) {
// If this doesn't spawn new work, complete the current work.
// 没有next,结束,向上
completeUnitOfWork(unitOfWork);
} else {
workInProgress = next;
}
ReactCurrentOwner.current = null;
}
js
function beginWork(
current: Fiber | null,
workInProgress: Fiber,
renderLanes: Lanes,
): Fiber | null {
// ...
// 根据tag执行不同类型的节点函数
switch (workInProgress.tag) {
// ...
case FunctionComponent: {
const Component = workInProgress.type;
const unresolvedProps = workInProgress.pendingProps;
const resolvedProps =
workInProgress.elementType === Component
? unresolvedProps
: resolveDefaultProps(Component, unresolvedProps);
return updateFunctionComponent(
current,
workInProgress,
Component,
resolvedProps,
renderLanes,
);
}
case ClassComponent: {
const Component = workInProgress.type;
const unresolvedProps = workInProgress.pendingProps;
const resolvedProps =
workInProgress.elementType === Component
? unresolvedProps
: resolveDefaultProps(Component, unresolvedProps);
return updateClassComponent(
current,
workInProgress,
Component,
resolvedProps,
renderLanes,
);
}
}
}
js
function updateClassComponent(
current: Fiber | null,
workInProgress: Fiber,
Component: any,
nextProps: any,
renderLanes: Lanes,
) {
// ...
const instance = workInProgress.stateNode;
let shouldUpdate;
// 若组件并未实例化
if (instance === null) {
resetSuspendedCurrentOnMountInLegacyMode(current, workInProgress);
// In the initial pass we might need to construct the instance.
// 执行构造函数,获取实例
constructClassInstance(workInProgress, Component, nextProps);
// 挂载类组件
mountClassInstance(workInProgress, Component, nextProps, renderLanes);
// 标记
shouldUpdate = true;
} else if (current === null) {
// 实例化后未渲染,当前为首次渲染
// In a resume, we'll already have an instance we can reuse.
// 复用实例
shouldUpdate = resumeMountClassInstance(
workInProgress,
Component,
nextProps,
renderLanes,
);
} else {
// 不是首次渲染
// 更新
shouldUpdate = updateClassInstance(
current,
workInProgress,
Component,
nextProps,
renderLanes,
);
}
const nextUnitOfWork = finishClassComponent(
current,
workInProgress,
Component,
shouldUpdate,
hasContext,
renderLanes,
);
// ...
return nextUnitOfWork;
}
js
function finishClassComponent(
current: Fiber | null,
workInProgress: Fiber,
Component: any,
shouldUpdate: boolean,
hasContext: boolean,
renderLanes: Lanes,
) {
// ...
reconcileChildren(current, workInProgress, nextChildren, renderLanes);
// ...
}
以mountClassInstance方法举例:
js
function mountClassInstance(
workInProgress: Fiber,
ctor: any,
newProps: any,
renderLanes: Lanes,
): void {
// ...
if (
typeof ctor.getDerivedStateFromProps !== 'function' &&
typeof instance.getSnapshotBeforeUpdate !== 'function' &&
(typeof instance.UNSAFE_componentWillMount === 'function' ||
typeof instance.componentWillMount === 'function')
) {
// 调用实例的生命周期方法
callComponentWillMount(workInProgress, instance);
// If we had additional state updates during this life-cycle, let's
// process them now.
processUpdateQueue(workInProgress, newProps, instance, renderLanes);
instance.state = workInProgress.memoizedState;
}
// ...
}
js
function callComponentWillMount(workInProgress: Fiber, instance: any) {
const oldState = instance.state;
if (typeof instance.componentWillMount === 'function') {
// 调用生命周期componentWillMount()
instance.componentWillMount();
}
if (typeof instance.UNSAFE_componentWillMount === 'function') {
instance.UNSAFE_componentWillMount();
}
if (oldState !== instance.state) {
if (__DEV__) {
console.error(
'%s.componentWillMount(): Assigning directly to this.state is ' +
"deprecated (except inside a component's " +
'constructor). Use setState instead.',
getComponentNameFromFiber(workInProgress) || 'Component',
);
}
classComponentUpdater.enqueueReplaceState(instance, instance.state, null);
}
}
Hooks
Hooks使得函数组件拥有了函数内状态以及多种方法(代替生命周期),使得组件逻辑变得更加简单清晰,更容易复用。
让我们回到beginWork函数中,其中对于FunctionComponent类型,调用updateFunctionComponent方法:
js
function updateFunctionComponent(
current: null | Fiber,
workInProgress: Fiber,
Component: any,
nextProps: any,
renderLanes: Lanes,
) {
// ...
let nextChildren;
let hasId;
// 读取上下文
prepareToReadContext(workInProgress, renderLanes);
if (enableSchedulingProfiler) {
markComponentRenderStarted(workInProgress);
}
if (__DEV__) {
ReactCurrentOwner.current = workInProgress;
setIsRendering(true);
// 调用renderWithHooks执行函数组件更新
nextChildren = renderWithHooks(
current,
workInProgress,
Component,
nextProps,
context,
renderLanes,
);
hasId = checkDidRenderIdHook();
setIsRendering(false);
} else {
nextChildren = renderWithHooks(
current,
workInProgress,
Component,
nextProps,
context,
renderLanes,
);
hasId = checkDidRenderIdHook();
}
if (enableSchedulingProfiler) {
markComponentRenderStopped();
}
// 判断是否可以直接复用fiber
if (current !== null && !didReceiveUpdate) {
bailoutHooks(current, workInProgress, renderLanes);
// 调用该方法判断fiber的子树是否需要更新
return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes);
}
if (getIsHydrating() && hasId) {
pushMaterializedTreeId(workInProgress);
}
// React DevTools reads this flag.
workInProgress.flags |= PerformedWork;
// diff 搞起来
reconcileChildren(current, workInProgress, nextChildren, renderLanes);
return workInProgress.child;
}
js
export function renderWithHooks<Props, SecondArg>(
current: Fiber | null,
workInProgress: Fiber,
Component: (p: Props, arg: SecondArg) => any,
props: Props,
secondArg: SecondArg,
nextRenderLanes: Lanes,
): any {
renderLanes = nextRenderLanes;
currentlyRenderingFiber = workInProgress;
if (__DEV__) {
hookTypesDev =
current !== null
? ((current._debugHookTypes: any): Array<HookType>)
: null;
hookTypesUpdateIndexDev = -1;
// Used for hot reloading:
ignorePreviousDependencies =
current !== null && current.type !== workInProgress.type;
}
workInProgress.memoizedState = null;
workInProgress.updateQueue = null;
workInProgress.lanes = NoLanes;
// 是否为开发环境
if (__DEV__) {
if (current !== null && current.memoizedState !== null) {
ReactCurrentDispatcher.current = HooksDispatcherOnUpdateInDEV;
} else if (hookTypesDev !== null) {
ReactCurrentDispatcher.current = HooksDispatcherOnMountWithHookTypesInDEV;
} else {
ReactCurrentDispatcher.current = HooksDispatcherOnMountInDEV;
}
} else {
// 判断当前是否为null,也就是是否为首次挂载
ReactCurrentDispatcher.current =
current === null || current.memoizedState === null
// 众所周知,同一个hooks在mount和update阶段是调用不一样的方法
// HooksDispatcherOnUpdateInDEV对应的是{useCallback: updateCallback,useEffect: updateEffect,...}
? HooksDispatcherOnMount
// HooksDispatcherOnMountWithHookTypesInDEV对应的是{useCallback: mountCallback,useEffect: mountEffect,...}
: HooksDispatcherOnUpdate;
}
const shouldDoubleRenderDEV =
__DEV__ &&
debugRenderPhaseSideEffectsForStrictMode &&
(workInProgress.mode & StrictLegacyMode) !== NoMode;
shouldDoubleInvokeUserFnsInHooksDEV = shouldDoubleRenderDEV;
// 返回当前函数组件的return jsx...
let children = Component(props, secondArg);
shouldDoubleInvokeUserFnsInHooksDEV = false;
// Check if there was a render phase update
// 如 English所说,趋向稳定
if (didScheduleRenderPhaseUpdateDuringThisPass) {
// Keep rendering until the component stabilizes (there are no more render
// phase updates).
children = renderWithHooksAgain(
workInProgress,
Component,
props,
secondArg,
);
}
if (shouldDoubleRenderDEV) {
// In development, components are invoked twice to help detect side effects.
// 开发环境下,组件调用两次检查副作用(也就是常见的组件re-render问题之一)
setIsStrictModeForDevtools(true);
try {
children = renderWithHooksAgain(
workInProgress,
Component,
props,
secondArg,
);
} finally {
setIsStrictModeForDevtools(false);
}
}
finishRenderingHooks(current, workInProgress, Component);
return children;
}