応答を理解する方法
理解することができる。このような状況に関連した状態変化にもすぐつまり、変更される場合には、DOMのステータス変更に関連するデータも正面から変更した後。データモデルは、単なるJavaScriptオブジェクトです。あなたがそれらを変更する場合と、ビューが更新されます。
質問を投げます
当社は、書面で、私たちの共通のVueを見てみましょう:
<div id="app" @click="changeNum">
{{ num }}
</div>
var app = new Vue({
el: '#app',
data: {
num: 1
},
methods: {
changeNum() {
this.num = 2
}
}
})
复制代码
言葉遣いは非常に一般的ですが、あなたがなぜ実装と考えたときに
this.num = 2
後方視界をなぜそれを更新しますか?この記事を通して、私は、この点を明確にするために努力しています。
あなたはVueのを使用しない場合、どのように達成すべき?
私の最初のアイデアは、このような何かを達成することです。
let data = {
num: 1
};
Object.defineProperty(data, 'num',{
value: value,
set: function( newVal ){
document.getElementById('app').value = newVal;
}
});
input.addEventListener('input', function(){
data.num = 2;
});
复制代码
このラフな要素が自動的にビューを更新し、クリックを達成するために。
ここでは、Object.definePropertyして、オブジェクトのアクセサのプロパティを操作する必要があります。操作はDOMに関連したときに、データの変更を監視するために。
パブリッシュ/サブスクライブ・モデル - と、ここでは一般的なモデルを使用しています。
私はオブザーバーモードを示しており、パブリッシュ/サブスクライブ・モデルをするためにラフなフローチャートを描きました。次のように:
慎重な学生は私のラフプロセスと異なる場所がVueのは、自分自身にDOM操作を再レンダリングする必要がある使用していることがわかります。
我々はVueのを使用する場合は、この手順は、Vueのを処理するための内部コードです。これはまた、我々は、手動でのVueのを使用している場合、DOMを操作する必要がない理由です。
Object.defineProperty
私はすでに以前の記事で述べたように、私はここでそれらを繰り返すことはしません。
Vueが応答を達成する方法であります
私たちは、オブジェクトができることを知っているObject.defineProperty
彼らのアクセスオブジェクトが持つプロパティで動作getter
し、setter
メソッドを。これは、応答を達成するための基礎となるものです。
非常に直感的なフロー・チャートを見てください:
initData 方法
Vueの初期化時には、その_init()
方法は、実行呼び出すinitState(vm)
方法を。initState
方法は、主にあるprops
、methods
、data
、computed
およびwathcer
その他の属性は、初期化を行います。
这里我们就对 data
初始化的过程做一个比较详细的分析。
function initData (vm: Component) {
let data = vm.$options.data
data = vm._data = typeof data === 'function'
? getData(data, vm)
: data || {}
if (!isPlainObject(data)) {
......
}
// proxy data on instance
const keys = Object.keys(data)
const props = vm.$options.props
const methods = vm.$options.methods
let i = keys.length
while (i--) {
const key = keys[i]
...... // 省略部分兼容代码,但不影响理解
if (props && hasOwn(props, key)) {
......
} else if (!isReserved(key)) {
proxy(vm, `_data`, key)
}
}
// observe data
observe(data, true /* asRootData */)
}
复制代码
initData
初始化 data 的主要过程也是做两件事:
- 通过
proxy
把每一个值vm._data.[key]
都代理到vm.[key]
上; - 调用
observe
方法观测整个 data 的变化,把 data 也变成响应式(可观察),可以通过vm._data.[key]
访问到定义 data 返回函数中对应的属性。
数据劫持 — Observe
通过这个方法将 data 下面的所有属性变成响应式(可观察)。
// 给对象的属性添加 getter 和 setter,用于依赖收集和发布更新
export class Observer {
value: any;
dep: Dep;
vmCount: number;
constructor (value: any) {
this.value = value
// 实例化 Dep 对象
this.dep = new Dep()
this.vmCount = 0
// 把自身实例添加到数据对象 value 的 __ob__ 属性上
def(value, '__ob__', this)
// value 是否为数组的不同调用
if (Array.isArray(value)) {
const augment = hasProto ? protoAugment : copyAugment
augment(value, arrayMethods, arrayKeys)
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])
}
}
}
复制代码
def
函数内封装了 Object.defineProperty
,所以你 console.log(data) ,会发现多了一个 __ob__
的属性。
defineReactive 方法遍历所有属性
// 定义一个响应式对象的具体实现
export function defineReactive (
obj: Object,
key: string,
val: any,
customSetter?: ?Function,
shallow?: boolean
) {
const dep = new Dep()
..... // 省略部分兼容代码,但不影响理解
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()
if (childOb) {
childOb.dep.depend()
if (Array.isArray(value)) {
dependArray(value)
}
}
}
return value
},
set: function reactiveSetter (newVal) {
const value = getter ? getter.call(obj) : val
..... // 省略部分兼容代码,但不影响理解
if (setter) {
setter.call(obj, newVal)
} else {
val = newVal
}
// 对新的值进行监听
childOb = !shallow && observe(newVal)
// 通知所有订阅者,内部调用 watcher 的 update 方法
dep.notify()
}
})
}
复制代码
defineReactive
方法最开始初始化 Dep 对象的实例,然后通过对子对象递归调用observe
方法,使所有子属性也能变成响应式的对象。并且在 Object.defineProperty
的 getter
和 setter
方法中调用 dep
的相关方法。
即:
getter
方法完成的工作就是依赖收集 ——dep.depend()
setter
方法完成的工作就是发布更新 ——dep.notify()
我们发现这里都和 Dep 对象有着不可忽略的关系。接下来我们就看看 Dep 对象。这个 Dep
调度中心作用的 Dep
前文中我们提到发布/订阅模式,在发布者和订阅者之前有一个调度中心。这里的 Dep 扮演的角色就是调度中心,主要的作用就是:
- 收集订阅者 Watcher 并添加到观察者列表 subs
- 接收发布者的事件
- 通知订阅者目标更新,让订阅者执行自己的 update 方法
详细代码如下:
// Dep 构造函数
export default class Dep {
static target: ?Watcher;
id: number;
subs: Array<Watcher>;
constructor () {
this.id = uid++
this.subs = []
}
// 向 dep 的观察者列表 subs 添加 Watcher
addSub (sub: Watcher) {
this.subs.push(sub)
}
// 从 dep 的观察者列表 subs 移除 Watcher
removeSub (sub: Watcher) {
remove(this.subs, sub)
}
// 进行依赖收集
depend () {
if (Dep.target) {
Dep.target.addDep(this)
}
}
// 通知所有订阅者,内部调用 watcher 的 update 方法
notify () {
const subs = this.subs.slice()
for (let i = 0, l = subs.length; i < l; i++) {
subs[i].update()
}
}
}
// Dep.target 是全局唯一的观察者,因为在任何时候只有一个观察者被处理。
Dep.target = null
// 待处理的观察者队列
const targetStack = []
export function pushTarget (_target: ?Watcher) {
if (Dep.target) targetStack.push(Dep.target)
Dep.target = _target
}
export function popTarget () {
Dep.target = targetStack.pop()
}
复制代码
Dep 可以理解成是对 Watcher
的一种管理,Dep 和 Watcher
是紧密相关的。所以我们必须看一看 Watcher
的实现。
订阅者 —— Watcher
Watcher
中定义了许多原型方法,这里我只粗略的讲 update
和 get
这三个方法。
// 为了方便理解,部分兼容代码已被我省去
get () {
// 设置需要处理的观察者
pushTarget(this)
const vm = this.vm
let value = this.getter.call(vm, vm)
// deep 是否为 true 的处理逻辑
if (this.deep) {
traverse(value)
}
// 将 Dep.target 指向栈顶的观察者,并将他从待处理的观察者队列中移除
popTarget()
// 执行依赖清空动作
this.cleanupDeps()
return value
}
update () {
if (this.computed) {
...
} else if (this.sync) {
// 标记为同步
this.run()
} else {
// 一般都是走这里,即异步批量更新:nextTick
queueWatcher(this)
}
}
复制代码
Vue 的响应式过程大概就是这样了。感兴趣的可以看看源码。
最后我们在通过这个流程图来复习一遍:
如果有想学习web前端的程序员,可加下v❤:TZ07900,免费送web前端的视频教程噢!