前言
Vue区别于传统的JS库,例如JQuery,其中一个最大的特点就是不用手动去操作DOM,只需要对数据进行变更之后,视图也会随之更新。 比如你想修改div#app里的内容:
/// JQuery <div id="app"></div> <script> $('#app').text('lxb') </script>
<template> <div id="app">{{ message }}</div> <button @click="change">点击修改message</button> </template> <script> export default { data () { return { message: 'lxb' } }, methods: { change () { this.message = 'lxb1' // 触发视图更新 } } } </script>
在代码层面上的最大区别就是,JQuery直接对DOM进行了操作,而Vue则对数据进行了操作,接下来我们通过分析源码来进一步分析,Vue是如何做到数据驱动的,而数据驱动主要分成两个部分依赖收集和派发更新。
数据驱动
// _init方法中 initLifecycle(vm) initEvents(vm) initRender(vm) callHook(vm, 'beforeCreate') initInjections(vm) // resolve injections before data/props initState(vm) // 重点分析 initProvide(vm) // resolve provide after data/props callHook(vm, 'created')
在Vue初始化会执行_init方法,并调用initState方法. initState相关代码在src/core/instance/state.js下
export function initState (vm: Component) { vm._watchers = [] const opts = vm.$options if (opts.props) initProps(vm, opts.props) // 初始化Props if (opts.methods) initMethods(vm, opts.methods) // 初始化方法 if (opts.data) { initData(vm) // 初始化data } else { observe(vm._data = {}, true /* asRootData */) } if (opts.computed) initComputed(vm, opts.computed) // 初始化computed if (opts.watch && opts.watch !== nativeWatch) { // 初始化watch initWatch(vm, opts.watch) } }
我们具体看看initData是如何定义的。
function initData (vm: Component) { let data = vm.$options.data data = vm._data = typeof data === 'function' // 把data挂载到了vm._data上 "${key}" has already been defined as a data property.`, vm ) } } if (props && hasOwn(props, key)) { // 避免props的key与data的key重复 process.env.NODE_ENV !== 'production' && warn( `The data property "${key}" is already declared as a prop. ` + `Use prop default value instead.`, vm ) } else if (!isReserved(key)) { // 判断是不是保留字段 proxy(vm, `_data`, key) // 代理 } } // observe data observe(data, true /* asRootData */) // 响应式处理 }
其中有两个重要的函数分别是proxy跟observe,在往下阅读之前,如果还有不明白Object.defineProperty作用的同学,可以点击这里进行了解,依赖收集跟派发更新都需要依靠这个函数进行实现。
proxy
proxy分别传入vm,'_data',data中的key值,定义如下:
const sharedPropertyDefinition = { enumerable: true, configurable: true, get: noop, set: noop } export function proxy (target: Object, sourceKey: string, key: string) { sharedPropertyDefinition.get = function proxyGetter () { return this[sourceKey][key] } sharedPropertyDefinition.set = function proxySetter (val) { this[sourceKey][key] = val } Object.defineProperty(target, key, sharedPropertyDefinition) }
proxy函数的逻辑很简单,就是对vm._data上的数据进行代理,vm._data上保存的就是data数据。通过代理的之后我们就可以直接通过this.xxx访问到data上的数据,实际上访问的就是this._data.xxx。
observe
oberse定义在src/core/oberse/index.js下,关于数据驱动的文件都存放在src/core/observe这个目录中:
export function observe (value: any, asRootData: "htmlcode">export class Observer { value: any; // 观察的数据 dep: Dep; // dep实例用于 派发更新 vmCount: number; // number of vms that have this object as root $data constructor (value: any) { this.value = value this.dep = new Dep() this.vmCount = 0 // 把__ob__变成不可枚举的,因为没有必要改变watcher本身 def(value, '__ob__', this) 会执行 value._ob_ = this(watcher实例)操作 if (Array.isArray(value)) { // 当value是数组 if (hasProto) { protoAugment(value, arrayMethods) // 重写Array.prototype的相关方法 } else { copyAugment(value, arrayMethods, arrayKeys) // 重写Array.prototype的相关方法 } this.observeArray(value) } else { this.walk(value) // 当value为对象 } } /** * Walk through all properties and convert them into * getter/setters. This method should only be called when * value type is Object. */ walk (obj: Object) { const keys = Object.keys(obj) for (let i = 0; i < keys.length; i++) { defineReactive(obj, keys[i]) // 对数据进行响应式处理 } } /** * Observe a list of Array items. */ observeArray (items: Array<any>) { for (let i = 0, l = items.length; i < l; i++) { observe(items[i]) // 遍历value数组的每一项并调用observe函数,进行响应式处理 } } }Observe类要做的事情通过查看源码也是清晰明了,对数据进行响应式处理,并对数组的原型方法进行重写!defineReactive函数就是实现依赖收集和派发更新的核心函数了,实现代码如下。
依赖收集
defineReactive
export function defineReactive ( obj: Object, // data数据 key: string, // data中对应的key值 val: any, // 给data[key] 赋值 可选 customSetter"htmlcode">let uid = 0 /** * A dep is an observable that can have multiple * directives subscribing to it. */ export default class Dep { static target: "htmlcode">Object.defineProperty(obj, key, { enumerable: true, configurable: true, get: function reactiveGetter () { const value = getter "htmlcode">class Watcher { 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) // 会把watcher插入到dep.subs数组中 } } } }可以通过下图以便理解data、Dep、Watcher的关系:
回到代码中,其中dep.addSub(this)就是会把当前的wathcer实例插入到dep.subs的数组中,为之后的派发更新做好准备,这样依赖收集就完成了。但是到现在为止,我们只分析了依赖收集是怎么实现的,但是依赖收集的时机又是在什么时候呢?什么时候会触发getter函数进而实现依赖收集的"htmlcode">
class Wachter { get () { pushTarget(this) // Dep.target = this let value const vm = this.vm try { value = this.getter.call(vm, vm) // 更新视图 } catch (e) { if (this.user) { handleError(e, vm, `getter for watcher "${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 } }new Watcher对于渲染watcher而言,会直接执行this.get()方法,然后执行pushTarget(this),所以当前的Dep.target为渲染watcher(用于更新视图)。 而在我们执行this.getter的时候,会调用render函数,此时会读取vm实例上的data数据,这个时候就触发了getter函数了,从而进行了依赖收集,这就是依赖收集的时机,比如
{{ message }} // 会读取vm._data.message, 触发getters函数派发更新
我们继续来看defineReactive函数里
export function defineReactive ( obj: Object, key: string, val: any, customSetter"text-align: center">总结
- 通过Object.defineProperty函数改写了数据的getter和setter函数,来实现依赖收集和派发更新。
- 一个key值对应一个Dep实例,一个Dep实例可以包含多个Watcher,一个Wathcer也可以包含多个Dep。
- Dep用于依赖的收集与管理,并通知对应的Watcher执行相应的操作。
- 依赖收集的时机是在执行render方法的时候,读取vm上的数据,触发getter函数。而派发更新即在变更数据的时候,触发setter函数,通过dep.notify(),通知到所收集的watcher,执行相应操作。
以上就是详解Vue数据驱动原理的详细内容,更多关于Vue数据驱动原理的资料请关注其它相关文章!
华山资源网 Design By www.eoogi.com广告合作:本站广告合作请联系QQ:858582 申请时备注:广告合作(否则不回)
免责声明:本站资源来自互联网收集,仅供用于学习和交流,请遵循相关法律法规,本站一切资源不代表本站立场,如有侵权、后门、不妥请联系本站删除!华山资源网 Design By www.eoogi.com暂无评论...
《魔兽世界》大逃杀!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分轨】