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

栏目: 编程语言 · 发布时间: 5年前

内容简介:在在在

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

<div id="app">
      <input type="text" id="input">
      <span id="text"></span>
 </div>

----------------------------------------------------------------------

 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;
 });

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

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

如上所述, 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 ,下面再给属性添加发布订阅

<div>{{name}}</div>

在解析模板的时候遇到 {{ 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 更新)

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


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

计算机系统基础

计算机系统基础

袁春风 / 机械工业出版社 / 2014-7-1 / CNY 49.00

《计算机类专业系统能力培养系列教材:计算机系统基础》主要介绍与计算机系统相关的核心概念,解释这些概念如何相互关联并最终影响程序执行的结果和性能。共分8章,主要内容包括数据的表示和运算、程序的转换及机器级表示、程序的链接、程序的执行、存储器层次结构、虚拟存储器、异常控制流和I/O操作的实现等。内容详尽,反映现实,概念清楚,通俗易懂,实例丰富,并提供大量典型习题供读者练习。本书可以作为计算机专业本科或......一起来看看 《计算机系统基础》 这本书的介绍吧!

RGB转16进制工具
RGB转16进制工具

RGB HEX 互转工具

随机密码生成器
随机密码生成器

多种字符组合密码

RGB HSV 转换
RGB HSV 转换

RGB HSV 互转工具