简单实现Vue之指令解析器Compile类

本文深入探讨了如何实现Vue.js中的核心指令,包括v-text、v-html、v-model、v-bind和v-on。通过创建自定义的Compile类,详细解释了每个指令的解析过程,从元素节点和文本节点的处理,到数据绑定和事件监听的实现,展示了Vue数据驱动视图的工作原理。
摘要由CSDN通过智能技术生成

简单实现Vue之指令解析器Compile类

创建html文件
  • 文件名为index.html
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Vue学习demo实现</title>
</head>

<body>
    <div id="app">
        <h2>大家好!我是{{person.name}},今年{{person.age}}岁。</h2>
        <h3>我最喜欢的游戏是{{person.fav}}</h3>
        <div v-text="msg"></div>
        <div v-html="htmlStr"></div>
        <input type="text" v-model="msg">
        <br />
        <button v-on:click="handlerClick">按钮</button>
        <button @click="handlerClick2">按钮2</button>
        <br />
        <a v-bind:href="url" target="_blank">{{url}}</a>
        <a :href="url" target="_blank">百度一下</a>
    </div>

    <!-- <script src="Vue.js"></script> -->
    <script src="MyVue.js"></script>
    <script>
        vm = new Vue({
            el: "#app",
            data: {
                person: {
                    name: "小明",
                    age: 19,
                    fav: "LOL"
                },
                msg: "我很喜欢聊天",
                htmlStr: "<h4>还喜欢和大家一起学习。</h4>",
                url: "https://www.yanfee.com"
            },
            methods: {
                handlerClick() {
                    console.log("this is a button!");
                },
                handlerClick2() {
                    this.$data.person.name = "大明"
                }
            }
        })
    </script>
    <style>
        #app {
            background-color: bisque;
        }
    </style>
    </div>
</body>

</html>

将实现的指令为 v-text,v-html,v-model,v-bind(:attr),v-on(@click)。

创建脚本文件
  • 文件名:MyVue.js
class MyVue {
    // 构造函数
    constructor(options) {
        this.$el = options.el;
        this.$data = options.data;
        this.$options = options;
        if (this.$el) {
            // 实现指令解析类
            new Compile(this.$el, this);
        }
    }
}

在实例化MyVue时,将选项传入并初始化,接下来将实现Compile类,将html文件中各个指令转化为html文本并替换。

class Compile {
    constructor(el, vm) {
        // 检查el是否为dom节点 如果不是则获取节点
        this.el = this.isElmentNode(el) ? el : document.querySelector(el);
        this.vm = vm;
        // 获取文档碎片
        const fragment = this.node2Fragment(this.el);
        // 编译文档碎片
        this.compile(fragment);
        // 追加编译好的元素到根节点
        this.el.appendChild(fragment);
    }
}

实现Compile类中各个方法

// 实现指令编译方法
compile(fragment) {
    // 获取子节点
    const childNodes = fragment.childNodes;
    // 识别节点类型并处理
    [...childNodes].forEach(child => {
        if (this.isElmentNode(child)) {
            // 元素节点处理
            this.compileElment(child);
        } else {
            // 文本节点处理
            this.compileText(child)
        }
        if (child.childNodes && child.childNodes.length) {
            this.compile(child)
        }
    })
}
}

对节点元素指令识别和实现,判断指令类型并提取指令 如 v-text=“msg”,将node中的html文本替换为Vue实例中data下msg的内容

// 处理元素节点 
compileElment(node) {
    const attributes = node.attributes; //获取元素属性
    [...attributes].forEach(attr => {
        const { name, value } = attr; // v-text="msg"
        // console.log(name, value); // v-text msg
        // 是否为指令
        if (this.isDirective(name)) { // 得到指令 v-text v-model... v-on:click v-bind
            const [, directive] = name.split("-"); // 得到指令 text model bind on:click
            // 对on:click 进一步处理
            const [directName, attrName] = directive.split(":")
            // 编译指令工具类
            compileUtil[directName](node, value, this.vm, attrName);
        } 
             // 删除有指令的标签属性
             node.removeAttribute('v-' + dirctive)
    })
}
/** 识别是否为指令 */
isDirective(attrName) {
    return attrName.startsWith("v-")
}

实现编译工具,单独抽出为一个工具对象中

/* 对指令进行编译实现 */
const compileUtil = {
    /**
     * 
     * @param {*} node 元素节点
     * @param {string} exp 指令表达式 在data中找值需要
     * @param {*} vm Vue实例
     */
    text(node, exp, vm) {
    },
    html(node, exp, vm) {
    },
    model(node, exp, vm) {
    },
    /**
     * 
     * @param {*} node 元素节点
     * @param {*} exp 指令表达式
     * @param {*} vm Vue实例
     * @param {*} eventName 事件名称
     */
    on(node, exp, vm, eventName) {
    },
    /**
     * 
     * @param {*} node 元素节点
     * @param {*} exp 指令表达式
     * @param {*} vm Vue实例
     * @param {*} attrName 属性名称
     */
    bind(node, exp, vm, attrName) {
    }
}

对各个指令具体实现

  • v-text 实现
/**
 *  获取data中的值 如person.name="小明"
 */
getValue(exp, vm) {
    // exp = person.fav
    return exp.split(".").reduce((data, curValue) => {
        return data[curValue]
    }, vm.$data)
},
text(node, exp, vm) {
    // 获取指令表达式的值
    let value = this.getValue(exp, vm);
    // 更新
    this.updater.textUpdater(node, value);
},
updater: {
    // 更新文本内容
    textUpdater(node, value) {
        node.textContent = value;
    },
}
  • v-html 实现
html(node, exp, vm) {
    let value = this.getValue(exp, vm);
    this.updater.htmlUpdater(node, value);
},
updater: {
    // ...
    // 插入html碎片
    htmlUpdater(node, value) {
        node.innerHTML = value;
    },
}
  • v-model 实现
model(node, exp, vm) {
    let value = this.getValue(exp, vm);
    this.updater.modelUpdater(node, value);
},
updater: {
    // ...
    // 绑定input的value
    modelUpdater(node, value) {
        node.value = value;
    },
}
  • v-bind 实现
/**
 * 
 * @param {*} node 元素节点
 * @param {*} exp 指令表达式
 * @param {*} vm Vue实例
 * @param {*} attrName 属性名称
 */
bind(node, exp, vm, attrName) {
    let value = this.getValue(exp, vm);
    this.updater.bindUpdater(node, attrName, value);
},
updater: {
    // ...
    // 绑定属性
    bindUpdater(node, attrName, attrValue) {
        node.setAttribute(attrName, attrValue)
    }
}
  • v-on:click实现
/**
 * 
 * @param {*} node 元素节点
 * @param {*} exp 指令表达式
 * @param {*} vm Vue实例
 * @param {*} eventName 事件名称
 */
on(node, exp, vm, eventName) {
    let fn = vm.$options.methods && vm.$options.methods[exp];
    node.addEventListener(eventName, fn.bind(vm), false)
},

@click指令和:src识别并处理

// 处理元素节点 
compileElment(node) {
    const attributes = node.attributes; //获取元素属性
    [...attributes].forEach(attr => {
        const { name, value } = attr; // v-text="msg"
        // console.log(name, value); // v-text msg
        // 是否为指令
        if (this.isDirective(name)) { // 得到指令 v-text v-model... v-on:click v-bind
		//...
        } else if (this.isBindName(name)) { //绑定属性简写 :src :style...
            // 获取绑定属性的key
            let [, attrName] = name.split(":");
            compileUtil["bind"](node, value, this.vm, attrName);
            // 删除带有指令的属性标签
            node.removeAttribute(':' + attrName);
        }
    })
}

对带标签文本进行替换 如<h2>大家好!我是{{person.name}},今年{{person.age}}岁。</h2>中的{{}}里的内容

// Compile类中
/**
 * 编译文本指令 如 {{person.name}}
 */
compileText(node) {
    const content = node.textContent;
    // 正则匹配 {{***}} 内容
    if (/\{\{(.+?)\}\}/.test(content)) {
        compileUtil["text"](node, content, this.vm)
    }
}

//compileUtil中
/**
 * 
 * @param {*} node 元素节点
 * @param {string} exp 指令表达式 在data中找值需要
 * @param {*} vm Vue实例
 */
text(node, exp, vm) {
    let value;
    if (exp.indexOf("{{") !== -1) {
        // 对{{person.name}}进行替换
        value = exp.replace(/\{\{(.+?)\}\}/g, (...args) => {
            // 获取到要替换的表达式 args中第二个元素即为需要的元素
            // console.log(args);
            return this.getValue(args[1], vm)
        })
    } else {
        // 获取指令表达式的值
        value = this.getValue(exp, vm);
    }
    // 更新
    this.updater.textUpdater(node, value);
},

至此,vue中又数据驱动视图更新部分完成。

完整源码如下:

// MyVue.js
/* 对指令进行编译实现 */
const compileUtil = {
    /**
     *  获取data中的值 如person.name="小明"
     */
    getValue(exp, vm) {
        // exp = person.fav
        return exp.split(".").reduce((data, curValue) => {
            return data[curValue]
        }, vm.$data)
    },
/**
 * 
 * @param {*} node 元素节点
 * @param {string} exp 指令表达式 在data中找值需要
 * @param {*} vm Vue实例
 */
text(node, exp, vm) {
    let value;
    if (exp.indexOf("{{") !== -1) {
        // 对{{person.name}}进行替换
        value = exp.replace(/\{\{(.+?)\}\}/g, (...args) => {
            // 获取到要替换的表达式 args中第二个元素即为需要的元素
            // console.log(args);
            return this.getValue(args[1], vm)
        })
    } else {
        // 获取指令表达式的值
        value = this.getValue(exp, vm);
    }
    // 更新
    this.updater.textUpdater(node, value);
},
    html(node, exp, vm) {
        let value = this.getValue(exp, vm);
        this.updater.htmlUpdater(node, value);
    },
    model(node, exp, vm) {
        let value = this.getValue(exp, vm);
        this.updater.modelUpdater(node, value);
    },
    /**
     * 
     * @param {*} node 元素节点
     * @param {*} exp 指令表达式
     * @param {*} vm Vue实例
     * @param {*} eventName 事件名称
     */
    on(node, exp, vm, eventName) {
        let fn = vm.$options.methods && vm.$options.methods[exp];
        node.addEventListener(eventName, fn.bind(vm), false)
    },
    /**
     * 
     * @param {*} node 元素节点
     * @param {*} exp 指令表达式
     * @param {*} vm Vue实例
     * @param {*} attrName 属性名称
     */
    bind(node, exp, vm, attrName) {
        let value = this.getValue(exp, vm);
        this.updater.bindUpdater(node, attrName, value);
    },
    updater: {
        // 更新文本内容
        textUpdater(node, value) {
            node.textContent = value;
        },
        // 插入html碎片
        htmlUpdater(node, value) {
            node.innerHTML = value;
        },
        // 绑定input的value
        modelUpdater(node, value) {
            node.value = value;
        },
        // 绑定属性
        bindUpdater(node, attrName, attrValue) {
            node.setAttribute(attrName, attrValue)
        }
    }
}
class Compile {
    constructor(el, vm) {
        // 检查el是否为dom节点 如果不是则获取节点
        this.el = this.isElmentNode(el) ? el : document.querySelector(el);
        this.vm = vm;
        // 获取文档碎片
        const fragment = this.node2Fragment(this.el);
        // 编译文档碎片
        this.compile(fragment);
        // 追加编译好的元素到根节点
        this.el.appendChild(fragment);
    }
    // 实现指令编译方法
    compile(fragment) {
        // 获取子节点
        const childNodes = fragment.childNodes;
        // 识别节点类型并处理
        [...childNodes].forEach(child => {
            if (this.isElmentNode(child)) {
                // 元素节点处理
                this.compileElment(child);
            } else {
                // 文本节点处理
                this.compileText(child)
            }
            if (child.childNodes && child.childNodes.length) {
                this.compile(child)
            }
        })
    }
    // 处理元素节点 
    compileElment(node) {
        const attributes = node.attributes; //获取元素属性
        [...attributes].forEach(attr => {
            const { name, value } = attr; // v-text="msg"
            // console.log(name, value); // v-text msg
            // 是否为指令
            if (this.isDirective(name)) { // 得到指令 v-text v-model... v-on:click v-bind
                const [, directive] = name.split("-"); // 得到指令 text model bind on:click
                // 对on:click 进一步处理
                const [directName, attrName] = directive.split(":")
                // 编译指令工具类
                compileUtil[directName](node, value, this.vm, attrName);
                // 删除带有指令的属性标签
                node.removeAttribute('v-' + directive);
            } else if (this.isEventName(name)) { //绑定事件指令 @click
                // 获取事件类型
                let [, eventName] = name.split("@");
                compileUtil["on"](node, value, this.vm, eventName);
                // 删除带有指令的属性标签
                node.removeAttribute('@' + eventName);
            } else if (this.isBindName(name)) { //绑定属性简写 :src :style...
                // 获取绑定属性的key
                let [, attrName] = name.split(":");
                compileUtil["bind"](node, value, this.vm, attrName);
                // 删除带有指令的属性标签
                node.removeAttribute(':' + attrName);
            }
        })
    }
/**
 * 编译文本指令 如 {{person.name}}
 */
compileText(node) {
    const content = node.textContent;
    // 正则匹配 {{***}} 内容
    if (/\{\{(.+?)\}\}/.test(content)) {
        compileUtil["text"](node, content, this.vm)
    }
}
    node2Fragment(node) {
        const fragment = document.createDocumentFragment();
        while (node.firstChild) {
            fragment.appendChild(node.firstChild);
        }
        return fragment;
    }
    /** 识别是否为属性简写绑定 */
    isBindName(attrName) {
        return attrName.startsWith(":")
    }
    /** 识别是否为事件简写 */
    isEventName(attrName) {
        return attrName.startsWith("@")
    }
    /** 识别是否为指令 */
    isDirective(attrName) {
        return attrName.startsWith("v-")
    }
    /** 识别是否为元素节点 */
    isElmentNode(node) {
        return node.nodeType === 1;
    }
}
class MyVue {
    // 构造函数
    constructor(options) {
        this.$el = options.el;
        this.$data = options.data;
        this.$options = options;
        if (this.$el) {
            // 实现指令解析类
            new Compile(this.$el, this);
        }
    }
}

demo 地址:Demo

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值