什么是响应式

用 Vue 官方文档的一句话介绍响应性:响应性是一种允许我们以声明式的方式去适应变化的编程范例。

如果我们的一个数据改变了,Vue 知道怎么去更新模板以及会更新模板的计算机属性。响应性是 Vue 用来实现UI的核心原理,在用户修改数据的时候,UI会自动更新。

响应式实现过程

  • 了解Proxy 原理及有哪些方法, 了解 Reflect 对象
  • 了解 WeakMap、Map、Set函数
  • 实现响应式

先来看一下 Proxy

Proxy是用于创建一个对象的代理,从而实现基本操作的拦截和自定义。

注意: 不是数据双向绑定,(数据双向绑定是model层和view层之间的关系)
个人理解proxy 应该是一个包装类,就是包装对象的一个方法,其实本质还是这个对象,包装完增加了一些功能而已

写法:

// target: 被 Proxy 代理的对象
// handler:  处理器的对象,设置拦截等基本操作
const p = new Proxy(target, handler)

介绍各个方法

get 方法: 用于拦截对象的读取操作 第三个参数receiver是最初被调用的对象

  var original = {
    a: 1,
    b: 2
  };
  var p = new Proxy(original, {
    get: function (target, property, receiver) {
      delete target.b;
      console.log(target, property, receiver);
      return target.a
    }
  });
  console.log(p.a);
  console.log(p);
  console.log(original);

set 方法: 用于拦截设置属性值的操作

  const monster1 = {eyeCount: 4, abc: 90};
  const handler1 = {
    set(obj, prop, value) {
      console.log(obj, prop, value, ' --------- 参数值');
      if (prop === 'eyeCount' && value % 2 !== 0) {
        console.log('Monsters must have an even number of eyes');
      } else {
        return Reflect.set(...arguments);
      }
    }
  };
  const proxy1 = new Proxy(monster1, handler1);

  proxy1.eyeCount = 1;
  console.log(proxy1.eyeCount);
  proxy1.eyeCount = 2;
  console.log(proxy1.eyeCount);

deleteProperty 方法: 用于拦截对对象属性的 delete 操作。

var p = new Proxy({}, {
      deleteProperty: function(target, prop) {
	      console.log('called: ' + prop);
	      return true;
	   }
 });
  delete p.a; // "called: a"

applay 方法: 用于拦截函数的调用

function sum(a, b) {
    return a + b;
  }
  const handler = {
    apply: function (target, thisArg, argumentsList) {
      console.log(`${argumentsList}`);
      return target(argumentsList[0], argumentsList[1]) * 10;
    }
  };
  const proxy1 = new Proxy(sum, handler);
  console.log(sum(1, 2));
  console.log(proxy1(1, 2));

construct 方法: 用于拦截new 操作符

function monster1(disposition) {
    this.disposition = disposition;
  }
  // const monster = new monster1();  提前 new 无法触发 construct
  const handler1 = {
    construct(target, args) {
      return  new target(...args);
    }
  };
  const proxy1 = new Proxy(monster1, handler1);
  console.log(new proxy1('fierce').disposition);

getPrototypeOf 方法: 当读取代理对象的原型时,该方法就会被调用

 const monster1 = {
    eyeCount: 4
  };
  const monsterPrototype = {
    eyeCount: 2
  };
  const handler = {
    getPrototypeOf(target) {
      return monsterPrototype;
    }
  };
  const proxy1 = new Proxy(monster1, handler);
  console.log(Object.getPrototypeOf(proxy1) === monsterPrototype);
  console.log(Object.getPrototypeOf(proxy1).eyeCount);

has 方法: 针对 in 操作符的代理方法

  const handler1 = {
    has(target, key) {
      console.log(target, key);
      if (key[0] === '_') { // 字符串可以通过这种方法获取到第一个字符
        return false;
      }
      return key in target;
    }
  };
  const monster1 = {
    _secret: 'easily scared',
    eyeCount: 4
  };
  const proxy1 = new Proxy(monster1, handler1);
  console.log('eyeCount' in proxy1);
  console.log('_secret' in proxy1);
  console.log('_secret' in monster1);

细节:

  1. target不能是基础数据类型
  2. handler可以是空对象 {}

Reflect 对象

  • Reflect 是一个内置的对象,它提供拦截 JavaScript 操作的方法。 和 Proxy的操作方法大致相同
  • Reflect不是一个函数对象,因此它是不可构造的
  • Reflect的所有属性和方法都是静态的(就像Math对象)。

Object.defineProperty 与 Reflect.defineProperty 对比

看一个例子:

var obj = {a: 1, b: 2}
 Object.defineProperty(obj, 'c',{
     get(){
         return 4
     }
 })
 // output {a: 1, b: 2}
 obj.c
 // output 4
 Object.defineProperty(obj, 'c',{
     get(){
         return 3
     }
 })
 //output VM1139:1 Uncaught TypeError: Cannot redefine property: c
 //          at Function.defineProperty (<anonymous>)
 //          at <anonymous>:1:8
 Reflect.defineProperty(obj, 'c', {
     get(){
         return 3
     }
 })
 // output false

结论:
Reflect 可以通过返回值就会知道成功还是失败。 不会导致代码单线程卡住,阻塞下面代码运行
vue3底层的对象响应式的雏形是这样的,利用proxy代理,利用reflect反射(对源数据进行操作)

Logo

前往低代码交流专区

更多推荐