首页 > 其他分享 >vue2 数据响应式原理模拟继续优化

vue2 数据响应式原理模拟继续优化

时间:2022-12-16 15:12:20浏览次数:43  
标签:依赖 函数 dep dev 响应 vue2 data id 模拟

上次简单的模拟了数据响应式,当我们第一次修改值依赖函数会执行一次,再修改一次值依赖函数会执行两次,这需要做下去重,去掉重复绑定的依赖。采用的方法是给每个dev实例添加一个唯一标识id,通过Set去重。

 代码如下:dep实例添加一个移除方法,和id属性

let uid = 0;
export default class Dep {
  static target = null; // 全局正在执行的函数
  constructor() {
    this.sub = []; // 存储依赖的函数
    this.id = uid++; // 创建标识的唯一ID,去重,避免一个属性会依赖多个相同的函数
  }

  addSub(sub) {
    this.sub.push(sub);
  }
  remove(sub) {
    //移除依赖

    let index = this.sub.indexOf(sub);
    index > -1 ? this.sub.splice(index, 1) : "";
  }
  depend() {
    if (Dep.target) {
      // 委托给 Dep.target 去调用 addSub
      Dep.target.addDep(this);
    }
  }
  notify() {
    // 循环执行依赖的
    this.sub.map((item) => item.update());
  }
}

  watcher 添加addDep方法,判断ID是否存在,存在则不添加

import Dep from "./dep";

export class Watcher {
  constructor(sub) {
    this.getter = sub; // 当前执行的函数
    this.depids = new Set(); // set拥有 has 函数可以判断是否存在某个 id,存储dep的唯一ID
    this.get();
  }
  get() {
    Dep.target = this;
    let value;
    try {
      value = this.getter.call(); // 执行函数
    } catch (e) {
      throw e;
    } 
    return value;
  }
  addDep(dep) {
    //如果当前函数已绑定不在添加
     if (!this.depids.has(dep.id)) {
       this.depids.add(dep.id);
       this.dev.push(dep);
       dep.addSub(this); // 当前正在执行的函数的 Watcher 保存到 dep 中的 subs 中
     }
  }
  // 重置每次新的依赖
  cleanupDeps() {
    //判断旧的dev与最新的dev的依赖是否减少,如果新的减少了,则减少的那的dev实例移除当前函数依赖
    let index = this.oldDev.length;

    while (index--) {
      let id = this.oldDev[index].id;
      if (!this.depids.has(id)) {
        //则移除当前函数依赖
        this.oldDev[index].remove(this);
      }
    }
    // 新的dev 和id 给旧的,新的重置
    this.oldDepids = this.depids;
    this.oldDev = this.dev;
    this.depids.clear();
    this.dev = [];
  }
  // 触发函数方法
  update() {
    this.get();
  }
}

  observer.js 改变了新增依赖的方法

import Dep from "./dep";
function defineReactive(obj, key, val) {
  // 获取当前属性有没有自定义方法;
  let property = Object.getOwnPropertyDescriptor(obj, key);
  // 判断当前属性有没有自定义get set 方法
  let getter = property && property.get;
  let setter = property && property.set;
  // 没有val值,去obj里面的
  if ((!getter || setter) && arguments.length == 2) {
    val = obj[key];
  }

  const dep = new Dep();

  Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    get: function () {
      //添加依赖函数
      if (Dep.target) {
        // dep.addSub(Dep.target);
        dep.depend();
      }
      //如果有自定义方法则调用
      let value = getter ? getter.call(obj) : val;
      return value;
    },
    set: function (newVal) {
      if (setter) {
        setter.call(obj, newVal);
      } else {
        val = newVal;
      }
      //执行依赖当前属性的依赖函数
      dep.notify();
    },
  });
}

function observer(data) {
  let keys = Object.keys(data);
  for (let key of keys) {
    defineReactive(data, key);
  }
}

export { observer };

  这样再重复调用只会执行一次

import { observer } from "./observer";
import { Watcher } from "./watcher";

const data = {
  test: "aa",
  test1: "cc",
};

const updateData = () => {
  console.log(data.test);
};

//Object.defineProperty 劫持数据 将数据变成响应式
observer(data);
// 注入依赖函数
new Watcher(updateData);
data.test = "bb";
data.test = ‘hh’;

 执行下面代码发现test为false后,再修改test1,依赖的函数还是会执行,因为test1这个dev在注入依赖函数时候就存储这个方法,所以我们要做一个依赖的重置更新,每次watcher执行将旧的dev 和新的dev 对比,如果新的减少了,则减少的那个dev调用remove方法移除当前的依赖函数。

import { observer } from "./observer";
import { Watcher } from "./watcher";

const data = {
  test: "aa",
  test1: "cc",
};

const updateData = () => {
  console.log(data.test ? data.test1 : "测试");
};

//Object.defineProperty 劫持数据 将数据变成响应式
observer(data);
// 注入依赖函数
new Watcher(updateData);
data.test = "bb";
data.test = false;
data.test1 = "kkk"; // 上一步已经返回了false,函数还是触发了

watche.jsr修改如下:定义新的和旧的存储dev和dev.id 的属性,dev新增当前依赖函数后,watcher都会新旧属性判断下,watcher如果有不依赖的dev,dev就移除当前的函数。再执行上面的代码,修改test1就不会触发updateData 方法了。

import Dep from "./dep";

export class Watcher {
  constructor(sub) {
    this.getter = sub; // 当前执行的函数
    this.depids = new Set(); // set拥有 has 函数可以判断是否存在某个 id,存储dep的唯一ID
    this.dev = []; // 存储每个函数依赖的dep
    this.oldDepids = new Set();
    this.oldDev = [];

    this.get();
  }
  get() {
    Dep.target = this;
    let value;
    try {
      value = this.getter.call(); // 执行函数
    } catch (e) {
      throw e;
    } finally {
      this.cleanupDeps();
    }
    return value;
  }
  addDep(dep) {
    //如果当前函数已绑定不在添加
    // if (!this.depids.has(dep.id)) {
    //   this.depids.add(dep.id);
    //   this.dev.push(dep);
    //   dep.addSub(this); // 当前正在执行的函数的 Watcher 保存到 dep 中的 subs 中
    // }

    if (!this.depids.has(dep.id)) {
      this.depids.add(dep.id);
      this.dev.push(dep);
      if (!this.oldDepids.has(dep.id)) {
        dep.addSub(this); // 当前正在执行的函数的 Watcher 保存到 dep 中的 subs 中
      }
    }
  }
  // 重置每次新的依赖
  cleanupDeps() {
    //判断旧的dev与最新的dev的依赖是否减少,如果新的减少了,则减少的那的dev实例移除当前函数依赖
    let index = this.oldDev.length;

    while (index--) {
      let id = this.oldDev[index].id;
      if (!this.depids.has(id)) {
        //则移除当前函数依赖
        this.oldDev[index].remove(this);
      }
    }
    // 新的dev 和id 给旧的,新的重置
    this.oldDepids = this.depids;
    this.oldDev = this.dev;
    this.depids.clear();
    this.dev = [];
  }
  // 触发函数方法
  update() {
    this.get();
  }
}

  

 

标签:依赖,函数,dep,dev,响应,vue2,data,id,模拟
From: https://www.cnblogs.com/hardmeng/p/16987408.html

相关文章

  • Qt之模拟键盘按下
     相关资料:https://www.ngui.cc/el/814378.html?action=onClick 一、前言最近在做QWT开发的时候碰到一个问题,QwtPlotZoomer提供的放大、缩小操作只支持鼠标事件或键盘......
  • 能不能手写Vue响应式?前端面试进阶
    Vue视图更新原理Vue的视图更新原理主要涉及的是响应式相关APIObject.defineProperty的使用,它的作用是为对象的某个属性对外提供get、set方法,从而实现外部对该属性的......
  • 「模拟」字符串转化后的各位数字之和(力扣第1945题)
    本题为12月6日力扣每日一题题目来源:力扣第1945题题目tag:模拟题面题目描述给你一个由小写字母组成的字符串s,以及一个整数k。首先,用字母在字母表中的位置替换该字......
  • 拓端tecdat|R语言代写辅导模型中的加总偏误与内生性:一种数值模拟方法
    引言本文中主题是内生性,它可能严重偏向回归估计。我将专门模拟由遗漏变量引起的内生性。在本系列的后续文章中,我将模拟其他规范问题,如异方差性,多重共线性和对撞机偏差。数......
  • 支持模拟量4-20ma的工业路由器应用于智慧城市
    什么是智慧城市?智慧城市包含的内容很多,一般指通过物联网、云计算、大数据、空间地理信息集成等智能计算技术的应用,使得城市管理、教育、医疗、房地产、交通运输、公用事业和......
  • 用JSON-server模拟REST API
    用JSON-server模拟RESTAPI live-server的使用---------------------------------------------生活的意义并不是与他人争高下,而在于享受努力实现目标的过程,结果是对......
  • vue2在scss中使用data的变量
    场景我想给一个css类设置scale,需要根据data的变化来变化。因为使用这个类的元素是个组件库的弹窗,一开始是不存在的,所以不能直接写行内样式。解决方案和示例在data或者co......
  • 活字格调用(6612345网页打印浏览器)打印无响应的解放方法_20221215_112738
    活字格页面标签页内打印无效无响应问题解决方法:测试1:点击打开标签页,点击打印测试按钮,执行无任何响应 命令如下图:测试2.点击打开页面非标签页,点击打印测试执行成功。解决方......
  • swt模拟radiobutton
    1publicclassRadioButtonextendsCanvas{23protectedfinalImageRADIO_CHECKED=createImage("radiobutton_checked.png");4protectedfin......
  • vue2 自定义指令22 directives 简写 全局自定义
      <pv-color="'red'">测试</p>  <button@click="color='green'">改变color的颜色值</button> data(){  return{   color:'blue' ......