懒得改变原始对象?JavaScript代理模式教你怎样一步步偷懒!

news2024/11/13 14:34:32

前言

系列首发gong zhong hao[『非同质前端札记』] ,若不想错过更多精彩内容,请“星标”一下,敬请关注gong zhong hao最新消息。

懒得改变原始对象?JavaScript代理模式教你怎样一步步偷懒!

何为代理模式

  • 例如,你想预约一家公司的董事长会面,按照正常流程,你只能通过先联系他的秘书,然后跟他的秘书预约时间,约好时间后你们两个才能会面。(也就是说,代理模式的关键是有个中间者来协调你与对方之间的事情,只能通过中间者将事情转达给另一方)。

代理模式的应用

  • 在 Web 开发中,我们通常会在网站或程序中用到图片,当某张图片过大时或网络不佳时,图片区域就是显示一段空白或者直接没有显示(没有设置图片区域高度,待图片加载完成后才会完成自适应图片高度来进行 layout)。友好的做法是在真正的图片还未加载完成时图片区域显示一个loading占位符。
  • 如下代码:
var createImg = (function () {
    var imgNode = document.createElement("img");
    document.appendChild(imgNode);

    return {
        setSrc: function (src) {
            imgNode.src = src;
        },
    };
})();

var proxyImg = (function () {
    var img = new Image();
    img.onload = function () {
        createImg.setSrc(this.src);
    };
    return {
        setSrc: function (src) {
            createImg.setSrc("loading.gif");
            img.src = src;
        },
    };
})();

proxyImg.setSrc("bg.jpg");

代理的意义

  • 可能会有人疑惑,为什么一个图片预加载功能,非要使用什么模式来实现,不适用照样也能实现啊,如下:
var createImg = (function () {
    var imgNode = document.createElement("img");
    document.appendChild(imgNode);

    var img = new Image();
    img.onload = function () {
        imgNode.src = img.src;
    };

    return {
        setSrc: function (src) {
            imgNode.src = "loading.gif";
            img.src = src;
        },
    };
})();

createImg.setSrc("bg.jpg");
  • 为了说明为什么要使用代理模式的意义,我们必须引入一个面向对象的设计原则——单一职责原则,单一职责原则指的是:对于一个类而言,应该仅有一个引起它改变的入口。如果一个对象承担了很多职责,那这个对象将变得很臃肿,那引起它变化的原因可能会有很多个。如果一个对象承担的职责过多,就等于把这些职责耦合在了一起,这种耦合会导致代码很脆弱和低内聚的设计。
  • 在面向对象的程序设计中,大多数情况下,若违反其他任何原则,同时将违反开放-封闭原则。

代理合并请求数据

  • 比如有一个定时任务,会每个一段时间要往数据库中存储一些数据,如果当有新数据进来时,就调用存储数据的接口,这样既浪费性能,代码的执行效率也不会太高。那要求我们写一个
  • 可参考如下:
// 真实对象类 - 用于存储数据
class Storage {
    constructor() {
        this.data = [];
    }

    storeData(data) {
        // 模拟存储数据的操作
        console.log(`Storing data: ${data}`);
        this.data.push(data);
    }

    displayData() {
        console.log("Stored data:", this.data);
    }
}

// 代理对象类 - 延迟存储操作
class ProxyStorage {
    constructor() {
        this.storage = new Storage();
        this.pendingData = [];
        this.timer = null;
        this.delay = 5000; // 定时存储的延迟时间设定为5秒
    }

    storeData(data) {
        this.pendingData.push(data);
        this.scheduleStorage();
    }

    scheduleStorage() {
        if (!this.timer) {
            this.timer = setTimeout(() => {
                this.flushPendingData();
                this.timer = null;
            }, this.delay);
        }
    }

    flushPendingData() {
        this.pendingData.forEach((data) => {
            this.storage.storeData(data);
        });
        this.pendingData = [];
    }

    displayData() {
        this.storage.displayData();
    }
}

// 使用代理对象进行数据存储
const proxyStorage = new ProxyStorage();

// 模拟数据产生
function generateData() {
    const data = Math.random(); // 这里使用随机数作为数据示例
    // 将数据添加到带存储的数据队列中,并启动定时器来延迟存储操作。当在延迟时间内再次调用 storeData 时,则会每次更新带存储的数据队列数据。当定时器触发时,代理对象则会调用 Storage 对象的存储方法,将所有带存储的数据存储起来。
    proxyStorage.storeData(data);
}

// 调用 generateData() 来模拟产生数据
// 在某一个时间段内连续产生数据,但实际触发存储的时间是延迟了的
setInterval(generateData, 1000);

// 模拟数据存储结束后,手动调用 displayData() 显示已存储的数据
setTimeout(() => {
    proxyStorage.displayData();
}, 15000); // 这里设定15秒后结束数据存储并展示存储结果
  • 上面我们实现了基本的数据定时存储功能,但并不完整,如果我们想再加一个暂停,重新开始,停止的逻辑,那如何编写呢?
  • 如下:
// 真实对象类 - 用于存储数据
class Storage {
    constructor() {
        this.data = [];
    }

    storeData(data) {
        // 模拟存储数据的操作
        console.log(`Storing data: ${data}`);
        this.data.push(data);
    }

    displayData() {
        console.log("Stored data:", this.data);
    }
}

// 代理对象类 - 延迟存储操作和定时暂停
class ProxyStorage {
    constructor() {
        this.storage = new Storage();
        this.pendingData = [];
        this.timer = null;
        this.delay = 5000; // 定时存储的延迟时间设定为5秒
        this.paused = false; // 初始状态为未暂停
    }

    storeData(data) {
        this.pendingData.push(data);
        this.scheduleStorage();
    }

    scheduleStorage() {
        if (!this.paused && !this.timer) {
            this.timer = setTimeout(() => {
                this.flushPendingData();
                this.timer = null;
            }, this.delay);
        }
    }

    flushPendingData() {
        this.pendingData.forEach((data) => {
            this.storage.storeData(data);
        });
        this.pendingData = [];
    }

    pause() {
        this.paused = true;
        clearTimeout(this.timer);
        this.timer = null;
    }

    restart() {
        this.paused = false;
        this.scheduleStorage();
    }

    stop() {
        this.pause();
        this.pendingData = [];
    }

    displayData() {
        this.storage.displayData();
    }
}

// 使用代理对象进行数据存储
const proxyStorage = new ProxyStorage();

// 模拟数据产生
function generateData() {
    const data = Math.random(); // 这里使用随机数作为数据示例
    proxyStorage.storeData(data);
}

// 调用 generateData() 来模拟产生数据
// 在某一个时间段内连续产生数据,但实际触发存储的时间是延迟了的
const intervalId = setInterval(generateData, 1000);

// 模拟数据存储进行一段时间后,停止定时器并清空待存储的数据
setTimeout(() => {
    // proxyStorage.stop();
    proxyStorage.pause();
    console.log("Timer stopped and pending data cleared");
}, 8000); // 这里设定8秒后停止定时器和清空待存储的数据

// 模拟数据存储结束后,手动调用 displayData() 显示已存储的数据
setTimeout(() => {
    proxyStorage.displayData();
}, 15000); // 这里设定15秒后结束数据存储并展示存储结果

// 模拟数据存储恢复定时器
setTimeout(() => {
    proxyStorage.restart();
    console.log("Timer restarted");
    clearInterval(intervalId);
}, 20000); // 这里设定20秒后恢复定时器

缓存代理

  • 在为了一些开销较大的运算结果和接口请求时,我们需要使用缓存代理来进行优化,为防止重复的请求造成性能浪费。
  • 题目如下:
  • 通过代理对象来实现对电影信息的缓存
    1. 创建一个真实对象 MovieService,其中包含一个方法 getMovie(id) 用于获取电影信息。模拟返回一些电影数据。
    2. 创建一个代理对象 CachedMovieServiceProxy,通过代理对象实现对电影信息的缓存。
    3. 代理对象内部维护一个缓存对象 cache,在第一次请求时将电影信息存入缓存,并在后续请求时直接从缓存中获取。
    4. 当调用代理对象的 getMovie(id) 方法时,如果缓存中存在对应的电影信息,则直接返回缓存数据;否则,调用真实对象的 getMovie(id) 方法获取电影信息,并将结果存入缓存。
  • 示例输入和输出:
// input:
const movieServiceProxy = new CachedMovieServiceProxy();

console.log(movieServiceProxy.getMovie(1)); // 输出电影信息并缓存
console.log(movieServiceProxy.getMovie(2)); // 输出电影信息并缓存
console.log(movieServiceProxy.getMovie(1)); // 从缓存中输出电影信息


// output:
// Fetching movie with id 1 from the database...
// Caching movie with id 1...
{ id: 1, title: "Movie A", director: "Director A" }

// Fetching movie with id 2 from the database...
// Caching movie with id 2...
{ id: 2, title: "Movie B", director: "Director B" }

// Retrieving movie with id 1 from cache...
{ id: 1, title: "Movie A", director: "Director A" }
  • 实现如下:
// 真实对象类 - 电影服务
class MovieService {
    constructor() {
        // 模拟电影数据
        this.movies = [
            { id: 1, title: "Movie A", director: "Director A" },
            { id: 2, title: "Movie B", director: "Director B" },
            { id: 3, title: "Movie C", director: "Director C" },
        ];
    }

    // 获取电影信息
    getMovie(id) {
        console.log(`Fetching movie with id ${id} from the database...`);
        // 模拟从数据库获取电影信息的操作
        const movie = this.movies.find((movie) => movie.id === id);
        return movie;
    }
}

// 代理对象类 - 缓存代理
class CachedMovieServiceProxy {
    constructor() {
        this.movieService = new MovieService();
        this.cache = {};
    }

    // 获取电影信息(代理方法)
    getMovie(id) {
        if (this.cache[id]) {
            // 如果缓存中有对应的电影信息,则直接返回缓存数据
            console.log(`Retrieving movie with id ${id} from cache...`);
            return this.cache[id];
        } else {
            // 否则,调用真实对象的方法获取电影信息,并将结果存入缓存
            const movie = this.movieService.getMovie(id);
            console.log(`Caching movie with id ${id}...`);
            this.cache[id] = movie;
            return movie;
        }
    }
}

// 使用代理对象获取电影信息
const movieServiceProxy = new CachedMovieServiceProxy();

console.log(movieServiceProxy.getMovie(1)); // 第一次请求,从真实对象获取并缓存
console.log(movieServiceProxy.getMovie(2)); // 第二次请求,从真实对象获取并缓存
console.log(movieServiceProxy.getMovie(1)); // 第三次请求,从缓存中获取

代理模式的优缺点

  • 优点:
    1. 控制访问/增加安全性:可通过代理对象对真实对象的访问进行控制,增加了对真实对象的保护
    2. 延迟初始化:将高开销的操作延迟到真正需要的时候,可优化一些性能
    3. 封装性:可隐藏对象的复杂性,只需要与代理对象打交道即可
  • 缺点:
    1. 增加复杂性:虽然代理模式可分离关注点,但同时也增加了代码的复杂性,因为需要创建和管理代理对象
    2. 透明性问题:虽然透明性是一个优点,但如果过度使用,可能导致代码难以理解和调试。
    3. 性能开销:代理对象需要拦截所有对原始对象的访问,这会导致一些性能开销。

代理模式的适用场景

  1. 访问控制:可用于限制对对象的访问,例如来控制对一些敏感数据的访问。
  2. 虚拟代理:对一个对象需要从网络上加载大量的数据时,可使用虚拟代理来优化,在需要时再加载数据。
  3. 保护代理:由于代理模式可以控制对真实对象的访问,因此可以保护代理。
  4. 缓存代理:可用于实现一个高度重用,并且这个操作很好使的情况。
  5. 智能引用代理:当需要在访问对象时需要执行一些额外的操作时,可使用智能引用代理。
  6. 日志记录:可用于在调用真实对象的方法前后进行日志记录,包括参数,返回结果等信息,便于调试和排查问题。
Tip: 文章部分内容参考于曾探大佬的《JavaScript 设计模式与开发实践》。文章仅做个人学习总结和知识汇总

特殊字符描述:

  1. 问题标注 Q:(question)
  2. 答案标注 R:(result)
  3. 注意事项标准:A:(attention matters)
  4. 详情描述标注:D:(detail info)
  5. 总结标注:S:(summary)
  6. 分析标注:Ana:(analysis)
  7. 提示标注:T:(tips)

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

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

相关文章

倍增与ST算法

倍增与ST算法 倍增倍增原理倍增法的局限例题 :国旗计划 (洛谷 P4155)例题题解带注释的代码 ST算法ST算法原理ST算法步骤ST算法应用场合例题 :【模板】ST表 (洛谷 P3865) 倍增 倍增原理 倍增法的局限 例题 :国旗计划 (洛谷 P4155) 例题题解 带…

华为OD机试真题 Java 实现【报文回路】【2023 B卷 100分】,俗称“礼尚往来”

目录 专栏导读一、题目描述二、输入描述三、输出描述四、解题思路1、报文回路2、异常情况:3、解题思路 五、Java算法源码六、效果展示1、输入2、输出 华为OD机试 2023B卷题库疯狂收录中,刷题点这里 专栏导读 本专栏收录于《华为OD机试(JAVA&…

《JavaSE-第二十章》之线程的创建与Thread类

文章目录 什么是进程?什么是线程?为什么需要线程? 基本的线程机制创建线程1.实现 Runnable 接口2.继承 Thread 类3.其他变形 Thread常见构造方法1. Thread()2. Thread(Runnable target)3. Thread(String name)4. Thread(Runnable target, Str…

epoll复用

cli #include <stdlib.h> #include <stdio.h> #include <string.h> #include <unistd.h> #include <arpa/inet.h> #include <sys/socket.h>// 服务器ip #define IP "192.168.250.100" // 服务器端口 #define PORT 8888int main…

c++11 标准模板(STL)(std::basic_ifstream)(一)

定义于头文件 <fstream> template< class CharT, class Traits std::char_traits<CharT> > class basic_ifstream : public std::basic_istream<CharT, Traits> 类模板 basic_ifstream 实现文件流上的高层输入操作。它将 std::basic_istream…

Flink - souce算子

水善利万物而不争&#xff0c;处众人之所恶&#xff0c;故几于道&#x1f4a6; 目录 1. 从Java的集合中读取数据 2. 从本地文件中读取数据 3. 从HDFS中读取数据 4. 从Socket中读取数据 5. 从Kafka中读取数据 6. 自定义Source 官方文档 - Flink1.13 1. 从Java的集合中读取数据 …

二叉树(C语言)

文章目录 1.树1.1概念1.2相关定义1.3 表示&#xff08;左孩子右兄弟&#xff09; 2.二叉树2.1概念2.2特殊的二叉树1. 满二叉树&#xff1a;2. 完全二叉树&#xff1a; 2.3二叉树的性质2.4练习 3.二叉树的存储结构1. 顺序存储2. 链式存储 4.完全二叉树的代码实现4.1堆的介绍1.堆…

ssm德宏贸易项目java人资企业办公jsp源代码mysql

本项目为前几天收费帮学妹做的一个项目&#xff0c;Java EE JSP项目&#xff0c;在工作环境中基本使用不到&#xff0c;但是很多学校把这个当作编程入门的项目来做&#xff0c;故分享出本项目供初学者参考。 一、项目描述 ssm德宏贸易项目 系统有1权限&#xff1a;管理员 二…

接口自动化测试平台

下载了大神的EasyTest项目demo修改了下<https://testerhome.com/topics/12648 原地址>。也有看另一位大神的HttpRunnerManager<https://github.com/HttpRunner/HttpRunnerManager 原地址>&#xff0c;由于水平有限&#xff0c;感觉有点复杂~~~ 【整整200集】超超超…

查询结果元数据-MetaData对象、数据库工具类的封装、通过反射实现数据查询的封装

六、查询结果元数据-MetaData对象 七、数据库工具类的封装 1、PropertieUtil类 2、DbUtil类 3、DBHepler类 查询&#xff1a; 4、TestDb测试类&#xff1a; 更新&#xff1a; 1&#xff09;插入&#xff1a; 2&#xff09;修改&#xff1a; 3&#xff09;删除&#xff1a; 查…

2024考研408-计算机网络 第二章-物理层学习笔记

文章目录 前言一、通信基础1.1、物理层基本概念1.1.1、认识物理层1.1.2、认识物理层的四种接口特性 1.2、数据通信基础知识1.2.1、典型的数据通信模型及相关术语1.2.2、数据通信相关术语1.2.3、设计数据通信系统要考虑的三个问题&#xff1a;问题1&#xff1a;采用单工通信/半双…

通讯录的实现(超详细)——C语言(进阶)

目录 一、创建联系人信息&#xff08;结构体&#xff09; 二、创建通讯录&#xff08;结构体&#xff09; 三、define定义常量 四、打印通讯录菜单 五、枚举菜单选项 六、初始化通讯录 七、实现通讯的的功能 7.1 增加加联系人 7.2 显示所有联系人的信息 ​7.3 单独查…

【自动化运维】Ansible常见模块的运用

目录 一、Ansible简介二、Ansible安装部署2.1环境准备 三、ansible 命令行模块3.1&#xff0e;command 模块3.2&#xff0e;shell 模块3.3&#xff0e;cron 模块3.4&#xff0e;user 模块3.5&#xff0e;group 模块3.6&#xff0e;copy 模块3.7&#xff0e;file 模块8&#xff…

C++之观察者模式(发布-订阅)

目录 模式简介 介绍 优点 缺点 代码实现 场景说明 实现代码 运行结果 模式简介 观察者模式&#xff08;Observer Pattern&#xff09;&#xff0c;也叫我们熟知的发布-订阅模式。 它是一种行为型模式。 介绍 观察者模式主要关注的是对象的一对多的关系&#xff0c; …

4-3 Working with time series

本文所用数据下载 Data from a Washington, D.C., bike-sharing system reporting the hourly count of rental bikes in 2011–2012 in the Capital Bikeshare system, along with weather and seasonal information. Our goal will be to take a flat, 2D dataset and trans…

搭建网站 --- 快速WordPress个人博客并内网穿透发布到互联网

文章目录 快速WordPress个人博客并内网穿透发布到互联网 快速WordPress个人博客并内网穿透发布到互联网 我们能够通过cpolar完整的搭建起一个属于自己的网站&#xff0c;并且通过cpolar建立的数据隧道&#xff0c;从而让我们存放在本地电脑上的网站&#xff0c;能够为公众互联…

JS——输入输出语法数组的操作

JavaScript输入输出语法 目标&#xff1a;能写出常见的JavaScript输入输出语法 输出语法 语法1&#xff1a; document.write(要输出的内容)作用&#xff1a; 向body内输出内容 注意&#xff1a; 如果输出的内容写的是标签&#xff0c;也会被解析成网页元素 语法2&#xff1a…

2023大同首届信息技术产业峰会举行,共话数字经济新未来

7月28日&#xff0c;“聚势而强共领信创”2023大同首届信息技术产业峰会圆满举行。本次峰会由中共大同市委、大同市人民政府主办&#xff0c;中国高科技产业化研究会国际交流合作中心、山西省信创协会协办&#xff0c;中共大同市云冈区委、大同市云冈区人民政府、诚迈科技&…

一文深入了解Cmk

目录 一、Cmk&#xff08;设备能力指数&#xff09;介绍&#xff1a;二、Cmk&#xff08;设备能力指数&#xff09; 概念&#xff1a;三、Cmk的应用时机&#xff1a;四、Cmk前期准备和要求&#xff1a;五、Cmk测试要求&#xff1a;六、CMK计算公式&#xff1a;七、Cmk实际操作&…

机器学习的关键词和算法总结

随着全球各行业的数据治理、数字化转型智能化辅助的引入发展&#xff0c;机器学习&#xff08;包括深度学习&#xff09;在逐步深入到各行各业&#xff0c;所以&#xff0c;有必要对机器学习的常见术语&#xff0c;经典算法及应用场景进行一次总结&#xff0c;其实机器学习兴起…