资讯专栏INFORMATION COLUMN

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

waltr / 461人阅读

摘要:原文地址的博客点击在线尝试一下最终效果如下构造器构造一个对象,包含基本的,,初始化编译器用于解析绑定到输入框和下拉框的和元素的点击事件。

原文地址:Bougie的博客

点击在线尝试一下

最终效果如下:

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



您输入的是:{{text1}}+{{text2}}+{{text3}}

您选择了:{{select}}

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.ucloud.cn/yun/94440.html

相关文章

  • vue for contacts项目总结

    摘要:用来主要前台的请求,并处理返回相关的数据,做后台服务。总结做完这个项目,其中的过程还是挺艰辛的,毕竟都是边学边做,不过最后能完成还是挺开心的,终于有了一个从到的项目过程。虽然只是一个小小的练手项目,不过对于目前的我,感觉还是不错的。 showImg(https://oc1gyfe6q.qnssl.com/17-3-30/43434844-file_1490879850754_14751...

    tulayang 评论0 收藏0
  • vue for contacts项目总结

    摘要:用来主要前台的请求,并处理返回相关的数据,做后台服务。总结做完这个项目,其中的过程还是挺艰辛的,毕竟都是边学边做,不过最后能完成还是挺开心的,终于有了一个从到的项目过程。虽然只是一个小小的练手项目,不过对于目前的我,感觉还是不错的。 showImg(https://oc1gyfe6q.qnssl.com/17-3-30/43434844-file_1490879850754_14751...

    ralap 评论0 收藏0
  • vue for contacts项目总结

    摘要:用来主要前台的请求,并处理返回相关的数据,做后台服务。总结做完这个项目,其中的过程还是挺艰辛的,毕竟都是边学边做,不过最后能完成还是挺开心的,终于有了一个从到的项目过程。虽然只是一个小小的练手项目,不过对于目前的我,感觉还是不错的。 showImg(https://oc1gyfe6q.qnssl.com/17-3-30/43434844-file_1490879850754_14751...

    ARGUS 评论0 收藏0
  • 前端面试题总结——VUE(持续更新中)

    摘要:前端面试题总结持续更新中是哪个组件的属性模块的组件。都提供合理的钩子函数,可以让开发者定制化地去处理需求。 前端面试题总结——VUE(持续更新中) 1.active-class是哪个组件的属性? vue-router模块的router-link组件。 2.嵌套路由怎么定义? 在 VueRouter 的参数中使用 children 配置,这样就可以很好的实现路由嵌套。 //引入两个组件 ...

    SimonMa 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<