vue源碼系列08_computed屬性

vue源碼系列07_computed屬性

state.js

給state添加 initComputed 方法
工作流程:

  1. 計算屬性是有緩存的,我們要創建兩個變量存數據
  2. 遍歷computed的值,創建帶{dirty:true}的watcher,並保存到watchers對象中
  3. 當用戶取值的時候,獲取當前watcher運行evalValue()進行運算
  4. 編寫watcher用dirty保存值是否改變,判斷其是否需要重新計算
  5. 取出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

  1. 編寫 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
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章