vue-源码剖析-双向绑定
栏目: JavaScript · 发布时间: 6年前
内容简介:拉到vue的代码之后,首先来看一下项目目录,因为本文讲的是双向绑定,所以这里主要看双向绑定这块的代码。从入口开始:
拉到vue的代码之后,首先来看一下项目目录,因为本文讲的是双向绑定,所以这里主要看双向绑定这块的代码。
入口
从入口开始: src/core/index.js
index.js
比较简单,第一句就引用了 Vue
进来,看下 Vue
是啥
import Vue from './instance/index' 复制代码
Vue构造函数
来到 src/core/instance/index.js
一进来,嗯,没错,定义了 Vue
构造函数,然后调用了好几个方法,这里我们看第一个 initMixin
import { initMixin } from './init' function Vue (options) { if (process.env.NODE_ENV !== 'production' && !(this instanceof Vue) ) { warn('Vue is a constructor and should be called with the `new` keyword') } this._init(options) } initMixin(Vue) ... export default Vue 复制代码
初始化
来到 src/core/instance/init.js
这个给 Vue
构造函数定义了 _init
方法,每次 new Vue
初始化实例时都会调用该方法。
然后看到 _init
中间的代码,调用了好多初始化的函数,这里我们只关注 data
的走向,所以这里看一下 initState
export function initMixin (Vue: Class<Component>) { Vue.prototype._init = function (options?: Object) { const vm: Component = this ... initState(vm) ... } } 复制代码
来到 src/core/instance/state.js
initState
调用了 initData
, initData
调用了 observe
,然后我们再往下找 observe
import { observe } from '../observer/index' export function initState (vm: Component) { vm._watchers = [] const opts = vm.$options ... if (opts.data) { initData(vm) } else { observe(vm._data = {}, true /* asRootData */) } ... } function initData (vm: Component) { let data = vm.$options.data ... observe(data, true /* asRootData */) } 复制代码
Observer(观察者)
来到 src/core/observer/index.js
这里,实例化 Observer
对象
首先, new Observer
实例化一个对象,参数为 data
export function observe (value: any, asRootData: ?boolean): Observer | void { let ob: Observer | void ob = new Observer(value) return ob } 复制代码
然后我们来看下 Observer
构造函数里面写了什么,这里给每个对象加了 value
和实例化了一个 Dep
,然后 data
为数组的话则递归,否则执行 walk
。
walk
这里是对对象遍历执行 defineReactive
export class Observer { value: any; dep: Dep; vmCount: number; // number of vms that has this object as root $data constructor (value: any) { this.value = value this.dep = new Dep() if (Array.isArray(value)) { ... this.observeArray(value) } else { this.walk(value) } } walk (obj: Object) { const keys = Object.keys(obj) for (let i = 0; i < keys.length; i++) { defineReactive(obj, keys[i]) } } observeArray (items: Array<any>) { for (let i = 0, l = items.length; i < l; i++) { observe(items[i]) } } } 复制代码
然后,我们来看 defineReactive
做了什么,嗯,这里就是 Observer
的核心。
用 Object.defineProperty
对对象进行配置,重写 get&set
get
:对原来 get
执行,然后执行 dep.depend
添加一个订阅者
set
:对原来 set
执行,然后执行 dep.notify
通知订阅者
Dep
是干啥的呢? Dep
其实是一个订阅者的管理中心,管理着所有的订阅者
import Dep from './dep' export function defineReactive ( obj: Object, key: string, val: any, customSetter?: ?Function, shallow?: boolean ) { const dep = new Dep() const property = Object.getOwnPropertyDescriptor(obj, key) const getter = property && property.get const setter = property && property.set let childOb = !shallow && observe(val) Object.defineProperty(obj, key, { enumerable: true, configurable: true, get: function reactiveGetter () { const value = getter ? getter.call(obj) : val if (Dep.target) { dep.depend() } return value }, set: function reactiveSetter (newVal) { const value = getter ? getter.call(obj) : val if (setter) { setter.call(obj, newVal) } else { val = newVal } dep.notify() } }) } 复制代码
Dep(订阅者管理中心)
那么,到这里了,疑问的是什么时候会触发 Observer
的 get
方法来添加一个订阅者呢?
这里的条件是有 Dep.target
的时候,那么我们找一下代码中哪里会对 Dep.target
赋值,找到了 Dep
定义的地方
来到 src/core/observer/dep.js
pushTarget
就对 Dep.target
赋值了,然后来看一下到底是哪里调用了 pushTarget
export default class Dep { ... } Dep.target = null const targetStack = [] export function pushTarget (_target: ?Watcher) { if (Dep.target) targetStack.push(Dep.target) Dep.target = _target } 复制代码
然后,找到了 Watcher
调用了 pushTarget
,那么我们来看一下 Watcher
的实现
来到 src/core/observer/watcher.js
这里可以看到每次 new Watcher
时,就会调用 get
方法
这里执行两步操作
第一:调用 pushTarget
第二:调用 getter
方法触发 Observer
的 get
方法将自己加入订阅者
export default class Watcher { vm: Component; constructor ( vm: Component ) { this.value = this.lazy ? undefined : this.get() } get () { pushTarget(this) let value const vm = this.vm try { value = this.getter.call(vm, vm) } return value } } 复制代码
接着, Dep.target
有了之后,接下来就要看一下 dep.depend()
这个方法,所以还是要到 Dep
来看下这里的实现。 来到 src/core/observer/dep.js
这里调用了 Dep.target.addDep
的方法,参数是 Dep
的实例对象,那么我们看下 addDep
export default class Dep { addSub (sub: Watcher) { this.subs.push(sub) } depend () { if (Dep.target) { Dep.target.addDep(this) } } } 复制代码
Watcher(观察者)
又来到 src/core/observer/watcher.js
到这, addDep
其实又调用时 Dep
实例的 addSub
方法,参数也是把 Watcher
实例传递过去
然后,我们看上面的 addSub
,这里就是把 Watcher
实例 push
到 dep
的 subs
数组中保存起来
到这里,就完成了把 Watcher
加入到 Dep
这里订阅器管理中心这里,后面的管理就由 Dep
来统一管理
export default 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) } } } } 复制代码
走完了添加订阅器,接着再来看下 Observer
的 set
方法,这里调用了 dep.notify
,我们来看一下这个方法
来到 src/core/observer/dep.js
这里,就是对 subs
中的所有 Watcher
,调用其 update
方法来更新数据
export default class Dep { notify () { const subs = this.subs.slice() for (let i = 0, l = subs.length; i < l; i++) { subs[i].update() } } } 复制代码
这里,我们就来看看 Watcher
是怎么更新的 又来到 src/core/observer/watcher.js
update
调用的是 run
方法, run
方法这里先用 get
拿到新的值,然后把新&旧值做为参数给 cb
调用
export default class Watcher { update () { this.run() } run () { if (this.active) { const value = this.get() const oldValue = this.value this.value = value this.cb.call(this.vm, value, oldValue) } } } 复制代码
这里的 cb
其实是实例化的时候传进来的,这里我们看一下什么时候会实例化 Watcher
回到一开始的 initState:src/core/instance/state.js
initState
的最后还调用了 initWatch
,然后再 createWatcher
,最后 $watch
的时候就实例化了 Watcher
对象,这里就把 cb
传到了 Watcher
实例中,当监听的数据改变的时候就会触发 cb
函数
import Watcher from '../observer/watcher' export function initState (vm: Component) { ... if (opts.watch && opts.watch !== nativeWatch) { initWatch(vm, opts.watch) } } function initWatch (vm: Component, watch: Object) { for (const key in watch) { const handler = watch[key] createWatcher(vm, key, handler) } } function createWatcher ( vm: Component, expOrFn: string | Function, handler: any, options?: Object ) { return vm.$watch(expOrFn, handler, options) } Vue.prototype.$watch = function ( expOrFn: string | Function, cb: any, options?: Object ): Function { const vm: Component = this const watcher = new Watcher(vm, expOrFn, cb, options) } 复制代码
写在最后
这里的思路,其实就是翻着源码走的,写的时候都是按自己的理解思路来的,存在问题的话欢迎指出~
以上所述就是小编给大家介绍的《vue-源码剖析-双向绑定》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Pro CSS and HTML Design Patterns
Michael Bowers / Apress / April 23, 2007 / $44.99
Design patterns have been used with great success in software programming. They improve productivity, creativity, and efficiency in web design and development, and they reduce code bloat and complexit......一起来看看 《Pro CSS and HTML Design Patterns》 这本书的介绍吧!