为了账号安全,请及时绑定邮箱和手机立即绑定

用ES6的class模仿Vue写一个双向绑定

标签:
Vue.js

原文地址:Bougie的博客

点击在线尝试一下

最终效果如下:

https://img1.sycdn.imooc.com//5d5aa8f8000123e500590021.gif

image


构造器(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_\-]+) *\}}/gthis.$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)

数据变化之后更新视图

_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()
                }
            }
        })
    })
}

使用

<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 class="lazyload" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAANSURBVBhXYzh8+PB/AAffA0nNPuCLAAAAAElFTkSuQmCC" data-original="./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}))
        })
    }
}



作者:儿时的烛光
链接:https://www.jianshu.com/p/78058c7922bf


点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消