总结

Vue的双向数据绑定主要通过Object.defineProperty来实现,先为所有的属性加上get/set的监控,这样当属性值改变时就会触发对应的set方法,然后再在set方法中通过观察者来更新视图,同时在get方法中进行依赖收集。


极简版的实现

  • index.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>极简双向数据绑定</title>
</head>
<body>
    <input type="text" id="message" />
    <div id="msg"></div>

    <script src="app.js"></script>
</body>
</html>
  • app.js
var obj = {}
Object.defineProperty(obj, "data", {
    get: function () {
        console.log("get")
    },
    set: function (newValue) {
        document.getElementById("message").value = newValue
        document.getElementById("msg").innerText = newValue
    }
})
document.getElementById("message").addEventListener('keyup', function () {
    obj.data = event.target.value
}) 
  • 分析
1)通过Object.defineProperty的方法为属性加上get/set的监控
(2)通过EventListener监听属性的改变,不断触发属性的set方法,从而实现数据的双向绑定

稍微复杂版的实现

  • index.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>双向数据绑定</title>
</head>
<body>
    <div id="app">
        <input type="text" v-model="message"> 
        {{ message }}
    </div>

    <script src="dist/bundle.js"></script>
</body>
</html>
  • main.js
import myVue from './myVue'

new myVue({
    el: '#app',
    data: {
        message: "hello myVue"
    }
})
  • myVue.js
import Observer from './Observer'
import Compiler from './Compiler'

class myVue {
    constructor(options) {
        // 获取对象传入的数据
        this.$options = options
        this.$el = this.$options.el
        this._data = this.$options.data

        // 将传入的所有属性添加get/set的属性监听
        // 属性值发生改变会触发set方法
        Object.keys(this._data).forEach(key=>{
            this.add_watch(key)
        })

        // 将所有属性加入订阅发布者模式的管理中
        new Observer(this._data)

        // 编译渲染页面
        new Compiler(this.$el, this)
    }
    add_watch(key) {
        var self = this
        Object.defineProperty(this, key, {
            get() {
                return self._data[key]
            },
            set(value) {
                self._data[key] = value
            }
        })
    }
}

export default myVue
  • Observer.js
import Dep from './Dep'

class Observer {
    constructor(data) {
        // 获取所有属性数据
        this.data = data

        // 为所有属性数据添加get/sete的属性监听
        Object.keys(this.data).forEach(key=>{
            this._bind(data, key, data[key])
        })
    }
    _bind(data, key, val) {
        var myDep = new Dep()
        Object.defineProperty(data, key, {
            get() {
                // 如果是为订阅的对象,则添订阅
                if(Dep.target) myDep.listen(Dep.target)
                return val
            },
            set(newValue) {
                if (newValue === val) return 
                val = newValue
                // 如果数值改变,则发布更新
                myDep.notify()    
            }
        })
    }
}

export default Observer
  • Watcher.js
import Dep from './Dep'

class Watcher {
    constructor(node, name, vm) {
        this.node = node
        this.name = name
        this.vm = vm

        Dep.target = this
        this.update()
        Dep.target = null
    }
    update() {
        this.node.nodeValue = this.vm[this.name]
    }
}

export default Watcher
  • Dep.js

class Dep {
    constructor() {
        this.list = []
    }
    listen(subs) {
        this.list.push(subs)
    }
    notify() {
        for(var i=0; i<this.list.length; i++){
            this.list[i].update()
        }
    }
}
Dep.prototype.target = null

export default Dep
  • Compiler.js
import Watcher from './Watcher'

const REG = /\{\{(.*)\}\}/

class Compiler {
    constructor(el, vm) {
        this.el = document.querySelector(el)
        this.vm = vm

        // 创建文档片段,编译完成后,挂载到el元素上
        this.frag = this._createFragment()
        this.el.appendChild(this.frag)
    }
    _createFragment() {
        var frag = document.createDocumentFragment()
        var child
        while (child = this.el.firstChild) {
            this._compile(child)
            frag.appendChild(child)
        }
        return frag
    }
    _compile(node) {
        // 如果传入的是节点node
        if(node.nodeType === 1) {
            var attr = node.attributes
            var self = this
            if(attr.hasOwnProperty('v-model')){
                var name = attr['v-model'].nodeValue
                node.addEventListener('input', function(e) {
                    self.vm[name] = e.target.value
                })
                node.value = this.vm[name]
            }
        }

        // 如果传入的是元素elemet
        if (node.nodeType === 3) {
            if(REG.test(node.nodeValue)) {
                var name = RegExp.$1
                name = name.trim()
                new Watcher(node, name, this.vm)
            }
        }
    }
}

export default Compiler
Logo

前往低代码交流专区

更多推荐