零、引言

参考文献
  • 计算姬 的博客文章:《JavaScript 的 Proxy详解》
  • 阮一峰的《ECMAScript 6 入门》- 15.Proxy

纠正了一些谬误并重新整理编辑排版,仅供集体学习应用。

问题的起源

vue3.0 开始 Proxy代替Object.defineProperty,产生了一些列纳闷。

  • Proxy是什么?
  • Proxy能干什么?
  • Vue用Object.defineProperty干了什么?
  • 为什么用Proxy代替Object.defineProperty?

一、Proxy是什么?

1.1 了解Proxy

MDN定义:Proxy 对象用于定义基本操作的自定义行为(如属性查找,赋值,枚举,函数调用等)。

艰深的讲Proxy是一个对象操作的拦截器,拦挡对指标对象的操作,进行一些自定义的行为,一种分层的思维有点相似spring的AOP。

1.2 Proxy怎么用

let p = new Proxy(target, handler);

语法非常简单,只有两个参数,很好了解

target

用Proxy包装的指标对象(能够是任何类型的对象,包含原生数组,函数,甚至另一个代理)。

handler

一个对象,其属性是当执行一个操作时定义代理的行为的函数。

var proxy = new Proxy({}, {  get: function(target, property) {    return 35;   }});let obj = Object.create(proxy);obj.time // 35
无操作转发代理

代理对象p会将所有利用到它的操作转发到指标对象target上,能够间接对代理对象进行操作,操作会转发到指标对象上。

let target = {};let p = new Proxy(target, {});p.a = 37;   // 操作转发到指标console.log(target.a);    // 37. 操作曾经被正确地转发

1.3 能够拦挡的操作

一共有 13 种可代理操作,每种操作的代号(属性名/办法名)和触发这种操作的形式列举如下。留神,如果没有定义某种操作,那么这种操作会被转发到指标对象身上。

  • handler.getPrototypeOf()

在读取代理对象的原型时触发该操作,比方在执行 Object.getPrototypeOf(proxy) 时。

  • handler.setPrototypeOf()

在设置代理对象的原型时触发该操作,比方在执行 Object.setPrototypeOf(proxy, null) 时。

  • handler.isExtensible()

在判断一个代理对象是否是可扩大时触发该操作,比方在执行 Object.isExtensible(proxy) 时。

  • handler.preventExtensions()

在让一个代理对象不可扩大时触发该操作,比方在执行 Object.preventExtensions(proxy) 时。

  • handler.getOwnPropertyDescriptor()

在获取代理对象某个属性的属性形容时触发该操作,比方在执行 Object.getOwnPropertyDescriptor(proxy, “foo”) 时。

  • handler.defineProperty()

在定义代理对象某个属性时的属性形容时触发该操作,比方在执行 Object.defineProperty(proxy, “foo”, {}) 时。

  • handler.has()

在判断代理对象是否领有某个属性时触发该操作,比方在执行 “foo” in proxy 时。

  • handler.get()

在读取代理对象的某个属性时触发该操作,比方在执行 proxy.foo 时。

  • handler.set()

在给代理对象的某个属性赋值时触发该操作,比方在执行 proxy.foo = 1 时。

  • handler.deleteProperty()

在删除代理对象的某个属性时触发该操作,比方在执行 delete proxy.foo 时。

  • handler.ownKeys()

在获取代理对象的所有属性键时触发该操作,比方在执行 Object.getOwnPropertyNames(proxy) 时。

  • handler.apply()

当指标对象为函数,且被调用时触发。

  • handler.construct()

在给一个指标对象为构造函数的代理对象结构实例时触发该操作,比方在执行new proxy() 时。

1.4 this的指向

须要特地留神代理对象和拦挡操作中的this指向问题

一旦对象被代理之后,他的this就指向了代理对象

const target = {  m: function () {    console.log(this === proxy);  }};const handler = {}; const proxy = new Proxy(target, handler); target.m() // falseproxy.m()  // true。指标函数的this指向proxy

handler定义的拦挡操作中this指向handler,receiver指向的才是proxy

const target = {    m: 100  };  const handler = {    get(target, property, receiver) {      console.log(this === handler) // true。 拦挡操作中的this指向 handler      console.log(receiver === proxy) // true。receiver 指向 代理对象proxy      return target[property]    }  };   const proxy = new Proxy(target, handler);  console.log(proxy.m)

二、Proxy能干什么?

Proxy 各种罕用的拦挡都能干什么,罕用的四种操作

2.1 get()

get办法用于拦挡某个属性的读取操作,能够承受三个参数,顺次为指标对象、属性名和 proxy 实例自身。所有的属性调用都会进入同一个get即便是没有的属性。

  • 能够发明一些原本没有的属性
  • 能够在取数据时对数据进行验证和转换
  • 能够自定义一些语法糖操作
  • get返回一个函数的话能够把一个属性转换成办法
var base = {  a: 100,  small: "hello world!!"};var proxy = new Proxy(base, {  get(target, property, receiver) {    //属性转换成函数    if ("fn" === property) {      return function(value) {        console.log(value);      };    }    if ("ghost" === property) {      return "ghost";    }    if ("fn" === property) {      return function(value) {        console.log(value);      };    }    //自定义语法糖    if (property.includes("_")) {      const direct = property.split("_")[1];      const propertyBase = property.split("_")[0];      switch (direct) {        case "Big":          return receiver[propertyBase].toLocaleUpperCase();        default:          break;      }    }    if (!(property in target)) {      throw new ReferenceError('Property "' + property + '" does not exist.'); //验证属性值    }    return target[property];  }});console.log(proxy.a)//输入100  失常拜访proxy.fn("fn")//输入fn  属性转换为办法console.log(proxy.small_Big)//输入 HELLO WORLD!!   自定义语法糖console.log(proxy.ghost)//输入ghost   创立属性console.log(proxy.ghost_Big)//输入GHOST  receiver的应用console.log(proxy.b)//数据验证  抛出谬误

2.2 set()

set办法用来拦挡某个属性的赋值操作,能够承受四个参数,顺次为指标对象、属性名、属性值和 Proxy 实例自身,其中最初一个参数可选。

  • 能够用来验证属性是否符合要求
  • 能够用来更改数据格式
  • 能够用来监听数据更改事件
  • 屏蔽一些赋值操作比方以"_"结尾的公有变量
var base = {  a: 100,  small: "hello world!!"};const A_Change_Event = "aChangeEvent";window.addEventListener(A_Change_Event, e => {  console.log(e.data.value);});var proxy = new Proxy(base, {  set(obj, property, value, receiver) {    if ("a" === property) {      if (!Number.isInteger(value)) {        throw new TypeError("The" + property + "is not an integer");      }      if (value > 100) {        throw new RangeError("The " + property + " seems invalid");      }      obj[property] = value;      //事件      const dataChangeEvent = new Event(A_Change_Event); //创立一个事件      dataChangeEvent.data = { value };      window.dispatchEvent(dataChangeEvent);    }  }});proxy.a = 80;console.log(proxy.a);proxy.a = 120;

2.3 apply()

函数对象也能够作为代理的指标对象

apply办法拦挡函数的调用、call和apply操作。

apply办法能够承受三个参数,别离是指标对象、指标对象的上下文对象(this)和指标对象的参数数组。

最简略的例子

var target = function () { return 'I am the target'; };var handler = {  apply: function () {    return 'I am the proxy';  }};var p = new Proxy(target, handler);p()// "I am the proxy"

2.4 construct ()

construct 拦挡的是new操作
承受三个参数

  • target:指标对象
  • args:构造函数的参数对象
  • newTarget:发明实例对象时,new命令作用的构造函数

三、Vue中的Proxy和Object.defineProperty

3.1 Object.defineProperty 实现observe

递归遍历所有属性,应用Object.defineProperty挨个定义监听

var data = {name: 'kindeng'};observe(data);data.name = 'dmq'; // 哈哈哈,监听到值变动了 kindeng --> dmqfunction observe(data) {    if (!data || typeof data !== 'object') {        return;    }    // 取出所有属性遍历    Object.keys(data).forEach(function(key) {        defineReactive(data, key, data[key]);    });};function defineReactive(data, key, val) {    observe(val); // 监听子属性    Object.defineProperty(data, key, {        enumerable: true, // 可枚举        configurable: false, // 不能再define        get: function() {            return val;        },        set: function(newVal) {            console.log('哈哈哈,监听到值变动了 ', val, ' --> ', newVal);            val = newVal;        }    });}

3.2 Proxy实现observe

不必提前挨个遍历绑定绑定。每次依据key来判断

observe(data) {    const that = this;    let handler = {    get(target, property) {        return target[property];    },    set(target, key, value) {        let res = Reflect.set(target, key, value);        that.subscribe[key].map(item => {          item.update();        });        return res;      }    }    this.$data = new Proxy(data, handler);  }

四、为什么用Proxy代替Object.defineProperty?

vue3.0应用了Proxy替换了原先遍历对象应用Object.defineProperty办法给属性增加set,get拜访器的蠢笨做法。也就是说不应遍历了,而是间接监控data对象了。