前言
watch
中每个属性都会new
一个用户watcher(new Watcher)
- 在数据初始化得时候 开始
new Watcher
, Dep.target 指向此时的用户watcher, 此时该属性中的加入用户watcherdep.addSub.push(watcher)
- 当data中的数据发生变化时, 调用该数据的所有watcher
- Watcher先将老值存起来 数据发生变化时 将新值与老值 返回给表达式(cb)
html 和 javascript 模板
<div id="app">{{ name }}</div>
<script src="dist/vue.js"></script>
<script>
var vm = new Vue({
data: {
name: 'one'
},
/** 用户watcher 几种方式 */
watch: {
// name(newVal, oldVal) {
// console.log(newVal, oldVal)
// },
// name: [
// function(newVal, oldVal) {
// console.log(newVal, oldVal)
// },
// function(a, b) {
// console.log(a, b)
// }
// ],
// 'obj.n'(newVal, oldVal) {
// console.log(newVal, oldVal)
// }
}
})
vm.$mount('#app')
vm.$watch('name', function(newValue, oldValue) {
console.log(newValue, oldValue)
})
setTimeout(() => {
vm.name = 'two'
}, 2000)
正题
$watch
export function stateMixin(Vue) {
Vue.prototype.$watch = function(key, handler, options={}) {
// 用户创建的watcher
options.user = true
new Watcher(this, key, handler, options)
}
}
options.watch 初始化开始执行
export function initState(vm) {
const opts = vm.$options
if (opts.watch) {
initWatch(vm, opts.watch)
}
}
/** initWatche module */
/**
* @description 调用$watch
*/
function createWatcher(vm, key, handler) {
return vm.$watch(key, handler)
}
/**
* @description 初始化 watch 将观察属性的function拆分出来
* @description 每个观察属性 创建new watcher
*/
function initWatch(vm, watch) {
for (const key in watch) {
const handler = watch[key]
if (Array.isArray(handler)) {
for (let i = 0; i < handler.length; i++) {
createWatcher(vm, key, handler[i])
}
} else {
createWatcher(vm, key, handler)
}
}
}
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
// 看这里 将获取的函数名 封装成表达式
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 默认取得第一次值 */
this.value = this.get()
}
/** render生成vnode时 dep.push(watcher) 并更新视图 */
get() {
pushTarget(this)
// 看这里 在VM上寻找劫持的数据 可将该属性上push用户Watcher
const value = this.getter.call(this.vm)
popTarget()
return value
}
update() {
queueWatcher(this)
}
run() {
/** 考虑1 渲染组件watcher */
/** 考虑2 用户watcher(newValue oldValue) */
let newValue = this.get()
let oldValue = this.value
this.value = newValue
/** 看这里 用户watcher 将值返回给cb, 并调用 */
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)
}
}
}
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
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
完
发表评论
还没有评论,快来抢沙发吧!