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

原文地址:用ES6的class模仿Vue写一个双向绑定

点击在线尝试一下

最终效果如下:

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_\-]+) *\}}/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)

数据变化之后更新视图

_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 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}))
        })
    }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,736评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,167评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,442评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,902评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,302评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,573评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,847评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,562评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,260评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,531评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,021评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,367评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,016评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,068评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,827评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,610评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,514评论 2 269

推荐阅读更多精彩内容