Vue2中Computed简单讲解以及实现
Computed 简单讲解上一回说了Vue的第二种watch也就是$Watch 接下来讲讲第三种 wachter也就是 computed(计算属性)计算属性的特点调用后执行依赖的数据改变会重新计算具有缓存性调用后执行,调用后执行,我们可以想想我们定义data的时候,一样是调用后执行,所以我们联想到老朋友Object.defineProperty(target, key, sharedPropert
Computed 简单讲解
上一回说了Vue的第二种watch也就是$Watch 接下来讲讲第三种 wachter也就是 computed(计算属性)
计算属性的特点
- 调用后执行
- 依赖的数据改变会重新计算
- 具有缓存性
调用后执行,调用后执行,我们可以想想我们定义data的时候,一样是调用后执行,所以我们联想到老朋友
Object.defineProperty(target, key, sharedPropertyDefinition)
而且依赖的数据发生改变,我们可以想象到我们可以在每个依赖的数据的Dep绑定Watcher,然后数据变动时候通过notify不就可以让数据去更新了吗
- 先列出大概的图,然后我们跟着源代码讲解下
- 第一步初始化
initComputed
这个方法,主要是为了定义一个lazy=true
的Watcher
// initComputed
const computedWatcherOptions = { lazy: true } // 注意这个时候 lazy 是为 true 的
function initComputed (vm: Component, computed: Object) {
// 省略部分跟开发环境相关的提示
const watchers = vm._computedWatchers = Object.create(null)
for (const key in computed) {
const userDef = computed[key]
const getter = typeof userDef === 'function' ? userDef : userDef.get
if (!isSSR) {
// 定义computed的Watcher
watchers[key] = new Watcher(
vm,
getter || noop,
noop,
computedWatcherOptions
)
}
if (!(key in vm)) {
defineComputed(vm, key, userDef)
} else if (process.env.NODE_ENV !== 'production') {
// 判断computed的值和data或者props不能一致
if (key in vm.$data) {
warn(`The computed property "${key}" is already defined in data.`, vm)
} else if (vm.$options.props && key in vm.$options.props) {
warn(`The computed property "${key}" is already defined as a prop.`, vm)
}
}
}
}
- 第二步 我们要为这个数据添加
Object.defineProperty()
这个属性
export function defineComputed (
target: any,
key: string,
userDef: Object | Function
) {
// 省略一些服务端渲染以及其他的东西 ...
sharedPropertyDefinition.get = createComputedGetter(key)
sharedPropertyDefinition.set = noop
// 如果试图改变computed的值 直接警告
if (process.env.NODE_ENV !== 'production' &&
sharedPropertyDefinition.set === noop) {
sharedPropertyDefinition.set = function () {
warn(
`Computed property "${key}" was assigned to but it has no setter.`,
this
)
}
}
Object.defineProperty(target, key, sharedPropertyDefinition)
}
// Object.defineProperty() 的 get属性
function createComputedGetter (key) {
return function computedGetter () {
const watcher = this._computedWatchers && this._computedWatchers[key]
if (watcher) {
if (watcher.dirty) {
watcher.evaluate()
}
if (Dep.target) {
watcher.depend()
}
return watcher.value
}
}
}
-
好 准备阶段我们已经完毕,现在是数据改变的阶段
-
数据调用的时候,会执行dep中的
subs[0].notify()
从而执行watcher的run()
-
普通Watcher的
run
主要是调用render
等操作 -
computed的Watcher会因为
lazy=true
导致不进行下一步
update () {
/* istanbul ignore else */
if (this.lazy) {
this.dirty = true
} else if (this.sync) {
this.run()
} else {
queueWatcher(this)
}
}
- 如果调用了
computed
,那么它将执行Watcher.evaluate()
方法
evaluate () {
this.value = this.get()
this.dirty = false
}
this.get()
方法会通过this.getter.call
去调用相关computed定义的方法了- 如果
this.getter.call
如果调用this.xxx
等 data的数据 ,这个时候的Dep.target是computed的Watcher
- 那么 他将再去访问data中的
get()
,然后通过get()
的depend
将data的Dep和computed的Watcher绑定
- 整个流程大致走完了,我们来说说对conputed的一些思考
问题
- 看看下面这段代码,既然
evaluate()
执行的时候会去绑定依赖数据的Watcher
,那我们为什么还有在去wacher.depend()
主动再去绑定一次吗,如果我们删掉这段代码会出现什么情况呢
if (watcher.dirty) {
watcher.evaluate()
}
// 下面这段代码 猜猜这个时候的Dep.target是computed还是data的
if (Dep.target) {
watcher.depend()
}
//watcher.js
我们注释掉代码,随便写点代码
<div id="demo">
{{color}}
<button @click='onclick'>点击我改变数据</button>
</div>
data:{
foo: 1,
foo1:3
},
methods:{
onclick(){
this.foo = 5
}
},
computed:{
color() {
return this.foo + this.foo1
}
},
打开页面并且点击按钮,发color并没有发生改变,这是为什么,其实这dep.target用的其实是data的Wacher,为的就是让data绑定两个Watcher。这里大家可以去打个断点试试
我给出下面两张图,大家可以自己去试试并理解下,过程太复杂,我就不说了
添加了代码
未添加代码
- watch能监听computed属性吗,答案是可以的,前提是依赖的数据发生改变
总结
- computed 执行需要两个条件 (1)数据被调用(触发get) (2)依赖的数据改变(lazy = true) [缓存性,依赖数据发生改变才改变]
- computed 在触发get属性的时候会去绑定他引用data的Dep
- computed和data的Watcher一样,通过
get()
来绑定依赖,$watch的expOrFn则是字符串通过parsePath(expOrFn)
来绑定依赖
简单实现过程
<!DOCTYPE html>
<html lang="ch">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div> o.b <div id='text'></div>
</div>
<div> c <div id='Computedtext1'></div></div>
<div> o.b + c = n<div id="Computedtext"></div>
</div>
<script>
// Watcher
var _this = this
var targetStack = []
class Watcher {
constructor(expOrFn, cb, user, deep, lazy) {
this.newDepIds = new Set()
this.newDeps = []
this.depIds = new Set()
this.getter = expOrFn;
this.lazy ? undefined : this.value = this.get()
this.cb = cb
this.user = user
this.deep = deep
this.lazy = lazy
}
addDep(dep) {
const id = dep.id
if (!this.newDepIds.has(id)) {
// watcher保存和它有关的dep
this.newDepIds.add(id)
this.newDeps.push(dep)
// 反过来
if (!this.depIds.has(id)) {
dep.addSub(this)
}
}
}
get() {
var val = ''
setTarget(this)
val = this.getter.call()
if (this.deep) {
_traverse(val)
}
removeTarget(this)
return val
}
update() {
if (this.lazy) {
this.dirty = true
} else {
this.run()
}
}
run() {
if (this.user) {
const oldValue = this.value
this.value = this.get()
this.cb(this.value, oldValue)
} else {
this.get()
}
}
evaluate() {
this.value = this.get()
this.dirty = false
}
depend() {
let i = this.newDeps.length
while (i--) {
this.newDeps[i].depend()
}
}
}
function $watch(func, data) {
// 调用一下这个方法,已添加这个方法的Watcher
new Watcher(data, func, true, true)
}
function $computed(func, data) {
var computedWatcher = new Watcher(func, null, false, false, true)
Object.defineProperty(_this.data, data, {
set: () => {
throw 'computed的值不能更改'
},
get: () => {
debugger
if (computedWatcher) {
if (computedWatcher.dirty) {
computedWatcher.evaluate()
}
if (Dep.target) {
computedWatcher.depend()
}
return computedWatcher.value
}
}
})
}
//Dep
var uid = 0
class Dep {
constructor() {
this.id = uid++;
this.subs = []
}
addSub(Watcher) {
this.subs.push(Watcher)
}
depend() {
Dep.target.addDep(this)
}
notify() {
for (let i = 0, l = this.subs.length; i < l; i++) {
this.subs[i].update()
}
}
}
function setTarget(Watcher) {
targetStack.push(Watcher)
Dep.target = Watcher
}
function removeTarget(Watcher) {
targetStack.pop()
Dep.target = targetStack[targetStack.length - 1]
}
// Observer
var data = {
o: { b: '1' },
c: '2'
}
function _traverse(val) {
let i, keys
const isA = Array.isArray(val)
if (!(isA || val instanceof Object)) {
return
}
keys = Object.keys(val)
i = keys.length
while (i--) _traverse(val[keys[i]])
}
class Observer {
constructor(value) {
this.value = value
this.dep = new Dep()
if (value instanceof Object) {
this.walk(value);
}
}
observe(value) {
// 初始化时创建一个
let ob;
if (value instanceof Object) {
ob = new Observer(value)
}
return ob
}
defineReactive(obj, key, value) {
const dep = new Dep();
if (arguments.length == 2) {
value = obj[key]
// console.log(value)
}
this.observe(obj[key])
// 递归遍历
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter() {
if (Dep.target) {
dep.depend()
}
// console.log(obj, key, value)
return value
},
set: function reactiveSetter(newVal) {
value = newVal
dep.notify()
}
})
}
walk(obj) {
const keys = Object.keys(obj)
for (let i = 0; i < keys.length; i++) {
this.defineReactive(obj, keys[i])
}
}
}
//模拟Vue执行的过程 暂时用setTimeout函数
setTimeout(() => {
// initData
new Observer(data)
// initWatch
this.$watch((val, oldValue) => {
console.log('发生变化', 'newValue:', val, 'oldValue:', oldValue)
}, () => {
// 这个其实相当于parsePath, parsePath 相当于解析字符串然后调用每个数据的get,这个方法直接去调用get,简化了过程
// 源码在lang.js parsePath()方法
// 这是有个bug,如果没有另提出一份空间,会导致对象新旧值一直一样
return JSON.parse(JSON.stringify(this.data.o))
})
// initComputed 这里n的值为computed的值 之后
this.$computed(() => {
return Number(this.data.o.b) + Number(this.data.c)
}, "n")
new Watcher(() => {
// .. render 渲染
console.log('...', '发生改变继续调用')
document.getElementById('Computedtext').innerText = this.data.n
document.getElementById('Computedtext1').innerText = this.data.c
document.getElementById('text').innerText = this.data.o.b
})
})
setTimeout(() => {
this.data.o.b = 2
this.data.c = 3
}, 5000)
</script>
</body>
</html>
更多推荐
所有评论(0)