<!DOCTYPE html>
<html lang="en">
<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 id="app">
</div>
<script src="vue.js"></script>
<!--<script src="<https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.14/vue.min.js>" integrity="sha512-XdUZ5nrNkVySQBnnM5vzDqHai823Spoq1W3pJoQwomQja+o4Nw0Ew1ppxo5bhF2vMug6sfibhKWcNJsG8Vj9tg==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>-->
<script>
const vm = new Vue({
el: '#app',
data: {
firstName: 'John',
lastName: 'Doe',
},
watch: {
// 第一种写法: 直接写一个函数
firstName: function (newValue, oldValue) { // 观察者
console.log(newValue, oldValue, '函数写法');
},
// 第二种写法:数组写法
// firstName: [
// (newValue, oldValue) => {
// console.log(newValue, oldValue);
// },
// (newValue, oldValue) => {
// console.log(newValue, oldValue);
// }
// ]
}
});
// 第三种写法 vm.$watch
vm.$watch(() => vm.firstName, (newValue, oldValue) => {
console.log(newValue, oldValue, 'vm.$watch + 箭头函数');
});
vm.$watch('firstName', (newValue, oldValue) => {
console.log(newValue, oldValue, 'vm.$watch + 字符串');
});
// 底层最终都调用的 $watch 的写法
// 依赖变化,重新计算
setTimeout(() => {
vm.firstName = 'Larry';
}, 1000);
</script>
</body>
</html>
watch
本质就是实例化一个个 watcher
去监听属性的变化,然后执行用户的回调。
所以 watch
底层也是 watcher
import { observe } from "./observe/index";
import Watcher from "./observe/watcher";
import {watch} from "rollup";
import Dep from "./observe/dep";
export function initState(vm) {
const opts = vm.$options; // 获取所有的选项
if (opts.data) {
initData(vm);
}
if (opts.computed) {
initComputed(vm);
}
if (opts.watch) {
initWatch(vm);
}
}
function initWatch(vm) {
let watch = vm.$options.watch;
for (let key in watch) {
const handler = watch[key]; // 字符串 数组 函数
if (Array.isArray(handler)) {
for (let i = 0, len = handler.length; i < len; i++) {
createWatcher(vm, key, handler[i]);
}
} else {
createWatcher(vm, key, handler);
}
}
}
...
import { initMixin } from "./init";
import { initLifeCycle } from "./lifecycle";
import Watcher, { nextTick } from "./observe/watcher";
import { initGlobalAPI } from "./globalAPI";
function Vue(options) {
this._init(options); // 调用 init 方法
}
// vue 源码不是这么挂载的:
Vue.prototype.$nextTick = nextTick;
initMixin(Vue); // 采用函数的形式,传入 Vue 构造函数,来扩展 Vue.prototype._init 方法
initLifeCycle(Vue);
initGlobalAPI(Vue);
/**
* 底层 $watch
* @param exprOrFn 表达式或者函数
* @param cb 回调
*/
Vue.prototype.$watch = function(exprOrFn, cb) {
// firstname // 可以把字符串形式也处理成函数
// () => vm.firstName
// firstName 的值变化了, 直接执行 cb 函数即可
new Watcher(this, exprOrFn, { user: true }, cb); // 标识用户自己写的 watcher
}
export default Vue;
对 Watcher
进行改造:
import Dep, {popTarget, pushTarget} from "./dep";
let id = 0;
class Watcher {
// 不同组件有不同的watcher 目前只有一个 渲染根实例的
constructor(vm, exprOrFn, options, cb) {
this.id = id++;
this.renderWatcher = options; // options = true 表明 watcher 是一个渲染 watcher
if (typeof exprOrFn === 'string') {
this.getter = function() {
return vm[exprOrFn]; // vm.xxxMethod
}
} else {
this.getter = exprOrFn; // getter意味着调用这个函数可以发生取值操作
}
this.user = options.user; // 标识是否是用户自己的 watcher
// 第一次执行时的老值
this.value = this.lazy ? undefined : this.get();
...
}
...
run() {
let oldValue = this.value;
let newValue = this.get(); // 渲染的时候用的是最新的vm来渲染的
if (this.user) {
this.cb.call(this.vm, newValue, oldValue);
}
}
}
...
export default Watcher;