侦听器属性原理
大约 3 分钟约 885 字
// Vue实例化
let vm = new Vue({
el: '#app',
data() {
return {
aa: 1,
bb: 2,
}
},
template: `<div id="a">hello 这是我自己写的Vue{{name}}</div>`,
methods: {
doSomething() {},
},
watch: {
aa(newVal, oldVal) {
console.log(newVal)
},
// aa: {
// handle(newVal, oldVal) {
// console.log(newVal);
// },
// deep: true
// },
// aa: 'doSomething',
// aa: [{
// handle(newVal, oldVal) {
// console.log(newVal);
// },
// deep: true
// }]
},
})
setTimeout(() => {
vm.aa = 1111
}, 1000)
一、侦听属性初始化
// src/state.js
// 统一初始化数据的方法
export function initState(vm) {
// 获取传入的数据对象
const opts = vm.$options
if (opts.watch) {
//侦听属性初始化
initWatch(vm)
}
}
// 初始化watch
function initWatch(vm) {
let watch = vm.$options.watch
for (let k in watch) {
const handler = watch[k] //用户自定义watch的写法可能是数组 对象 函数 字符串
if (Array.isArray(handler)) {
// 如果是数组就遍历进行创建
handler.forEach((handle) => {
createWatcher(vm, k, handle)
})
} else {
createWatcher(vm, k, handler)
}
}
}
// 创建watcher的核心
function createWatcher(vm, exprOrFn, handler, options = {}) {
if (typeof handler === 'object') {
options = handler //保存用户传入的对象
handler = handler.handler //这个代表真正用户传入的函数
}
if (typeof handler === 'string') {
// 代表传入的是定义好的methods方法
handler = vm[handler]
}
// 调用vm.$watch创建用户watcher
return vm.$watch(exprOrFn, handler, options)
}
二、$watch
// src/state.js
import Watcher from './observer/watcher'
export function stateMixin(Vue) {
Vue.prototype.$nextTick = function (cb) {
nextTick(cb)
}
Vue.prototype.$watch = function (exprOrFn, cb, options = {}) {
// 数据应该依赖这个watcher 数据变化后应该让watcher从新执行
// vm,name,用户回调,options.user
new Watcher(this, exprOrFn, cb, { ...options, user: true })
if (options.immediate) {
cb() // 如果是immediate应该立刻执行
}
}
}
// src/index.js
import { initGlobalApi } from './global-api/index'
import { initMixin } from './init'
import { lifecycleMixin } from './lifecycle'
import { stateMixin } from './state'
import { renderMixin } from './vdom/index'
function Vue(options) {
this._init(options) // 入口方法,做初始化操作
}
// 写成一个个的插件进行对原型的扩展
initMixin(Vue)
// 混合生命周期 组件挂载、组件更新
lifecycleMixin(Vue)
// _render
renderMixin(Vue)
stateMixin(Vue)
initGlobalApi(Vue)
export default Vue
三、修改 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.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.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() // 调用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() {
// 这里不要每次都调用get方法 get方法会重新渲染页面
queueWatcher(this)
}
}
export default Watcher
// ./observer/scheduler.js
import { nextTick } from '../utils'
let queue = [] // 将需要批量更新的watcher存到一个队列中,稍后让watcher执行
let has = {}
let pending = false
function flushSchedulerQueue() {
queue.forEach((watcher) => {
watcher.run()
if (watcher.isWatcher) {
watcher.cb()
}
})
queue = [] // 清空watcher队列
has = {} // 清空标识id
pending = false
}
export function queueWatcher(watcher) {
const id = watcher.id
if (has[id] == null) {
queue.push(watcher) // 将watcher存到队列中
has[id] = true
if (!pending) {
//如果还没清空队列,就不要再开定时器
// 等待所有同步代码执行完毕后再执行
nextTick(flushSchedulerQueue)
pending = true
}
}
}