专栏首页纸上得来终觉浅Vue3源码阅读笔记之整体执行顺序简介(1)
原创

Vue3源码阅读笔记之整体执行顺序简介(1)

从Vue官网得到源码(https://unpkg.com/vue@next),拷贝到本地文件,然后创建如下html:

<html>

<head></head>

<body>
    <div id="counter">
        Counter: {{ counter }}
    </div>
    <script src="./vue.js"></script>
    <script>

        const Counter = {
            data() {
                return {
                    counter: 0,
                }
            },
            mounted() {
                setTimeout(() => {
                    this.counter++
                }, 1000)
            }
        }
        debugge
        let app =  Vue.createApp(Counter);
        app.mount('#counter')
    </script>
</body>

</html>

用浏览器访问可以看到我们自定义的{{counter}}内存被正确的替换成了下面js中声明的Counter对象中的值,并且mounted中的方法也被执行了,赋值发生后页面也自动更新了。

问题:这一切是怎么做到的?

答案:从断点开始慢慢阅读Vue做了什么。

首先,Vue.js文件声明了一个Vue变量,通过立即执行函数,在内部做了很多变量和函数声明,而暴露给外部使用的只有一部分:

var Vue = (function(exports){
    ...
    exports.createApp = createApp;
    ...
    return exports;
}({}))

看下createApp做了什么:

const createApp = ((...args) => {
    // createApp 得到实例和上下文 并相互绑定
    const app = ensureRenderer().createApp(...args);
    {
        // 给上下文注入2个属性检测方法
        injectNativeTagCheck(app);
        injectCustomElementCheck(app);
    }
    // 取出我们创建的实例上的 mount 方法
    const { mount } = app;
    // 重新赋值一个 所以我们创建好实例后调用的mount其实是这个
    app.mount = (containerOrSelector) => {
        // 得到模板挂载DOM节点 所有渲染出来的节点都会被挂载到这个下面
        const container = normalizeContainer(containerOrSelector);
        if (!container)
            return;
        const component = app._component;
        // 实例上的 _component 在初始化的时候就是 我们声明的实例参数对象 它此时不是函数
        if (!isFunction(component) && !component.render && !component.template) {
            // 赋值 template
            component.template = container.innerHTML;
        }
        // clear content before mounting
        // 清除原DOM节点的内容 不需要了 因为会整体被替换成vue渲染的
        container.innerHTML = '';
        // 执行 mount 后得到一个代理 proxy:
        const proxy = mount(container);
        if (container instanceof Element) {
            // 修改2个属性
            container.removeAttribute('v-cloak');
            container.setAttribute('data-v-app', '');
        }
        return proxy;
    };
    return app;
});

它做了如下工作:

1. 创建全局render,初始化好必备的渲染函数

2. 调用render的createApp得到一个app实例

3. 注入2个方法

4. 重写实例mount方法

5. 返回app实例

我们重点看下1和2

function baseCreateRenderer(options, createHydrationFns) {
    ...
    声明了很多方法 这些方法以后会用到 现在先放一放
    ...
    return {
        render,
        hydrate,
        createApp: createAppAPI(render, hydrate)
    }
}

function createAppContext() {
        return {
            app: null,
            config: {
                isNativeTag: NO,
                performance: false,
                globalProperties: {},
                optionMergeStrategies: {},
                isCustomElement: NO,
                errorHandler: undefined,
                warnHandler: undefined
            },
            mixins: [],
            components: {},
            directives: {},
            provides: Object.create(null)
        };
    }
    let uid$1 = 0;

function createAppAPI(render, hydrate) {
        return function createApp(rootComponent, rootProps = null) {
            if (rootProps != null && !isObject(rootProps)) {
                warn(`root props passed to app.mount() must be an object.`);
                rootProps = null;
            }
            // 上下文 实例和上下文相互持有绑定对应
            const context = createAppContext();
            const installedPlugins = new Set();
            let isMounted = false;
            const app = (context.app = {
                _uid: uid$1++,
                _component: rootComponent,
                _props: rootProps,
                _container: null,
                _context: context,
                version,
                get config() {
                    return context.config;
                },
                set config(v) {
                    {
                        warn(`app.config cannot be replaced. Modify individual options instead.`);
                    }
                },
                ...
                // 挂载到根节点下
                mount(rootContainer, isHydrate) {
                    ...
                },
                ...
            });
            return app;
        };
    }

可以看到,除了得到app实例外,其实还有一个app上下文和实例相互指向。

至此,createApp的工作简要的分析完了。接下来看下我们调用app实例上的mount方法做了什么:

 mount(rootContainer, isHydrate) {
    if (!isMounted) {
        // 创建一个虚拟DOM节点
        const vnode = createVNode(rootComponent, rootProps);
        console.log('cur vnode: ', vnode)
        // store app context on the root VNode.
        // this will be set on the root instance on initial mount.
        // 绑定vnode的上下文
        vnode.appContext = context;
        // HMR root reload
        {
            context.reload = () => {
                render(cloneVNode(vnode), rootContainer);
            };
        }
        if (isHydrate && hydrate) {
            hydrate(vnode, rootContainer);
        }
        else {
            // 渲染:把虚拟dom节点转化为实际的dom节点并插入到实际dom根节点下
            render(vnode, rootContainer);
        }
        isMounted = true;
        // 实例 和 dom根节点相互指向对方
        app._container = rootContainer;
        rootContainer.__vue_app__ = app;
        {
            devtoolsInitApp(app, version);
        }
        return vnode.component.proxy;
    }
    else {
        warn(`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)\``);
    }
   }

它做了如下工作:

1. 初始化VNode节点

2. 调用render方法

3. 设置挂载flag和app的dom容器

4. 返回vnode的组件实例的代理

先看下1:

// 创建虚拟DOM节点
    function _createVNode(type, props = null, children = null, patchFlag = 0, dynamicProps = null, isBlockNode = false) {
        ...
        const shapeFlag = isString(type)
            ? 1 /* ELEMENT */
            : isSuspense(type)
                ? 128 /* SUSPENSE */
                : isTeleport(type)
                    ? 64 /* TELEPORT */
                    : isObject(type)
                        ? 4 /* STATEFUL_COMPONENT */
                        : isFunction(type)
                            ? 2 /* FUNCTIONAL_COMPONENT */
                            : 0;
        if (shapeFlag & 4 /* STATEFUL_COMPONENT */ && isProxy(type)) {
            type = toRaw(type);
            warn(`Vue received a Component which was made a reactive object. This can ` +
                `lead to unnecessary performance overhead, and should be avoided by ` +
                `marking the component with \`markRaw\` or using \`shallowRef\` ` +
                `instead of \`ref\`.`, `\nComponent that was made reactive: `, type);
        }
        // vnode模板 初始化时:currentScopeId 为 null
        const vnode = {
            __v_isVNode: true,
            ["__v_skip" /* SKIP */]: true,
            type,
            props,
            key: props && normalizeKey(props),
            ref: props && normalizeRef(props),
            scopeId: currentScopeId,
            children: null,
            component: null,
            suspense: null,
            ssContent: null,
            ssFallback: null,
            dirs: null,
            transition: null,
            el: null,
            anchor: null,
            target: null,
            targetAnchor: null,
            staticCount: 0,
            shapeFlag,
            patchFlag,
            dynamicProps,
            dynamicChildren: null,
            appContext: null
        };
        ...
        return vnode;
    }

从目前简单的参数来看,它就是返回一个vnode结构,注意此时它的shapeFlag为4,type就是我们传入的参数对象

而 render函数就是之前的render对象中声明的,通过闭包访问到它:

const render = (vnode, container) => {
    if (vnode == null) {
        if (container._vnode) {
            unmount(container._vnode, null, null, true);
        }
    }
    else {
        // 直接调用patch 把vnode的内容当做补丁打到实际的dom跟节点上
        patch(container._vnode || null, vnode, container);
    }
    flushPostFlushCbs();
    container._vnode = vnode;
};

它调用了 patch 方法,而 path 方法有能力可以把一个vonde节点映射到宿主dom节点上;flushPostFlushCbs方法由于此时没有任务需要执行,先跳过不看。

const patch = (n1, n2, container, anchor = null, parentComponent = null, parentSuspense = null, isSVG = false, optimized = false) => {
    // 初始化的时候 n1 为 null n2为准备渲染的vnode container为待插入的父节点

    // patching & not same type, unmount old tree

    ...

    // 初始化的时候直接走这里
    const { type, ref, shapeFlag } = n2;
    switch (type) {
        case Text:
            processText(n1, n2, container, anchor);
            break;
        case Comment:
            processCommentNode(n1, n2, container, anchor);
            break;
        case Static:
            if (n1 == null) {
                mountStaticNode(n2, container, anchor, isSVG);
            }
            else {
                patchStaticNode(n1, n2, container, isSVG);
            }
            break;
        case Fragment:
            processFragment(n1, n2, container, anchor, parentComponent, parentSuspense, isSVG, optimized);
            break;
        default:
            // 不是以上4种实际的dom节点
            if (shapeFlag & 1 /* ELEMENT */) {
                processElement(n1, n2, container, anchor, parentComponent, parentSuspense, isSVG, optimized);
            }
            else if (shapeFlag & 6 /* COMPONENT */) {
                // 初始化的时候走这里
                processComponent(n1, n2, container, anchor, parentComponent, parentSuspense, isSVG, optimized);
            }
            else if (shapeFlag & 64 /* TELEPORT */) {
                type.process(n1, n2, container, anchor, parentComponent, parentSuspense, isSVG, optimized, internals);
            }
            else if (shapeFlag & 128 /* SUSPENSE */) {
                type.process(n1, n2, container, anchor, parentComponent, parentSuspense, isSVG, optimized, internals);
            }
            else {
                warn('Invalid VNode type:', type, `(${typeof type})`);
            }
    }
    // set ref
    if (ref != null && parentComponent) {
        setRef(ref, n1 && n1.ref, parentSuspense, n2);
    }
};

可以看到 path 也是一个比较高级的方法,它根据传入参数的类型来决定调用其他的渲染方法,当前情况下我们走的是 processComponent

const processComponent = (n1, n2, container, anchor, parentComponent, parentSuspense, isSVG, optimized) => {
    if (n1 == null) {
        if (n2.shapeFlag & 512 /* COMPONENT_KEPT_ALIVE */) {
            parentComponent.ctx.activate(n2, container, anchor, isSVG, optimized);
        }
        else {
            // 初始化走这里 挂载组件到根目标节点
            mountComponent(n2, container, anchor, parentComponent, parentSuspense, isSVG, optimized);
        }
    }
    else {
        updateComponent(n1, n2, optimized);
    }
};

继续走 mountComponent 很明显:把n2这个vnode节点挂载到 container 节点下就可以了。

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);
   
};

它做了如下工作:

1. 创建一个组件实例, 同时 initialVNode.component 也指向它,我们之前 app实例上的mount方法返回的 proxy就是来自于组件实例上的属性

2. 设置组件实例(内部render方法以及参数响应式等等很多工作)

3. 创建renderEffect并且同步执行一次,触发渲染,完成依赖收集,更新页面等等

先看下1:

const publicPropertiesMap = extend(Object.create(null), {
        $: i => i,
        $el: i => i.vnode.el,
        $data: i => i.data,
        $props: i => (shallowReadonly(i.props) ),
        $attrs: i => (shallowReadonly(i.attrs) ),
        $slots: i => (shallowReadonly(i.slots) ),
        $refs: i => (shallowReadonly(i.refs) ),
        $parent: i => getPublicInstance(i.parent),
        $root: i => getPublicInstance(i.root),
        $emit: i => i.emit,
        $options: i => (resolveMergedOptions(i) ),
        $forceUpdate: i => () => queueJob(i.update),
        $nextTick: i => nextTick.bind(i.proxy),
        $watch: i => (instanceWatch.bind(i) )
    });

function createRenderContext(instance) {
        const target = {};
        // expose internal instance for proxy handlers
        Object.defineProperty(target, `_`, {
            configurable: true,
            enumerable: false,
            get: () => instance
        });
        // expose public properties
        Object.keys(publicPropertiesMap).forEach(key => {
            Object.defineProperty(target, key, {
                configurable: true,
                enumerable: false,
                get: () => publicPropertiesMap[key](instance),
                // intercepted by the proxy so no need for implementation,
                // but needed to prevent set errors
                set: NOOP
            });
        });
        // expose global properties
        const { globalProperties } = instance.appContext.config;
        Object.keys(globalProperties).forEach(key => {
            Object.defineProperty(target, key, {
                configurable: true,
                enumerable: false,
                get: () => globalProperties[key],
                set: NOOP
            });
        });
        return target;
    }

function createComponentInstance(vnode, parent, suspense) {
    const type = vnode.type;
    // inherit parent app context - or - if root, adopt from root vnode
    const appContext = (parent ? parent.appContext : vnode.appContext) || emptyAppContext;
    const instance = {
        uid: uid$2++,
        vnode,
        type,
        parent,
        appContext,
        root: null,
        next: null,
        subTree: null,
        update: null,
        render: null,
        proxy: null,
        exposed: null,
        withProxy: null,
        effects: null,
        provides: parent ? parent.provides : Object.create(appContext.provides),
        accessCache: null,
        renderCache: [],
        // local resovled assets
        components: null,
        directives: null,
        // resolved props and emits options
        propsOptions: normalizePropsOptions(type, appContext),
        emitsOptions: normalizeEmitsOptions(type, appContext),
        // emit
        emit: null,
        emitted: null,
        // state
        ctx: EMPTY_OBJ,
        data: EMPTY_OBJ,
        props: EMPTY_OBJ,
        attrs: EMPTY_OBJ,
        slots: EMPTY_OBJ,
        refs: EMPTY_OBJ,
        setupState: EMPTY_OBJ,
        setupContext: null,
        // suspense related
        suspense,
        suspenseId: suspense ? suspense.pendingId : 0,
        asyncDep: null,
        asyncResolved: false,
        // lifecycle hooks
        // not using enums here because it results in computed properties
        isMounted: false,
        isUnmounted: false,
        isDeactivated: false,
        bc: null,
        c: null,
        bm: null,
        m: null,
        bu: null,
        u: null,
        um: null,
        bum: null,
        da: null,
        a: null,
        rtg: null,
        rtc: null,
        ec: null
    };
    {
        instance.ctx = createRenderContext(instance);
    }
    instance.root = parent ? parent.root : instance;
    instance.emit = emit.bind(null, instance);
    return instance;
}

可以看到,组件实例上面放了很多属性,其中有一个ctx代表数据上下文,通过createRenderContext得到,其实我们平常在生命周期方法中方法访问到this其实就是指向这个组件实例的,

而我们的数据读写其实就是来自于ctx,只不过它等下会被代理拦截而已。这个组件实例上面拥有我们目前为止所有得到的信息。

function setupComponent(instance, isSSR = false) {
        isInSSRComponentSetup = isSSR;
        const { props, children } = instance.vnode;
        const isStateful = isStatefulComponent(instance);
        ...
        const setupResult = isStateful
            ? setupStatefulComponent(instance, isSSR)
            : undefined;
        ...
        isInSSRComponentSetup = false;
        return setupResult;
    }



function setupStatefulComponent(instance, isSSR) {
        const Component = instance.type;
        ...
        // 这里的 proxy 就是前文mount返回的proxy 它确实是ctx的代理 而 PublicInstanceProxyHandlers 控制着我们对 组件实例的数据字段的访问结果 下文再仔细分析
        instance.proxy = new Proxy(instance.ctx, PublicInstanceProxyHandlers);
        
        const { setup } = Component;
        if (setup) {
            ...
        }
        else {
            finishComponentSetup(instance);
        }
    }


function finishComponentSetup(instance, isSSR) {
        const Component = instance.type;
        // template / render function normalization
        if (!instance.render) {
            // could be set from setup()
            if (compile && Component.template && !Component.render) {
                {
                    startMeasure(instance, `compile`);
                }
                // 在这里编译模板得到render函数
                
                Component.render = compile(Component.template, {
                    isCustomElement: instance.appContext.config.isCustomElement,
                    delimiters: Component.delimiters
                });
                {
                    endMeasure(instance, `compile`);
                }
            }
            instance.render = (Component.render || NOOP);
            // for runtime-compiled render functions using `with` blocks, the rende
            // proxy used needs a different `has` handler which is more performant and
            // also only allows a whitelist of globals to fallthrough.
            if (instance.render._rc) {
                instance.withProxy = new Proxy(instance.ctx, RuntimeCompiledPublicInstanceProxyHandlers);
            }
        }
        // support for 2.x options
        {
            currentInstance = instance;
            pauseTracking();
            applyOptions(instance, Component);
            resetTracking();
            currentInstance = null;
        }
        ...
    }

重要的只有2个方法:

1. compile 得到当前template对应的render函数

2. applyOptions 把 我们设置的组件对象的属性添加到组件实例上

先看下1:

const compileCache = Object.create(null);
    // 这里就是编译函数 
    function compileToFunction(template, options) {
        if (!isString(template)) {
            if (template.nodeType) {
                template = template.innerHTML;
            }
            else {
                warn(`invalid template option: `, template);
                return NOOP;
            }
        }
        const key = template;
        const cached = compileCache[key];
        if (cached) {
            return cached;
        }
        if (template[0] === '#') {
            const el = document.querySelector(template);
            if (!el) {
                warn(`Template element not found or is empty: ${template}`);
            }
            // __UNSAFE__
            // Reason: potential execution of JS expressions in in-DOM template.
            // The user must make sure the in-DOM template is trusted. If it's rendered
            // by the server, the template should not contain any user data.
            template = el ? el.innerHTML : ``;
        }
        const { code } = compile$1(template, extend({
            hoistStatic: true,
            onError(err) {
                {
                    const message = `Template compilation error: ${err.message}`;
                    const codeFrame = err.loc &&
                        generateCodeFrame(template, err.loc.start.offset, err.loc.end.offset);
                    warn(codeFrame ? `${message}\n${codeFrame}` : message);
                }
            }
        }, options));
        // The wildcard import results in a huge object with every export
        // with keys that cannot be mangled, and can be quite heavy size-wise.
        // In the global build we know `Vue` is available globally so we can avoid
        // the wildcard object.
        const render = (new Function(code)()
            );
        render._rc = true;
        return (compileCache[key] = render);
    }
    registerRuntimeCompiler(compileToFunction);

可以看到 它主要调用了 compile$1 得到 render的函数代码,然后new Function生成了它,并且做了缓存

function compile$1(template, options = {}) {
        return baseCompile(template, extend({}, parserOptions, options, {
            nodeTransforms: [
                // ignore <script> and <tag>
                // this is not put inside DOMNodeTransforms because that list is used
                // by compiler-ssr to generate vnode fallback branches
                ignoreSideEffectTags,
                ...DOMNodeTransforms,
                ...(options.nodeTransforms || [])
            ],
            directiveTransforms: extend({}, DOMDirectiveTransforms, options.directiveTransforms || {}),
            transformHoist: null 
        }));
    }

// options对下包含很多函数方法 都是来自上面合并过来的
function baseCompile(template, options = {}) {
        const onError = options.onError || defaultOnError;
        const isModuleMode = options.mode === 'module';
        /* istanbul ignore if */
        {
            if (options.prefixIdentifiers === true) {
                onError(createCompilerError(45 /* X_PREFIX_ID_NOT_SUPPORTED */));
            }
            else if (isModuleMode) {
                onError(createCompilerError(46 /* X_MODULE_MODE_NOT_SUPPORTED */));
            }
        }
        const prefixIdentifiers = !true ;
        if (options.cacheHandlers) {
            onError(createCompilerError(47 /* X_CACHE_HANDLER_NOT_SUPPORTED */));
        }
        if (options.scopeId && !isModuleMode) {
            onError(createCompilerError(48 /* X_SCOPE_ID_NOT_SUPPORTED */));
        }
        const ast = isString(template) ? baseParse(template, options) : template;
        const [nodeTransforms, directiveTransforms] = getBaseTransformPreset();
        transform(ast, extend({}, options, {
            prefixIdentifiers,
            nodeTransforms: [
                ...nodeTransforms,
                ...(options.nodeTransforms || []) // user transforms
            ],
            directiveTransforms: extend({}, directiveTransforms, options.directiveTransforms || {} // user transforms
            )
        }));
        return generate(ast, extend({}, options, {
            prefixIdentifiers
        }));
    }

上述方法 主要做了3个事情:

1. basrParse得到抽象语法树

2. transform转化节点信息

3. generate生成render函数代码

接下来我们仔细看下它们的输入输出变化:

basrParse 入参为:"

Counter: {{ counter }}

" 和 一些辅助函数

返回的是:

{
    "type": 0,
    "children": [
        {
            "type": 2,
            "content": " Counter: ",
            "loc": {
                "start": {
                    "column": 1,
                    "line": 1,
                    "offset": 0
                },
                "end": {
                    "column": 18,
                    "line": 2,
                    "offset": 18
                },
                "source": "\n        Counter: "
            }
        },
        {
            "type": 5,
            "content": {
                "type": 4,
                "isStatic": false,
                "constType": 0,
                "content": "counter",
                "loc": {
                    "start": {
                        "column": 21,
                        "line": 2,
                        "offset": 21
                    },
                    "end": {
                        "column": 28,
                        "line": 2,
                        "offset": 28
                    },
                    "source": "counter"
                }
            },
            "loc": {
                "start": {
                    "column": 18,
                    "line": 2,
                    "offset": 18
                },
                "end": {
                    "column": 31,
                    "line": 2,
                    "offset": 31
                },
                "source": "{{ counter }}"
            }
        }
    ],
    "helpers": [],
    "components": [],
    "directives": [],
    "hoists": [],
    "imports": [],
    "cached": 0,
    "temps": 0,
    "loc": {
        "start": {
            "column": 1,
            "line": 1,
            "offset": 0
        },
        "end": {
            "column": 5,
            "line": 3,
            "offset": 36
        },
        "source": "\n        Counter: {{ counter }}\n    "
    }
}

可以看到,我们的模板字符串被转化成了ast,其中包含了每个节点的位置信息和字段信息以及节点类型信息

源码先不看,后面单独再写文章分析

再看下 transform 用一堆转换函数对ast执行完后的变化:

{
    "type": 0,
    "children": [
        {
            "type": 8,
            "loc": {
                "start": {
                    "column": 1,
                    "line": 1,
                    "offset": 0
                },
                "end": {
                    "column": 18,
                    "line": 2,
                    "offset": 18
                },
                "source": "\n        Counter: "
            },
            "children": [
                {
                    "type": 2,
                    "content": " Counter: ",
                    "loc": {
                        "start": {
                            "column": 1,
                            "line": 1,
                            "offset": 0
                        },
                        "end": {
                            "column": 18,
                            "line": 2,
                            "offset": 18
                        },
                        "source": "\n        Counter: "
                    }
                },
                " + ",
                {
                    "type": 5,
                    "content": {
                        "type": 4,
                        "isStatic": false,
                        "constType": 0,
                        "content": "counter",
                        "loc": {
                            "start": {
                                "column": 21,
                                "line": 2,
                                "offset": 21
                            },
                            "end": {
                                "column": 28,
                                "line": 2,
                                "offset": 28
                            },
                            "source": "counter"
                        }
                    },
                    "loc": {
                        "start": {
                            "column": 18,
                            "line": 2,
                            "offset": 18
                        },
                        "end": {
                            "column": 31,
                            "line": 2,
                            "offset": 31
                        },
                        "source": "{{ counter }}"
                    }
                }
            ]
        }
    ],
    "helpers": [
        Symbol(toDisplayString)
    ],
    "components": [],
    "directives": [],
    "hoists": [],
    "imports": [],
    "cached": 0,
    "temps": 0,
    "codegenNode": {
        "type": 8,
        "loc": {
            "start": {
                "column": 1,
                "line": 1,
                "offset": 0
            },
            "end": {
                "column": 18,
                "line": 2,
                "offset": 18
            },
            "source": "\n        Counter: "
        },
        "children": [
            {
                "type": 2,
                "content": " Counter: ",
                "loc": {
                    "start": {
                        "column": 1,
                        "line": 1,
                        "offset": 0
                    },
                    "end": {
                        "column": 18,
                        "line": 2,
                        "offset": 18
                    },
                    "source": "\n        Counter: "
                }
            },
            " + ",
            {
                "type": 5,
                "content": {
                    "type": 4,
                    "isStatic": false,
                    "constType": 0,
                    "content": "counter",
                    "loc": {
                        "start": {
                            "column": 21,
                            "line": 2,
                            "offset": 21
                        },
                        "end": {
                            "column": 28,
                            "line": 2,
                            "offset": 28
                        },
                        "source": "counter"
                    }
                },
                "loc": {
                    "start": {
                        "column": 18,
                        "line": 2,
                        "offset": 18
                    },
                    "end": {
                        "column": 31,
                        "line": 2,
                        "offset": 31
                    },
                    "source": "{{ counter }}"
                }
            }
        ]
    },
    "loc": {
        "start": {
            "column": 1,
            "line": 1,
            "offset": 0
        },
        "end": {
            "column": 5,
            "line": 3,
            "offset": 36
        },
        "source": "\n        Counter: {{ counter }}\n    "
    }
}

可以看到目前主要多了2个地方:

1. helpers 代表可能要用到的一些转换辅助函数的key名

2. codegenNode 里面存放接下来要用到的代码生成节点信息

从 generate方法中返回的 code 字段为:

"const _Vue = Vue

return function render(_ctx, _cache) {
  with (_ctx) {
    const { toDisplayString: _toDisplayString } = _Vue

    return " Counter: " + _toDisplayString(counter)
  }
}"

可以看到 基本上都是和上面的codegenNode信息是相对应,而_ctx也明确指示了这个render函数执行的时候会绑定特定的this

由于文章字数限制,剩下的内容在第二篇中。

原创声明,本文系作者授权云+社区发表,未经许可,不得转载。

如有侵权,请联系 yunjia_community@tencent.com 删除。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • Vue3源码阅读笔记之整体执行顺序简介(2)

    可以看到,目前只是直接对组件实例的data做了一次代理,handlers在普通对象情况下为 baseHandlers

    wanyicheng
  • Vue3源码解析 —— 编译器(一)入口

    Vue3 发布已经有一段时间了,最近也有机会在公司项目中用上了 Vue3 + TypeScript + Vite 的技术栈,所以闲暇之余抽空也在抽空阅读 Vue...

    Originalee
  • Vue3 源码解析(三):静态提升

    Vue3 尚未发布正式版本前,尤大在一次关于 Vue3 的分享中提及了静态提升,当时笔者就对这个亮点产生了好奇,所以在源码阅读时,静态提升也是笔者的一个重点阅读...

    Originalee
  • Vue3 源码解析(三):静态提升

    Vue3 尚未发布正式版本前,尤大在一次关于 Vue3 的分享中提及了静态提升,当时笔者就对这个亮点产生了好奇,所以在源码阅读时,静态提升也是笔者的一个重点阅读...

    Originalee
  • Vue3 深度解析

    距离尤雨溪首次公开 Vue3 (vue-next)源码有一个多月了。青笔观察到,刚发布国庆期间,出现不少解读 Vue3 源码的文章。当然不少有追风蹭热之嫌,文章...

    我是一条小青蛇
  • 从零开始构建 vue3

    2019年10月5日凌晨,Vue 的作者尤雨溪公布了 Vue3 的源代码。当然,它暂时还不是完整的 Vue3,而是 pre-alpha 版,只完成了一些核心功能...

    我是一条小青蛇
  • Vue3 源码解析(六):响应式原理与 reactive

    今天这篇文章是笔者会带着大家一起深入剖析 Vue3 的响应式原理实现,以及在响应式基础 API 中的 reactive 是如何实现的。对于 Vue 框架来说,其...

    Originalee
  • Vue3 源码解析(五):Patch 算法

    与 React 一样,Vue 在处理虚拟 DOM 的更新时,也有自己的 diff 算法 —— patch。

    Originalee
  • Vue3发布半年我不学,摸鱼爽歪歪,哎~就是玩儿

    是从 Vue 2 开始学基础还是直接学 Vue 3 ?尤雨溪给出的答案是:“直接学 Vue 3 就行了,基础概念是一模一样的。”

    程序员十三

扫码关注云+社区

领取腾讯云代金券