初探Vue3.0 中的一大亮点Proxy的使用

前言

不久前,也就是11月14日-16日于多伦多举办的 VueConf TO 2018 大会上,尤雨溪发表了名为 Vue3.0 Updates 的主题演讲,对 Vue3.0 的更新计划、方向进行了详细阐述,表示已经放弃使用了 Object.defineProperty,而选择了使用更快的原生 Proxy !!

这将会消除了之前 Vue2.x 中基于 Object.defineProperty 的实现所存在的很多限制:无法监听 属性的添加和删除、数组索引和长度的变更,并可以支持 Map、Set、WeakMap 和 WeakSet!

做为一个 “前端工程师” ,有必要安利一波 Proxy !!

什么是 Proxy?

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

官方的描述总是言简意赅,以至于不明觉厉...

其实就是在对目标对象的操作之前提供了拦截,可以对外界的操作进行过滤和改写,修改某些操作的默认行为,这样我们可以不直接操作对象本身,而是通过操作对象的代理对象来间接来操作对象,达到预期的目的~

什么?还没表述清楚?下面我们看个例子,就一目了然了~

  let obj = {
   a : 1
  }
  let proxyObj = new Proxy(obj,{
    get : function (target,prop) {
      return prop in target ? target[prop] : 0
    },
    set : function (target,prop,value) {
      target[prop] = 888;
    }
  })

  console.log(proxyObj.a);    // 1
  console.log(proxyObj.b);    // 0

  proxyObj.a = 666;
  console.log(proxyObj.a)     // 888

上述例子中,我们事先定义了一个对象 obj , 通过 Proxy 构造器生成了一个 proxyObj 对象,并对其的 set(写入) 和 get (读取) 行为重新做了修改。

当我们访问对象内原本存在的属性时,会返回原有属性内对应的值,如果试图访问一个不存在的属性时,会返回0 ,即我们访问 proxyObj.a 时,原本对象中有 a 属性,因此会返回 1 ,当我们试图访问对象中不存在的 b 属性时,不会再返回 undefined ,而是返回了 0 ,当我们试图去设置新的属性值的时候,总是会返回 888 ,因此,即便我们对 proxyObj.a 赋值为 666 ,但是并不会生效,依旧会返回 888!

语法

ES6 原生提供的 Proxy 语法很简单,用法如下:

let proxy = new Proxy(target, handler);

参数 target 是用 Proxy 包装的目标对象(可以是任何类型的对象,包括原生数组,函数,甚至另一个代理), 参数 handler 也是一个对象,其属性是当执行一个操作时定义代理的行为的函数,也就是自定义的行为。

Proxy 的基本用法就如同上面这样,不同的是 handler 对象的不同,handler 可以是空对象 {} ,则表示对 proxy 操作就是对目标对象 target 操作,即:

  let obj = {}

  let proxyObj = new Proxy(obj,{})

  proxyObj.a = 1;
  proxyObj.fn = function () {
    console.log('it is a function')
  }

  console.log(proxyObj.a); // 1
  console.log(obj.a);   // 1
  console.log(obj.fn())  // it is a function

但是要注意的是,handler 不能 设置为 null ,会抛出一个错误——Cannot create proxy with a non-object as target or handler!

要想 Proxy 起作用,我们就不能去操作原来对象的对象,也就是目标对象 target (上例是 obj 对象 ),必须针对的是 Proxy 实例(上例是 proxyObj 对象)进行操作,否则达不到预期的效果,以刚开始的例子来看,我们设置 get 方法后,视图继续从原对象 obj 中读取一个不存在的属性 b , 结果依旧返回 undefined :

  console.log(proxyObj.b);   // 1
  console.log(obj.b);     // undefined

对于可以设置、但没有设置拦截的操作,则对 proxy 对象的处理结果也同样会作用于原来的目标对象 target 上,怎么理解呢?还是以刚开始的例子来看,我们重新定义了 set 方法,所有的属性设置都返回了 888 , 并没有对某个特殊的属性(这里指的是 obj 的 a 属性 )做特殊的拦截或处理,那么通过 proxyObj.a = 666 操作后的结果同样也会作用于原来目标对象(obj 对象)上,因此 obj 对象的 a 的值也将会变为 888 !

  proxyObj.a = 666;
  console.log( proxyObj.a);  // 888
  console.log( obj.a);    // 888

API

ES6 中 Proxy 目前提供了 13 种可代理操作,下面我对几个比较常用的 api 做一些归纳和整理,想要了解其他方法的同学可自行去官网查阅 :

--handler.get(target,property,receiver)

用于拦截对象的读取属性操作,target 是指目标对象,property 是被获取的属性名 , receiver 是 Proxy 或者继承 Proxy 的对象,一般情况下就是 Proxy 实例。

let proxy = new Proxy({},{
  get : function (target,prop) {
    console.log(`get ${prop}`);
    return 10;
  }
})

console.log(proxy.a)  // get a
            // 10

我们拦截了一个空对象的 读取get操作, 当获取其内部的属性是,会输出 get ${prop} , 并返回 10 ;

let proxy = new Proxy({},{
  get : function (target,prop,receiver) {
      return receiver;
    }
  })

console.log(proxy.a)  // Proxy{}
console.log(proxy.a === proxy) //true

上述 proxy 对象的 a 属性是由 proxy 对象提供的,所以 receiver 指向 proxy 对象,因此 proxy.a === proxy 返回的是 true。

要注意,如果要访问的目标属性是不可写以及不可配置的,则返回的值必须与该目标属性的值相同,也就是不能对其进行修改,否则会抛出异常~

let obj = {};
Object.defineProperty(obj, "a", {
 configurable: false,
 enumerable: false,
 value: 10,
 writable: false
});

let proxy = new Proxy(obj,{
  get : function (target,prop) {
    return 20;
  }
})

console.log(proxy.a)  // Uncaught TypeError

上述 obj 对象中的 a 属性不可写,不可配置,我们通过 Proxy 创建了一个 proxy 的实例,并拦截了它的 get 操作,当我们输出 proxy.a 时会抛出异常,此时,如果我们将 get 方法的返回值修改跟目标属性的值相同时,也就是 10 , 就可以消除异常~

--handler.set(target, property, value, receiver)

用于拦截设置属性值的操作,参数于 get 方法相比,多了一个 value ,即要设置的属性值~

在严格模式下,set方法需要返回一个布尔值,返回 true 代表此次设置属性成功了,如果返回false且设置属性操作失败,并且会抛出一个TypeError。

let proxy = new Proxy({},{
  set : function (target,prop,value) {
    if( prop === 'count' ){
      if( typeof value === 'number'){
        console.log('success')
       target[prop] = value;
      }else{
       throw new Error('The variable is not an integer')
      }
    }
  }
})

 proxy.count = '10';  // The variable is not an integer

 proxy.count = 10;   // success

上述我们通过修改 set方法,对 目标对象中的 count 属性赋值做了限制,我们要求 count 属性赋值必须是一个 number 类型的数据,如果不是,就返回一个错误 The variable is not an integer,我们第一次为 count 赋值字符串 '10' , 抛出异常,第二次赋值为数字 10 , 打印成功,因此,我们可以用 set 方法来做一些数据校验!

同样,如果目标属性是不可写及不可配置的,则不能改变它的值,即赋值无效,如下:

let obj = {};
Object.defineProperty(obj, "count", {
  configurable: false,
  enumerable: false,
  value: 10,
  writable: false
});

let proxy = new Proxy(obj,{
  set : function (target,prop,value) {
    target[prop] = 20;
  }
})

proxy.count = 20 ;
console.log(proxy.count)  // 10

上述 obj 对象中的 count 属性,我们设置它不可被修改,并且默认值,我们给定为 10 ,那么即使给其赋值为 20 ,结果仍旧没有变化!

--handler.apply(target, thisArg, argumentsList)

用于拦截函数的调用,共有三个参数,分别是目标对象(函数)target,被调用时的上下文对象 thisArg 以及被调用时的参数数组 argumentsList,该方法可以返回任何值。

target 必须是是一个函数对象,否则将抛出一个TypeError;

function sum(a, b) {
 return a + b;
}

const handler = {
  apply: function(target, thisArg, argumentsList) {
   console.log(`Calculate sum: ${argumentsList}`);
   return target(argumentsList[0], argumentsList[1]) * 10;
  }
};

let proxy = new Proxy(sum, handler);

console.log(sum(1, 2));   // 3
console.log(proxy(1, 2));  // Calculate sum:1,2
              // 6

实际上,apply 还会拦截目标对象的 Function.prototype.apply() 和 Function.prototype.call(),以及 Reflect.apply() 操作,如下:

console.log(proxy.call(null, 3, 4));  // Calculate sum:3,4
                    // 14

console.log(Reflect.apply(proxy, null, [5, 6]));  // Calculate sum: 5,6
                          // 22

--handler.construct(target, argumentsList, newTarget)

construct 用于拦截 new 操作符,为了使 new 操作符在生成的 Proxy对象上生效,用于初始化代理的目标对象自身必须具有[[Construct]]内部方法;它接收三个参数,目标对象 target ,构造函数参数列表 argumentsList 以及最初实例对象时,new 命令作用的构造函数,即下面例子中的 p。

let p = new Proxy(function() {}, {
  construct: function(target, argumentsList, newTarget) {
   console.log(newTarget === p );             // true
   console.log('called: ' + argumentsList.join(', '));   // called:1,2
   return { value: ( argumentsList[0] + argumentsList[1] )* 10 };
  }
});

console.log(new p(1,2).value);   // 30

另外,该方法必须返回一个对象,否则会抛出异常!

var p = new Proxy(function() {}, {
  construct: function(target, argumentsList, newTarget) {
   return 2
  }
});

console.log(new p(1,2));  // Uncaught TypeError

--handler.has(target,prop)

has方法可以看作是针对 in 操作的钩子,当我们判断对象是否具有某个属性时,这个方法会生效,典型的操作就是 in ,改方法接收两个参数 目标对象 target 和 要检查的属性 prop,并返回一个 boolean 值。

let p = new Proxy({}, {
  has: function(target, prop) {
   if( prop[0] === '_' ) {
   console.log('it is a private property')
   return false;
   }
   return true;
  }
});

console.log('a' in p);   // true
console.log('_a' in p )   // it is a private property
              // false

上述例子中,我们用 has 方法隐藏了属性以下划线_开头的私有属性,这样在判断时候就会返回 false,从而不会被 in 运算符发现~

要注意,如果目标对象的某一属性本身不可被配置,则该属性不能够被代理隐藏,如果目标对象为不可扩展对象,则该对象的属性不能够被代理隐藏,否则将会抛出 TypeError。

let obj = { a : 1 };

Object.preventExtensions(obj); // 让一个对象变的不可扩展,也就是永远不能再添加新的属性

let p = new Proxy(obj, {
 has: function(target, prop) {
 return false;
 }
});

console.log('a' in p); // TypeError is thrown

数据绑定

上面介绍了这么多,也算是对 Proxy 又来一个初步的了解,那么我们就可以利用 Proxy 手动实现一个极其简单数据的双向绑定(Object.defineProperty() 的实现方式可以参考我上篇文章的末尾有涉及到)~

主要看功能的实现,所以布局方面我就随手一挥了~

页面结构如下:

<!--html-->
<div id="app">
  <h3 id="paragraph"></h3>
  <input type="text" id="input"/>
</div>

主要还是得看逻辑部分:

//获取段落的节点
const paragraph = document.getElementById('paragraph');
//获取输入框节点
const input = document.getElementById('input');

//需要代理的数据对象
const data = {
 text: 'hello world'
}

const handler = {
 //监控 data 中的 text 属性变化
 set: function (target, prop, value) {
   if ( prop === 'text' ) {
        //更新值
        target[prop] = value;
        //更新视图
        paragraph.innerHTML = value;
        input.value = value;
        return true;
   } else {
   return false;
   }
 }
}

//添加input监听事件
input.addEventListener('input', function (e) {
  myText.text = e.target.value;  //更新 myText 的值
}, false)

//构造 proxy 对象
const myText = new Proxy(data,handler);

//初始化值
myText.text = data.text;

上述我们通过Proxy 创建了 myText 实例,通过拦截 myText 中 text 属性 set 方法,来更新视图变化,实现了一个极为简单的 双向数据绑定~

总结

说了这么多 , Proxy 总算是入门了,虽然它的语法很简单,但是要想实际发挥出它的价值,可不是件容易的事,再加上其本身的 Proxy 的兼容性方面的问题,所以我们实际应用开发中使用的场景的并不是很多,但不代表它不实用,在我看来,可以利用它进行数据的二次处理、可以进行数据合法性的校验,甚至还可以进行函数的代理,更多有用的价值等着你去开发呢~

况且,Vue3.0 都已经准备发布了,你还不打算让学习一下?

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • 详解Vuejs2.0 如何利用proxyTable实现跨域请求

    前言: 本地项目在请求远端服务器接口时,不可避免的会遇到跨域问题,即便是设置了Access-Control-Allow-Origin:* ,在遇到登录这些需要本地存入cookie的也会很头痛,这里笔者介绍一个在vue-cli中配置代理来解决的办法. 在~/config/dev-server.js中 使用了非常强大的http-proxy-middleware包.更多高级用法,请查阅其文档. 用法: 比如我们要请求的远端服务器为:http://192.168.400:3000 proxyTable:

  • vue.js中proxyTable 转发请求的实现方法

    找到config/index.js 配置文件 proxyTable: { '/api': { target: 'http://your_website', changeOrigin: true, pathRewrite: { //需要rewrite重写的, 如果在服务器端做了处理则可以不要这段 '^/api': '' } } } 以上这篇vue.js中proxyTable 转发请求的实现方法就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持我们.

  • vue2.0设置proxyTable使用axios进行跨域请求的方法

    这里请求的是知乎日报的api,由@izzyleung这位大神提供的,这是github地址. 在vue-cli构建的项目中先安装axios npm install axios -S 这里暂不考虑用vuex封装的方式,只讲在当前组件中直接使用. 先在<script>中引入 用prototype将axios添加为vue原生的属性,$ajax相当于axios的别名. 然后在要调用的方法或是钩子中请求,$ajax就是上面注册的axios,如果想直接用axios不想设置prototype也可以直接按官方的

  • 详解vue-cli项目中的proxyTable跨域问题小结

    什么是跨域? 同源策略规定了如果两个 url 的协议.域名.端口中有任何一个不等,就认定它们跨源了. 跨域的解决方式有哪几种? 1.JSONP 是 JSON with padding(填充式 JSON 或参数式 JSON)的简写. JSONP实现跨域请求的原理简单的说,就是动态创建<script>标签,然后利用<script>的src 不受同源策略约束来跨域获取数据. JSONP 由两部分组成:回调函数和数据.回调函数是当响应到来时应该在页面中调用的函数.回调函数的名字一般是在请求

  • 使用proxy实现一个更优雅的vue【推荐】

    如果你有读过Vue的源码,或者有了解过Vue的响应原理,那么你一定知道Object.defineProperty(), 那么你也应该知道,Vue 2.x里,是通过 递归 + 遍历 data 对象来实现对数据的监控的, 你可能还会知道,我们使用的时候,直接通过数组的下标给数组设置值,不能实时响应,是因为Object.defineProperty() 无法监控到数组下标的变化,而我们平常所用的数组方法 push , pop , shift , unshift , splice , sort , re

  • vue proxyTable 接口跨域请求调试的示例

    在不同域之间访问是比较常见,在本地调试访问远程服务器....这就是有域问题. VUE解决通过proxyTable: 在 config/index.js 配置文件中 dev: { env: require('./dev.env'), port: 8080, autoOpenBrowser: true, assetsSubDirectory: 'static', assetsPublicPath: '/', //proxyTable: {}, proxyTable: proxyConfig.pro

  • Vue-cli proxyTable 解决开发环境的跨域问题详解

    和后端联调时总是会面对恼人的跨域问题,最近基于Vue开发项目时也遇到了这个问题,两边各自想了一堆办法,查了一堆资料,加了一堆参数,最后还得我把自己的localhost映射成上线时将要使用的域名. 今天翻看代码时,突然发现vue-cli的config文件里有一个参数叫proxyTable,看这个名字就感觉能解决问题,于是我就去搜了一下,果然.在vuejs-templates,也就是vue-cli的使用的模板插件里,有关于API proxy的说明,使用的就是这个参数. https://vuejs-t

  • Vue中如何实现proxy代理

    Vue 框架开发的时候,会遇到跨域的问题,可在config/index.js 里配置proxyTable内容,使用proxy 代理. // config/index.js 文件 proxyTable: { '/api': { target: 'http://192.168.149.90:8080/', // 设置你调用的接口域名和端口号 changeOrigin: true, // 跨域 pathRewrite: { '^/api': '/' } } }, 这里理解成用'/api'代替targe

  • vue 本地环境跨域请求proxyTable的方法

    主要在config->index.js中配置 proxyTable: { '/gameapi': { changeOrigin: true, // target: 'http://rap.id.cn/mockjs/20', mock地址 target: 'http://192.168.1.124',服务器地址 pathRewrite: { '^/gameapi': '/gameapi' }, secure:false } } 因此,发送请求时候url写为('/gameapi/gift-apply

  • 初探Vue3.0 中的一大亮点Proxy的使用

    前言 不久前,也就是11月14日-16日于多伦多举办的 VueConf TO 2018 大会上,尤雨溪发表了名为 Vue3.0 Updates 的主题演讲,对 Vue3.0 的更新计划.方向进行了详细阐述,表示已经放弃使用了 Object.defineProperty,而选择了使用更快的原生 Proxy !! 这将会消除了之前 Vue2.x 中基于 Object.defineProperty 的实现所存在的很多限制:无法监听 属性的添加和删除.数组索引和长度的变更,并可以支持 Map.Set.W

  • vue3.0中的双向数据绑定方法及优缺点

    熟悉vue的人都知道在vue2.x之前都是使用object.defineProperty来实现双向数据绑定的 而在vue3.0中这个方法被取代了 1. 为什么要替换Object.defineProperty 替换不是因为不好,是因为有更好的方法使用效率更高 Object.defineProperty的缺点: 1. 在Vue中,Object.defineProperty无法监控到数组下标的变化, 导致直接通过数组的下标给数组设置值,不能实时响应. push() pop() shift() unsh

  • vue3.0中setup使用(两种用法)

    一.setup函数的特性以及作用 可以确定的是 Vue3.0 是兼容 Vue2.x 版本的 也就是说我们再日常工作中 可以在 Vue3 中使用 Vue2.x 的相关语法 但是当你真正开始使用 Vue3 写项目时 你会发现他比 Vue2.x 方便的多 Vue3 的一大特性函数 ---- setup 1.setup函数是处于 生命周期函数 beforeCreate 和 Created 两个钩子函数之间的函数 也就说在 setup函数中是无法 使用 data 和 methods 中的数据和方法的 2.

  • Vue3.0中Ref与Reactive的区别示例详析

    目录 Ref与Reactive Ref Reactive Ref与Reactive的区别 shallowRef 与shallowReactive toRaw ---只修改数据不渲染页面 markRaw --- 不追踪数据 toRef --- 跟数据源关联 不修改UI toRefs ---设置多个toRef属性值 customRef ---自定义一个ref ref 捆绑页面的标签 总结 Ref与Reactive Ref Ref 用来创建基础类型的响应式数据,模板默认调用value显示数据.方法中修

  • vue3.0中setup的两种用法实例

    目录 前言 一.setup函数的特性以及作用 二.setup函数的注意点: 用法1:结合ref使用 用法2:代码分割 总结 前言 这篇文章主要介绍了vue3.0中setup使用,本文通过两种用法给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下 什么是setup setup是vue3新增的生命周期函数,setup的加入就是为了让vue3使用组合式API(Composition API).使用组合式API更符合大型项目的开发,通过setup可以将该部分抽离成函数,

  • Vue3.0中的monorepo管理模式的实现

    前言 前段时间9月21日参加了在成都举办的第五届FEDAY, 印象比较深刻的是白鹭引擎首席架构师@王泽分享的<框架开发中的基础设施搭建>,提到了在下一代白鹭引擎中使用到monorepo模式,以用来管理多个模块,协调各个模块之间的依赖更新. 正好在国庆期间10月5日尤大公开了vue3.0已完成的源码,也是采用了monorepo管理模式,看来monorepo确实有其独到的优势,再加上以前在项目中遇到过相关的痛点,所以深入地了解了一下这种模式,本文将基于vue3.0讨论如何通过monorepo模式来

  • vue3.0中友好使用antdv示例详解

    前言 随着我们vue3.0的出现,我们的ui组件库也有了一些变化,像我们的旧版的element-ui已经不能在vue3.0中使用了,如果要使用element的话需要使用最新版的element-plus,由于发现它并不太好用,因此我选择了Ant Design Vue. 如果我们以前经常使用antd的话,我们使用起来这个上手会非常方便. 在vue3.0中引入我们的antdv 1.首先使用我们的vue/cli创建vue3.0项目并使用less 2. 在vue3.0中使用的话我们需要安装 ant-des

  • vue3.0中使用element的完整步骤

    前言: 在vue3.0中使用element框架,因为element是支持vue2.0的,他推出的支持vue3.0的版本叫 element-plus 官网入口:点我进入 一.个人遇到的问题与解决办法: 遇到的问题: 我直接用cnpm  i element-plus -S 装的版本,不知道为啥,一直报错 图1,版本型号 图2 ,我的代码中使用图3 解决办法: 我把package.json中的element-plus的版本更换成    下面这句话,解决了问题(是否为最新版本问题,还在求证中) "ele

  • 快速掌握Vue3.0中如何上手Vuex状态管理

    Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式.它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化.Vuex 也集成到 Vue 的官方调试工具 devtools,提供了诸如零配置的 time-travel 调试.状态快照导入导出等高级调试功能. 如果您不打算开发大型单页应用,使用 Vuex 可能是繁琐冗余的.确实是如此--如果您的应用够简单,您最好不要使用 Vuex.一个简单的 store 模式就足够您所需了.但是,如果您需要构建一个中大型

  • 如何在vue3.0+中使用tinymce及实现多图上传文件上传公式编辑功能

    相关文档 本文部分内容借鉴: https://www.cnblogs.com/zhongchao666/p/11142537.html tinymce中文文档: http://tinymce.ax-z.cn/ 安装tinymce 1.安装相关依赖 yarn add tinymce || npm install tinymce -S yarn add @tinymce/tinymce-vue || npm install @tinymce/tinymce-vue -S 2.汉化编辑器前往此地址下载

随机推荐