前言
本章项目地址- 计算属性默认不执行
Object.defineProperty => getter - 多次取值 如果依赖值不发生变化,就不会重新执行,主要是
计算watcher里dirty dirty = true就是脏的, 取值时需要重新执行, 如果dirty = false就直接去取老值dirty这样也可起到缓存作用, 多次调用只执行一次- 以下面的示例为例
- 当在页面上直接computedName,但computedName不会去依赖
渲染watcher, 因为computed没有Dep, 无法收集 - 当调用computedName时,computedName里的firstName和lastName会收集当前的
计算watcher - 如何让其依赖值(firstName,lastName)也收集
渲染watcher - 在Dep的文件加了个
let stacks = [], 首先页面初次new渲染watcher此时的Dep.target 指向渲染watcher将其push到stracks中 - 当执行到开始调用computedName时 此时的Dep.target 指向
计算watcher也将其push到stracks中 - 调用computedName结束时, 将其popTarget移除, 此时的依赖值(firstName,lastName)只收集了
计算watcher, 但在stacks还有值, 通过watcher中的depend方法,将各个依赖值(firstName,lastName)在收集渲染watcher - 思维导图 以下面示例为模板
示例
<div id="app">{{ computedName }}</div>
var vm = new Vue({
data: {
firstName: 'one',
lastName: 'two'
},
/** computed 几种方式 */
computed: {
// computedName() {
// return this.firstName + '--' + this.lastName
// },
computedName: {
get() {
return this.firstName + '--' + this.lastName
},
set(newValue) {
console.log(newValue);
}
}
}
})
vm.$mount('#app')
setTimeout(() => {
vm.firstName = 'testComputed'
}, 2000)
初始化 和 computed方法(重点)
export function initState(vm) {
const opts = vm.$options
if (opts.computed) {
initComputed(vm, opts.computed)
}
}
/** initComputed module */
/**
* @description 通过computed watcher 里的dirty 判断是不是脏(true) 来取值 从而达到缓存(多次取值只执行一次 dirty = false) 如果依赖值发生变化 dirty = true
* @description 并将所有依赖的watcher 通过watcher中的deps push去
*/
function createComputedGetter(key) {
return function computedGetter() {
// 取值时 this -> vm
let watcher = this._computedWactchers[key]
// 看这里 根据 dirty 判断是否重新求值 实现缓存
if (watcher.dirty) {
watcher.evaluate()
}
// 看这里 Dep.target有值 再次收集
if (Dep.target) {
watcher.depend()
}
return watcher.value
}
}
/**
* @description 将computed上的属性 定义在vm Object.defineProperty -> getter
* @description computed属性 没有Dep
*/
function defineComputed(vm, key, userDef) {
let sharedProperty = {}
if (typeof userDef == 'function') {
sharedProperty.get = createComputedGetter(key)
} else {
sharedProperty.get = createComputedGetter(key)
sharedProperty.set = userDef.set
}
Object.defineProperty(vm, key, sharedProperty)
}
/**
* @description 初始化computed 并创建watcher
*/
function initComputed(vm, computed) {
const watchers = vm._computedWactchers = {}
for (const key in computed) {
const userDef = computed[key]
let getter = typeof userDef == 'function' ? userDef : userDef.get
watchers[key] = new Watcher(vm, getter, ()=>{}, { lazy: true })
// 将key 定义在vm上
defineComputed(vm, key, userDef)
}
}
watcher(重点)
import { pushTarget, popTarget } from './dep'
import { queueWatcher } from './scheduler'
/** 给new Watcher一个id */
let id = 0
/**
* @description 数据劫持时 期望一个属性对应多个watcher 同时一个watcher对应多个属性
*/
class Watcher {
constructor(vm, exprOrFn, cb, options) {
this.vm = vm
this.exprOrFn = exprOrFn
this.cb = cb
this.options = options
this.id = id++
this.deps = []
this.depsId = new Set()
/** 用户watcher */
this.user = !!options.user
/** computed watcher */
this.lazy = !!options.lazy
this.dirty = options.lazy
if (typeof exprOrFn == 'string') {
this.getter = function() {
// vm取值 'obj.n' -> ['obj', 'n'] -> vm['obj']['n']
let path = exprOrFn.split('.')
let obj = vm
for (let i = 0; i < path.length; i++) {
obj = obj[path[i]]
}
return obj
}
} else {
this.getter = exprOrFn
}
/** 用户watcher 默认取得第一次值 */
/** 看这里 computed watcher 默认不执行 */
this.value = this.lazy ? undefined : this.get()
}
/** render生成vnode时 dep.push(watcher) 并更新视图 */
get() {
pushTarget(this)
const value = this.getter.call(this.vm)
popTarget()
return value
}
update() {
// 更新时 将其lazy -> true
if (this.lazy) {
this.dirty = true
} else {
queueWatcher(this)
}
}
run() {
/** 考虑1 渲染组件watcher */
/** 考虑2 用户watcher(newValue oldValue) */
let newValue = this.get()
let oldValue = this.value
this.value = newValue
/** 用户watcher */
if (this.user) {
this.cb.call(this.vm, newValue, oldValue)
}
}
/** 存储dep 并排除生成vnode时多次调用一样属性 只存一个 dep 或 watcher */
/** 其次当属性发生变化时将不再存储dep 和 watcher */
addDep(dep) {
let id = dep.id
if (!this.depsId.has(id)) {
this.depsId.add(id)
this.deps.push(dep)
dep.addSub(this)
}
}
/** 看这里computed watcher */
/** 取值时 */
evaluate() {
this.dirty = false
this.value = this.get()
}
/** 将当前的计算watcher里的所有deps都去依赖渲染watcher */
depend() {
let i = this.deps.length
while(i--) {
this.deps[i].depend()
}
}
}
export default Watcher
Dep
/** 每个劫持的属性 加上唯一的标识 */
let id = 0
/**
* @description 每个劫持的属性 new Dep
*/
class Dep {
constructor() {
this.id = id++
this.subs = []
}
/** dep传给watcher */
depend() {
if (Dep.target) {
Dep.target.addDep(this)
}
}
addSub(watcher) {
this.subs.push(watcher)
}
notify() {
this.subs.forEach(watcher => watcher.update())
}
}
Dep.target = null
/** 看这里 收集watcher */
let stack = []
/** 看这里 指向时收集 */
export function pushTarget(watcher) {
Dep.target = watcher
stack.push(watcher)
}
/** 看这里 结束后删除 */
export function popTarget() {
stack.pop()
Dep.target = stack[stack.length - 1]
}
export default Dep