vue 源码阅读随笔

293 阅读9分钟

1.我是怎么去读源码的

参考资料:《我是如何阅读源码的》 这个大佬讲的文章特别好。如果只是想去看看源码,可以通过这种方式来阅读源码。 但是文章中没有详细说明是如何去配置在vue项目中怎么去阅读的。 所以基于上面的文章和我本人的一些研究后,加入了几个详细的步骤。方法跟大佬讲的react源码阅读方式类似。

如何基于vue文件去阅读源码

1.初始化一个vue3的项目 2.配置vue.config.js文件

module.exports = {
  configureWebpack:{
    externals: {
       vue: 'Vue'
    }
  }
}

3.在public/index.html文件添加,端口就是你服务启动的端口

<!DOCTYPE html>
<html lang="">
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width,initial-scale=1.0">
    <link rel="icon" href="<%= BASE_URL %>favicon.ico">
    <title><%= htmlWebpackPlugin.options.title %></title>
    + <script src="http://127.0.0.1:8080/script/mvue.js"></script>
  </head>
  <body>
    <noscript>
      <strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
    </noscript>
    <div id="app"></div>
    <!-- built files will be auto injected -->
  </body>
</html>

4.npm run start 即可

react 源码配置补充

react 的源码文件必须放到public 文件夹下,不可以放到其他地方,不然会有报错。(血的教训)

进入render 方法前,先准备一些常用知识:位运算、 移位运算、weakset weakmap,准备好了再往下。不然有点蒙蔽

接下来就是正菜了
import {createApp} from 'vue'
import App from './App.vue'
const app = createApp(App)
// app.config.kk = '123'
app.mount('#app')
从入口开始:

研究createApp 方法干了啥。找到什么时候节点挂载到root根节点,什么时候进行的挂载。

提供的源码都是精简过的,切记!!!


    const createApp = ((...args) => {
      const app = ensureRenderer().createApp(...args); //先不看这句,看了会放弃
      const { mount } = app;// 这句也是
      // 下面的可以看下了,app.mount 这个语句就是重写我们写的app.mount 方法
      app.mount = (containerOrSelector) => {
      
          // 获取根节点 一种是string格式 一种是dom对象格式
          const container = normalizeContainer(containerOrSelector);
          if (!container)
              return;
              
          // clear content before mounting,清除root 节点内的内容
          container.innerHTML = '';
          // 调用 前面声明的 app 内的 mount 方法 
          const proxy = mount(container, false, container instanceof SVGElement);
          // 返回proxy ,来实现响应式数据
          return proxy;
      };
      
      
      
 // 看下 normalizeContainer 方法
    function normalizeContainer(container) {
    // 如果传入string 则通过 selector 方法获取节点
      if (isString(container)) {
          const res = document.querySelector(container);
          if (!res) {
           warn$1(`Failed to mount app: mount target selector "${container}" returned null.`);
          }
          
          return res;
      }
      // 如果传入节点直接返回
      return container;
  }

现在准备去探索 ensureRenderer 的内心世界了,先上代码

let renderer;
function ensureRenderer() {
  return (renderer ||
      (renderer = createRenderer(rendererOptions)));
}
// 这个很重要,首先看renderer , 这里是个惰性声明。如果存在则return ,如果不存在则初始化
// 现在开始头秃的 rendererOptions 这个东西吧

const rendererOptions = extend({ patchProp }, nodeOps);
// 就是去读取dom 节点属性的方法。例如<div data-a='111'>123</div>,patchProp就是读取data-a方法的,然后根据不通的key值进行不同的操作,比如 style class on:click等 。

const patchProp = function(){
    if (key === 'class') {
          patchClass(el, nextValue, isSVG);
      }
      else if (key === 'style') {
          patchStyle(el, prevValue, nextValue);
      }
      ...
}

// nodeOps 就是对dom节点的操作方法集合 。

const nodeOps = {
      insert: (child, parent, anchor) => {
          parent.insertBefore(child, anchor || null);
      },
      remove: child => {
          const parent = child.parentNode;
          if (parent) {
              parent.removeChild(child);
          }
      },
      ...
}

// 所有 rendererOptions 是操作dom节点,及操作dom属性的 一些方法集合成的对象。这个要记清楚,后面全是跟这鬼玩意有关系的。


现在开始进入 createRenderer 吧

// 这次真没删代码,就这句
// 记住 options = rendererOptions 
function createRenderer(options) {
      return baseCreateRenderer(options);
 }

进入 baseCreateRenderer ,首先深吸一口气。

// baseCreateRenderer 这函数有1215行代码

function baseCreateRenderer(options) {
 target.__VUE__ = true;
      const { insert: hostInsert, remove: hostRemove, patchProp: hostPatchProp, createElement: hostCreateElement, setText: hostSetText, setElementText: hostSetElementText, parentNode: hostParentNode, nextSibling: hostNextSibling, setScopeId: hostSetScopeId = NOOP} = options;
 // options = rendererOptions 就是dom 操作的方法,切记
 const patch = (xxxx)=>{
     ...
 }
 const processElement = ()=>{}
 const processComponent = ()=>{}
 ... 
 const render = (vnode, container, isSVG) => {
          if (vnode == null) {
              if (container._vnode) {
                  unmount(container._vnode, null, null, true);
              }
          }
          else {
              patch(container._vnode || null, vnode, container, null, null, null, isSVG);
          }
          flushPostFlushCbs();
          container._vnode = vnode;
      };
      return {
          render,
          hydrate,
          //const app = ensureRenderer().createApp(...args); 到这就回到了我们的第一句了。
          createApp: createAppAPI(render, hydrate)
      };
}

这个函数就看到这里,先不管他干了啥吧,会掉头发的,当然如果觉得自己每次剪头发都要打薄的可以试试,反正我不敢,接下来看下 createAppAPI 这个函数

function createAppAPI(render, hydrate) {
    //hydrate 这个参数可以直接跳过了。
    //const app = ensureRenderer().createApp(...args); 到这就回到了我们的第一句了。
      return function createApp(rootComponent, rootProps = null) {
      // 初始化时rootprops 肯定是空的。这里跳过
          if (rootProps != null && !isObject(rootProps)) {
              warn$1(`root props passed to app.mount() must be an object.`);
              rootProps = null;
          }
          //初始化一个app的context ,用来描述 app这个对象的特性 
          const context = createAppContext();
          // 使用 new set 保证插件引入的唯一性
          const installedPlugins = new Set();
          // 这个也要记住,程序运行到这一步时。 节点是没有挂载的。所以isMounted = false 
          let isMounted = false;
          const app = (context.app = {
              _uid: uid++,
              _component: rootComponent,
              _props: rootProps,
              _container: null,
              _context: context,
              _instance: null,
              version,
              get config() {
                  return context.config;
              },
              set config(v) {
                  {
                      warn$1(`app.config cannot be replaced. Modify individual options instead.`);
                  }
              },
              use(plugin, ...options) {
                   // 已删代码,
                   // 使用方式
                   app.use('element UI')
                  return app;
              },
              mixin(mixin) {
                   // 已删代码,
                   // 使用方式
                  // app.mixins('xxx.js')
                  return app;
              },
              component(name, component) {
                   // 已删代码,
                   // 使用方式
                  // app.component(App)
                  context.components[name] = component;
                  return app;
              },
              directive(name, directive) {
                    // 已删代码,自定义指令
                   // 使用方式
                  // app.directive('v-cli',xxxx)
                  // 然后使用方式:<div v-cli='xxxxx'>3333</div>
                  return app;
              },
              mount(rootContainer, isSVG) {
              //重头戏
                  if (!isMounted) {
                      // 把 根节点转换成 虚拟dom vnode
                      const vnode = createVNode(rootComponent, rootProps);
                      // 声明 
                      vnode.appContext = context;
                      // HMR root reload, vue 热更新相关。
                      {
                          context.reload = () => {
                              render(cloneVNode(vnode), rootContainer, isSVG);
                          };
                      }
                      // 执行render 方法去做dom节点挂载
                      // 有兴趣可以打印一下 rootComponent rootProps vnode  rootContainer 这几个的值 。
                      render(vnode, rootContainer, isSVG);
                      isMounted = true;
                      app._container = rootContainer;
                      rootContainer.__vue_app__ = app;
                      {
                          app._instance = vnode.component;
                          devtoolsInitApp(app, version);
                      }
                      return getExposeProxy(vnode.component) || vnode.component.proxy;
                  }
                  else {
                      warn$1(`App has already been mounted.\n` +
                          `If you want to remount the same app, move your app creation logic ` +
                          `into a factory function and create fresh app instances for each ` +
                          `mount - e.g. \`const createMyApp = () => createApp(App)\``);
                  }
              },
              unmount() {
                  if (isMounted) {
                      render(null, app._container);
                      {
                          app._instance = null;
                          devtoolsUnmountApp(app);
                      }
                      delete app._container.__vue_app__;
                  }
                  else {
                      warn$1(`Cannot unmount an app that is not mounted.`);
                  }
              },
              provide(key, value) {
                  if (key in context.provides) {
                      warn$1(`App already provides property with key "${String(key)}". ` +
                          `It will be overwritten with the new value.`);
                  }
                  // TypeScript doesn't allow symbols as index type
                  // https://github.com/Microsoft/TypeScript/issues/24587
                  context.provides[key] = value;
                  return app;
              }
          });
          return app;
      };
  }

看下 createAppContext 方法

function createAppContext() {
      return {
          app: null,
          config: {
              isNativeTag: NO,
              performance: false,
              globalProperties: {},
              optionMergeStrategies: {},
              errorHandler: undefined,
              warnHandler: undefined,
              compilerOptions: {}
          },
          mixins: [],
          components: {},
          directives: {},
          provides: Object.create(null),
          optionsCache: new WeakMap(),
          propsCache: new WeakMap(),
          emitsCache: new WeakMap()
      };
  }

认识一下 patchFlag

patchFlag 是 complier 时的 transform 阶段解析 AST Element 打上的优化标识。并且,顾名思义 patchFlag,patch 一词表示着它会为 runtime 时的 patchVNode 提供依据,从而实现靶向更新 VNode 的效果。因此,这样一来一往,也就是耳熟能详的 Vue3 巧妙结合 runtime 与 compiler 实现靶向更新和静态提升。

而在源码中 patchFlag 被定义为一个数字枚举类型,每一个枚举值对应的标识意义会是这样:

50e0f4598fdd859e1ed86bf2ca31750b.png

并且,值得一提的是整体上 patchFlag 的分为两大类:

当 patchFlag 的值大于 0 时,代表所对应的元素在 patchVNode 时或 render 时是可以被优化生成或更新的。 当 patchFlag 的值小于 0 时,代表所对应的元素在 patchVNode 时,是需要被 full diff,即进行递归遍历 VNode tree 的比较更新过程。

// patchFlags 声明
PatchFlags {
		TEXT = 1,1/动态文本节点
		CLASS = 1<<11/ 2// 动态 classSTYLE= 1<<2,// 4//动态 style
		PROPS = 1<< 3,// 8// 动态属性,但不包含类名和样式
		FULL_PROPS = 1<<4,// 16 //具有动态 key属性,当key改变时,需要进行完整的 diff 比较。
		HYDRATE_EVENTS = 1<<5,// 32//带有监听事件的节点
		STABLE_FRAGMENT = 1<<6,// 64//一个不会改变子节点顺序的 fragment
		KEYED_FRAGMENT = 1<<7,// 128//带有key属性的 fragment 或部分子字节有
		keyUNKEYED_FRAGMENT = 1<<8,// 256//子节点没有key 的 fragment
		NEED_ PATCH =1<<9,//512//一个节点只会进行非 props比较
		DYNAMIC_SLOTS = 1 << 10,//1024 // 动态的插槽
		
		// SPECIAL FLAGS (下面是特殊的)--------------------------------------------------------- 
		// 以下是特殊的flag,不会在优化中被用到,是内置的特殊flag
		// 表示他是静态节点,他的内容永远不会改变,对于hydrate的过程中,不会需要再对其子节点进行diff
		HOISTED = -1,
		BAIL = -2, // 用来表示一个节点的diff应该结束
}

认识一下 shapeFlag

v2-862de5557bf2f480990c3dd80668dbb8_1440w.jpeg 这里的定义就涉及到了以为运算了。将节点类型枚举定义。参考类似dom的nodeType (不晓得nodeType是啥的自己百度)

v2-4500e5b22dcc3e3e5913be2f7f8f0d01_1440w.jpeg

参考资料2:segmentfault.com/a/119000002… 参考资料3:zhuanlan.zhihu.com/p/356382676 假装自己准备好了。然后进入 render 方法。render 方法来源于 baseCreateRenderer 函数内

 const render = (vnode, container, isSVG) => {
          if (vnode == null) {
              if (container._vnode) {
                  unmount(container._vnode, null, null, true);
              }
          }
          else {
          // 先不考虑错误情况,所以一定会进入 patch 方法, patch 方法也是来源于baseCreateRenderer 方法
              patch(container._vnode || null, vnode, container, null, null, null, isSVG);
          }
          flushPostFlushCbs();
          container._vnode = vnode;
      };

进入patch 方法

// 删减后的代码
     const patch = (n1, n2, container, anchor = null, parentComponent = null, parentSuspense = null, isSVG = false, slotScopeIds = null, optimized = isHmrUpdating ? false : !!n2.dynamicChildren) => {
        const { shapeFlag } = n2;
        console.log('patch 会进入两次 => 注意')
        // shapeFlag & 1  位运算 
        if (shapeFlag & 1 /* ELEMENT */) {
                console.log('第二次进入','processElement 函数')
                processElement(n1, n2, container, anchor, parentComponent, parentSuspense, isSVG, slotScopeIds, optimized);
        }else if (shapeFlag & 6 /* COMPONENT */) {
                console.log('第一次进入','processComponent 函数')
                processComponent(n1, n2, container, anchor, parentComponent, parentSuspense, isSVG, slotScopeIds, optimized);
        }
      };

第一次进入 processComponent 方法,现在看下 processComponent 方法

   const processComponent = (n1, n2, container, anchor, parentComponent, parentSuspense, isSVG, slotScopeIds, optimized) => {
          n2.slotScopeIds = slotScopeIds;
          if (n1 == null) {
          // 初始化所以只能是挂载component 不可能是updatecomponent
            console.log('挂载 component ')
            mountComponent(n2, container, anchor, parentComponent, parentSuspense, isSVG, optimized);
          }
          else {
              updateComponent(n1, n2, optimized);
          }
      };
mountComponent
      const mountComponent = (initialVNode, container, anchor, parentComponent, parentSuspense, isSVG, optimized) => {
            const instance = (initialVNode.component = createComponentInstance(initialVNode, parentComponent, parentSuspense));

            setupComponent(instance);
            setupRenderEffect(instance, initialVNode, container, anchor, parentSuspense, isSVG, optimized);
      };
      // 重点知识 instance 
      // instance Vue 实例初始化的对象,
      //setupComponent 初始化instance 上的一些属性和方法,可以先不关注
      //setupRenderEffect 主要关注这个方法
      
setupRenderEffect
   const setupRenderEffect = (instance, initialVNode, container, anchor, parentSuspense, isSVG, optimized) => {
   // componentUpdateFn 方法是最重要的。将实现组件的挂载和渲染。
          const componentUpdateFn = () => {
              if (!instance.isMounted) {
                  console.log('将component 转化为vnode 虚拟dom节点 ')
                  const subTree = (instance.subTree = renderComponentRoot(instance));
                //   console.log('vnode树',subTree)
                  console.log('准备第二次进入 patch')
                  patch(null, subTree, container, anchor, instance, parentSuspense, isSVG);                
                  initialVNode.el = subTree.el;
                  instance.isMounted = true;
                  initialVNode = container = anchor = null;
              }
          };
          // create reactive effect for rendering
          // 1 注册effect 。这个极其重要。 关系到后面的数据响应式刷新视图。 此次先不深究
          const effect = (instance.effect = new ReactiveEffect(componentUpdateFn, () => queueJob(instance.update), instance.scope // track it in component's effect scope
          ));
          //
          const update = (instance.update = effect.run.bind(effect));
          // 
          update.id = instance.uid;
         
          update();
      };

再次进入了patch ,此时 组件会进入 processElement ,因为此时 component 组件转为vnode,shapeFlag值已经改了。

processElement
const processElement = (n1, n2, container, anchor, parentComponent, parentSuspense, isSVG, slotScopeIds, optimized) => {
          isSVG = isSVG || n2.type === 'svg';
          if (n1 == null) {
          // 只是初始化,不存在更新动作,所以直接进入了挂载节点方法
              mountElement(n2, container, anchor, parentComponent, parentSuspense, isSVG, slotScopeIds, optimized);
          }
          else {
              patchElement(n1, n2, parentComponent, parentSuspense, isSVG, slotScopeIds, optimized);
          }
      };
mountElement
      const mountElement = (vnode, container, anchor, parentComponent, parentSuspense, isSVG, slotScopeIds, optimized) => {
          let el;
          const {  props, shapeFlag } = vnode;
          //hostCreateElement 来自 options = rendererOptions 就是dom 操作的方法,切记
          el = vnode.el = hostCreateElement(vnode.type, isSVG, props && props.is, props);
          // mount children first, since some props may rely on child content
          // being already rendered, e.g. `<select value>`
          // 位运算,得出节点的类型,如果是节点是text ,直接写入
          if (shapeFlag & 8 /* TEXT_CHILDREN */) {
          
              hostSetElementText(el, vnode.children);
          }
          // 给el挂载当前的vnode ,为更新做数据缓存
          Object.defineProperty(el, '__vnode', {
            value: vnode,
            enumerable: false
        });
        // 缓存父组件
        Object.defineProperty(el, '__vueParentComponent', {
            value: parentComponent,
            enumerable: false
        });
        // 插入节点
          hostInsert(el, container, anchor);
      };

到了这一步,节点已经插入到了跟节点,已经渲染到页面了。然后就是return proxy。

生命周期源码阅读

生命周期源码阅读是要从 mountComponent 方法开始的,因为此时生成了instance 组件实例

     const mountComponent = (initialVNode, container, anchor, parentComponent, parentSuspense, isSVG, optimized) => {
            const instance = (initialVNode.component = createComponentInstance(initialVNode, parentComponent, parentSuspense));
            // 生命周期主要是看 setupComponent 函数,传入了 组件实例
            setupComponent(instance);
            
            setupRenderEffect(instance, initialVNode, container, anchor, parentSuspense, isSVG, optimized);
      };

进入 setupComponent 函数,脑动屏蔽掉ssr的方法

function setupComponent(instance, isSSR = false) {
    isInSSRComponentSetup = isSSR;
    const { props, children } = instance.vnode;
    // 判断组件是有状态的还是无状态的。这个对于react 同学来说还是很常见的
    const isStateful = isStatefulComponent(instance);
    // 初始化传入的props
    initProps(instance, props, isStateful, isSSR);
    // 初始化slot 引入
    initSlots(instance, children);
    
    const setupResult = isStateful
        ? setupStatefulComponent(instance, isSSR)
        : undefined;
    isInSSRComponentSetup = false;
    return setupResult;
}

正常 vue 的写法都是有状态组件 。所以我们进入了setupStatefulComponent 方法, 因为我们现在探索的是组件声明周期源码,所以可以不去看关于setup 相关的代码

function setupStatefulComponent(instance, isSSR) {
    const Component = instance.type;
    {
        if (Component.name) {
            validateComponentName(Component.name, instance.appContext.config);
        }
        if (Component.components) {
            const names = Object.keys(Component.components);
            for (let i = 0; i < names.length; i++) {
                validateComponentName(names[i], instance.appContext.config);
            }
        }
        if (Component.directives) {
            const names = Object.keys(Component.directives);
            for (let i = 0; i < names.length; i++) {
                validateDirectiveName(names[i]);
            }
        }
        if (Component.compilerOptions && isRuntimeOnly()) {
            warn$1(`jinggao`);
        }
    }
    // 0. create render proxy property access cache
    instance.accessCache = Object.create(null);
    // 1. create public instance / render proxy
    // also mark it raw so it's never observed
    instance.proxy = markRaw(new Proxy(instance.ctx, PublicInstanceProxyHandlers));
    {
        exposePropsOnRenderContext(instance);
    }
    // 2. call setup()
    const { setup } = Component;
   // 这个方法暂时先关注 finishComponentSetup 方法
        finishComponentSetup(instance, isSSR);
}

进入 finishComponentSetup 方法,

function finishComponentSetup(instance, isSSR, skipOptions) {
    const Component = instance.type;
    ...

    {
        setCurrentInstance(instance);
        pauseTracking();
        // 注入options ,关注这个方法即可。先放弃其他的方法
        applyOptions(instance);
        resetTracking();
        unsetCurrentInstance();
    }
    ...

}

现在进入 applyOptions 方法

function applyOptions(instance) {
    const options = resolveMergedOptions(instance);
    const publicThis = instance.proxy;
    const ctx = instance.ctx;
    // do not cache property access on public proxy during state initialization
    shouldCacheAccess = false;

    if (options.beforeCreate) {
        callHook(options.beforeCreate, instance, "bc" /* BEFORE_CREATE */);
    }
    ```
const { 
// state
data: dataOptions, computed: computedOptions, methods, watch: watchOptions, provide: provideOptions, inject: injectOptions, 
// lifecycle
created, beforeMount, mounted, beforeUpdate, updated, activated, deactivated, beforeDestroy, beforeUnmount, destroyed, unmounted, render, renderTracked, renderTriggered, errorCaptured, serverPrefetch, 
// public API
expose, inheritAttrs, 
// assets
components, directives, filters } = options;
...
// 当声明周期是 beforecreate 和 created 时 是直接调用 callhook 方法,这里先不去看callhook 方法
if (created) {
    callHook(created, instance, "c" /* CREATED */);
}

function registerLifecycleHook(register, hook) {
    if (isArray(hook)) {
        hook.forEach(_hook => register(_hook.bind(publicThis)));
    }
    else if (hook) {
        register(hook.bind(publicThis));
    }
}
// 我们来看下在其他声明周期的声明下,是做了什么呢。
registerLifecycleHook(onBeforeMount, beforeMount);
// registerLifecycleHook 这个方法就可以发现,是调用了 onBeforeMount 方法 
...

    }

现在我们进入 createHook 方法

const createHook = (lifecycle) => (hook, target = currentInstance) => 
// post-create lifecycle registrations are noops during SSR (except for serverPrefetch)
(!isInSSRComponentSetup || lifecycle === "sp" /* SERVER_PREFETCH */) &&
    injectHook(lifecycle, hook, target);
const onBeforeMount = createHook("bm" /* BEFORE_MOUNT */);
// 通过 createHOOK 又进入了 injectHook 方法

现在是进入 injectHook 方法

function injectHook(type, hook, target = currentInstance, prepend = false) {
    if (target) {
        const hooks = target[type] || (target[type] = []);

        const wrappedHook = hook.__weh ||
            (hook.__weh = (...args) => {
                if (target.isUnmounted) {
                    return;
                }
                setCurrentInstance(target);
                // callWithAsyncErrorHandling 进入
                const res = callWithAsyncErrorHandling(hook, target, type, args);
                unsetCurrentInstance();
                resetTracking();
                return res;
            });
        if (prepend) {
            hooks.unshift(wrappedHook);
        }
        else {
            hooks.push(wrappedHook);
        }
        return wrappedHook;
    }
  
}

进入 callWithAsyncErrorHandling 方法

function callWithAsyncErrorHandling(fn, instance, type, args) {
    if (isFunction(fn)) {
        // 因为在源码中默认传入的就是个function,所以只需要关注 callWithErrorHandling
        const res = callWithErrorHandling(fn, instance, type, args);
        if (res && isPromise(res)) {
            res.catch(err => {
                handleError(err, instance, type);
            });
        }
        return res;
    }
 ...
}

进入 callWithErrorHandling 方法

function callWithErrorHandling(fn, instance, type, args) {
    let res;
    try {
        // 在这里执行生命了周期 ,总算找到了最后生命周期函数执行的调用了。
        res = args ? fn(...args) : fn();
    }
    catch (err) {
        handleError(err, instance, type);
    }
    return res;
}

现在再来看下 beforecreate 和 created 的 callhook 方法

function callHook(hook, instance, type) {
    callWithAsyncErrorHandling(isArray(hook)
        ? hook.map(h => h.bind(instance.proxy))
        : hook.bind(instance.proxy), instance, type);
}

发现执行了 callWithAsyncErrorHandling 方法

function callWithAsyncErrorHandling(fn, instance, type, args) {
    if (isFunction(fn)) {
        //最后还是来到了这里
        const res = callWithErrorHandling(fn, instance, type, args);
        if (res && isPromise(res)) {
            res.catch(err => {
                handleError(err, instance, type);
            });
        }
        return res;
    }
    const values = [];
    for (let i = 0; i < fn.length; i++) {
        values.push(callWithAsyncErrorHandling(fn[i], instance, type, args));
    }
    return values;
}

生命周期流转与触发

组件生命周期都是在组件实例化成instance 时,注入到 instance实例上的,然后根据组件不同的状态流转的过程中,判断对应的生命周期函数是否存在,然后去执行。

blog.csdn.net/newway007/a…

proxy 监听数据变动是怎么更新到视图的呢
diff算法的优化点
diff算法的双while执行顺序分析

... 未完待续(内容预告:proxy代理的数据是怎么去更新视图的。diff算法解析。)