Vue2源码解析(4)——生成虚拟DOM和真实DOM

目录

一:前言

二:ats->虚拟DOM->真实DOM的源码

1、项目目录

2、complier文件夹下的index.js文件

3、vdom文件夹下的index.js文件

4、lifecycle.js文件

三:总结


 

一:前言

        Vue2是一种渐进式框架,从最初的响应式数据,到生成真实DOM的步骤如下:

  1. 创建响应式数据
  2. 模板转换成ats树
  3. 将ats树转换成render函数(虚拟DOM)
  4. 后续每次数据更新只需要执行render函数,而不需要执行ats转化过程
  5. 根据虚拟DOM生成真实DOM

        在前面几节中已经完成了前两步,在本文中将为大家讲解3-5点。这里附一下之前的文章

vue2源码解析(1)——rollup详解-CSDN博客rollup是适用于打包JavaScript库和组件,以及需要生成更小包体积的应用程序。相比于webpack可以生成更小、更精简的代码包。同时rollup和webpack是不排斥的,在项目中可以同时使用哦!有兴趣的小伙伴快来实践操作一下吧!https://blog.csdn.net/c18559787293/article/details/133432987?spm=1001.2014.3001.5502

二:ats->虚拟DOM->真实DOM的源码

1、项目目录

        以下是项目的目录,其中本次新增的是vdom文件夹,以及根目录下的lifecycle.js文件,基于上一篇文章完善的是complier文件夹下的index.js文件。其中在complier文件夹下的index.js文件是将ats树转化为render函数(虚拟DOM)。而vdom起到创建节点的功能,lifecycle.js是将虚拟DOM转化为真实DOM。接下来将会详细的讲解各文件的实现逻辑代码。

2、complier文件夹下的index.js文件

        该文件是将生成的ats树转换为render函数,通过在compileToFunction函数中调用codegen函数,进行字符串的处理与拼接,这里要注意,节点可能有文本节点和标签节点,这里需要进行判断,然后通过正则去匹配。

注意:render函数的模板如下,所以我们要将字符串处理成以下的格式:

_c('div',{id:'app'},_c('div',{style:{color:'red'}},  _v(_s(vm.name)+'hello'),_c('span',undefined,  _v(_s(age)))) 

import { parseHTML } from "./parse";

function genProps(attrs) {
    let str = ''// {name,value}
    for (let i = 0; i < attrs.length; i++) {
        let attr = attrs[i];
        if (attr.name === 'style') {
            // color:red;background:red => {color:'red'}
            let obj = {};
            attr.value.split(';').forEach(item => { // qs 库
                let [key, value] = item.split(':');
                obj[key] = value;
            });
            attr.value = obj
        }
        str += `${attr.name}:${JSON.stringify(attr.value)},` // a:b,c:d,
    }
    return `{${str.slice(0, -1)}}`
}
const defaultTagRE = /\{\{((?:.|\r?\n)+?)\}\}/g; // {{ asdsadsa }}  匹配到的内容就是我们表达式的变量
function gen(node) {
    if (node.type === 1) {
        return codegen(node);
    } else {
        // 文本
        let text = node.text
        if (!defaultTagRE.test(text)) {
            return `_v(${JSON.stringify(text)})`
        } else {
            //_v( _s(name)+'hello' + _s(name))
            let tokens = [];
            let match;
            defaultTagRE.lastIndex = 0;
            let lastIndex = 0;
            // split
            while (match = defaultTagRE.exec(text)) {
                let index = match.index; // 匹配的位置  {{name}} hello  {{name}} hello 
                if (index > lastIndex) {
                    tokens.push(JSON.stringify(text.slice(lastIndex, index)))
                }
                tokens.push(`_s(${match[1].trim()})`)
                lastIndex = index + match[0].length
            }
            if (lastIndex < text.length) {
                tokens.push(JSON.stringify(text.slice(lastIndex)))
            }
            return `_v(${tokens.join('+')})`
        }
    }
}
function genChildren(children) {
    return children.map(child => gen(child)).join(',')
}
function codegen(ast) {
    let children = genChildren(ast.children);
    let code = (`_c('${ast.tag}',${ast.attrs.length > 0 ? genProps(ast.attrs) : 'null'
        }${ast.children.length ? `,${children}` : ''
        })`)

    return code;
}
export function compileToFunction(template) {

    // 1.就是将template 转化成ast语法树
    let ast = parseHTML(template);

    // 2.生成render方法 (render方法执行后的返回的结果就是 虚拟DOM)

    // 模板引擎的实现原理 就是 with  + new Function

    let code = codegen(ast);
    code = `with(this){return ${code}}`;
    let render = new Function(code); // 根据代码生成render函数

    //  _c('div',{id:'app'},_c('div',{style:{color:'red'}},  _v(_s(vm.name)+'hello'),_c('span',undefined,  _v(_s(age))))

    return render;
}


// <xxx
// <namepsace:xxx
// color   =   "asdsada"     c= 'asdasd'  d=  asdasdsa

3、vdom文件夹下的index.js文件

         该文件是向外提供了两个方法,根据不同的内容生成不同的节点,实现比较简单。


// h()  _c()
export function createElementVNode(vm, tag, data, ...children) {
    if (data == null) {
        data = {}
    }
    let key = data.key;
    if (key) {
        delete data.key
    }
    return vnode(vm, tag, key, data, children);
}
// _v();
export function createTextVNode(vm, text) {
    return vnode(vm, undefined, undefined, undefined, undefined, text);
}
// ast一样吗? ast做的是语法层面的转化 他描述的是语法本身 (可以描述js css html)
// 我们的虚拟dom 是描述的dom元素,可以增加一些自定义属性  (描述dom的)
function vnode(vm, tag, key, data, children, text) {
    return {
        vm,
        tag,
        key,
        data,
        children,
        text
        // ....
    }
}

4、lifecycle.js文件

        该文件是将虚拟DOM转换为真实DOM的主要文件,向外提供了initLifeCycle和mountComponent两个方法,前者是写在init.js文件的初始化方法,而后者是挂载节点的方法。

        初始化方法比较简单,就是_c,_v 这些的初始化。这里不做太多的讲解。

        而mountComponent挂载方法,是生成真实DOM进行挂载的,

import { createElementVNode, createTextVNode } from "./vdom"


function createElm(vnode){
    let {tag,data,children,text} = vnode;
    if(typeof tag === 'string'){ // 标签
        vnode.el =  document.createElement(tag); // 这里将真实节点和虚拟节点对应起来,后续如果修改属性了
        patchProps(vnode.el,data);
        children.forEach(child => {
            vnode.el.appendChild( createElm(child))
        });
    }else{
        vnode.el = document.createTextNode(text)
    }
    return vnode.el
}
function patchProps(el,props){
    for(let key in props){
        if(key === 'style'){ // style{color:'red'}
            for(let styleName in props.style){
                el.style[styleName] = props.style[styleName];
            }
        }else{
            el.setAttribute(key,props[key]);
        }
    }
}
function patch(oldVNode,vnode){
    // 写的是初渲染流程 
    const isRealElement = oldVNode.nodeType; // 这个nodeType是原生的,如果等于表示是元素
    if(isRealElement){
        const elm = oldVNode; // 获取真实元素
        const parentElm = elm.parentNode; // 拿到父元素
        let newElm =  createElm(vnode);

        parentElm.insertBefore(newElm,elm.nextSibling);//把当前新节点插入到老节点下面
        parentElm.removeChild(elm); // 然后删除老节点
        console.log(newElm)
        return newElm
    }else{
        // diff算法
    }
}


export function initLifeCycle(Vue){
    Vue.prototype._update = function(vnode){ // 将vnode转化成真实dom
        const vm = this;
        const el = vm.$el;

        // patch既有初始化的功能  又有更新的逻辑 
        vm.$el = patch(el,vnode);
    }

    // _c('div',{},...children)
    Vue.prototype._c = function(){
       return  createElementVNode(this,...arguments)
    }
    // _v(text)
    Vue.prototype._v = function(){
        return createTextVNode(this,...arguments)
    }
    Vue.prototype._s = function(value){
        if(typeof value !== 'object') return value
        return JSON.stringify(value)
    }
    Vue.prototype._render = function(){
        // 当渲染的时候会去实例中取值,我们就可以将属性和视图绑定在一起
        
        return this.$options.render.call(this); // 通过ast语法转义后生成的render方法
    }
}

export function mountComponent(vm,el){ // 这里的el 是通过querySelector处理过的
    vm.$el = el;

    // 1.调用render方法产生虚拟节点 虚拟DOM
    vm._update(vm._render()); // vm.$options.render() 虚拟节点

    // 2.根据虚拟DOM产生真实DOM 

    // 3.插入到el元素中

}
// vue核心流程 1) 创造了响应式数据  2) 模板转换成ast语法树  
// 3) 将ast语法树转换了render函数 4) 后续每次数据更新可以只执行render函数 (无需再次执行ast转化的过程)
// render函数会去产生虚拟节点(使用响应式数据)
// 根据生成的虚拟节点创造真实的DOM

三:总结

        当真实DOM挂载完成后,其Vue2的基本构成已经可以实现了,但是还有需要完善的地方。这些会在下一篇文章中继续完善,希望各位小伙伴能够有所收货哦!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

暴怒的代码

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

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

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

打赏作者

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

抵扣说明:

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

余额充值