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-源码剖析-双向绑定》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
JavaScript修炼之道
波顿纽威 / 巩朋、张铁 / 人民邮电 / 2011-11 / 29.00元
《JavaScript修炼之道》是JavaScript的实战秘籍。作者将自己多年的编程经验融入其中,不仅可以作为学习之用,更是日常JavaScript开发中不可多得的参考手册,使读者少走很多弯路。《JavaScript修炼之道》的内容涵盖了当今流行的JavaScript库的运行机制,也提供了许多应用案例。《JavaScript修炼之道》针对各任务采取对页式编排,在对各任务的讲解中,左页解释了任务的......一起来看看 《JavaScript修炼之道》 这本书的介绍吧!