内容简介:在在在
MVVM由以下三个内容组成
- View:视图模板
- Model:数据模型
- ViewModel:作为桥梁负责沟通
View
和Model
,自动渲染模板
在 JQuery
时期,如果需要刷新 UI
时,需要先取到对应的 DOM
再更新 UI
,这样数据和业务的逻辑就和页面有强耦合。
在 MVVM
中, UI
是挺数据驱动的,数据一旦改变就会刷新相应的 UI
, UI
变化也会改变相应的数据。这种方式在开发中只需要关心数据的变化,不用直接去操作 DOM
。并且可以将一些可复用的逻辑放在一个 ViewModel
中,多个 View
复用这个 ViewModel
。
在 MVVM
中,最核心的也就是数据双向绑定,例如 Angluar 的脏数据检测 , Vue 的数据劫持 , React的数据绑定
Angluar 的脏数据检测
当触发了指定事件后进入脏数据检测,这时期会调用 $digest
循环遍历所有的数据观察者,判断当前值是否和先前的值有区别,如果检测到变化的话,会调用 $watch
函数,然后再次调用 $digest
循环直到发现没有变化。所以这个过程可能会循环几次,一直到不再有数据变化发生后,将变更的数据发送到视图,更新页面展现。如果是手动对 ViewModel
的数据进行变更,为确保变更同步到视图,需要手动触发一次“脏值检测”。
脏数据检测虽然需要每次去循环遍历查看是否有数据变化,存在低效的问题,与 Vue
的双向绑定原理不同,但是脏数据检测能够同时检测出要更新的值,再去统一更新 UI
,这样也可以减少操作 DOM
的次数。
Vue 的数据劫持
Vue2.0
版本内部使用了 Object.defineProperty()
来实现数据与视图的双向绑定,体现在对数据的读写处理过程中。即 Object.defineProperty()
中定义的数据 set
、 get
函数。
使用 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; });
如上所述, vue.js
通过 Object.defineProperty()
来劫持各个属性的 setter
, getter
。再结合发布者-订阅者的方式,发布消息给订阅者,触发相应的监听回调。通过 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 } }) }
以上代码简单的实现了如何监听数据的 set
和 get
,下面再给属性添加发布订阅
<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
更新)
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Web Data Mining
Bing Liu / Springer / 2011-6-26 / CAD 61.50
Web mining aims to discover useful information and knowledge from Web hyperlinks, page contents, and usage data. Although Web mining uses many conventional data mining techniques, it is not purely an ......一起来看看 《Web Data Mining》 这本书的介绍吧!