资讯专栏INFORMATION COLUMN

学习MVVM及框架的双向绑定笔记

VioletJack / 1055人阅读

摘要:的数据劫持版本内部使用了来实现数据与视图的双向绑定,体现在对数据的读写处理过程中。这样就形成了数据的双向绑定。

MVVM由以下三个内容组成

View:视图模板

Model:数据模型

ViewModel:作为桥梁负责沟通ViewModel,自动渲染模板

JQuery时期,如果需要刷新UI时,需要先取到对应的DOM再更新UI,这样数据和业务的逻辑就和页面有强耦合。

MVVM中,UI是挺数据驱动的,数据一旦改变就会刷新相应的UIUI变化也会改变相应的数据。这种方式在开发中只需要关心数据的变化,不用直接去操作DOM。并且可以将一些可复用的逻辑放在一个ViewModel中,多个View复用这个ViewModel

MVVM 中,最核心的也就是数据双向绑定,例如 Angluar 的脏数据检测Vue 的数据劫持React的数据绑定

Angluar 的脏数据检测

当触发了指定事件后进入脏数据检测,这时期会调用$digest循环遍历所有的数据观察者,判断当前值是否和先前的值有区别,如果检测到变化的话,会调用$watch函数,然后再次调用$digest循环直到发现没有变化。所以这个过程可能会循环几次,一直到不再有数据变化发生后,将变更的数据发送到视图,更新页面展现。如果是手动对 ViewModel 的数据进行变更,为确保变更同步到视图,需要手动触发一次“脏值检测”。

脏数据检测虽然需要每次去循环遍历查看是否有数据变化,存在低效的问题,与Vue的双向绑定原理不同,但是脏数据检测能够同时检测出要更新的值,再去统一更新UI,这样也可以减少操作DOM的次数。

Vue 的数据劫持

Vue2.0版本内部使用了Object.defineProperty()来实现数据与视图的双向绑定,体现在对数据的读写处理过程中。即Object.defineProperty()中定义的数据setget函数。

使用Object.defineProperty()实现Vue2.0双向绑定的小demo

 
---------------------------------------------------------------------- var obj = {}; Object.defineProperty(obj, "prop", { get: function() { return val; }, set: function(newVal) { val = newVal; document.getElementById("input").value = val; document.getElementById("text").innerHTML = val; } }); document.addEventListener("keyup", function(e) { obj.prop = e.target.value; });

如上所述,vue.js通过Object.defineProperty()来劫持各个属性的settergetter。再结合发布者-订阅者的方式,发布消息给订阅者,触发相应的监听回调。通过Directives指令去对DOM做封装,当数据发生变化,会通知指令去修改对应的DOM,数据驱动DOM的变化。vue.js还会对操作做一些监听(DOM Listener),当我们修改视图的时候,vue.js监听到这些变化,从而改变数据。这样就形成了数据的双向绑定。

在对数据进行读取时,如果当前有 Watcher(对数据的观察者,watcher 会负责将获取的新数据发送给视图),那将该 Watcher 绑定到当前的数据上(dep.depend(),dep 关联当前数据和所有的 watcher 的依赖关系),是一个检查并记录依赖的过程。而在对数据进行赋值时,如果数据发生改变,则通知所有的 watcher(借助 dep.notify())。这样,即便是我们手动改变了数据,框架也能够自动将数据同步到视图。
完整的简易双向绑定代码如下:

var data = { name: "yck" }
observe(data)
let name = data.name // -> get value
data.name = "yyy" // -> change value

function observe(obj) {
  // 判断类型
  if (!obj || typeof obj !== "object") {
    return
  }
  Object.keys(obj).forEach(key => {
    defineReactive(obj, key, obj[key])
  })
}

function defineReactive(obj, key, val) {
  // 递归子属性
  observe(val)
  Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    get: function reactiveGetter() {
      console.log("get value")
      return val
    },
    set: function reactiveSetter(newVal) {
      console.log("change value")
      val = newVal
    }
  })
}

以上代码简单的实现了如何监听数据的setget,下面再给属性添加发布订阅

{{name}}

在解析模板的时候遇到{{ name }}就给属性name添加发布订阅

// 通过 Dep 解耦
class Dep {
  constructor() {
    this.subs = []
  }
  addSub(sub) {
    // sub 是 Watcher 实例
    this.subs.push(sub)
  }
  notify() {
    this.subs.forEach(sub => {
      sub.update()
    })
  }
}
// 全局属性,通过该属性配置 Watcher
Dep.target = null

function update(value) {
  document.querySelector("div").innerText = value
}

class Watcher {
  constructor(obj, key, cb) {
    // 将 Dep.target 指向自己
    // 然后触发属性的 getter 添加监听
    // 最后将 Dep.target 置空
    Dep.target = this
    this.cb = cb
    this.obj = obj
    this.key = key
    this.value = obj[key]
    Dep.target = null
  }
  update() {
    // 获得新值
    this.value = this.obj[this.key]
    // 调用 update 方法更新 Dom
    this.cb(this.value)
  }
}
var data = { name: "yck" }
observe(data)
// 模拟解析到 `{{name}}` 触发的操作
new Watcher(data, "name", update)
// update Dom innerText
data.name = "yyy"

下面对defineReactive函数进行改造:

function defineReactive(obj, key, val) {
  // 递归子属性
  observe(val)
  let dp = new Dep()
  Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    get: function reactiveGetter() {
      console.log("get value")
      // 将 Watcher 添加到订阅
      if (Dep.target) {
        dp.addSub(Dep.target)
      }
      return val
    },
    set: function reactiveSetter(newVal) {
      console.log("change value")
      val = newVal
      // 执行 watcher 的 update 方法
      dp.notify()
    }
  })
}

核心思路就是手动触发一次属性的 getter 来实现发布订阅的添加。

Vue3.0将用proxy代替Object.defineProperty()

Object.defineProperty()的缺陷:

1.只能对属性进行数据劫持,所以需要深度遍历整个对象

2.对于数组不能监听到数据的变化

虽然 Vue 2.0中能检测到数组数据的变化,但是其实是使用了 hack 的办法,并且也是有缺陷的

const arrayProto = Array.prototype
export const arrayMethods = Object.create(arrayProto)
// hack 以下几个函数
const methodsToPatch = [
  "push",
  "pop",
  "shift",
  "unshift",
  "splice",
  "sort",
  "reverse"
]
methodsToPatch.forEach(function(method) {
  // 获得原生函数
  const original = arrayProto[method]
  def(arrayMethods, method, function mutator(...args) {
    // 调用原生函数
    const result = original.apply(this, args)
    const ob = this.__ob__
    let inserted
    switch (method) {
      case "push":
      case "unshift":
        inserted = args
        break
      case "splice":
        inserted = args.slice(2)
        break
    }
    if (inserted) ob.observeArray(inserted)
    // 触发更新
    ob.dep.notify()
    return result
  })
})

Proxy原生支持监听数组变化,并且可以直接对整个对象进行拦截,所以Vue3.0使用 Proxy 替换 Object.defineProperty

let onWatch = (obj, setBind, getLogger) => {
  let handler = {
    get(target, property, receiver) {
      getLogger(target, property)
      return Reflect.get(target, property, receiver)
    },
    set(target, property, value, receiver) {
      setBind(value)
      return Reflect.set(target, property, value)
    }
  }
  return new Proxy(obj, handler)
}

let obj = { a: 1 }
let value
let p = onWatch(
  obj,
  v => {
    value = v
  },
  (target, property) => {
    console.log(`Get "${property}" = ${target[property]}`)
  }
)
p.a = 2 // bind `value` to `2`
p.a // -> Get "a" = 2
React的数据绑定

React的数据绑定是虚拟DOM树的更新相关:
属性更新,组件自己处理

结构更新,重新“渲染”子树(虚拟DOM),找出最小改动步骤,打包DOM操作,给真实DOM树打补丁

单纯从数据绑定来看,React虚拟DOM没有数据绑定,因为setState()不维护上一个状态(状态丢弃)

从数据更新机制来看,React类似于提供数据模型的方式(必须通过state更新)

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/101461.html

相关文章

  • 基于VueMVVM学习笔记

    摘要:发布订阅现在每个人应该都用微信吧,一个人可以关注多个公众号,多个人可以同时关注相同的公众号。公众号每周都会更新内容,并推送给我们,把写好的文章在微信管理平台更新就好了,点击推送,就相当于发布。 什么是MVVM MVVM——Model-View-ViewModle的缩写,MVC设计模式的改进版。Model是我们应用中的数据模型,View是我们的UI层,通过ViewModle,可以把我们M...

    Alan 评论0 收藏0
  • JS中双向数据绑定Object.defineProperty方法

    摘要:,而且每种框架双向数据绑定的实现方式都不太一致,比如内部使用的是脏检查,而内部实现方式的本质是设置属性访问器。在中也有类似的概念,不过不叫魔术方法,而是叫做访问器。 缘起前几天在看一些流行的迷你mvvm框架(比如avalon.js、 vue.js 这种较轻的框架,而非Angularjs、Emberjs这种较重的框架)的实现。现代流行的mvvm框架一般都会将数据双向绑定(two-ways...

    szysky 评论0 收藏0
  • JS中双向数据绑定Object.defineProperty方法

    摘要:,而且每种框架双向数据绑定的实现方式都不太一致,比如内部使用的是脏检查,而内部实现方式的本质是设置属性访问器。在中也有类似的概念,不过不叫魔术方法,而是叫做访问器。 缘起前几天在看一些流行的迷你mvvm框架(比如avalon.js、 vue.js 这种较轻的框架,而非Angularjs、Emberjs这种较重的框架)的实现。现代流行的mvvm框架一般都会将数据双向绑定(two-ways...

    苏丹 评论0 收藏0
  • JavaScript 进阶之深入理解数据双向绑定

    摘要:当我们的视图和数据任何一方发生变化的时候,我们希望能够通知对方也更新,这就是所谓的数据双向绑定。返回值返回传入函数的对象,即第一个参数该方法重点是描述,对象里目前存在的属性描述符有两种主要形式数据描述符和存取描述符。 前言 谈起当前前端最热门的 js 框架,必少不了 Vue、React、Angular,对于大多数人来说,我们更多的是在使用框架,对于框架解决痛点背后使用的基本原理往往关注...

    sarva 评论0 收藏0

发表评论

0条评论

VioletJack

|高级讲师

TA的文章

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