原理
当你把一个普通的 JavaScript 对象传给 Vue 实例的 data 选项,Vue 将遍历此对象所有的属性,并使用 Object.defineProperty 把这些属性全部转为 getter/setter。Object.defineProperty 是 ES5 中一个无法 shim 的特性,这也就是为什么 Vue 不支持 IE8 以及更低版本浏览器。
上面那段话是Vue官方文档中截取的,可以看到是使用Object.defineProperty实现对数据改变的监听。Vue主要使用了观察者模式来实现数据与视图的双向绑定。
function initData(vm) { //将data上数据复制到_data并遍历所有属性添加代理 vm._data = vm.$options.data; const keys = Object.keys(vm._data); let i = keys.length; while(i--) { const key = keys[i]; proxy(vm, `_data`, key); } observe(data, true /* asRootData */) //对data进行监听 }
在第一篇数据初始化中,执行new Vue()操作后会执行initData()去初始化用户传入的data,最后一步操作就是为data添加响应式。
实现
在Vue内部存在三个对象:Observer、Dep、Watcher,这也是实现响应式的核心。
Observer
Observer对象将data中所有的属性转为getter/setter形式,以下是简化版代码,详细代码请看这里。
export function observe (value) { //递归子属性时的判断 if (!isObject(value) || value instanceof VNode) { return } ... ob = new Observer(value) } export class Observer { constructor (value) { ... //此处省略对数组的处理 this.walk(value) } walk (obj: Object) { const keys = Object.keys(obj) for (let i = 0; i < keys.length; i++) { defineReactive(obj, keys[i]) //为每个属性创建setter/getter } } ... } //设置set/get export function defineReactive ( obj: Object, key: string, val: any ) { //利用闭包存储每个属性关联的watcher队列,当setter触发时依然能访问到 const dep = new Dep() ... //如果属性为对象也创建相应observer let childOb = observe(val) Object.defineProperty(obj, key, { enumerable: true, configurable: true, get: function reactiveGetter () { if (Dep.target) { dep.depend() //将当前dep传到对应watcher中再执行watcher.addDep将watcher添加到当前dep.subs中 if (childOb) { //如果属性是对象则继续收集依赖 childOb.dep.depend() ... } } return value }, set: function reactiveSetter (newVal) { ... childOb = observe(newVal) //如果设置的新值是对象,则为其创建observe dep.notify() //通知队列中的watcher进行更新 } }) }
创建Observer对象时,为data的每个属性都执行了一遍defineReactive方法,如果当前属性为对象,则通过递归进行深度遍历。该方法中创建了一个Dep实例,每一个属性都有一个与之对应的dep,存储所有的依赖。然后为属性设置setter/getter,在getter时收集依赖,setter时派发更新。这里收集依赖不直接使用addSub是为了能让Watcher创建时自动将自己添加到dep.subs中,这样只有当数据被访问时才会进行依赖收集,可以避免一些不必要的依赖收集。
Dep
Dep就是一个发布者,负责收集依赖,当数据更新是去通知订阅者(watcher)。源码地址
export default class Dep { static target: "color: #ff0000">Watcher源码地址
//解析表达式(a.b),返回一个函数 export function parsePath (path: string): any { if (bailRE.test(path)) { return } const segments = path.split('.') return function (obj) { for (let i = 0; i < segments.length; i++) { if (!obj) return obj = obj[segments[i]] //遍历得到表达式所代表的属性 } return obj } } export default class Watcher { constructor ( vm: Component, expOrFn: string | Function, cb: Function, options"${this.expression}"`) } else { throw e } } finally { // "touch" every property so they are all tracked as // dependencies for deep watching if (this.deep) { traverse(value) } popTarget() this.cleanupDeps() //清空上一轮的依赖 } return value } /** * Add a dependency to this directive. */ addDep (dep: Dep) { const id = dep.id if (!this.newDepIds.has(id)) { //同一个数据只收集一次 this.newDepIds.add(id) this.newDeps.push(dep) if (!this.depIds.has(id)) { dep.addSub(this) } } } //每轮收集结束后去除掉上轮收集中不需要跟踪的依赖 cleanupDeps () { let i = this.deps.length while (i--) { const dep = this.deps[i] if (!this.newDepIds.has(dep.id)) { dep.removeSub(this) } } let tmp = this.depIds this.depIds = this.newDepIds this.newDepIds = tmp this.newDepIds.clear() tmp = this.deps this.deps = this.newDeps this.newDeps = tmp this.newDeps.length = 0 }, update () { ... //经过一些优化处理后,最终执行this.get this.get(); } // ... }依赖收集的触发是在执行render之前,会创建一个渲染Watcher:
updateComponent = () => { vm._update(vm._render(), hydrating) //执行render生成VNode并更新dom } new Watcher(vm, updateComponent, noop, { before () { if (vm._isMounted) { callHook(vm, 'beforeUpdate') } } }, true /* isRenderWatcher */)在渲染Watcher创建时会将Dep.target指向自身并触发updateComponent也就是执行_render生成VNode并执行_update将VNode渲染成真实DOM,在render过程中会对模板进行编译,此时就会对data进行访问从而触发getter,由于此时Dep.target已经指向了渲染Watcher,接着渲染Watcher会执行自身的addDep,做一些去重判断然后执行dep.addSub(this)将自身push到属性对应的dep.subs中,同一个属性只会被添加一次,表示数据在当前Watcher中被引用。
当_render结束后,会执行popTarget(),将当前Dep.target回退到上一轮的指,最终又回到了null,也就是所有收集已完毕。之后执行cleanupDeps()将上一轮不需要的依赖清除。当数据变化是,触发setter,执行对应Watcher的update属性,去执行get方法又重新将Dep.target指向当前执行的Watcher触发该Watcher的更新。
这里可以看到有deps,newDeps两个依赖表,也就是上一轮的依赖和最新的依赖,这两个依赖表主要是用来做依赖清除的。但在addDep中可以看到if (!this.newDepIds.has(id))已经对收集的依赖进行了唯一性判断,不收集重复的数据依赖。为何又要在cleanupDeps中再作一次判断呢?
while (i--) { const dep = this.deps[i] if (!this.newDepIds.has(dep.id)) { dep.removeSub(this) } } let tmp = this.depIds this.depIds = this.newDepIds this.newDepIds = tmp this.newDepIds.clear() tmp = this.deps this.deps = this.newDeps this.newDeps = tmp this.newDeps.length = 0在cleanupDeps中主要清除上一轮中的依赖在新一轮中没有重新收集的,也就是数据刷新后某些数据不再被渲染出来了,例如:
<body> <div id="app"> <div v-if='flag'> </div> <div v-else> </div> <button @click="msg1 += '1'">change</button> <button @click="flag = !flag">toggle</button> </div> <script type="text/javascript"> var vm = new Vue({ el: '#app', data: { flag: true, msg1: 'msg1', msg2: 'msg2' } }) </script> </body>每次点击change,msg1都会拼接一个1,此时就会触发重新渲染。当我们点击toggle时,由于flag改变,msg1不再被渲染,但当我们点击change时,msg1发生了变化,但却没有触发重新渲染,这就是cleanupDeps起的作用。如果去除掉cleanupDeps这个步骤,只是能防止添加相同的依赖,但是数据每次更新都会触发重新渲染,又去重新收集依赖。这个例子中,toggle后,重新收集的依赖中并没有msg1,因为它不需要被显示,但是由于设置了setter,此时去改变msg1依然会触发setter,如果没有执行cleanupDeps,那么msg1的依赖依然存在依赖表里,又会去触发重新渲染,这是不合理的,所以需要每次依赖收集完毕后清除掉一些不需要的依赖。
总结
依赖收集其实就是收集每个数据被哪些Watcher(渲染Watcher、computedWatcher等)所引用,当这些数据更新时,就去通知依赖它的Watcher去更新。
以上所述是小编给大家介绍的Vue源码学习之双向绑定详解整合,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对网站的支持!
免责声明:本站资源来自互联网收集,仅供用于学习和交流,请遵循相关法律法规,本站一切资源不代表本站立场,如有侵权、后门、不妥请联系本站删除!
《魔兽世界》大逃杀!60人新游玩模式《强袭风暴》3月21日上线
暴雪近日发布了《魔兽世界》10.2.6 更新内容,新游玩模式《强袭风暴》即将于3月21 日在亚服上线,届时玩家将前往阿拉希高地展开一场 60 人大逃杀对战。
艾泽拉斯的冒险者已经征服了艾泽拉斯的大地及遥远的彼岸。他们在对抗世界上最致命的敌人时展现出过人的手腕,并且成功阻止终结宇宙等级的威胁。当他们在为即将于《魔兽世界》资料片《地心之战》中来袭的萨拉塔斯势力做战斗准备时,他们还需要在熟悉的阿拉希高地面对一个全新的敌人──那就是彼此。在《巨龙崛起》10.2.6 更新的《强袭风暴》中,玩家将会进入一个全新的海盗主题大逃杀式限时活动,其中包含极高的风险和史诗级的奖励。
《强袭风暴》不是普通的战场,作为一个独立于主游戏之外的活动,玩家可以用大逃杀的风格来体验《魔兽世界》,不分职业、不分装备(除了你在赛局中捡到的),光是技巧和战略的强弱之分就能决定出谁才是能坚持到最后的赢家。本次活动将会开放单人和双人模式,玩家在加入海盗主题的预赛大厅区域前,可以从强袭风暴角色画面新增好友。游玩游戏将可以累计名望轨迹,《巨龙崛起》和《魔兽世界:巫妖王之怒 经典版》的玩家都可以获得奖励。
更新日志
- 中国武警男声合唱团《辉煌之声1天路》[DTS-WAV分轨]
- 紫薇《旧曲新韵》[320K/MP3][175.29MB]
- 紫薇《旧曲新韵》[FLAC/分轨][550.18MB]
- 周深《反深代词》[先听版][320K/MP3][72.71MB]
- 李佳薇.2024-会发光的【黑籁音乐】【FLAC分轨】
- 后弦.2012-很有爱【天浩盛世】【WAV+CUE】
- 林俊吉.2012-将你惜命命【美华】【WAV+CUE】
- 晓雅《分享》DTS-WAV
- 黑鸭子2008-飞歌[首版][WAV+CUE]
- 黄乙玲1989-水泼落地难收回[日本天龙版][WAV+CUE]
- 周深《反深代词》[先听版][FLAC/分轨][310.97MB]
- 姜育恒1984《什么时候·串起又散落》台湾复刻版[WAV+CUE][1G]
- 那英《如今》引进版[WAV+CUE][1G]
- 蔡幸娟.1991-真的让我爱你吗【飞碟】【WAV+CUE】
- 群星.2024-好团圆电视剧原声带【TME】【FLAC分轨】