JavaScript

超轻量级php框架startmvc

用ES6的class模仿Vue写一个双向绑定的示例代码

更新时间:2020-07-06 10:54:01 作者:startmvc
本文介绍了用ES6的class模仿Vue写一个双向绑定的示例代码,分享给大家,具体如下:最终效

本文介绍了用ES6的class模仿Vue写一个双向绑定的示例代码,分享给大家,具体如下:

最终效果如下:

构造器(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()
 }
}

编译器(compile)

用于解析绑定到输入框和下拉框的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

这里实现方法有点low,仅想到正则替换{{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}))
 })
 }
}

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持脚本之家。

ES6 class 双向绑定 ES6 class 绑定