state.js
在該方法中,保存了vue實例初始的5種屬性,我們找到watch屬性的位置,編寫其方法即可
import { observe } from './observer/index.js'
import Watcher from './watcher.js';
import Dep from './observer/dep.js';
export function initState(vm) {
const opts = vm.$options;
// vue的數據來源 屬性 方法 數據 計算屬性 watch
if (opts.props) {
initProps(vm);
}
if (opts.methods) {
initMethod(vm);
}
if (opts.data) {
initData(vm);
}
if (opts.computed) {
initComputed(vm, opts.computed);
}
if (opts.watch) {
initWatch(vm);
}
}
function initProps() { }
function initMethod() { }
function proxy(vm, source, key) {
Object.defineProperty(vm, key, { // 給vm添加了監聽屬性
get() {
return vm[source][key]
},
set(newValue) {
return vm[source][key] = newValue
}
})
}
function initData(vm) {
// 數據初始化工作
let data = vm.$options.data; //用戶傳遞的data
data = vm._data = typeof data === 'function' ? data.call(vm) : data; //如果data是一個函數,就直接執行,並把this指向vue實例,否則就獲取data對象
// 對象劫持,用戶改變了數據 我希望可以得到通知 => 刷新頁面
// MVVM 模式 數據變化驅動視圖變化
// Object.defineProperty() 給屬性添加get方法和set方法
for (let key in data) {
proxy(vm, "_data", key)
}
observe(data); // 響應式原理
}
function initComputed(vm, computed) {}
function initWatch(vm) {}
$watch
在原型上添加該方法,用於創建watcher
Vue.prototype.$watch = function(key,handler) {
let vm = this
new Watcher(vm,key,handler,{user:true}) // user:true 代表是用戶單獨調用
}
修改watcher
- 主要是判斷 exprOrFn 是否爲函數,由於我們傳入的是key並非函數,所以我們得編寫getValue方法,取出value
- 修改get() 方法,返回一個老值
- 修改run()方法, 判斷新值是否相等於老值,如果不相等,就觸發cb(用戶寫在watch裏的函數)
import { pushTarget, popTarget } from "./observer/dep";
import { util } from "./utils/index";
let id = 0
class Watcher {
constructor(vm, exprOrFn, cb = () => { }, opts) {
this.vm = vm
this.exprOrFn = exprOrFn
this.cb = cb
this.id = id++
this.deps = []
this.depsId = new Set()
if (opts && opts.lazy) {
this.lazy = opts.lazy
}
this.dirty = this.lazy // 緩存屬性
if (typeof exprOrFn === 'function') {
this.getter = exprOrFn
} else {
// 現在 exprOrFn 是我們傳進來的key
this.getter = function () {
return util.getValue(vm, exprOrFn)
}
}
this.value = this.lazy ? undefined : this.get() // 獲取老值
if (opts && opts.user) {
this.user = true
}
// 如果當前是我們的計算屬性的話 不會默認調用get方法
}
evalValue() {
this.value = this.get()
this.dirty = false; //第二次計算的時候走緩存
}
get() {
pushTarget(this)
let value = this.getter.call(this.vm)
popTarget()
return value // 返回老值
}
update() {
if (this.lazy) {
this.dirty = true
} else {
queueWatcher(this)
}
// this.get()
}
run() {
let value = this.get(); //獲取新值
if (this.value !== value) {
this.cb(value, this.value)
}
}
addDep(dep) {
let id = dep.id
// 當該watcher沒有相同的 dep
if (!this.depsId.has(id)) {
this.depsId.add(id)
this.deps.push(dep)
dep.addSub(this)
}
}
depend() {
let i = this.deps.length
while(i--){
this.deps[i].depend()
}
}
}
let has = {}
let queue = [];
function flusqueue() {
queue.forEach(watcher => watcher.run())
has = {};
queue = []
}
function queueWatcher(watcher) {
let id = watcher.id
if (has[id] == null) {
has[id] = true
queue.push(watcher)
}
nextTick(flusqueue)
}
// 異步執行
let callbacks = [];
function flushCallbacks() {
callbacks.forEach(cb => cb())
}
function nextTick(flusqueue) {
callbacks.push(flusqueue)
let asyncFn = () => {
flushCallbacks()
}
if (Promise) {
Promise.resolve().then(asyncFn)
}
setTimeout(asyncFn, 0)
}
export default Watcher
initWatch()
工作步驟:
- 獲取watch屬性
- 將watch的值取出
- 給每個值創建對應的watcher