速览Vue3.0中的 proxy

更新日期: 2020-10-30阅读: 2.1k标签: proxy

Proxy 简介

Proxy 可以理解为,在操作对象时要先经过一层“拦截器”。访问对象时,都必须先经过这一层拦截。这就意味着你可以在拦截里做各种骚操作。 比如,整一个 Proxy 的对象用来对后端返回的数据类型进行类型校验,不通过直接 throw new Error('大兄弟,说好的对象,你给我返回一个数组?!'),记得搭配 try catch 食用,风味更佳!

让我们先看看怎么使用 Proxy.

const p = new Proxy(target, handler);

target: 要代理的原始对象

handler: 定义哪些操作将被拦截以及如何重新定义被拦截的操作的对象

const p = new Proxy({}, {
    get(target, propKey) {
        Ha ha, you have been intercepted by me;
    }
});

console.log(p.name);
//Ha ha, you're intercepted by me

以上例子,只是展示了 proxy 怎么操作对象属性,而他的核心是为了扩展对象的能力。

让我看看另外一个例子 我们可以使用 set 方法,让对象的 name 属性无法被修改

const p = new Proxy({}, {
    set(target, propKey, value) {
        if (propKey === 'name') {
            Throw new typeerror ('name attribute is not allowed to be modified ');
        }
        //Not name attribute, save directly
        target[propKey] = value;
    }
});
p.name = 'proxy';
//Typeerror: the name property is not allowed to be modified
p.a = 111;
console.log(p.a); // 111

babel is used to translate syntax, such as new apis (such as Array.from , Array.prototype.includes )We need to install additional packages for support, such as [core JS / stable] () and [regenerator Runtime / runtime] () (PS: Babel 7. X @ Babel / Polyfill is not recommended), and then there are some APIs (string normalize, proxy, fetch, etc.)core-jsFor details, please refer to the official document core JS ා missing polyfills.


vue2.X 是如何实现响应式系统的?

当你把一个普通的 JavaScript 对象传入 Vue 实例作为 data 选项,Vue 将遍历此对象所有的 property,并使用 Object.defineProperty() 把这些 property 全部转为 getter/setter。在 getter 方法中收集数据依赖,在 setter 中监听数据变化。一旦数据发生变化,再通知订阅者。

每个组件实例都对应一个 watcher 实例,它会在组件渲染的过程中把“接触”过的数据 property 记录为依赖。之后当依赖项的 setter 触发时,会通知 watcher,从而使它关联的组件重新渲染。 


图片来源:Vue 官网

让我们先看看部分源码 Src / core / observer/ index.js#L156 -L193, version 2.6.11

let childOb = !shallow && observe(val)
 //The data in the data is deeply traversed, and a response formula is added to each attribute of the object
  Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    get: function reactiveGetter () {
      const value = getter ? getter.call(obj) : val
      if (Dep.target) {
         //Do dependency collection
        dep.depend()
        if (childOb) {
          childOb.dep.depend()
          if (Array.isArray(value)) {
            //If it is an array, you need to collect the dependency of each member. If the member of the array is still an array, it will be recursive.
            dependArray(value)
          }
        }
      }
      return value
    },
    set: function reactiveSetter (newVal) {
      const value = getter ? getter.call(obj) : val
      /* eslint-disable no-self-compare */
      if (newVal === value || (newVal !== newVal && value !== value)) {
        return
      }
      /* eslint-enable no-self-compare */
      if (process.env.NODE_ENV !== 'production' && customSetter) {
        customSetter()
      }
      if (getter && !setter) return
      if (setter) {
        setter.call(obj, newVal)
      } else {
        val = newVal
      }
      //New values need to be observed again to ensure data response
      childOb = !shallow && observe(newVal)
      //Inform all observers of data changes
      dep.notify()
    }
  })

能看出 defineProperty 的痛点吗?

他无法发现对象中新增被删除的属性:当你给一个对象添加一个新的属性时,这个新增的属性没有被添加到 Vue 的数据更新侦查机制里。vue.set可以让Vue知道你新增了一个属性,其实Vue.set 可以让 Vue 知道你新增了一个属性,其实 Vue.set内部也是通过调用 Object.defineProperty() 来实现的

当你利用索引直接设置一个数组项或修改数组长度时,Vue 不能检测到数组的变动

当对象嵌套层数特别深时,递归遍历带来的性能开销就会比较大

Vue.set: 向响应式对象中添加一个 property,并确保这个新 property 同样是响应式的,且触发视图更新。它必须用于向响应式对象上添加新 property,因为 Vue 无法探测普通的新增 property (比如 this.myObject.newProperty = 'hi')


Proxy 在 Vue3.0 中上位

为什么 Proxy 可以解决以上的痛点呢? 本质的原因在于 Proxy 是一个内置了拦截器的对象,所有的外部访问都得先经过这一层拦截。不管是先前就定义好的,还是新添加属性,访问时都会被拦截。


Take a simple one

先用 Object.defineProperty() 来实现:

class Observer {
  constructor(data) {
      //Traverse the property of parameter data and add it to this
      for(let key of Object.keys(data)) {
          if(typeof data[key] === 'object') {
              data[key] = new Observer(data[key]);
          }
          Object.defineProperty(this, key, {
              enumerable: true,
              configurable: true,
              get() {
                  console.log('You visited '+ key);
                  return data[key]; // the bracket method can use a variable as the attribute name, but the dot method cannot;
              },
              set(newVal) {
                  console.log ('You set '+ key);
                  console.log ('New '+ key +' = '+ newVal);
                  if(newVal === data[key]) {
                      return;
                  }
                  data[key] = newVal;
              }
          })
      }
  }
}

const obj = {
  name: 'app',
  age: '18',
  a: {
      b: 1,
      c: 2,
  },
}
const app = new Observer(obj);
app.age = 20;
console.log(app.age);
app.newPropKey  ='new attribute';
console.log(app.newPropKey);

上述代码的输出结果是

//Modify the output of obj's original attribute age
You set age
New age = 20
You visited age
20
//Set the output of the new property
new property

正如你看到的,新添加的属性没有被监测到。只有手动调用 Object.defineProperty() 才能被 Vue 添加到侦查机制里。 这就是为什么在 $set 添加或删除对象属性失败后,会调用 Object.defineProperty() 来解决的原因.

让我们再用 Proxy 来实现:

const obj = {
  name: 'app',
  age: '18',
  a: {
      b: 1,
      c: 2,
  },
}
const p = new Proxy(obj, {
  get(target, propKey, receiver) {
      console.log ('You visited '+ propKey);
      return Reflect.get(target, propKey, receiver);
  },
  set(target, propKey, value, receiver) {
      console.log ('you set ' + propKey);
      console.log ('New '+ propKey +' = '+ value);
      Reflect.set(target, propKey, value, receiver);
  }
});
p.age = '20';
console.log(p.age);
p.newPropKey = 'new attribute';
console.log(p.newPropKey);

输出如下:

//Modify the age property of the original object
You set age
New age = 20
You visited age
20

//Set new properties
You set up newpropkey
New newpropkey = new property
You visited newpropkey
new property

正如你看到的,新增的属性不需要再重新执行响应式操作就能被拦截。待到天荒地老,海枯石烂,Proxy 对对象属性的拦截永不变。

Reflect (introduced in ES6) is a built-in object that provides a way to intercept JavaScript operations. Some object objects are obviously internal methods of the language (such as Object.defineProperty())PutReflectObject. Modify the returned results of some object methods to make them more reasonable. Make object operations function behavior. See MDN for details


总结

Proxy 是用来操作对象并且扩展对象能力的。而 Object.defineProperty() 只是单纯地操作对象的属性

Vue2.x 是用 Object.defineProperty() 实现数据响应的。但是受限于 Object.defineProperty() 的实现,必须递归遍历至对象的最底层

Vue3.0 用 Proxy 来拦截对象。不管对对象执行任何操作,都会先通过 Proxy 的处理逻辑

除了 Vue3.0,还有其他的库也在使用 Proxy。所以要跟上大佬的步伐,赶紧上手 Proxy 吧!

本文翻译自 Simple and popular understanding of proxy in vue3.0
译者:Yeo Huang  


链接: https://fly63.com/article/detial/9790

Javascript Proxy对象 简介

ES6 中引入Proxies,让你可以自定义Object的基本操作。例如,get就是Object的基础操作方法。

js_es6中对象代理proxy用法实例浅析

ES6中提出了一个新的特性,就是proxy,用来拦截在一个对象上的指定操作。这个功能非常的有用。每当代理对象被赋值,处理器函数就会调用,这样就可以用来调试某些问题。

拿Proxy可以做哪些有意思的事儿

Proxy是什么意思?Proxy是ES6中提供的新的API,可以用来定义对象各种基本操作的自定义行为,在我们需要对一些对象的行为进行控制时将变得非常有效。

ES6 系列之 defineProperty 与 proxy

我们或多或少都听过数据绑定这个词,数据绑定”的关键在于监听数据的变化,可是对于这样一个对象:var obj = {value: 1},我们该怎么知道 obj 发生了改变呢?ES5 提供了 Object.defineProperty 方法,该方法可以在一个对象上定义一个新属性

Js中Proxy

Proxy 用于修改某些操作的默认行为(基本操作有属性查找,赋值,枚举,函数调用等)。get(target, propKey, receiver):拦截对象属性的读取;set: function(obj, prop, value,receive) : 拦截某个属性的赋值操作

Proxy 的巧用

使用Proxy,你可以将一只猫伪装成一只老虎。下面大约有6个例子,我希望它们能让你相信,Proxy 提供了强大的 Javascript 元编程。尽管它不像其他ES6功能用的普遍,但Proxy有许多用途

使用 Proxy 更好的封装 Storage API

这篇文章提到 Proxy 这种语法可以用来封装 sessionStorage、 localStorage 甚至是 IndexedDB。可以使用 Proxy 代理来使 API 更容易使用。首先介绍一下 Proxy 的基本用法:

Proxy及其优势

通常,当谈到JavaScript语言时,我们讨论的是ES6标准提供的新特性,本文也不例外。 我们将讨论JavaScript代理以及它们的作用,但在我们深入研究之前,我们先来看一下Proxy的定义是什么。

ES6中代理和反射(proxy)

通过调用new proxy()你可以创建一个代理来替代另一个对象(被称为目标),这个代理对目标对象进行了虚拟,因此该代理与该目标对象表面上可以被当做同一个对象来对待。

ES6之Proxy

Proxy用于修改某些操作的默认行为,等同于在语言层面做出修改,所以属于一种『元编程』即对编程语言进行编程。Proxy 是在目标对象之前架设一层『拦截』,外部对对象的访问,都需要经过该层拦截。因此在拦截中对外界的访问进行过滤和改写。

点击更多...

内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!