一.Watch实现原理

  1. let vm = new Vue({
  2. el: '#app',
  3. data(){
  4. return {name:'zf'}
  5. },
  6. watch:{
  7. name(newValue,oldValue){
  8. console.log(newValue,oldValue);
  9. }
  10. }
  11. });

watch用于监控用户的data变化,数据变化后会触发对应的watch的回调方法

  1. if (opts.watch) {
  2. initWatch(vm,opts.watch);
  3. }

选项中如果有watch则对watch进行初始化

  1. function initWatch(vm, watch) {
  2. for (const key in watch) {
  3. const handler = watch[key];
  4. // 如果结果值是数组循环创建watcher
  5. if (Array.isArray(handler)) {
  6. for (let i = 0; i < handler.length; i++) {
  7. createWatcher(vm,key,handler[i]);
  8. }
  9. }else{
  10. createWatcher(vm,key,handler)
  11. }
  12. }
  13. }
  14. function createWatcher(vm,exprOrFn,handler,options){
  15. // 如果是对象则提取函数 和配置
  16. if(isObject(handler)){
  17. options = handler;
  18. handler = handler.handler;
  19. }
  20. // 如果是字符串就是实例上的函数
  21. if(typeof handler == 'string'){
  22. handler = vm[handler];
  23. }
  24. return vm.$watch(exprOrFn,handler,options);
  25. }

这里涉及了watch的三种写法,1.值是对象、2.值是数组、3.值是字符串 (如果是对象可以传入一些watch参数),最终会调用vm.$watch来实现。

扩展Vue原型上的方法,都通过mixin的方式来进行添加的

  1. stateMixin(Vue);
  2. export function stateMixin(Vue) {
  3. Vue.prototype.$watch = function(exprOrFn, cb, options = {}) {
  4. options.user = true; // 标记为用户watcher
  5. // 核心就是创建个watcher
  6. const watcher = new Watcher(this, exprOrFn, cb, options);
  7. if(options.immediate){
  8. cb.call(vm,watcher.value)
  9. }
  10. }
  11. }
  1. class Watcher {
  2. constructor(vm, exprOrFn, callback, options) {
  3. // ...
  4. this.user = !! options.user
  5. if(typeof exprOrFn === 'function'){
  6. this.getter = exprOrFn;
  7. }else{
  8. this.getter = function (){ // 将表达式转换成函数
  9. let path = exprOrFn.split('.');
  10. let obj = vm;
  11. for(let i = 0; i < path.length;i++){
  12. obj = obj[path[i]];
  13. }
  14. return obj;
  15. }
  16. }
  17. this.value = this.get(); // 将初始值记录到value属性上
  18. }
  19. get() {
  20. pushTarget(this); // 把用户定义的watcher存起来
  21. const value = this.getter.call(this.vm); // 执行函数 (依赖收集)
  22. popTarget(); // 移除watcher
  23. return value;
  24. }
  25. run(){
  26. let value = this.get(); // 获取新值
  27. let oldValue = this.value; // 获取老值
  28. this.value = value;
  29. if(this.user){ // 如果是用户watcher 则调用用户传入的callback
  30. this.callback.call(this.vm,value,oldValue)
  31. }
  32. }
  33. }

还是借助vue响应式原理,默认在取值时将watcher存放到对应属性的dep中,当数据发生变化时通知对应的watcher重新执行

二.Computed实现原理

  1. if (opts.computed) {
  2. initComputed(vm,opts.computed);
  3. }
  1. function initComputed(vm, computed) {
  2. // 存放计算属性的watcher
  3. const watchers = vm._computedWatchers = {};
  4. for (const key in computed) {
  5. const userDef = computed[key];
  6. // 获取get方法
  7. const getter = typeof userDef === 'function' ? userDef : userDef.get;
  8. // 创建计算属性watcher
  9. watchers[key] = new Watcher(vm, userDef, () => {}, { lazy: true });
  10. defineComputed(vm, key, userDef)
  11. }
  12. }

每个计算属性也都是一个watcher,计算属性需要表示lazy:true,这样在初始化watcher时不会立即调用计算属性方法

  1. class Watcher {
  2. constructor(vm, exprOrFn, callback, options) {
  3. this.vm = vm;
  4. this.dirty = this.lazy
  5. // ...
  6. this.value = this.lazy ? undefined : this.get(); // 调用get方法 会让渲染watcher执行
  7. }
  8. }

默认计算属性需要保存一个dirty属性,用来实现缓存功能

  1. function defineComputed(target, key, userDef) {
  2. if (typeof userDef === 'function') {
  3. sharedPropertyDefinition.get = createComputedGetter(key)
  4. } else {
  5. sharedPropertyDefinition.get = createComputedGetter(userDef.get);
  6. sharedPropertyDefinition.set = userDef.set;
  7. }
  8. // 使用defineProperty定义
  9. Object.defineProperty(target, key, sharedPropertyDefinition)
  10. }

创建缓存getter

  1. function createComputedGetter(key) {
  2. return function computedGetter() {
  3. const watcher = this._computedWatchers[key];
  4. if (watcher) {
  5. if (watcher.dirty) { // 如果dirty为true
  6. watcher.evaluate();// 计算出新值,并将dirty 更新为false
  7. }
  8. // 如果依赖的值不发生变化,则返回上次计算的结果
  9. return watcher.value
  10. }
  11. }
  12. }

watcher.evaluate

  1. evaluate() {
  2. this.value = this.get()
  3. this.dirty = false
  4. }
  1. update() {
  2. if (this.lazy) {
  3. this.dirty = true;
  4. } else {
  5. queueWatcher(this);
  6. }
  7. }

当依赖的属性变化时,会通知watcher调用update方法,此时我们将dirty置换为true。这样再取值时会重新进行计算。

  1. if (watcher) {
  2. if (watcher.dirty) {
  3. watcher.evaluate();
  4. }
  5. if (Dep.target) { // 计算属性在模板中使用 则存在Dep.target
  6. watcher.depend()
  7. }
  8. return watcher.value
  9. }
  1. depend() {
  2. let i = this.deps.length
  3. while (i--) {
  4. this.deps[i].depend()
  5. }
  6. }

如果计算属性在模板中使用,就让计算属性中依赖的数据也记录渲染watcher,这样依赖的属性发生变化也可以让视图进行刷新