defineProperty 与 proxy 详解

目录

前言

definePropety

Setters 和 Getters

watch API

proxy

watch API 优化

Vue3.0 里为什么要用 Proxy 替代 defineProperty ?

Proxy 和 Object.defineProperty 的区别?


前言

我们或多或少都听过“数据绑定”这个词,“数据绑定”的关键在于监听数据的变化,可是对于这样一个对象:var obj = {value: 1},我们该怎么知道 obj 发生了改变呢?

definePropety

ES5 提供了 Object.defineProperty 方法,该方法可以在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回这个对象。

语法

Object.defineProperty(obj, prop, descriptor)

  • obj: 要在其上定义属性的对象
  • prop:  要定义或修改的属性的名称
  • descriptor: 将被定义或修改的属性的描述符

举个例子:

var obj = {};
Object.defineProperty(obj, "num", {value : 1,writable : true,enumerable : true,configurable : true
});
//  对象 obj 拥有属性 num,值为 1

虽然我们可以直接添加属性和值,但是使用这种方式,我们能进行更多的配置。

函数的第三个参数 descriptor 所表示的属性描述符有两种形式:数据描述符和存取描述符

两者均具有以下两种键值:

  • configurable:当且仅当该属性的 configurable 为 true 时,该属性描述符才能够被改变,也能够被删除。默认为 false。
  • enumerable:当且仅当该属性的 enumerable 为 true 时,该属性才能够出现在对象的枚举属性中。默认为 false。

数据描述符同时具有以下可选键值:

  • value:该属性对应的值。可以是任何有效的 JavaScript 值(数值,对象,函数等)。默认为 undefined
  • writable:当且仅当该属性的 writable 为 true 时,该属性才能被赋值运算符改变。默认为 false。

存取描述符同时具有以下可选键值:

  • get:一个给属性提供 getter 的方法,如果没有 getter 则为 undefined。该方法返回值被用作属性值。默认为 undefined。
  • set:一个给属性提供 setter 的方法,如果没有 setter 则为 undefined。该方法将接受唯一参数,并将该参数的新值分配给该属性。默认为 undefined

值得注意的是:

属性描述符必须是数据描述符或者存取描述符两种形式之一,不能同时是两者。

这就意味着你可以:

Object.defineProperty({}, "num", {value: 1,writable: true,enumerable: true,configurable: true
});

也可以:

var value = 1;
Object.defineProperty({}, "num", {get : function(){return value;},set : function(newValue){value = newValue;},enumerable : true,configurable : true
});

但是不可以:

// 报错
Object.defineProperty({}, "num", {value: 1,get: function() {return 1;}
});

此外,所有的属性描述符都是非必须的,但是 descriptor 这个字段是必须的,如果不进行任何配置,你可以这样:

var obj = Object.defineProperty({}, "num", {});
console.log(obj.num); // undefined

Setters 和 Getters

之所以讲到 defineProperty,是因为我们要使用存取描述符中的 get 和 set,这两个方法又被称为 getter 和 setter。由 getter 和 setter 定义的属性称做”存取器属性“。

当程序查询存取器属性的值时,JavaScript 调用 getter方法。这个方法的返回值就是属性存取表达式的值;当程序设置一个存取器属性的值时,JavaScript 调用 setter 方法,将赋值表达式右侧的值当做参数传入 setter。从某种意义上讲,这个方法负责“设置”属性值。可以忽略 setter 方法的返回值。

举个例子:

var obj = {}, value = null;
Object.defineProperty(obj, "num", {get: function(){console.log('执行了 get 操作')return value;},set: function(newValue) {console.log('执行了 set 操作')value = newValue;}
})
// 执行了 set 操作
obj.num = 1
// 执行了 get 操作
console.log(obj.num); 
// 输出 1

这不就是我们要的监控数据改变的方法吗?我们再来封装一下:

function Archiver() {var value = null;// archive n. 档案var archive = [];Object.defineProperty(this, 'num', {get: function() {console.log('执行了 get 操作')return value;},set: function(value) {console.log('执行了 set 操作')value = value;archive.push({ val: value });}});this.getArchive = function() { return archive; };
}var arc = new Archiver();
// 执行了 get 操作
arc.num; 
// 执行了 set 操作
arc.num = 11;
// 执行了 set 操作 
arc.num = 13; 
console.log(arc.getArchive()); 
// [{ val: 11 }, { val: 13 }]

watch API

既然可以监控数据的改变,那我可以这样设想,即当数据改变的时候,自动进行渲染工作。举个例子:

HTML 中有个 span 标签和 button 标签

<span id="container">1</span>
<button id="button">点击加 1</button>

当点击按钮的时候,span 标签里的值加 1。

传统的做法是:

document.getElementById('button').addEventListener("click", function(){var container = document.getElementById("container");container.innerHTML = Number(container.innerHTML) + 1;
});

如果使用了 defineProperty:

var obj = {value: 1
}// 储存 obj.value 的值
var value = 1;Object.defineProperty(obj, "value", {get: function() {return value;},set: function(newValue) {value = newValue;document.getElementById('container').innerHTML = newValue;}
});document.getElementById('button').addEventListener("click", function() {obj.value += 1;
});

代码看似增多了,但是当我们需要改变 span 标签里的值的时候,直接修改 obj.value 的值就可以了。

然而,现在的写法,我们还需要单独声明一个变量存储 obj.value 的值,因为如果你在 set 中直接 obj.value = newValue 就会陷入无限的循环中。此外,我们可能需要监控很多属性值的改变,要是一个一个写,也很累呐,所以我们简单写个 watch 函数。使用效果如下:

var obj = {value: 1
}watch(obj, "value", function(newvalue){document.getElementById('container').innerHTML = newvalue;
})document.getElementById('button').addEventListener("click", function(){obj.value += 1
});

我们来写下这个 watch 函数:

(function(){var root = this;function watch(obj, name, func){var value = obj[name];Object.defineProperty(obj, name, {get: function() {return value;},set: function(newValue) {value = newValue;func(value)}});if (value) obj[name] = value}this.watch = watch;
})()

现在我们已经可以监控对象属性值的改变,并且可以根据属性值的改变,添加回调函数

proxy

使用 defineProperty 只能重定义属性的读取(get)和设置(set)行为,到了 ES6,提供了 Proxy,可以重定义更多的行为,比如 in、delete、函数调用等更多行为。

Proxy 这个词的原意是代理,用在这里表示由它来“代理”某些操作,可以理解成,在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。

ES6 原生提供 Proxy 构造函数,用来生成 Proxy 实例。我们来看看它的语法:

var proxy = new Proxy(target, handler);

proxy 对象的所有用法,都是上面这种形式,不同的只是 handler 参数的写法。

  • target 参数表示所要拦截的目标对象
  • handler 参数也是一个对象,用来定制拦截行为
var proxy = new Proxy({}, {get: function(obj, prop) {console.log('设置 get 操作')return obj[prop];},set: function(obj, prop, value) {console.log('设置 set 操作')obj[prop] = value;}
});// 设置 set 操作
proxy.time = 35; 
// 设置 get 操作
console.log(proxy.time);  
// 35

除了 get 和 set 之外,proxy 可以拦截多达 13 种操作,比如 has(target, propKey),可以拦截 propKey in proxy 的操作,返回一个布尔值。

// 使用 has 方法隐藏某些属性,不被 in 运算符发现
var handler = {has (target, key) {if (key[0] === '_') {return false;}return key in target;}
};
var target = { _prop: 'foo', prop: 'foo' };
var proxy = new Proxy(target, handler);
console.log('_prop' in proxy); 
// false

又比如说 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"

又比如说 ownKeys 方法可以拦截对象自身属性的读取操作。具体来说,拦截以下操作:

  • Object.getOwnPropertyNames()

  • Object.getOwnPropertySymbols()

  • Object.keys()

下面的例子是拦截第一个字符为下划线的属性名,不让它被 for of 遍历到。

let target = {_bar: 'foo',_prop: 'bar',prop: 'baz'
};let handler = {ownKeys (target) {return Reflect.ownKeys(target).filter(key => key[0] !== '_');}
};let proxy = new Proxy(target, handler);
for (let key of Object.keys(proxy)) {console.log(target[key]);
}
// "baz"

值得注意的是,proxy 的最大问题在于浏览器支持度不够,而且很多效果无法使用 polyfill 来弥补。

watch API 优化

我们使用 proxy 再来写一下 watch 函数。使用效果如下:

(function() {var root = this;function watch(target, func) {var proxy = new Proxy(target, {get: function(target, prop) {return target[prop];},set: function(target, prop, value) {target[prop] = value;func(prop, value);}});return proxy;}this.watch = watch;
})()var obj = {value: 1
}var newObj = watch(obj, function(key, newvalue) {if (key == 'value') {document.getElementById('container').innerHTML = newvalue;}
})document.getElementById('button').addEventListener("click", function() {newObj.value += 1
});

我们也可以发现,使用 defineProperty 和 proxy 的区别,当使用 defineProperty,我们修改原来的 obj 对象就可以触发拦截,而使用 proxy,就必须修改代理对象,即 Proxy 的实例才可以触发拦截。

Vue3.0 里为什么要用 Proxy 替代 defineProperty ?

主要是从性能方面考量 

defineProperty:该 API 存在一些局限性,比如对于数组的拦截有问题,为此 Vue 需要专门为数组响应式做一套实现。另外不能拦截那些新增、删除属性。最后 defineProperty 方案在初始化时需要深度递归遍历待处理的对象才能对它进行完全拦截,明显增加了初始化的时间。

以上两点在 Proxy 出现之后迎刃而解。

Proxy:不仅可以对数组实现拦截,还能对 Map、Set 实现拦截。另外 Proxy 的拦截也是懒处理行为。如果用户没有访问嵌套对象,那么也不会实施拦截,这就让初始化的速度和内存占用都改善了。

Vue的代理也是最开始只代理最外层的对象,在访问的时候去判断是否为一个 object,然后再去用 proxy 包裹

当然 Proxy 是有兼容性问题的,IE 完全不支持,所以如果需要 IE 兼容就不合适 

Proxy 和 Object.defineProperty 的区别?

都可以用来实现 JavaScript 对象的响应式,但是它们有一些区别:

① 实现方式

  • Proxy 是 ES6 新增的一种特性,使用了一种代理机制来实现响应式。
  • Object.defineProperty 是在 ES5 中引入的,使用了 getter 和 setter 方法来实现。

② 作用对象

  • Proxy 可以代理整个对象,包括对象的所有属性、数组的所有元素以及类似数组对象的所有元素。
  • Object.defineProperty 只能代理对象上定义的属性。

③ 监听属性

  • Proxy 可以监听到新增属性和删除属性的操作
  • Object.defineProperty 只能监听到已经定义的属性的变化。

④ 性能

由于 Proxy 是 ES6 新增特性,其内部实现采用了更加高效的算法,相对于 Object.defineProperty来说在性能方面有一定的优势。

综上所述,虽然 Object.defineProperty 在 Vue.js 2.x 中用来实现响应式,但是在 Vue.js 3.0 中已经采用了 Proxy 来替代。

这是因为 Proxy 相对于 Object.defineProperty 拥有更优异的性能和更强大的能力。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/818244.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

#新版Onenet云平台使用(ESP8266 AT指令上报数据以及公网MQTT服务器连接测试)

1.上云方式&#xff1a;MQTT 参考&#xff1a; 新版ONENET物联网开放平台ATMQTT指令连接_at指令连接onenet的mqtt-CSDN博客https://blog.csdn.net/lilbye/article/details/131770196 ESP8266-01s入门&#xff1a;AT指令讲解、上云与MQTT通信教程-物联沃-IOTWORD物联网https:…

七:ReentrantReadWriteLock —— 读写锁

目录 1、ReentrantReadWriteLock 入门1.1、概念1.2、案例1.2.1、写写互斥1.2.2 锁降级 2、ReentrantReadWriteLock 源码解析2.1、属性2.2、构造方法2.3、内部类2.4、读写状态的设计 —— 按位切割使用2.5、【写锁】加锁方法 lock() —— ReentrantReadWriteLock.WriteLock2.4.1…

力扣:49. 字母异位词分组

知识点&#xff1a; 散列函数 散列函数能使对一个数据序列的访问过程更加迅速有效&#xff0c;通过散列函数&#xff0c;数据元素将被更快地定位&#xff1a; 1. 直接寻址法&#xff1a;取关键字或关键字的某个线性函数值为散列地址。即H&#xff08;key&#xff09;key或H&a…

蓝桥杯物联网竞赛_STM32L071_16_EEPROM

仍然是没有考过的知识点 朴素的讲就是板子中一块不会因为断电重启而导致数值初始化的一片地址 要注意的是有时候容易把板子什么写错导致板子什么地址写坏了导致程序无法烧录&#xff0c;这个时候记得一直按flash键烧录&#xff0c;烧录时会报错&#xff0c;点击确定&#xff0…

linux的线程概念

目录 1.原理 2.线程的周边概念 3.创建线程的接口 1.pthread_create 2.pthread_join 3.pthread_detach 4.终止线程 5.C11封装的多线程库 4.线程库的大概结构 5.__thread&#xff08;只能修饰内置类型&#xff09; 6.线程的互斥 1.了解原理 2.加锁 1.接口 2.代码示…

机器学习深度学习中的上采样技术

上采样技术&#xff0c;也称为增取样或内插&#xff0c;是信号处理和图像处理中常用的技术&#xff0c;用于增加信号或图像的采样率或分辨率&#xff0c;从而使其变得更大。 在图像处理中&#xff0c;上采样主要用于放大图像&#xff0c;增加图像的尺寸或分辨率。 上采样的实…

【网络编程】Linux网络内核结构以及分布剖析

hello &#xff01;大家好呀&#xff01; 欢迎大家来到我的网络编程系列之Linux网络内核结构以及分布剖析&#xff0c;在这篇文章中&#xff0c;你将会学习到在Linux内核中如何实现网络数据的输入和输出的&#xff0c;并且我会给出源码进行剖析&#xff0c;以及手绘UML图来帮助…

如何应对Android面试官 -> ActivityManagerService 是怎么启动的?

前言 本章主要讲解下 什么是 AMS&#xff0c;以及它是如何启动的&#xff1b; SystemServer SystemServer 通过按下电源键接通电源之后就会启动 BootRoom&#xff0c;BootRoom 就会拉起一个 BootLoader 程序&#xff0c;此程序会拉起 Linux Kernel「系统内核」&#xff0c;我们…

树 和 二叉树

目录 一、树 1、初识树 2、树的一些概念 3、树的表示形式 二、二叉树 1、初识二叉树 2、两种特殊的二叉树 3、二叉树的性质 4、二叉树的遍历 5、实现一棵二叉树 6、二叉树题目&#xff08;没代码的后面会给补上&#xff09; 一、树 1、初识树 &#xff08;1&…

【Java】图片处理工具ImageMagick简介及其在Java中的应用

ImageMagick是一款强大的图像处理软件&#xff0c;它可以用于创建、编辑、合并和转换图像。它支持超过200种图像格式&#xff0c;并且提供了丰富的功能&#xff0c;包括图像缩放、旋转、裁剪、加水印、添加特效等。ImageMagick还支持批量处理图像&#xff0c;可以通过命令行或者…

浅谈网络代理 proxy

总会有各种各样需要用到代理的场景 比如批量提交漏洞、批量注册、批量扫描.... 简单总结一下 代理简介 IP 代理是一种网络技术&#xff0c;通过这种技术&#xff0c;用户可以隐藏其真实的 IP 地址&#xff0c;并使用另一个 IP 地址来代表自己进行互联网上的活动。IP 代理通常由…

xhci 数据结构

xhci 数据结构 xhci 数据结构主要在手册上有详细的定义&#xff0c;本文根据手册进行归纳总结&#xff1a; 重点关注的包括&#xff1a; device contexttrb ringtrb device context设备上下文 设备上下文数据结构由xHC管理&#xff0c;用于向系统软件报告设备配置和状态信息。…

《CSS 知识点》仅在文本有省略号时添加 tip 信息

html <div ref"btns" class"btns"><div class"btn" >这是一段很短的文本.</div><div class"btn" >这是一段很短的文本.</div><div class"btn" >这是一段很长的文本.有省略号和tip.<…

MongoDB副本集部署(windows)

环境准备 本教程演示mongodb4.4 副本集部署&#xff08;一主两从&#xff0c;伪分布式&#xff09; 节点配置主节点localhost:27017从节点1localhost:27018从节点2localhost:27019 每一个节点&#xff08;实例&#xff09;都创建对应的数据文件&#xff08;data&#xff09;…

MySQL中的存储过程详解(上篇)

使用语言 MySQL 使用工具 Navicat Premium 16 代码能力快速提升小方法&#xff0c;看完代码自己敲一遍&#xff0c;十分有用 拖动表名到查询文件中就可以直接把名字拉进来中括号&#xff0c;就代表可写可不写 目录 1.认识存储过程 1.1 存储过程的作用 1.2 存储过程简介…

OpenHarmony轻量系统开发【13】鸿蒙小车开发

13.1 小车介绍 基于鸿蒙系统 Hi3861 的WiFi小车 首先&#xff0c;我们得有一套WiFi小车套件&#xff0c;其实也是Hi3861 加上电机、循迹模块、超声波等模块。 小车安装完大概是这样&#xff1a; 13.2 电机驱动 我们这里先只做最简单的&#xff0c;驱动小车的电机&#xff…

ModuleNotFoundError: No module named ‘llama_index.readers“解决方案

大家好,我是爱编程的喵喵。双985硕士毕业,现担任全栈工程师一职,热衷于将数据思维应用到工作与生活中。从事机器学习以及相关的前后端开发工作。曾在阿里云、科大讯飞、CCF等比赛获得多次Top名次。现为CSDN博客专家、人工智能领域优质创作者。喜欢通过博客创作的方式对所学的…

Centos7,部署etcd集群,基于二进制包,https安全通讯

由于etcd集群https通讯&#xff0c;所以需要自建CA数字证书&#xff0c;学习使用https部署etcd集群前&#xff0c;可以先完成一下&#xff0c;基于http通信的etcd集群&#xff1a; 关于CA原理以及工作可以阅读&#xff0c;以下两篇文章&#xff1a; CA工作原理 对称加密与非对…

【洛谷题解】 CF1468E Four Segments

分析 我们可以知道&#xff0c;如果两个相对的边所在的棍子的长度分别为 a a a 和 b b b&#xff0c;那么这两条边最大只能就为 min ⁡ ( a , b ) \min(a,b) min(a,b)。所以如果让最长的棍子&#xff08;设其长度为 a a a&#xff09;与最短的棍子&#xff08;设其长度为 …