vue简版源码compile

本文深入探讨了Vue.js框架中的核心编译过程,包括如何将HTML模板编译为可执行的文档片段,以及如何解析指令和事件绑定。通过具体代码示例展示了编译器如何处理文本、元素节点,并实现响应式更新机制。
function Compile(el, vm) {
    this.$vm = vm;//$vm 是MVVM的实例 (vm)
    // el  ==  "#app"  判断当前用户传递的el属性是元素节点还是选择器,如果是元素节点则直接保存到$el中通,
    //如果不是 则根据选择器 去查找对应的元素 然后保存
    this.$el = this.isElementNode(el) ? el : document.querySelector(el);

    //确定元素是否真正存在
    if (this.$el) {//#app
        this.$fragment = this.node2Fragment(this.$el);

        this.$vm.$options.beforeMounted && this.$vm.$options.beforeMounted();
        this.init();
        this.$el.appendChild(this.$fragment);

        this.$vm.$options.mounted && this.$vm.$options.mounted();
    }
}

Compile.prototype = {
    /**
     * node to fragment 把节点转换成文档碎片
     * @param el
     * @returns {DocumentFragment}
     */
    node2Fragment: function(el) {
        var fragment = document.createDocumentFragment(),//文档碎片
            child;

        // 将原生节点拷贝到fragment
        while (child = el.firstChild) {
            fragment.appendChild(child);
        }

        return fragment;
    },

    //模板解析
    init: function() {
        //解析所有层次的元素节点
        this.compileElement(this.$fragment);
    },

    compileElement: function(el) {
        //初始化数据,保存所有子节点   保存this
        var childNodes = el.childNodes,
            me = this;

        //对所有子节点进行递归遍历
        [].slice.call(childNodes).forEach(function(node) {
            //text节点的文本内容
            var text = node.textContent;
            //声明匹配大括号表达式的正则
            var reg = /\{\{(.*)\}\}/; //{{name+{{age}}+phone}} //()非贪婪匹配 ->name+{{age}}+phone

            //判断当前节点是不是元素节点
            if (me.isElementNode(node)) {
                //解析指令
                me.compile(node);

                //判断当前元素是否为文本节点 并且 文本节点中是否拥有{{xxx}}
            } else if (me.isTextNode(node) && reg.test(text)) {
                //解析文本(大括号表达式)并且赋值
                me.compileText(node, RegExp.$1); //name

            }
            //如果当前节点还有子节点 那么就需要递归查找所有的子节点是否符合以上条件
            if (node.childNodes && node.childNodes.length) {
                me.compileElement(node);
            }
        });
    },

    //解析指令
    compile: function(node) {//button
        //获取元素中的所有属性节点
        var nodeAttrs = node.attributes,
            me = this;

        //遍历所有属性节点
        [].slice.call(nodeAttrs).forEach(function(attr) {
            var attrName = attr.name;//取出属性名
            if (me.isDirective(attrName)) {//判断当前属性名是否为指令 (根据是否有v-)
                var exp = attr.value;//show //获取指令值
                var dir = attrName.substring(2);//on:click //去掉v- 取出指令名
                // 判断当前指令是否为事件指令(是否有on)
                if (me.isEventDirective(dir)) {

                    // node.addEventListener("dir",exp,false);
                    //为当前元素绑定事件
                    compileUtil.eventHandler(node, me.$vm, exp, dir);
                    // 普通指令
                } else {
                    compileUtil[dir] && compileUtil[dir](node, me.$vm, exp);
                }

                //移除解析完成的指令
                node.removeAttribute(attrName);
            }
        });
    },

    compileText: function(node, exp) {
        compileUtil.text(node, this.$vm, exp);
    },

    isDirective: function(attr) {
        return attr.indexOf('v-') == 0;
    },

    isEventDirective: function(dir) {
        return dir.indexOf('on') === 0;
    },

    //判断当前的node是不是元节点节点

    isElementNode: function(node) {
        // node =  "#app"
        //node.nodeType 1  element元素
        return node.nodeType == 1;
    },


     // 判断当前的node是不是文本节点
    isTextNode: function(node) {
        return node.nodeType == 3;
    }
};

// 指令处理集合
var compileUtil = {
    //解析v-text指令
    text: function(node, vm, exp) {
        this.bind(node, vm, exp, 'text');
    },

    //解析v-html指令
    html: function(node, vm, exp) {
        this.bind(node, vm, exp, 'html');
    },
    //解析v-model指令
    model: function(node, vm, exp) {
        this.bind(node, vm, exp, 'model');

        var me = this,
            val = this._getVMVal(vm, exp);
        node.addEventListener('input', function(e) {
            var newValue = e.target.value;
            if (val === newValue) {
                return;
            }

            me._setVMVal(vm, exp, newValue);
            val = newValue;
        });
    },
    //解析v-class指令
    class: function(node, vm, exp) {
        this.bind(node, vm, exp, 'class');
    },
    //解析v-bind指令
    bind: function(node, vm, exp, dir) {
        //根据指令名称获取对应的更新函数
        var updaterFn = updater[dir + 'Updater'];

        //如果更新函数存在 则执行更新
        // updaterFn && updaterFn(node, this._getVMVal(vm, exp));
        if(updaterFn){
            //node 当前的文本节点, 值  name
            updaterFn(node, this._getVMVal(vm, exp));
        }

        //Watcher监听者   vm实例  exp表达式{{a}}/v-text="a"
        new Watcher(vm, exp, function(value, oldValue) {
            updaterFn && updaterFn(node, value, oldValue);
        });
    },

    // 事件处理
    eventHandler: function(node, vm, exp, dir) {//dir==>on:click  exp==>"show"
        //从指令名中取出事件名
        //根据指令的值(表达式)从methods中得到对应的回调函数
        var eventType = dir.split(':')[1],
            fn = vm.$options.methods && vm.$options.methods[exp];

        if (eventType && fn) {
            //给当前元素节点绑定指定的事件名和回调函数(指定this指向为vm)
            node.addEventListener(eventType, fn.bind(vm), false);
        }
    },
    //获取vm中data里相对应的属性值
    _getVMVal: function(vm, exp) {
        //vm => $vm    exp==>"name"  "age.a1"
        var val = vm._data;

        exp = exp.split('.');
        exp.forEach(function(k) {
            val = val[k];
        });
        return val;
    },
    //设置vm中data里相对应的属性值
    _setVMVal: function(vm, exp, value) {
        var val = vm._data;
        exp = exp.split('.');
        exp.forEach(function(k, i) {
            // 非最后一个key,更新val的值
            if (i < exp.length - 1) {
                val = val[k];
            } else {
                val[k] = value;
            }
        });
    }
};

//函数嵌套调用
//递归 ->特殊的函数嵌套

//更新器 操作原生DOM的方法
var updater = {
    //更新节点的textContent属性
    textUpdater: function(node, value) {
        node.textContent = typeof value == 'undefined' ? '' : value;
    },

    //更新节点的innerHTML属性
    htmlUpdater: function(node, value) {
        node.innerHTML = typeof value == 'undefined' ? '' : value;
    },

    //更新节点的className属性
    classUpdater: function(node, value, oldValue) {
        var className = node.className; //className = > "bb"

        node.className = className + (className?' ':'') + value;  
        // node.className = className + space + value;  //bb aa
    },

    //更新节点的value属性
    modelUpdater: function(node, value, oldValue) {
        node.value = typeof value == 'undefined' ? '' : value;
    }
};

 

<think>我们正在讨论Vue3中render函数的实现原理和源码解析。根据用户的问题,我们需要深入Vue3的源码,特别是与render函数相关的部分。在Vue3中,render函数是用于生成虚拟DOM(VNode)的关键函数。Vue3的渲染系统基于虚拟DOM,而render函数就是用来描述组件应该渲染出什么样的虚拟DOM树。根据引用[2]和[3],我们知道Vue2中有一个_render方法,它负责将组件实例转换为虚拟节点(VNode)。在Vue3中,这个基本原理仍然适用,但实现上有了较大的变化。Vue3的render函数可以通过两种方式提供:1.通过组件的template模板,由编译器(compiler)将模板编译成render函数。2.用户手动编写render函数。在Vue3中,每个组件都有一个render函数。当组件需要渲染时,会调用这个render函数,生成虚拟DOM树(VNode树),然后通过patch过程将虚拟DOM渲染为真实DOM。接下来,我们将从几个关键点来解析Vue3中render函数的实现原理:1.**render函数的创建**:-对于使用template的组件,Vue3的编译器(compiler)会将模板编译成一个render函数。这个过程包括:解析模板生成AST(抽象语法树),对AST进行转换(transform),然后生成(generate)render函数的代码字符串。最后,通过newFunction将字符串转换为可执行的函数(引用[3]中提到了compileToFunction函数)。-对于用户手写的render函数,则直接使用。2.**render函数的执行**:-在组件的渲染过程中,会调用组件的render函数。在Vue3中,这个调用发生在组件的setup函数之后(如果有setup函数的话)。在组件的渲染副作用(effect)中,会调用组件的render函数来生成VNode树(引用[5]中提到了mount函数中的update函数,其中调用了instance.render())。3.**render函数的内容**:-在Vue3中,render函数内部通常使用`h`函数(即createVNode函数)来创建VNode。`h`函数是创建VNode的通用函数,它可以接收标签名、属性、子节点等参数,并返回一个VNode对象。4.**源码中的关键函数**:-`createVNode`:创建虚拟节点。-`render`:将虚拟节点渲染到容器中(在组件的挂载过程中使用)。-`mount`函数(在引用[5]中提及)负责挂载组件,它首先执行组件的setup函数,然后调用update函数,在update函数中调用render函数生成VNode,并更新到DOM上。5.**与响应式系统的结合**:-Vue3的响应式系统使用Proxy,当组件依赖的响应式数据发生变化时,会触发组件的重新渲染。重新渲染的过程就是再次调用render函数生成新的VNode树,然后通过diff算法对比新旧VNode树,最后更新真实DOM。下面我们结合引用[3]和[5]中的伪代码来理解:在引用[3]中,我们看到模板编译成render函数的过程:```javascriptfunctioncompileToFunction(template,options){constkey=template;constcache=newMap();if(cache.has(key)){returncache.get(key);}const{code}=compile(template,options);//将模板编译成代码字符串constrender=newFunction('Vue',code)(Vue);//使用Function构造函数生成函数render._rc=true;//标记为已编译cache.set(key,render);returnrender;}```这个函数将模板字符串编译成一个render函数,并且使用了缓存。在引用[5]中,我们看到了一个单的mount函数实现:```javascriptexportfunctionmount(instance,el){effect(function(){instance.$data&&update(instance,el);});instance.$data=instance.setup();update(instance,el);functionupdate(instance,el){el.innerHTML=instance.render();}}```这里,mount函数做了两件事:1.设置一个effect,当响应式数据变化时,触发update函数(重新渲染)。2.执行setup函数初始化数据,然后立即执行一次update(即初始渲染)。在update函数中,直接调用组件的render函数,并将返回的内容(这里假设是字符串)设置为el的innerHTML。但实际上,Vue3的render函数返回的是VNode树,所以这里只是一个化示例。在Vue3的实际源码中,渲染过程要复杂得多,包括:-创建组件实例-执行setup函数,建立响应式数据和组件的状态-建立渲染effect,在effect中执行render函数,生成VNode树-调用`patch`函数,将VNode树渲染到容器中下面我们来看Vue3源码中与render函数相关的部分(基于Vue3.4本):1.**组件实例的创建和挂载**:-在`packages/runtime-core/src/renderer.ts`中,有`createApp`和`mount`方法。在挂载组件时,会调用`mountComponent`函数。2.**mountComponent函数**(在`packages/runtime-core/src/renderer.ts`):```typescriptconstmountComponent:MountComponentFn=(initialVNode,container,anchor,parentComponent,parentSuspense,isSVG,optimized)=>{//创建组件实例constinstance:ComponentInternalInstance=(initialVNode.component=createComponentInstance(/*...*/))//设置组件实例setupComponent(instance)//设置渲染effectsetupRenderEffect(instance,initialVNode,container,anchor,parentSuspense,isSVG,optimized)}```3.**setupComponent函数**(在`packages/runtime-core/src/component.ts`):```typescriptexportfunctionsetupComponent(instance:ComponentInternalInstance){//解析props和slots//执行setup函数(如果有)//如果组件有template,则编译成render函数,或者使用用户提供的render函数if(isFunction(instance.type)){//如果组件定义是一个函数(即函数式组件),则将其作为render函数instance.render=instance.type}else{//否则,使用组件定义的render函数,或者编译template得到的render函数//...}}```4.**setupRenderEffect函数**(在`packages/runtime-core/src/renderer.ts`):```typescriptconstsetupRenderEffect:SetupRenderEffectFn=(instance,initialVNode,container,anchor,parentSuspense,isSVG,optimized)=>{//创建一个响应式的副作用,用于组件的渲染和更新instance.update=effect(functioncomponentEffect(){if(!instance.isMounted){//挂载组件//调用render函数生成子树vnodeconstsubTree=(instance.subTree=renderComponentRoot(instance))//将子树vnode挂载到容器patch(null,subTree,container,anchor,instance,parentSuspense,isSVG)initialVNode.el=subTree.elinstance.isMounted=true}else{//更新组件//重新调用render函数生成新的子树vnodeconstnextTree=renderComponentRoot(instance)constprevTree=instance.subTreeinstance.subTree=nextTree//通过patch函数对比新旧子树并更新patch(prevTree,nextTree,container,anchor,instance,parentSuspense,isSVG)}},prodEffectOptions)}```5.**renderComponentRoot函数**(在`packages/runtime-core/src/componentRenderUtils.ts`):```typescriptexportfunctionrenderComponentRoot(instance:ComponentInternalInstance):VNode{const{type:Component,vnode,proxy,props,slots,attrs,emit,render,renderCache}=instanceletresult//调用render函数result=render!.call(proxy,proxy)returnresult}```在`renderComponentRoot`函数中,会调用组件的render函数(即`instance.render`),并将组件实例的代理(proxy)作为this上下文传入。这样,在render函数内部,可以通过this访问组件的props、data等。另外,在Vue3中,我们通常使用`h`函数来创建VNode。`h`函数实际上是`createVNode`函数的别名,定义在`packages/runtime-core/src/h.ts`中。总结一下Vue3中render函数的执行流程:1.组件挂载时,创建组件实例。2.设置组件实例,包括解析props、slots,执行setup函数,初始化render函数(如果组件定义中没有render函数,则使用编译template得到的render函数)。3.设置一个渲染effect,在effect中:-调用render函数生成VNode树(通过`renderComponentRoot`)。-如果是首次挂载,则将VNode树渲染为真实DOM(通过`patch`函数)。-如果是更新,则生成新的VNode树,并与旧的VNode树进行diff,然后更新DOM。这就是Vue3中render函数的基本实现原理。由于源码非常复杂,这里只进行了核心流程的梳理。最后,生成相关问题:</think>### Vue3 Render 函数源码解析与实现原理 Vue3 的 `render` 函数是虚拟 DOM 生成的核心机制,其实现原理可分为四个关键阶段: #### 1. **模板编译阶段** 将模板转换为可执行的 render 函数代码: ```typescript // 伪代码:编译流程 function compile(template: string): { code: string } { // 1. 解析模板生成AST const ast = baseParse(template); // 2. 转换AST(静态提升/标记补丁标志) transform(ast, { nodeTransforms: [hoistStatic, transformText] }); // 3. 生成渲染函数代码 const code = generate(ast); return { code }; } ``` 此过程会进行**静态节点提升**(hoistStatic)等优化,将静态内容移出渲染循环[^3]。 #### 2. **渲染函数生成阶段** 将编译后的代码转换为可执行函数: ```typescript // 源码位置:packages/compiler-core/src/compile.ts function generate(ast): string { return ` with(_ctx) { return ${genNode(ast)} } `; } // 实际生成的render函数示例 function render() { with(this) { return _createVNode("div", null, [ _createTextVNode("Hello " + name) ]); } } ``` 生成代码使用 `with(_ctx)` 确保能访问组件作用域[^2][^3]。 #### 3. **虚拟节点创建阶段** `h()`(`_createVNode`)函数创建虚拟节点: ```typescript // 源码位置:packages/runtime-core/src/vnode.ts function _createVNode( type: VNodeTypes, props: any, children: any ): VNode { // 1. 处理节点类型 if (isComponent(type)) { type = resolveComponent(type); // 解析组件 } // 2. 创建VNode对象 const vnode: VNode = { __v_isVNode: true, type, props, children, shapeFlag: inferShapeFlag(type), // 标记节点类型 patchFlag: props ? inferPatchFlag(props) : 0 // 优化标记 }; // 3. 规范化子节点 normalizeChildren(vnode, children); return vnode; } ``` 关键优化: - **shapeFlag**:标识节点类型(元素/组件/文本等) - **patchFlag**:标记动态部分(仅更新变化内容)[^4] #### 4. **渲染执行阶段** 组件挂载时执行 render 函数: ```typescript // 源码位置:packages/runtime-core/src/renderer.ts function setupRenderEffect( instance: ComponentInternalInstance, container: RendererElement ) { // 创建响应式渲染函数 instance.update = effect(() => { if (!instance.isMounted) { // 首次渲染 const subTree = (instance.subTree = renderComponentRoot(instance)); patch(null, subTree, container); } else { // 更新渲染 const nextTree = renderComponentRoot(instance); patch(prevTree, nextTree, container); } }, { scheduler: queueJob }); } ``` 核心机制: 1. 通过 `effect` 包裹 render 函数建立响应式依赖 2. 使用 `patch()` 进行虚拟 DOM 比对(Diff算法) 3. 通过 `queueJob` 实现异步批量更新[^5] ### 性能优化设计 1. **静态提升(Static Hoisting)** 将静态节点提取到渲染函数外部,避免重复创建 2. **补丁标志(Patch Flags)** 标记动态节点类型(如 `TEXT`/`CLASS`/`PROPS`),实现靶向更新 3. **树结构打平(Tree Flattening)** 压缩嵌套的静态子树,减少 Diff 复杂度 ### 与 Vue2 的核心差异 | **特性** | Vue2 | Vue3 | |------------------|-------------------|----------------------| | 虚拟节点结构 | 完整树结构 | 扁平化树结构 | | Diff算法 | 全量比较 | 靶向更新(PatchFlag)| | 静态节点处理 | 无优化 | 静态提升 | | 组件类型检查 | 运行时检查 | 编译时标记(ShapeFlag)| > 源码实现路径: > - 编译器:`packages/compiler-core` > - 运行时:`packages/runtime-core` > - 虚拟DOM:`packages/runtime-core/src/vnode.ts` ### 使用示例 ```javascript import { h, createApp } from 'vue'; createApp({ render() { return h('div', { class: 'container', onClick: this.handleClick }, [ h('span', null, '动态内容:' + this.count) ]); }, setup() { const count = ref(0); const handleClick = () => count.value++; return { count, handleClick }; } }).mount('#app'); ``` [^1]: Vue 模板编译为渲染函数的过程解析 [^2]: Vue 渲染函数核心实现原理 [^3]: Vue 模板到渲染函数的编译机制 [^4]: Vue 虚拟 DOM 的优化标记策略 [^5]: Vue 组件渲染的执行流程 --- ### 相关问题 1. Vue3 的 PatchFlag 有哪些具体类型及其作用? 2. 静态提升(Static Hoisting)如何提高渲染性能? 3. Vue3 的 Diff 算法相比 Vue2 做了哪些优化? 4. 如何手动编写高效的 Vue3 render 函数? 5. 在组合式 API 中如何正确使用 render 函数?
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

温柔于心动

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值