vue2计算属性依赖收集的问题

computed出现的问题

<div id="app"> {{fullName}} </div>
<script src="https://cdn.bootcdn.net/ajax/libs/vue/2.7.6/vue.js"></
script>
<script>
  let vm = new Vue({
    el: '#app',
    data() {
      return { age: 23, name: '哈哈哈' }
    },
    computed: {
      fullName() {
        return { age: this.age, name: this.name }
      }
    }
  });
  // 将vm.age = 100;和vm.fullName.name = '啊啊啊';上下换位结果不同
  setTimeout(() => { // {age:100,name:'啊啊啊'}
    vm.age = 100;
    vm.fullName.name = '徐志峰';
  }, 1000);
  /* setTimeout(() => { // {age:100,name:'哈哈哈'}
    vm.fullName.name = '啊啊啊';
    vm.age = 100;
  }, 1000); */
</script>

原理

// age收集了计算属性watcher和渲染watcher,在这里已经走了计算属性watcher将dirty的值改为true。
vm.age = 100;
// 1、vm.fullName.name会走get,因为只是对fullName做了defineProperty,而fullName里的值没有做代理,此时fullName的值已经为{age:100,name:'啊啊啊'}了。
// 2、当执行这行,会走computed的get取值,此时dirty已经为true了,会走evaluate,执行this.get(),将this.dirty = false。
vm.fullName.name = '啊啊啊';
// 当更新操作执行完,要走run(),执行get,此时dirty已经是false了,则直接返回计算属性老值{age:100,name:'啊啊啊'}。
// vm.fullName.name会走get(因为只是对fullName做了defineProperty,而fullName里的值没有做代理),此时dirty为false,返回fullName的老值{age:23,name:'哈哈哈'}。
vm.fullName.name = '啊啊啊';
// 虽然fullName={age:23,name:'啊啊啊'},但是age收集了计算属性watcher,和渲染watcher,执行计算属性watcher将dirty改为true,把渲染watcher放入更新队列中。
vm.age = 100;
// 因为是异步渲染,多次改值只执行一次,当更新操作执行完,要走run(),执行get,会去编译模板中取值{{fullName}},会走计算属性的defineProperty的get,此时dirty已经是true了,又会走evaluate(),执行get,会重新执行computed中用户传的函数,在this上取name和age的值,此时age=100,name='哈哈哈',所以fullName={age:100,name:'哈哈哈'}

Vue2依赖收集

Vue初始化会先执行用户传入的options选项,包括data、props、methods、computed、watch等,对于data中的对象类型数据进行defineProperty代理,对于计算属性会new Watcher()。而vue的编译流程是将模板通过正则 => AST抽象语法树再通过字符串拼接 => 渲染函数 => 虚拟DOM => 真实DOM,当执行挂载逻辑时,会new Watcher()默认执行渲染函数去更新DOM。

state.js 初始化options
export function initState(vm) {
  let opts = vm.$options;
  if (opts.data) {
    initData(vm)
  }
  if (opts.computed) {
    initComputed(vm);
  }
}

function proxy(vm, key, value) {
  Object.defineProperty(vm, value, { // vm._data.arr  = vm.arr
    get() {
      return vm[key][value]
    },
    set(newValue) {
      vm[key][value] = newValue;
    }
  })
}

function initData(vm) {
  let data = vm.$options.data;
  data = typeof data == "function" ? data.call(vm) : data;
  vm._data = data;
  for (let key in data) {
    proxy(vm, '_data', key);;
  }
  observe(data);
}

function initComputed(vm) {
  let computed = vm.$options.computed;
  const watchers = vm._computedWatchers = {};

  for (let key in computed) {
    const userDef = computed[key]; 
    const getter = typeof userDef == "function" ? userDef : userDef.get; 
    watchers[key] = new Watcher(vm, getter, () => {}, {
      lazy: true 
    });
    defineComputed(vm, key, userDef);
  }
};

function defineComputed(target, key, userDef) { 
  let setter = userDef.set || (() => {});
  Object.defineProperty(target, key, {
    get: createComputedGetter(key),
    set: setter
  });
}

// 计算属性根本不会收集依赖,只会让自己的依赖属性去收集依赖
function createComputedGetter(key) {
  return function () { 
    const watcher = this._computedWatchers[key]; 
    if (watcher) {
      if (watcher.dirty) {
        watcher.evaluate(); 
      }
      if (Dep.target) { 
        watcher.depend();
      }
      return watcher.value; 
    }
  }
}
watcher.js 依赖收集
let id = 0;
class Watcher {
  constructor(vm, exprOrFn, cb, options={}) {
    this.id = id++;
    this.vm = vm;
    this.exprOrFn = exprOrFn;
    this.deps = [];
    this.depsId = new Set();
    this.cb = cb;
    this.options = options;
    this.lazy = options.lazy; // 如果watcher上有lazy属性 说明是一个计算属性
    this.dirty = this.lazy; // dirty代表取值时是否执行用户提供的方法
    this.getter = exprOrFn;
    this.lazy ? undefined : this.get();
  }
  addDep(dep) {
    let id = dep.id;
    if (!this.depsId.has(id)) {
      this.deps.push(dep);
      this.depsId.add(id);
      dep.addSub(this); // watcher已经记住了dep了而且去重了,此时让dep也记住watcher
    }
  }
  evaluate() {
    this.value = this.get();//获取到用户函数的返回值,并且还要标识为脏
    this.dirty = false; // 取过一次值之后 就标识成已经取过值了
  }
  get() {
    pushTarget(this); // 当前watcher实例
    let result = this.getter.call(this.vm);
    popTarget(); // 渲染完成后 将watcher删掉了
    return result;
  }
  depend(){
    let i=this.deps.length;
    while(i--){
      // dep.depend
       this.deps[i].depend(); // 让dep去存储渲染watcher
    }
 }
  update() {
    if(this.lazy){
      // 如果是计算属性 依赖的值变化了 就标识计算属性是脏值了
      this.dirty=true;
    }else{
      queueWatcher(this);
    }
  }
  run() {
    console.log('执行');
    this.get(); 
  }
}
dep.js
let id = 0;
class Dep {
  constructor() {
    this.id = id++;
    this.subs = [];// 这里存放着当前属性对应的watcher有哪些
  }
  depend() {
    Dep.target.addDep(this);// 让watcher记住dep
  }
  addSub(watcher){
    this.subs.push(watcher);
  }
  notify() {
    this.subs.forEach(watcher => watcher.update())
  }
}

Dep.target = null;

let stack=[];
export function pushTarget(watcher) {
  stack.push(watcher);// 有渲染watcher  其它的watcher
   Dep.target = watcher;// 保留watcher
}

export function popTarget() {
   stack.pop();
   Dep.target =stack[stack.length-1];// 将变量删除掉
}

export default Dep;
lifecycle.js

初始化和更新逻辑

export function mountComponent(vm, el) {
  vm.$el = el;
  callHook(vm,'beforeMount');
  let updateComponent = () => {
    vm._update(vm._render());
  };
  let watcher = new Watcher(vm, updateComponent, () => {
    callHook(vm,'updated');
  });
  callHook(vm,'mounted');
}

export function lifecycleMixin(Vue) {
  Vue.prototype._update = function (vnode) {
    const vm = this;
    // 既有初始化的功能 又有更新
    vm.$el = patch(vm.$el, vnode);
  }
}

export function callHook(vm, hook) {
  const handlers = vm.$options[hook]; // vm.$options.created=[a1,a2,a3]
  if (handlers) {
    for (let i = 0; i < handlers.length; i++) {
      handlers[i].call(vm); // 更改生命周期中的this
    }
  }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值