前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Vue3源码阅读笔记之整体执行顺序简介(1)

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

原创
作者头像
wanyicheng
修改2021-04-13 10:04:06
1.1K0
修改2021-04-13 10:04:06
举报

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

代码语言:javascript
复制
<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变量,通过立即执行函数,在内部做了很多变量和函数声明,而暴露给外部使用的只有一部分:

代码语言:javascript
复制
var Vue = (function(exports){
    ...
    exports.createApp = createApp;
    ...
    return exports;
}({}))

看下createApp做了什么:

代码语言:javascript
复制
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

代码语言:javascript
复制
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: ?developer/article/1812081/undefined,
                warnHandler: ?developer/article/1812081/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方法做了什么:

代码语言:javascript
复制
 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:

代码语言:javascript
复制
// 创建虚拟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对象中声明的,通过闭包访问到它:

代码语言:javascript
复制
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方法由于此时没有任务需要执行,先跳过不看。

代码语言:javascript
复制
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

代码语言:javascript
复制
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 节点下就可以了。

代码语言:javascript
复制
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:

代码语言:javascript
复制
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,只不过它等下会被代理拦截而已。这个组件实例上面拥有我们目前为止所有得到的信息。

代码语言:javascript
复制
function setupComponent(instance, isSSR = false) {
        isInSSRComponentSetup = isSSR;
        const { props, children } = instance.vnode;
        const isStateful = isStatefulComponent(instance);
        ...
        const setupResult = isStateful
            ? setupStatefulComponent(instance, isSSR)
            : ?developer/article/1812081/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:

代码语言:javascript
复制
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生成了它,并且做了缓存

代码语言:javascript
复制
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 }}

" 和 一些辅助函数

返回的是:

代码语言:javascript
复制
{
    "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执行完后的变化:

代码语言:javascript
复制
{
    "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 字段为:

代码语言:javascript
复制
"const _Vue = Vue

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

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

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

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

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

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

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

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

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
容器服务
腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
http://www.vxiaotou.com