整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:

如何使用Proxy实现JavaScript中的观察者模式

软件开发中,尤其是JavaScript中,观察者模式是一种行为设计模式,它定义了一种一对多的关系。它允许多个观察者对象监听一个主题对象,并在主题状态发生变化时自动得到通知。这种模式常用于事件系统、数据绑定等场景。

在JavaScript中,我们可以利用Proxy对象来实现观察者模式。Proxy对象允许我们拦截和自定义对目标对象的操作,如属性访问、赋值、枚举和函数调用。

本文将逐步讲解如何使用Proxy在JavaScript中实现观察者模式。我们将创建一个观察者类,定义一个处理程序对象,并创建一个可观察对象。此外,我将展示一个常见的前端场景,应用我们的Proxy基础观察者实现来解决问题。最后,我将对文章内容进行总结。

1. 什么是观察者模式?

观察者模式是一种设计模式,其中一个对象(主题)维护一组依赖于它的对象(观察者),并在其状态发生变化时通知这些观察者,通常是通过调用它们的方法。此模式常用于实现分布式事件处理系统。

2. 使用Proxy实现观察者模式

第一步:创建观察者类

首先,我们需要创建一个观察者类,该类将包含添加、删除和通知观察者的方法。

class Observer {
    constructor() {
        this.observers = [];
    }

    addObserver(observer) {
        this.observers.push(observer);
    }

    removeObserver(observer) {
        this.observers = this.observers.filter(obs => obs !== observer);
    }

    notifyObservers(message) {
        this.observers.forEach(observer => observer.update(message));
    }
}

class ConcreteObserver {
    update(message) {
        console.log('Received message:', message);
    }
}

在这个例子中,Observer类维护一个观察者列表,并提供添加、删除和通知观察者的方法。ConcreteObserver类是一个具体的观察者,实现了处理接收通知的update方法。

第二步:定义处理程序对象

接下来,我们定义一个处理程序对象,以拦截和处理对可观察对象的操作。

const handler = {
    set(target, property, value, receiver) {
        target[property] = value;
        target.notifyObservers({ property, value });
        return true;
    }
};

在这个例子中,处理程序对象包含一个set方法,用于拦截对可观察对象的属性赋值操作。每当可观察对象的属性发生变化时,处理程序将通知所有观察者。

第三步:创建可观察对象

然后,我们创建一个可观察对象并用Proxy包装它。

class Observable extends Observer {
    constructor(target) {
        super();
        return new Proxy(target, handler);
    }
}

const observableObject = new Observable({ name: 'John', age: 30 });

在这个例子中,Observable类继承了Observer类,并用Proxy包装目标对象,以拦截和处理属性操作。

3. 在前端场景中应用观察者模式

接下来,我们将展示如何在一个经典的前端数据绑定场景中使用Proxy实现观察者模式。假设我们有一个简单的HTML表单,需要实现双向数据绑定。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Observer Pattern with Proxy</title>
</head>
<body>
    <input type="text" id="nameInput" placeholder="Enter your name">
    <p id="nameDisplay"></p>
    <script>
        // 观察者类和具体观察者类
        class Observer {
            constructor() {
                this.observers = [];
            }
            addObserver(observer) {
                this.observers.push(observer);
            }
            removeObserver(observer) {
                this.observers = this.observers.filter(obs => obs !== observer);
            }
            notifyObservers(message) {
                this.observers.forEach(observer => observer.update(message));
            }
        }
        class ConcreteObserver {
            constructor(element) {
                this.element = element;
            }
            update(message) {
                this.element.textContent = message.value;
            }
        }
        // 处理程序对象
        const handler = {
            set(target, property, value, receiver) {
                target[property] = value;
                target.notifyObservers({ property, value });
                return true;
            }
        };
        // 可观察类
        class Observable extends Observer {
            constructor(target) {
                super();
                return new Proxy(target, handler);
            }
        }
        // 创建可观察对象
        const data = new Observable({ name: '' });
        // 创建具体观察者
        const nameDisplayObserver = new ConcreteObserver(document.getElementById('nameDisplay'));
        data.addObserver(nameDisplayObserver);
        // 处理输入更改事件
        document.getElementById('nameInput').addEventListener('input', (event) => {
            data.name = event.target.value;
        });
    </script>
</body>
</html>

在这个例子中,我们创建了一个可观察对象data,并将其name属性绑定到一个输入字段和一个显示段落。当用户在输入字段中键入时,data.name的值会发生变化。处理程序会拦截此更改并通知所有观察者。观察者nameDisplayObserver随后更新显示段落的内容,实现了双向数据绑定。

通过使用Proxy实现观察者模式,我们可以有效地拦截和处理对象上的属性操作,实现双向数据绑定和响应式更新。本文介绍了观察者模式的基本概念,并详细讲解了如何在JavaScript中使用Proxy实现该模式。希望本文能帮助你更好地理解和应用这种强大的设计模式在你的项目中。

天给大家讲解的是include标签,在打代码的时候总会出现一些重复的样式,这个时候就可以用include标签来减少打代码的次数。


文件名index.html,代码:

{% from 'macros/forms.html' import input %}<!DOCTYPE html><html lang="en"><head>
    <meta charset="UTF-8">
    <title>宏</title>
    <style>
        *{            list-style: none;            text-decoration: none;        }
        .header{            height: 60px;            background: #3a3a3a;            color: #fff;            margin-bottom: 20px;        }
        .header .nav-group{            margin-left: 10px;        }
        .header .nav-group li{            float: left;            line-height: 60px;            margin: 0px 20px;        }
        .nav-group li a{            color: #fff;        }
        .footer{            height: 60px;            background: #3a3a3a;        }
        .footer p{            color: #fff;            margin-left: 30px;            padding-top: 20px;        }
    </style></head><body>
    <div class="header">
        <ul class="nav-group">
            <li><a href="#">新闻</a></li>
            <li><a href="#">音乐</a></li>
            <li><a href="#">贴吧</a></li>
            <li><a href="#">视频</a></li>
        </ul>
    </div>
    <table>
        <tbody>
            <tr>
                <td>账号</td>
                <td>{{ input(placeholder="请输入账号") }}</td>
            </tr>
            <tr>
                <td>密码</td>
                <td>{{ input(type="password", placeholder="请输入密码") }}</td>
            </tr>
            <tr>
                <td></td>
                <td>{{ input(type="submit", value="提交") }}</td>
            </tr>
        </tbody>
    </table>
    <div class="footer">
        <p>页面底部</p>
    </div></body></html>

现在考虑这样一个问题,如果页面头部和底部是很多页面要用的样式,那么如果在每一个新的文件中都要复制相同的代码肯定不是我们希望的,这时候就可以用到include标签了:

用法

{% include '引用文件路径' %}

用include前提是把相同的代码先提取出来,所以我们将对应的代码先提取成文件:

文件结构:

headers.html

<style>
    *{        list-style: none;        text-decoration: none;    }
    .header{        height: 60px;        background: #3a3a3a;        color: #fff;        margin-bottom: 20px;    }
    .header .nav-group{        margin-left: 10px;    }
    .header .nav-group li{        float: left;                line-height: 60px;                margin: 0px 20px;        }
    .nav-group li a{        color: #fff;        }</style><div class="header">
    <ul class="nav-group">
        <li><a href="#">新闻</a></li>
        <li><a href="#">音乐</a></li>
        <li><a href="#">贴吧</a></li>
        <li><a href="#">视频</a></li>
    </ul></div>

footers.html

<style>
    .footer{        height: 60px;        background: #3a3a3a;    }
    .footer p{        color: #fff;        margin-left: 30px;                padding-top: 20px;        }</style><div class="footer">
    <p>页面底部</p></div>

将公共部分提取出以后在调用的地方只需要用include标签调用即可:

index.html

{% from 'macros/forms.html' import input %}<!DOCTYPE html><html lang="en"><head>
    <meta charset="UTF-8">
    <title>宏</title></head><body>
    {% include 'index/headers.html' %}    <table>
        <tbody>
            <tr>
                <td>账号</td>
                <td>{{ input(placeholder="请输入账号") }}</td>
            </tr>
            <tr>
                <td>密码</td>
                <td>{{ input(type="password", placeholder="请输入密码") }}</td>
            </tr>
            <tr>
                <td></td>
                <td>{{ input(type="submit", value="提交") }}</td>
            </tr>
        </tbody>
    </table>
    {% include 'index/footers.html' %}</body></html>

如果还有一个详情页,那么只需要:

detail.html

<!DOCTYPE html><html lang="en"><head>
    <meta charset="UTF-8">
    <title>Detail</title></head><body>
    {% include 'index/headers.html' %}        <p>这是详情页</p>
    {% include 'index/footers.html' %}</body></html>

显示

如果对接口、性能、自动化测试、面试经验交流等感兴趣的,可以关注我的头条号,我会不定期的发放免费的资料,这些资料都是从各个技术网站搜集、整理出来的,如果你有好的学习资料可以私聊发我,我会注明出处之后分享给大家。欢迎分享,欢迎评论,欢迎转发。需要资料的同学可以关注小编+转发文章+私信【测试资料】

文为大家介绍如何使用 CSS 创建一个带搜索的导航栏。

以下实例均是响应式的。

可以先看下效果图:

创建一个搜索栏

HTML 代码

<div class="topnav">

<a class="active" href="#home">主页</a>

<a href="#about">关于</a>

<a href="#contact">联系我们</a>

<input type="text" placeholder="搜索..">

</div>

CSS 代码

/* 在顶部导航栏中添加黑色背景颜色 */

.topnav {

overflow: hidden;

background-color: #e9e9e9;

}