state.js
在该方法中,保存了vue实例初始的5种属性,我们找到watch属性的位置,编写其方法即可
import { observe } from './observer/index.js'
import Watcher from './watcher.js';
import Dep from './observer/dep.js';
export function initState(vm) {
const opts = vm.$options;
// vue的数据来源 属性 方法 数据 计算属性 watch
if (opts.props) {
initProps(vm);
}
if (opts.methods) {
initMethod(vm);
}
if (opts.data) {
initData(vm);
}
if (opts.computed) {
initComputed(vm, opts.computed);
}
if (opts.watch) {
initWatch(vm);
}
}
function initProps() { }
function initMethod() { }
function proxy(vm, source, key) {
Object.defineProperty(vm, key, { // 给vm添加了监听属性
get() {
return vm[source][key]
},
set(newValue) {
return vm[source][key] = newValue
}
})
}
function initData(vm) {
// 数据初始化工作
let data = vm.$options.data; //用户传递的data
data = vm._data = typeof data === 'function' ? data.call(vm) : data; //如果data是一个函数,就直接执行,并把this指向vue实例,否则就获取data对象
// 对象劫持,用户改变了数据 我希望可以得到通知 => 刷新页面
// MVVM 模式 数据变化驱动视图变化
// Object.defineProperty() 给属性添加get方法和set方法
for (let key in data) {
proxy(vm, "_data", key)
}
observe(data); // 响应式原理
}
function initComputed(vm, computed) {}
function initWatch(vm) {}
$watch
在原型上添加该方法,用于创建watcher
Vue.prototype.$watch = function(key,handler) {
let vm = this
new Watcher(vm,key,handler,{user:true}) // user:true 代表是用户单独调用
}
修改watcher
- 主要是判断 exprOrFn 是否为函数,由于我们传入的是key并非函数,所以我们得编写getValue方法,取出value
- 修改get() 方法,返回一个老值
- 修改run()方法, 判断新值是否相等于老值,如果不相等,就触发cb(用户写在watch里的函数)
import { pushTarget, popTarget } from "./observer/dep";
import { util } from "./utils/index";
let id = 0
class Watcher {
constructor(vm, exprOrFn, cb = () => { }, opts) {
this.vm = vm
this.exprOrFn = exprOrFn
this.cb = cb
this.id = id++
this.deps = []
this.depsId = new Set()
if (opts && opts.lazy) {
this.lazy = opts.lazy
}
this.dirty = this.lazy // 缓存属性
if (typeof exprOrFn === 'function') {
this.getter = exprOrFn
} else {
// 现在 exprOrFn 是我们传进来的key
this.getter = function () {
return util.getValue(vm, exprOrFn)
}
}
this.value = this.lazy ? undefined : this.get() // 获取老值
if (opts && opts.user) {
this.user = true
}
// 如果当前是我们的计算属性的话 不会默认调用get方法
}
evalValue() {
this.value = this.get()
this.dirty = false; //第二次计算的时候走缓存
}
get() {
pushTarget(this)
let value = this.getter.call(this.vm)
popTarget()
return value // 返回老值
}
update() {
if (this.lazy) {
this.dirty = true
} else {
queueWatcher(this)
}
// this.get()
}
run() {
let value = this.get(); //获取新值
if (this.value !== value) {
this.cb(value, this.value)
}
}
addDep(dep) {
let id = dep.id
// 当该watcher没有相同的 dep
if (!this.depsId.has(id)) {
this.depsId.add(id)
this.deps.push(dep)
dep.addSub(this)
}
}
depend() {
let i = this.deps.length
while(i--){
this.deps[i].depend()
}
}
}
let has = {}
let queue = [];
function flusqueue() {
queue.forEach(watcher => watcher.run())
has = {};
queue = []
}
function queueWatcher(watcher) {
let id = watcher.id
if (has[id] == null) {
has[id] = true
queue.push(watcher)
}
nextTick(flusqueue)
}
// 异步执行
let callbacks = [];
function flushCallbacks() {
callbacks.forEach(cb => cb())
}
function nextTick(flusqueue) {
callbacks.push(flusqueue)
let asyncFn = () => {
flushCallbacks()
}
if (Promise) {
Promise.resolve().then(asyncFn)
}
setTimeout(asyncFn, 0)
}
export default Watcher
initWatch()
工作步骤:
- 获取watch属性
- 将watch的值取出
- 给每个值创建对应的watcher