计算属性原理
大约 3 分钟约 899 字
// Vue实例化
let vm = new Vue({
el: '#app',
data() {
return {
aa: 1,
bb: 2,
cc: 3,
}
},
// render(h) {
// return h('div',{id:'a'},'hello')
// },
template: `<div id="a">hello 这是我自己写的Vue{{computedName}}{{cc}}</div>`,
computed: {
computedName() {
return this.aa + this.bb
},
},
})
// 当我们每一次改变数据的时候 渲染watcher都会执行一次 这个是影响性能的
setTimeout(() => {
vm.cc = 4
}, 2000)
console.log(vm)
一、计算属性的初始化
// src/state.js
function initComputed(vm) {
let computed = vm.$options.computed
const watchers = (vm._computedWatchers = {}) // 稍后存放计算属性的
for (let key in computed) {
const userDef = computed[key] // 取出对应的值
// 获取get方法
const getter = typeof userDef == 'function' ? userDef : userDef.get // watcher使用
watchers[key] = new Watcher(vm, getter, () => {}, { lazy: true })
definecomputed(vm, key, userDef)
}
}
二、对计算属性进行属性劫持
// src/state.js
const sharePropertyDefinition = {
enumerable: true,
configurable: true,
get: () => {},
set: () => {},
}
function definecomputed(target, key, userDef) {
if (typeof userDef == 'function') {
sharePropertyDefinition.get = createComputedGetter(key)
} else {
sharePropertyDefinition.get = createComputedGetter(key) // 需要加缓存
sharePropertyDefinition.set = userDef.set
}
Object.defineProperty(target, key, sharePropertyDefinition)
}
function createComputedGetter(key) {
// 此方法是我们包装的方法 每次取值会调用此方法
return function computedGetter() {
const watcher = this._computedWatchers[key] //拿到属性对应的watcher
if (watcher) {
// 判断到底要不要执行用户传递的方法
if (watcher.dirty) {
watcher.evaluate() // 对当前的watcher求值
}
if (Dep.target) {
watcher.depend()
}
return watcher.value // 默认返回watcher上
}
}
}
三、修改watcher
import { popTarget, pushTarget } from './dep'
import { queueWatcher } from './scheduler'
let id = 0
class Watcher {
// vm实例
// exprOrFn vm._update(vm._render())
constructor(vm, exprOrFn, cb, options) {
this.vm = vm
this.exprOrFn = exprOrFn
this.cb = cb
this.options = options
this.isWatcher = typeof options == 'boolean' // 是否为渲染watcher
this.user = !!options.user // 是否为用户watcher
this.lazy = !!options.lazy // 如果watcher上有lazy属性 说明是一个计算属性
this.dirty = options.lazy // dirty代表取值时是否执行用户提供的方法
this.id = id++ // watcher的唯一标识
this.deps = [] //记录有多少dep依赖它
this.depsId = new Set()
if (typeof exprOrFn == 'function') {
this.getter = exprOrFn
} else {
// exprOrFn可能传递过来的是一个字符串
this.getter = function () {
// 当去当前实例上取值时,才会出发依赖收集
// age.n vm['age.n'] =》 vm['age']['n']
let path = exprOrFn.split('.') // [age,n]
let obj = vm
for (let i = 0; i < path.length; i++) {
obj = obj[path[i]]
}
return obj
}
}
// 默认会先调用一次get方法,进行取值,将结果保留下来
this.value = this.lazy ? void 0 : this.get() // 默认会调用get方法
}
addDep(dep) {
let id = dep.id
if (!this.depsId.has(id)) {
this.deps.push(dep)
this.depsId.add(id)
dep.addSub(this)
}
}
get() {
pushTarget(this) // 当前watcher实例
let result = this.getter.call(this.vm) // 调用exporOrFn 渲染页面 取值(执行了get方法)
this.getter()
popTarget()
return result
}
run() {
let newValue = this.get()
let oldValue = this.value
this.value = newValue // 更新老值
if (this.user) {
this.cb.call(this.vm, newValue, oldValue)
}
}
update() {
if (this.lazy) {
// 是计算属性
this.dirty = true
} else {
// 这里不要每次都调用get方法 get方法会重新渲染页面
queueWatcher(this)
}
}
evaluate() {
this.dirty = false // 取过一次值之后就表示已经取过值了
this.value = this.get()
}
depend() {
let i = this.deps.length
while (i--) {
this.deps[i].depend() // 让depend存储渲染watcher
}
}
}
实例化的时候如果是计算属性 不会去调用 get 方法访问值进行依赖收集
update 方法只是把计算 watcher 的 dirty 标识为 true 只有当下次访问到了计算属性的时候才会重新计算
新增 evaluate 方法专门用于计算属性重新计算
新增 depend 方法 让计算属性的依赖值收集外层 watcher--这个方法非常重要 我们接下来分析
// src/observer/dep.js
let id = 0
class Dep {
constructor() {
this.subs = [] // 用来存放watcher的
this.id = id++
}
depend() {
Dep.target.addDep(this) // 实现双向记忆,让watcher记住dep的同时,让dep也记住wathcer
}
addSub(watcher) {
this.subs.push(watcher)
}
notify() {
this.subs.forEach((watcher) => watcher.update())
}
}
Dep.target = null
let stack = []
export function pushTarget(watcher) {
Dep.target = watcher
stack.push(watcher)
}
export function popTarget() {
Dep.target = stack[stack.length - 1]
}
export default Dep
源码
https://github.com/luotianxu1/learn/tree/main/vue2/07.computed