state.js
給state添加 initComputed 方法
工作流程:
- 計算屬性是有緩存的,我們要創建兩個變量存數據
- 遍歷computed的值,創建帶{dirty:true}的watcher,並保存到watchers對象中
- 當用戶取值的時候,獲取當前watcher運行evalValue()進行運算
- 編寫watcher用dirty保存值是否改變,判斷其是否需要重新計算
- 取出watcher中的dep,觸發depend()添加渲染的watcher
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 createComputedGetter(vm, key) {
let watcher = vm._watcherComputed[key];
return function () {
if (watcher) {
if (watcher.dirty) {
// 頁面取值的時候dirty如果是true 就會調用get方法 計算
watcher.evalValue()
}
if (Dep.target) {
watcher.depend()
}
return watcher.value
}
}
}
function initComputed(vm, computed) {
// 計算屬性是有緩存的
let watchers = vm._watcherComputed = Object.create(null);
for (let key in computed) {
let userDef = computed[key]
watchers[key] = new Watcher(vm, userDef, () => { }, { lazy: true }) // 存watcher
// 當用戶取值的時候,我們將key定義到我們的vm上
Object.defineProperty(vm, key, {
get: createComputedGetter(vm, key)
})
}
}
function createWatch(vm, key, handler) {
return vm.$watch(key, handler)
}
function initWatch(vm) {
let watch = vm.$options.watch
for (let key in watch) {
let handler = watch[key]
createWatch(vm, key, handler)
}
}
watcher
- 編寫 evalValue()
evalValue() {
this.value = this.get()
this.dirty = false; //第二次計算的時候走緩存
}
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