写点什么

【Vue2.x 源码学习】第二十三篇 - 依赖收集 - 视图更新部分

用户头像
Brave
关注
发布于: 2 小时前
【Vue2.x 源码学习】第二十三篇 - 依赖收集 - 视图更新部分

一,前言

上篇,主要介绍了依赖收集过程中 dep 和 watcher 关联:


利用 js 单线程特性,在 Watcher 类中 get 方法即将触发视图更新前,利用全局的类静态属性 Dep.target 记录 Watcher 实例并且,在视图渲染的取值过程中,在Object.defineProperty的get方法中,让数据的dep记住渲染watcher从而,实现了 dep 与 watcher 相关联,只有参与视图渲染的数据发生变化才会触发视图的更新
复制代码


本篇,继续依赖收集的视图更新部分


二,实现视图更新逻辑

1.查重 watcher

问题:同一数据在视图中多次使用会怎样?


按照当前逻辑,同一数据在一个视图中被多次使用时,相同 watcher 会在 dep 中多次保存


<div id="app">  <li>{{name}}</li>  <li>{{name}}</li>  <li>{{name}}</li></div>
复制代码

这时 name 的 dep 中,会保存三个相同的渲染 watcher


所以,watcher 需要进行查重


同 Dep 类的做法:给 Watcher 添加一个 id,

每次 new Watcher 是 id 自增,以此作为标记对 watcher 实例进行查重


let id = 0;class Watcher {  constructor(vm, fn, cb, options){    this.vm = vm;    this.fn = fn;    this.cb = cb;    this.options = options;    this.id = id++;   // watcher 唯一标记
this.getter = fn; this.get(); } get(){ Dep.target = this; this.getter(); Dep.target = null; }}export default Watcher
复制代码

2. 让 watcher 也记住 dep

前面,让数据的 dep 记住了渲染 watcher 同样的,watcher 也有必要记住 dep


let id = 0;
class Dep { constructor(){ this.id = id++; this.subs = []; } // 让 watcher 记住 dep(查重),再让 dep 记住 watcher depend(){ // 相当于 watcher.addDep:使当前 watcher 记住 dep Dep.target.addDep(this); } // 让 dep 记住 watcher - 在 watcher 中被调用 addSub(watcher){ this.subs.push(watcher); }}
Dep.target = null; // 静态属性,用于记录当前 watcher
export default Dep;
复制代码


为什么要这样实现?


如果要互相记住,watcher 中要对 dep 做查重;dep 中也要对 watcher 做查重;

用这种方法使 dep 和 watcher 关联在一起后,只要判断一次就可以了


import Dep from "./dep";let id = 0;class Watcher {  constructor(vm, fn, cb, options){    this.vm = vm;    this.fn = fn;    this.cb = cb;    this.options = options;        this.id = id++;    this.depsId = new Set();  // 用于当前 watcher 保存 dep 实例的唯一id    this.deps = []; // 用于当前 watcher 保存 dep 实例    this.getter = fn;    this.get();  }  addDep(dep){    let did = dep.id;    // dep 查重     if(!this.depsId.has(did)){      // 让 watcher 记住 dep      this.depsId.add(did);      this.deps.push(dep);      // 让 dep 也记住 watcher      dep.addSub(this);     }  }  get(){    Dep.target = this;    this.getter();    Dep.target = null;  }}
export default Watcher;
复制代码


这种实现方式,会让 dep 和 watcher 保持一种相对的关系:

如果 watcher 中存过 dep;那么 dep 中一定存过 watcher ;

如果 watcher 中没存过 dep;那么 dep 中一定没存过 watcher ;

所以只需要判断一次,就能完成 dep 和 watcher 的查重;


3. 数据改变,触发视图更新


当视图更新时,会进入 Object.defineProperty 的 set 方法

需要在 set 方法中,通知 dep 中所有收集的 watcher,执行视图更新方法


// src/observe/index.js#defineReactive
function defineReactive(obj, key, value) { observe(value); let dep = new Dep(); // 为每个属性添加一个 dep Object.defineProperty(obj, key, { get() { if(Dep.target){ dep.depend(); } return value; }, set(newValue) { if (newValue === value) return observe(newValue); value = newValue; dep.notify(); // 通知当前 dep 中收集的所有 watcher 依次执行视图更新 } })}
复制代码

4. Dep 中添加 notify 方法:

let id = 0;
class Dep { constructor(){ this.id = id++; this.subs = []; } depend(){ Dep.target.addDep(this); } addSub(watcher){ this.subs.push(watcher); } // dep 中收集的全部 watcher 依次执行更新方法 update notify(){ this.subs.forEach(watcher => watcher.update()) }}
Dep.target = null;
export default Dep;
复制代码

5. Watcher 中添加 update 方法:

import Dep from "./dep";let id = 0;class Watcher {  constructor(vm, fn, cb, options){    this.vm = vm;    this.fn = fn;    this.cb = cb;    this.options = options;
this.id = id++; this.depsId = new Set(); this.deps = []; this.getter = fn; this.get(); } addDep(dep){ let did = dep.id; if(!this.depsId.has(did)){ this.depsId.add(did); this.deps.push(dep); dep.addSub(this); } } get(){ Dep.target = this; this.getter(); Dep.target = null; } // 执行视图渲染逻辑 update(){ this.get(); }}export default Watcher;
复制代码

6. 问题

多次频繁更新同一个数据,会使视图频繁进行重新渲染操作


let vm = new Vue({  el: '#app',  data() {    return { name: "Brave" , age: 123}  }}); vm.name = "Brave Wang";vm.name = "Brave";vm.name = "Brave Wang";vm.name = "Brave";vm.name = "Brave Wang";vm.name = "Brave";
复制代码


name 的值变化了 6 次,但最终其实没有变化还是 Brave,

这里就需要改为做异步更新的机制



三,结尾


本篇,介绍了 Vue 依赖收集的视图更新部分,主要涉及以下几点:


视图初始化时:


  • render 方法中会进行取值操作,进入 Object.defineProperty 的 get 方法

  • get 方法中为数据添加 dep,并记录当前的渲染 watcher

  • 记录方式:watcher 查重并记住 dep,dep 再记住 watcher


数据更新时:


  • 当数据发生改,会进入 Object.defineProperty 的 set 方法

  • 在 set 方法中,使 dep 中收集的全部 watcher 执行视图渲染操作 watcher.get()

  • 在视图渲染前(this.getter 方法执行前),通过 dep.target 记录当前的渲染 watcher

  • 重复视图初始化流程


下一篇:Vue 异步更新

用户头像

Brave

关注

还未添加个人签名 2018.12.13 加入

还未添加个人简介

评论

发布
暂无评论
【Vue2.x 源码学习】第二十三篇 - 依赖收集 - 视图更新部分