Vue数据绑定原理

源码地址
VueJS双向数据绑定是通过对数据的劫持来实现的。核心就是Object.defineProperty(data, key, descriptor)

如何实现一个简单的vue数据双向绑定呢?

  1. 实现数据劫持,监听数据对象
function Mvue(options) {
    this.$options = options || {}
    let data = (this.$data = options.data)
    let vm = this

    //代理 data上属性代理到vm实例上
    _proxy(data, vm)

    //observe data
    //监测数据变化
    _observe(data, vm)
}

function _proxy(data, target) {
    let vm = target
    Object.keys(data).forEach(key => {
        Object.defineProperty(vm, key, {
            enumerable: true,
            configurable: false,
            get: function() {
                return vm.$data[key]
            },
            set: function(newVal) {
                vm.$data[key] = newVal
            }
        })
    })
}

// -----------------observe ------------------------

function _observe(data, vm) {
    if (!data || typeof data !== 'object') {
        return
    }
    return new Observer(data)
}

function Observer(data) {
    this.data = data
    this.walk(data)
}

Observer.prototype.walk = function(data) {
    const keys = Object.keys(data)
    for (let i = 0; i < keys.length; i++) {
        let key = keys[i]
        this.defineReactive(data, key, data[key])
    }
}

Observer.prototype.defineReactive = function(data, key, val) {
    Object.defineProperty(data, key, {
        enumerable: true,
        configurable: false,
        get: function() {
            return val
        },
        set: function(newVal) {
            val = newVal
            console.log(`this is change message: ${val}`)
        }
    })
}
<body>
  <div id="app">
    {{message}}
  </div>
  <script src="./index.js"></script>
  <script>
    var app = new Mvue({
      el: '#app',
      data: {
        message: 'hello Mvue'
      }
    });
  </script>
</body>

在控制台进行验证修改,已经可以获取数据并且监听数据的变化


image.png
  1. 消息订阅
    在监听数据变化的同时,需要去通知订阅者
function Mvue(options) {
    this.$options = options || {}
    let data = (this.$data = options.data)
    let vm = this

    //代理 data上属性代理到vm实例上
    _proxy(data, vm)

    //observe data
    //监测数据变化
    _observe(data, vm)

    //订阅 message
    new Watcher(vm, 'message')
}

function _proxy(data, target) {
    let vm = target
    Object.keys(data).forEach(key => {
        Object.defineProperty(vm, key, {
            enumerable: true,
            configurable: false,
            get: function() {
                return vm.$data[key]
            },
            set: function(newVal) {
                vm.$data[key] = newVal
            }
        })
    })
}

// -----------------observe ------------------------

function _observe(data, vm) {
    if (!data || typeof data !== 'object') {
        return
    }
    return new Observer(data)
}

function Observer(data) {
    this.data = data
    this.walk(data)
}

Observer.prototype.walk = function(data) {
    const keys = Object.keys(data)
    for (let i = 0; i < keys.length; i++) {
        let key = keys[i]
        this.defineReactive(data, key, data[key])
    }
}

Observer.prototype.defineReactive = function(data, key, val) {
    //收集订阅者
    let dep = new Dep()

    Object.defineProperty(data, key, {
        enumerable: true,
        configurable: false,
        get: function() {
            // 由于需要在闭包内添加watcher,所以通过Dep定义一个全局target属性,暂存watcher, 添加完移除
            if (Dep.target) {
                //通过depend方法转到watcher中进行添加,防止重复
                dep.depend()
            }
            return val
        },
        set: function(newVal) {
            val = newVal
            dep.notify()
        }
    })
}

// -----------------observe ------------------------

// ----------------- Dep ------------------------
let uid = 0
function Dep() {
    this.subs = []
    this.uid = uid++
}

Dep.prototype.addSub = function(watcher) {
    this.subs.push(watcher)
}

Dep.prototype.notify = function() {
    this.subs.forEach(watcher => {
        watcher.update()
    })
}

Dep.prototype.depend = function() {
    Dep.target.addDep(this)
}

Dep.target = null

// ----------------- Dep ------------------------

// ----------------- Watcher ------------------------
function Watcher(vm, datakey) {
    this.vm = vm
    this.depIds = {}
    this.datakey = datakey

    //触发 监听数据的get方法, 触发Dep 将自身收集
    this.vaule = this.get()
}

Watcher.prototype.get = function() {
    let datakey = this.datakey
    Dep.target = this
    let value = this.vm.$data[datakey]
    Dep.target = null
    return value
}

Watcher.prototype.addDep = function(dep) {
  //防止重复添加
    if (!this.depIds.hasOwnProperty(dep.uid)) {
        dep.addSub(this)
        this.depIds[dep.uid] = dep
    }
}

Watcher.prototype.update = function() {
    let datakey = this.datakey
    let value = this.get()
    let oldVal = this.value
    if (value !== oldVal) {
        this.value = value
    }
    console.log(`this is ${datakey} : ${this.value}`)
}

// ----------------- Watcher ------------------------
image.png

很关键的一部分就是Watcher.prototype.get里触发 监听数据的get方法。
这样就简单实现了vue的数据绑定,双向绑定无非就是在view层添加了事件监听并触发数据的set,来同步数据。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。