基于ES6 Class实现简易Vue双向绑定原理
本文通过ES6的class语法构建一个微型Vue实例,详细解析了从构造器初始化、DOM节点编译、事件监听绑定到视图更新及数据监听器的完整实现流程。代码展示了如何利用input、select等元素的事件模拟v-model,并通过正则替换实现{{text}}插值表达式的渲染,帮助开发者深入理解前端框架的核心机制。
ES6 Class模拟Vue双向绑定实现
本文通过ES6的class语法构建一个微型Vue实例,详细解析了从构造器初始化、DOM节点编译、事件监听绑定到视图更新及数据监听器的完整实现流程,供读者参考学习。
最终运行效果如下图所示:

构造器初始化
构造器(constructor)
构建一个TinyVue类,初始化时接收el、data和methods三个核心参数。
class TinyVue{
constructor({el, data, methods}){
this.$data = data
this.$el = document.querySelector(el)
this.$methods = methods
// 初始化
this._compile()
this._updater()
this._watcher()
}
}
DOM编译与事件绑定
编译器(compile)
负责解析DOM节点,主要处理绑定在输入框和下拉框上的v-model指令,以及元素的@click点击事件。
首先定义一个公共函数用于统一载入各类事件监听。
// el为元素tagName,attr为元素属性(v-model,@click)
_initEvents(el, attr, callBack) {
this.$el.querySelectorAll(el).forEach(i => {
if(i.hasAttribute(attr)) {
let key = i.getAttribute(attr)
callBack(i, key)
}
})
}
针对输入框和文本域加载事件监听逻辑。
this._initEvents('input, textarea', 'v-model', (i, key) => {
i.addEventListener('input', () => {
Object.assign(this.$data, {[key]: i.value})
})
})
针对选择框加载事件监听逻辑。
this._initEvents('select', 'v-model', (i, key) => {
i.addEventListener('change', () => Object.assign(this.$data, {[key]: i.options[i.options.selectedIndex].value}))
})
针对点击事件加载监听逻辑。
点击事件的处理逻辑对应methods中定义的方法。
this._initEvents('*', '@click', (i, key) => {
i.addEventListener('click', () => this.$methods[key].bind(this.$data)())
})
视图更新机制
视图更新器(updater)
同样创建一个公共函数,用于处理不同元素类型的视图更新,涵盖input、textarea的value属性,select的选中值,以及div的innerHTML内容。
_initView(el, attr, callBack) {
this.$el.querySelectorAll(el, attr, callBack).forEach(i => {
if(i.hasAttribute(attr)) {
let key = i.getAttribute(attr),
data = this.$data[key]
callBack(i, key, data)
}
})
}
具体实现输入框视图的更新逻辑。
this._initView('input, textarea', 'v-model', (i, key, data) => {
i.value = data
})
具体实现选择框视图的更新逻辑。
this._initView('select', 'v-model', (i, key, data) => {
i.querySelectorAll('option').forEach(v => {
if(v.value == data) v.setAttribute('selected', true)
else v.removeAttribute('selected')
})
})
具体实现innerHTML内容的更新逻辑。
此处实现方式较为简单,仅考虑使用正则表达式替换{{text}}形式的插值内容。
let regExpInner = /\{{ *([\w_\-]+) *\}}/g
this.$el.querySelectorAll("*").forEach(i => {
let replaceList = i.innerHTML.match(regExpInner) || (i.hasAttribute('vueID') && i.getAttribute('vueID').match(regExpInner))
if(replaceList) {
if(!i.hasAttribute('vueID')) {
i.setAttribute('vueID', i.innerHTML)
}
i.innerHTML = i.getAttribute('vueID')
replaceList.forEach(v => {
let key = v.slice(2, v.length - 2)
i.innerHTML = i.innerHTML.replace(v, this.$data[key])
})
}
})
数据监听与视图同步
监听器(watcher)
当数据发生变化时,触发视图的自动更新。
<div id="app">
<input type="text" v-model="text1"><br>
<input type="text" v-model="text2"><br>
<textarea type="text" v-model="text3"></textarea><br>
<button @click="add">加一</button>
<h1>您输入的是:{{text1}}+{{text2}}+{{text3}}</h1>
<select v-model="select">
<option value="volvo">Volvo</option>
<option value="saab">Saab</option>
</select>
<select v-model="select">
<option value="volvo">Volvo</option>
<option value="saab">Saab</option>
</select>
<h1>您选择了:{{select}}</h1>
</div>
<script src="./TinyVue.js"></script>
<script>
let app = new TinyVue({
el: '#app',
data: {
text1: 123,
text2: 456,
text3: '文本框',
select: 'saab'
},
methods: {
add() {
this.text1 ++
this.text2 ++
}
}
})
</script>
TinyVue类的完整代码实现如下。
class TinyVue{
constructor({el, data, methods}){
this.$data = data
this.$el = document.querySelector(el)
this.$methods = methods
this._compile()
this._updater()
this._watcher()
}
_watcher(data = this.$data) {
let that = this
Object.keys(data).forEach(i => {
let value = data[i]
Object.defineProperty(data, i, {
enumerable: true,
configurable: true,
get: function () {
return value;
},
set: function (newVal) {
if (value !== newVal) {
value = newVal;
that._updater()
}
}
})
})
}
_initEvents(el, attr, callBack) {
this.$el.querySelectorAll(el).forEach(i => {
if(i.hasAttribute(attr)) {
let key = i.getAttribute(attr)
callBack(i, key)
}
})
}
_initView(el, attr, callBack) {
this.$el.querySelectorAll(el, attr, callBack).forEach(i => {
if(i.hasAttribute(attr)) {
let key = i.getAttribute(attr),
data = this.$data[key]
callBack(i, key, data)
}
})
}
_updater() {
this._initView('input, textarea', 'v-model', (i, key, data) => {
i.value = data
})
this._initView('select', 'v-model', (i, key, data) => {
i.querySelectorAll('option').forEach(v => {
if(v.value == data) v.setAttribute('selected', true)
else v.removeAttribute('selected')
})
})
let regExpInner = /\{{ *([\w_\-]+) *\}}/g
this.$el.querySelectorAll("*").forEach(i => {
let replaceList = i.innerHTML.match(regExpInner) || (i.hasAttribute('vueID') && i.getAttribute('vueID').match(regExpInner))
if(replaceList) {
if(!i.hasAttribute('vueID')) {
i.setAttribute('vueID', i.innerHTML)
}
i.innerHTML = i.getAttribute('vueID')
replaceList.forEach(v => {
let key = v.slice(2, v.length - 2)
i.innerHTML = i.innerHTML.replace(v, this.$data[key])
})
}
})
}
_compile() {
this._initEvents('*', '@click', (i, key) => {
i.addEventListener('click', () => this.$methods[key].bind(this.$data)())
})
this._initEvents('input, textarea', 'v-model', (i, key) => {
i.addEventListener('input', () => {
Object.assign(this.$data, {[key]: i.value})
})
})
this._initEvents('select', 'v-model', (i, key) => {
i.addEventListener('change', () => Object.assign(this.$data, {[key]: i.options[i.options.selectedIndex].value}))
})
}
}
以上就是本文的全部内容,希望能为读者的学习提供有益帮助,同时也欢迎大家继续支持本站。
怎么在 Vue 3 和 React 项目中封装使用 Quill.js 编辑器
2026-11-02 10:28:27
Vue 和 React 项目中集成 qrcode.js 的注意事项
2026-11-02 10:28:19
Vue3实现多内容滑动轮播组件完整代码示例
2026-10-31 20:20:59
Element Plus 中 mt-x 类的使用与卡片间距调整
2026-10-31 16:50:52
前端qrcode生成二维码安装及使用示例详解
2026-10-31 15:22:14
