Vue背后机制


响应性基础:Proxy

Proxy是ES6中新添加的特性,用于给一个对象创建代理,用来拦截对象的操作。这样可以添加额外的业务逻辑或对原对象进行保护。

拦截方法参考:

image-20220909103853546

image-20220909103911409

示例:

let obj = {
  a: 1,
  b: 2,
};

let objProxy = new Proxy(obj, {
  get(target, key, receiver) {
    console.log(`访问了 ${target} 中的 ${key},值为 ${target[key]}`);
    // return target[key];
    // return 10;
    return Reflect.get(target, key, receiver); // 调用Reflect.get()直接返回原对象的属性值
  },
  set(target, key, value, receiver) {
    console.log(`修改了 ${target} 中的 ${key},值为 ${value}`);
    // target[key] = value;
    return Reflect.set(target, key, value, receiver); // 如果不调用Reflect.set()则原对象的值不会发生变化,可用来验证修改是否合法,如果合法返回Reflect.get(),否则提示错误
  },
});

// console.log(objProxy.a);

objProxy.a = 5;
// 访问 proxy
console.log(objProxy.a);
// 访问原始对象
console.log(obj.a);

实现一个响应性示例(简化版):

目标:在修改对象的属性后,自动更新原对象的属性。

基础原理:

let obj = {
  a: 1,
  b: 2,
};

let reactiveObj = new Proxy(obj, {
  set(target, key, value, receiver) {
    let result = Reflect.set(target, key, value, receiver);
    sum();
    return result;
  },
});

function sum() {
  console.log(reactiveObj.a + reactiveObj.b);
}

sum();

setTimeout(() => {
  reactiveObj.a = 5;
}, 1000);

setTimeout(() => {
  reactiveObj.b = 10;
}, 2000);

封装一部分后:

const observers = new WeakMap();

let currentObserver = null;

function observe(fn) {
  currentObserver = fn;
  fn();
  currentObserver = null;
}

let obj = {
  a: 1,
  b: 2,
};
// observe
let reactiveObj = new Proxy(obj, {
  get(target, key, receiver) {
    if (currentObserver) {
      let targetObserver = observers.get(target);
      if (targetObserver.has(key)) {
        targetObserver.get(key).add(currentObserver);
      } else {
        targetObserver.set(key, new Set([currentObserver]));
      }
    }
    return Reflect.get(target, key, receiver);
  },
  set(target, key, value, receiver) {
    let observersForkey = observers.get(target).get(key);
    let result = Reflect.set(target, key, value, receiver);
    observersForkey.forEach((fn) => fn());
    return result;
  },
});

observers.set(obj, new Map());

function sum() {
  console.log(reactiveObj.a + reactiveObj.b);
}

observe(sum);

setTimeout(() => {
  reactiveObj.a = 5;
}, 1000);

setTimeout(() => {
  reactiveObj.b = 10;
}, 2000);

最后完整封装:

const observers = new WeakMap();

let currentObserver = null;

function observe(fn) {
  currentObserver = fn;
  fn();
  currentObserver = null;
}

function reactive(obj) {
  observers.set(obj, new Map());
  return new Proxy(obj, {
    get(target, key, receiver) {
      registerObserver(target, key);
      return Reflect.get(target, key, receiver);
    },
    set(target, key, value, receiver) {
      const result = Reflect.set(target, key, value, receiver);
      execute(target, key);
      return result;
    },
  });
}

function registerObserver(target, key) {
  if (currentObserver) {
    let targetObserver = observers.get(target);
    if (targetObserver.has(key)) {
      targetObserver.get(key).add(currentObserver);
    } else {
      targetObserver.set(key, new Set([currentObserver]));
    }
  }
}

function execute(target, key) {
  let observersForkey = observers.get(target).get(key);
  observersForkey.forEach((fn) => fn());
}

let obj = {
  a: 1,
  b: 2,
};

let reactiveObj = reactive(obj);

function sum() {
  console.log(reactiveObj.a + reactiveObj.b);
}

function sub() {
  console.log(reactiveObj.a - reactiveObj.b);
}

// 类似于调用 computed()
observe(sum);
observe(sub);

setTimeout(() => {
  reactiveObj.a = 5;
}, 1000);

setTimeout(() => {
  reactiveObj.b = 10;
}, 2000);

Virtual DOM:Vue 渲染机制的核心

因为使用Vue编写的程序,经常因为响应性的变化而重新渲染视图,如果直接操作真实的DOM,会是比较耗时的操作。这是因为需要找到某个节点,并将其换为新的节点,这样会触发浏览器的重新布局和重绘流程,对性能会有影响。

VirtualDOM的结构:

image-20220909111405303

Vue有渲染工具,会将虚拟的DOM转换为真实的DOM,在节点变化后,会根据算法调整节点顺序或更换节点。这种比对算法Vue中称为Patch,其他框架有称为diff或reconciliation。

对于静态的DOM元素,Vue会保存这些节点,后续使用时会直接使用之前的节点;

对于响应性的节点,Vue会对包含这些节点的父节点维护一个记录,包含响应性数据的节点在更新时直接更新记录中的节点。


文章作者: QT-7274
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 QT-7274 !
评论
  目录