proxy的应用实例

习题一

实现下面例子中的效果,需要怎么做?

var arr = ['a', 'b', 'c', 'd', 'e', 'f'];
console.log(a + a.a + a.a.a + a.a.a.a + a.a.a.a.a); // abcdef

看到a + a.a + a.a.a + a.a.a.a + a.a.a.a.a的形式,第一个出现的解决办法就是:Proxy代理。其次就是toString()方法,因为我们最后的结果需要对应数组中的元素,并且为字符串的形式abcdef
特别注意:开始想a + a.a + a.a.a的形式,只要我将结果对应成'a' + 'b' + 'c'的形式不就可以了吗?其实这个想法是错误的,因为如果a.a对应的结果是字符串'b'的话,那么a.a.a对应的结果就会是'b'.a的形式,这样程序肯定是会抛出异常的,因为字符串'b'不存在属性a。所以这样思路是错误的。
正确的思路:数据类型隐式转换,其实a + a.a + a.a.a这样的形式中牵扯着数据计算时的隐式转换问题。什么意思呢?其实就是按照我们上面的思路来处理,虽然我们上面的思路是错误的,但是错误的原因是我们将a + a.a + a.a.a的形式转为字符串的形式相加。如果我们现在让a + a.a + a.a.a的形式对应为{} + {} + {}的形式应该处理?
{} + {} + {}其实本质上就是隐式类型转换的问题,程序会如何进行隐式转换?

  1. 首先对象是无法进行加法计算的,JS会尝试将对象的形式转为原始类型,因为转为原始数据类型才能够运行加法运算。所以程序会首先寻找对象是否存在Symbol.toPrimitive属性,Symbol.toPrimitive可以将对象转为一个原始值。该函数被调用时,会被传递一个字符串参数hint,表示要转换到原始值的预期类型。hint参数的取值是number、string、default中的任意一个。
  2. 如果该对象不存在Symbol.toPrimitive属性的话,那么就要去寻找toString/valueOf方法。特别注意:这里存在一个顺序问题,是先调用toString方法还是先调用valueOf方法?其实这就牵扯到一个更细的知识点:如果是将对象转换为number的话,那么就先调用valueOf方法;如果是将对象转为string类型的话,那么就先调用toString方法。
  3. {} + {} + {}使用的是加法运算,那么自然就会触发number的形式转换,也就是将对象的形式转为number类型。

所以按照上面的说法,将{} + {} + {}进行改造,看看程序是如何执行的:
在每个对象中分别都添加了toString,valueOf方法,为什么str输出的结果是'abc',而不是'123'?特别注意下:并不是说,一看到+运算符就认为程序直接调用toString()方法。在隐式转换类型中,并不是这样的。上面说过,对象转为原始数据类型:那么首先要看Symbol.toPrimitive,如果不存在Symbol.toPrimitive属性的话,那么对象会通过不同的顺序调用valueOftoString方法将其转为原始数据类型。
所以说,我们下面的例子str输出的是字符串'abc'

var str = {valueOf() {return 'a'},toString() {return '1'}
} + {valueOf() {return 'b'},toString() {return '2'}
} + {valueOf() {return 'c'},toString() {return '3'}
};
console.log(str); // abc

明白了对象转为原始值类型的顺序之后就很简单了。a + a.a + a.a.a + a.a.a.a这种形式,我们可以用Proxy代理对象,很显然被代理的对象是a
首先我们要明白Proxy到底有什么用呢?Proxy用于创建一个目标对象的代理对象,从而实现目标对象基本操作的拦截与自定义(如属性查找、赋值、枚举、函数)。
所以下面例子中的变量a就是代理对象,而new Proxy()构造函数中的参数就是目标对象。
目标对象中的参数是什么意思?

  1. values:结果需要对应的数组集合。
  2. step:计步器,既然结果要对应数组中的元素,就需要一个类似计步器的东西。
  3. valueOf/toString:对象转为原始数据类型需要调用的方法,valueOftoString方法都可以,二者只不过是调用的顺序不同。

get函数是什么意思呢?其实get函数就是getter函数,也就是说当对代理对象进行属性读取操作时执行的方法。get函数中的参数对应着什么意思呢?

  1. target:目标对象,也就是需要被代理的对象。(与new Proxy()构造函数的参数相等)
  2. key:读取操作时,获取到的属性名。
  3. receiverProxy或者继承Proxy对象。

为什么我们在读取操作的时候,需要判断key值呢?因为在读取操作的时候,受到一些其它属性的影响,例如key的值可能是Symbol.toPrimitive、valueOf、toString之类的,所以我们要进行排除。当读取操作获取到的属性名不存在目标对象的身上时,此时我们就要返回receiver代理对象。
这样做的目的,是将a + a.a + a.a.a的形式转换为{} + {} + {}的形式,为什么step能够实现不停的自增操作呢?因为我们在get函数中判断key值,当key值满足条件时,返回的是代理对象a引用。也就是说a.a, a.a.a, a.a.a.a指向的都是同一个引用,也就是代理对象a,所以step肯定会自增。

let obj = {step:0,arr:['a','b','c','d','e','f'],valueOf:function(){return this.arr[this.step++]}
}
let a = new Proxy(obj,{get(target, key, receiver){if(!Reflect.has(target,key)&& typeof key!=='symbol'){return receiver}else{return target[key]}}
});
console.log(a + a.a + a.a.a + a.a.a.a + a.a.a.a.a+a.a.a.a.a.a); // abcdef

习题二

console.log(a[10][20] + 30); // 60
console.log(a[100][200] + 300); // 600
console.log(a[200][300] + 1000); // 1500

看到这样的形式,我们需要马上想到Proxy的解决方案。这题也有几个需要注意的特点:

  1. 首先是a[10]的形式,这很显然是对象属性读取操作,并且是通过[]中括号的方式进行的。
  2. 其次是a[10][20]的形式,这说明a[10]返回的也是对象,这样才能够链式的调用。如果a[10]返回的不是对象或者没有对象没有20属性,程序肯定是抛出错误的。
  3. 然后是隐式转换的问题,因为a[100][200] + 300需要运算出结果,那么对于a[100][200]来说,程序需要尝试对其进行数据的隐式转换。
  4. 最后是结果,我们需要将[]中括号传入的属性值都累加起来。显然需要一个累加器,用来保存最后返回的结果。

首先还是需要Proxy代理对象,让每一次对代理对象进行读取操作的时候,我们都要累加sum的值,因为最后我们要返回这个累计的结果。
为什么要在get函数最后返回receiver呢?因为receiver表示的是代理对象引用,所以我们返回代理对象就能够实现a[10][20]的调用方式。
为什么有key === Symbol.toPrimitive的判断呢?因为当程序执行到a[10][20] + 30的时候,程序会尝试将a[10][20]从代理对象的形式转为原始数据类型。当进行数据类型转换的时候,首先会去对象上寻找Symbol.toPrimitive的属性,也就是说明程序需要调用Symbol.toPrimitive方法将对象转为原始数据类型。所以我们需要返回一个函数用于Symbol.toPrimitive方法。
注意我们需要重置target对象中sum的值,否则会影响到后面的程序。

const a = new Proxy({// 累加器sum: 0
}, {get(target, key, receiver) {// 当key等于Symbol.toPrimitive的时候,说明a[10][20]需要从对象转换为原始数据类型if (key === Symbol.toPrimitive) {let { sum } = target;// 重置sum值target.sum = 0;// 返回Symbol.toPrimitive方法return (hint) => sum;} else {target.sum += Number(key);}return receiver;}
})

Symbol.toPrimitive()

对象的Symbol.toPrimitive属性,指向一个方法。该对象被转为原始类型的值时,会调用这个方法,返回该对象对应的原始类型值。
Symbol.toPrimitive被调用时,会接受一个字符串参数,表示当前运算的模式,一共有三种模式:

  1. Number:该场合需要转为数值。
  2. String:该场合需要转为字符串。
  3. Default:该场合可以转为数值,也可以转为字符串。

为什么3 + obj会走default模式呢?因为+运算符的特殊性,因为它有可能是字符串拼接操作,也有可能是数值的加法运算。所以只能走default模式。

let obj = {[Symbol.toPrimitive](hint) {switch (hint) {case 'number':return 123;case 'string':return 'str';case 'default':return 'default';default:throw new Error();}}
};2 * obj // 246
3 + obj // '3default'
obj == 'default' // true
String(obj) // 'str'

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

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

相关文章

LateX的基础学习

what can i say 在text.tex中写下 \documentclass{article} \begin{document]Hello \LaTeX. \end{document} 关闭记事本,cmd中dir保存,用latex text.tex来编译,可以命令行慢慢编译,这可以做成bat文件 为什么不直接开始在texst…

Java基本语法(基础部分)

Java基本语法 文章目录 Java基本语法前言一、准备工作1.1 计算机软件与硬件1.2 计算机编程语言1.3 Java语言概述&程序分析1.4 Java环境搭建&Java API1.5 Java核心机制JVM 二、变量2.1 关键字&标识符2.2 变量2.3 数据类型(基本数据类型)2.3.1 基本数据类型2.3.2 基本…

Go源码--Strings库

1. 简介 strings库 存储了 一些针对 字符串的具体操作 其 代码短小精悍 可以学习到很多编程的思路 尤其是 涉及到字符串使用性能的方面,其源码库有好多的优秀案例可以学习。向强者对齐不一定成为强者,但向弱者对齐一定变为弱者。 介绍思路是先介绍 stri…

oracle操作系统OS认证和密码文件认证

1 说明 1.1 常见认证方式 Oracle登录认证方式主要涉及到如何验证用户身份以访问数据库。Oracle数据库提供了多种认证机制来确保数据的安全性和访问控制,每种方式都有其特定的使用场景和安全性考虑。以下是Oracle中常见的登录认证方式: 1、基于操作系统…

图像处理技术与应用(一)

图像处理技术与应用入门 使用skimage进行图像读取和显示 skimage库(Scikit-image)提供了一个强大的工具集,用于执行各种图像处理任务。以下是如何使用skimage读取和显示图像的基本示例: from skimage import ioimg io.imread(…

Unpaired Image Super-Resolution using Pseudo-Supervision

CVPR2020解决的问题是当前的SR模型基于成对的数据,且其中LR的图片是通过HR经过bicubic下采样得到的,而实际图片的degradation过程更为复杂,且本文提出的方法不需要成对的HR-LR数据;提出的模型包含两个部分: – correct…

中北大学软件学院操作系统实验二进程调度算法

实验时间 2024年 4 月13日14时至16时 学时数 2 1.实验名称 实验二进程调度算法 2.实验目的 (1)加深对进程的概念及进程调度算法的理解; (2)在了解和掌握进程调度算法的基础上,编制进程调度算法通用程序,将调试结果显示在计算机屏幕上&am…

什么是大语言模型以及如何构建自己的大型语言模型?

一、关于大语言模型 LLM 对于无数的应用程序非常有用,如果我们自己从头开始构建一个,那我们可以了解底层的ML技术,并可以根据特定需求定制LLM,但是对资源的需求巨大。大型语言模型是一种 ML 模型,可以执行各种自然语言…

LeetCode 315—— 计算右侧小于当前元素的个数

阅读目录 1. 题目2. 解题思路一3. 代码实现一4. 解题思路二5. 代码实现二 1. 题目 2. 解题思路一 参考 剑指 Offer——数组中的逆序对,我们依然借助于归并排序中的合并操作来计算某个元素右侧小于它的元素个数。 如上图最左边所示,第五行开始进行第一次…

uthash哈希库使用详解(增删改查和遍历,示例代码)

在C语言中,标准库并没有提供哈希表的实现,因此很多开发者需要自己实现哈希表,这通常是一个复杂且容易出错的过程。幸运的是,有像uthash这样的开源库可以帮助我们简化这一过程。本文将对uthash的使用进行详尽的讲解,包括…

国内首个48小时大模型极限挑战赛落幕,四位“天才程序员”共同夺冠

4月21日晚,第四届ATEC科技精英赛(ATEC2023)线下赛落幕。本届赛事以大模型为技术基座,围绕“科技助老”命题,是国内首个基于真实场景的大模型全链路应用竞赛。ATEC2023线下赛采用48小时极限挑战的形式,来自东…

【Linux开发 第十一篇】rpm和yum

rpm rpm用于互联网下载包的打包及安装工具,它包含在某些Linux分发版中,就是一种Linux中软件包的管理工具 rpm包指令 rpm -qa:查询所安装的所有的rpm软件包 rpm -qa | more rom -qa | grep X rpm -q 软件包名:查询软件包是否安装 rpm -qi 软…

2024年最新版云开发cms开通步骤,开始开发微信小程序前的准备工作,认真看完奥!

小程序官方有改版了,搞得石头哥不得不紧急的再新出一版,教大家开通最新版的cms网页管理后台 一,技术选型和技术点 1,小程序前端 wxml css JavaScript MINA原生小程序框架 2,数据库 云开发 云数据库 云…

一般转行嵌入式应该怎么做?

转行嵌入式可以考虑以下方向: 嵌入式软件开发:包括嵌入式操作系统的开发、应用软件开发等。 嵌入式硬件设计:涉及电路设计、微处理器应用等。 物联网应用开发:利用嵌入式技术实现物联网设备的连接、控制和数据处理。 华清远见的…

options表的service

目录 1、 * options表的service 1.1、 insertOption 1.2、 saveOptions 1.3、 getOptions package com.my.blog.website.service.impl; import com.my.blog.website.dao.OptionVoMapper;

Mysql优化之分区分表

为什么要分区分表 分区和分表是两种用于优化大型数据集查询性能的技术,它们有不同的应用场景和优势。随着数据库数据越来越大,单个表中数据太多。以至于查询速度变慢,而且由于表的锁机制导致应用操作也受到严重影响,就出现了数据…

springboot 批量下载文件, zip压缩下载

一、使用hutool 工具类 效果&#xff1a;下载速度可以 1、依赖&#xff1a;hutool <dependency><groupId>cn.hutool</groupId><artifactId>hutool-all</artifactId><version>5.8.26</version> </dependency>2、调用方式 im…

Android Studio开发工具学习之Git分支操作

这里写目录标题 2.1 查看、创建本地分支2.1.1 命令行查看与创建2.1.2 Git窗口查看与创建 2.2 切换分支&#xff1a;Checkout2.3.1 通过命令行切换 2.3.2 通过Git窗口切换 2.3 合并分支&#xff1a;Merge2.3.1 操作command_new、gui-new分支2.3.1 通过命令行将gui-new分支合并至…

Day35代码随想录贪心part04:860.柠檬水找零、406.根据身高重建队列、452. 用最少数量的箭引爆气球

Day35 贪心part04 860.柠檬水找零 leetcode题目链接&#xff1a;860. 柠檬水找零 - 力扣&#xff08;LeetCode&#xff09; **复习一下dict的语法&#xff1a;**wallet[i] wallet.get(i, 0)1 本题一开始尝试用逐层判断去做&#xff0c;但这样好像并不合理 思路&#xff1…

深度学习网络训练,Loss出现Nan的解决办法

文章目录 前言 一、原因 二、典型实例 1. 梯度爆炸 2. 不当的损失函数 3. 不当的输入 前言 模型的训练不是单纯的调参&#xff0c;重要的是能针对出现的各种问题提出正确的解决方案。本文就训练网络loss出现Nan的原因做了具体分析&#xff0c;并给出了详细的解决方案&#xff…