Vue原理分析与实现1-模板编译

前端日记

今天是前端日记的第三天,断更了不好意思! [害羞] ,由于本人才疏学浅,花费比较多时间理解Vue原理,以后还是坚持至少每3天一个额外知识点,然后与大家一起分享。一个前端小白的学习之路。

分析Vue

   <div id="app">
        <p v-text="msg"></p>
    </div>
     <h1 v-text="msg"></h1>//不会被vue编译
    <script src="./vue.js"></script>
    <script>
        const vue = new Vue({
            el: '#app',
            data: {
                msg: '我是信息',
            }
        })
    </script>

首先分析简单的一个Vue片段,我们new Vue()其实是实例化Vue这个类,中传入了 el、data两个参数

1、el代表了整个vue挂载的根元素(可以传入字符串或者一个节点对象),意思是,在id为app以外的dom节点不会被vue这个类所编译,如上图的h1标签,渲染后的内容为空而不是 我是信息 这几个字。
2、data代表了整个Vue类的数据,如果dom元素上有指定data对象中的属性,则vue会根据指令把data对应属性的值替换到dom元素的值,如上图的p元素指定了自己的文本节点是data对象的msg属性,所以p元素渲染出来后的内容是 我是信息 这几个字。

实现Vue

class Vue {
    constructor(option) {
        this.$el = option.el;//入口
        this.$data = option.data;//数据
        this.$option = option;
        this.$el && new Compile(this.$el, this)//创建模板编译类
    }
}

Vue类实现是比较简单的,Vue考虑到性能问题,会先把根元素下(即div#app)下所有的节点放在文档碎片中,如果不了解文档碎片概念可自行百度,大概意思是在内存中开辟一个空间,存储所有待编译的节点,然后编译完成后在整体append到根元素下,这样只会操作一次dom,大大减少了回流(涉及重绘回流也可自行百度)

//编译模板类
class Compile {
    constructor(el, vm) {
        this.vm = vm;
        //判断是否是元素节点
        this.el = this.isElementNode(el) ? el : document.querySelector(el);

        if (this.el) {
            const f = this.createFragment(this.el) //返回文档碎片
            this.compile(f)
            this.el.appendChild(f)
        }
    }

    // 判断是否元素节点
    isElementNode(node) {
        return node && node.nodeType === 1;
    }

    //判断是否指令
    isDirective(name) {
        return name.includes('v-')
    }

    //创建文档碎片
    createFragment(node) {
        let f = document.createDocumentFragment();
        let first;
        while (first = node.firstChild) {
            f.appendChild(first)
        }
        return f;
    }
    //编译文档碎片
    compile(fragment) {
        let childNodes = fragment.childNodes;
        [...childNodes].forEach(node => {
            if (this.isElementNode(node)) {
                //是元素节点 
                this.compileElement(node);//先编译元素节点         
            } 
        });
    }
    //编译元素节点
    compileElement(node) {
        const attributes = node.attributes;
        //如果有属性
        if (attributes.length) {
            [...attributes].forEach(attribute => {
                const { name, value } = attribute
                //判断是否vue指令
                if (this.isDirective(name)) {
                    const [, type] = name.split('-');//分割
                    //这里执行compileUtil[text]
                    compileUtil[directiveType](node, value, this.vm)
                }
            })
        }
    }
}

自定义一个对象存储,解析不同指令或者文本编译集合

// 解析不同指令或者文本编译集合
const compileUtil = {
    getValue(expr, vm) {
        //当遇到多重对象 如 person.age.fav =>[person,age,fav]
        return expr.split('.').reduce((data, currentValue) => {
            return data[currentValue]
        }, vm.$data)
    },
    text(node, expr, vm) {
        //v-text 
        //第一步获取的初始值
        const value = this.getValue(expr, vm);
        this.updater.textUpdater(node, value)
    },
    //更新的函数
    updater: {
        textUpdater(node, value) {
            node.textContent = value;
        }
    }
}

至此,经过模板编译后的的html已经渲染出正确信息
在这里插入图片描述
下一节,实现如何实现多指令编译
谢谢观赏,如有不足,请下方留言,共勉之! —一个菜鸡前端的路程

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

会跳舞的鸡

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

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

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

打赏作者

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

抵扣说明:

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

余额充值