本文将通过几个具体的代码片段,深入探讨 JavaScript 中的拖拽功能和观察者模式(发布-订阅模式)的实现及其应用场景。
这些代码片段不仅展示了如何实现这些功能,还解释了其背后的原理和实际用途。通过阅读本文,读者可以更好地理解 JavaScript 的高级特性,并将其应用到实际项目中。
- <!DOCTYPE html>
- <html lang="en">
- <head>
- <meta charset="UTF-8">
- <meta http-equiv="X-UA-Compatible" content="IE=edge">
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
- <title>Document</title>
- <style>
- #father {
- width: 100px;
- height: 100px;
- background: red;
- position: absolute;
- }
- #son {
- width: 100px;
- height: 100px;
- background: green;
- position: absolute;
- left: 110px;
- }
- </style>
- </head>
- <body>
- <div id="father">father</div>
- <div id="son">son</div>
- <script src="js/drag.js"></script>
- <script src="js/subdrag.js"></script>
- <script>
- new Drag('#father');
- new Subdrag('#son');
- </script>
- </body>
- </html>
-
- class Drag {
- constructor(selector) {
- // 获取拖拽的对象
- this.ele = document.querySelector(selector);
- // 添加事件
- this.ele.onmousedown = function (evt) {
- // this: 原来指向了 ele 对象,这里需要一个实例对象
- this.fnDown(evt);
- }.bind(this);
- }
-
- fnDown(evt) {
- let e = evt || window.event;
- // 求鼠标的相对坐标值
- this.dis_x = e.offsetX;
- this.dis_y = e.offsetY;
-
- // 移动事件
- document.onmousemove = function (evt) {
- // this: document
- this.fnMove(evt);
- }.bind(this);
-
- // 鼠标抬起事件
- document.onmouseup = this.fnUp.bind(this);
-
- // 取消默认行为
- document.ondragstart = function () {
- return false;
- };
- }
-
- fnMove(evt) {
- let e = evt || window.event;
- this.ele.style.left = e.pageX - this.dis_x + 'px';
- this.ele.style.top = e.pageY - this.dis_y + 'px';
- }
-
- fnUp() {
- document.onmousemove = null;
- }
- }
-
- class Subdrag extends Drag {
- constructor(selector) {
- // 调用父类的构造函数
- super(selector);
- }
-
- fnMove(evt) {
- let e = evt || window.event;
- let left = e.pageX - this.dis_x;
- let top = e.pageY - this.dis_y;
-
- // 设置边界
- if (left <= 0) {
- left = 0;
- } else if (left >= document.documentElement.clientWidth - this.ele.offsetWidth) {
- left = document.documentElement.clientWidth - this.ele.offsetWidth;
- }
-
- if (top <= 0) {
- top = 0;
- } else if (top >= document.documentElement.clientHeight - this.ele.offsetHeight) {
- top = document.documentElement.clientHeight - this.ele.offsetHeight;
- }
-
- this.ele.style.left = left + 'px';
- this.ele.style.top = top + 'px';
- }
- }
-
拖拽功能 是一种常见的用户交互方式,允许用户通过鼠标移动元素。在上述代码中,Drag 类实现了基本的拖拽功能,包括:
Subdrag 类继承自 Drag 类,并在其基础上增加了边界限制,确保拖拽的元素不会超出视口范围。这使得拖拽体验更加友好和实用。
- <!DOCTYPE html>
- <html lang="en">
- <head>
- <meta charset="UTF-8">
- <meta http-equiv="X-UA-Compatible" content="IE-edge">
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
- <title>Document</title>
- </head>
- <body>
- <script>
- // 发布者 - 药店
- let drugstore = {
- // 花名册
- obj: {},
- // 订阅方法
- listen: function (eventName, fn) {
- if (!(this.obj[eventName])) {
- this.obj[eventName] = [];
- }
- this.obj[eventName].push(fn);
- },
- // 发布方法
- publish: function (eventName, data) {
- this.obj[eventName]?.map(fn => {
- fn(data);
- });
- }
- };
-
- let data = {
- type: "N95",
- price: 30,
- num: 500
- };
-
- let liaohuadata = {
- type: '连花',
- price: 200,
- num: 50
- };
-
- // 小周
- function xiaozhou(data) {
- if (data.price > 20) {
- console.log('黑店,太贵了,我就算不出门,我也不去买!');
- }
- }
- drugstore.listen('mask', xiaozhou);
-
- // 小易
- function xiaoyi(data) {
- if (data.type === 'N95') {
- console.log('先少买一点,等有普通医用口罩时,再多屯点');
- }
- }
- drugstore.listen('mask', xiaoyi);
-
- // 小王
- function xiaowang(data) {
- if (data.num > 100) {
- console.log('多屯点,再高价卖给别人');
- }
- }
- drugstore.listen('mask', xiaowang);
-
- // 老王
- function laowang(data) {
- console.log('有佛祖保佑,不需要口罩');
- }
- drugstore.listen('mask', laowang);
-
- drugstore.publish('mask', data);
- </script>
- </body>
- </html>
-
观察者模式(发布-订阅模式)是一种设计模式,用于定义对象间的一种一对多依赖关系。当一个对象的状态发生改变时,所有依赖于它的对象都会收到通知并自动更新。在上述代码中,drugstore 作为发布者,维护了一个名为 obj 的花名册,记录了不同事件的订阅者列表。
具体实现如下:
通过这种方式,多个订阅者可以根据不同的条件对同一事件做出响应。例如,在上述代码中,四个不同的订阅者(小周、小易、小王、老王)根据药店发布的口罩信息做出了不同的反应。
- <!DOCTYPE html>
- <html lang="en">
- <head>
- <meta charset="UTF-8">
- <meta http-equiv="X-UA-Compatible" content="IE-edge">
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
- <title>Document</title>
- </head>
- <body>
- <script>
- // 发布者--售楼处
- let salesOffice = {
- // 花名册
- arr: [],
- // 订阅方法
- listen: function (fn) {
- this.arr.push(fn);
- },
- // 发布消息
- publish: function (data) {
- // 遍历花名册
- this.arr.forEach(fn => {
- fn(data);
- });
- }
- };
-
- // 发布的消息
- let data = {
- size: 80,
- money: '10000元/平',
- num: '50套'
- };
-
- // 小明
- salesOffice.listen(function (data) {
- if (data.size < 100) {
- console.log('房子太小,不在考虑');
- }
- });
-
- // 小龙
- salesOffice.listen(function (data) {
- if (parseInt(data.money) < 150000) {
- console.log('预算充足,价格还可以,马上去订购一套');
- }
- });
-
- // 小强
- salesOffice.listen(function (data) {
- if (parseInt(data.num) > 30) {
- console.log('赶紧去,先屯20套再说');
- }
- });
-
- // 发布消息
- salesOffice.publish(data);
- </script>
- </body>
- </html>
-
在售楼处示例中,salesOffice 作为发布者,同样使用了花名册 arr 来记录订阅者的回调函数。不同的是,这里的订阅者只关心新楼盘推出的消息,而不需要区分不同的事件类型。
具体实现如下:
通过这种方式,多个订阅者可以根据新楼盘的信息做出不同的反应。例如,在上述代码中,三个不同的订阅者(小明、小龙、小强)根据售楼处发布的房屋信息做出了不同的决策。
通过以上两个主要部分的详细探讨,我们深入分析了 JavaScript 中的拖拽功能和观察者模式的实现及其应用场景。拖拽功能使得用户能够直观地操作页面元素,而观察者模式则提供了一种优雅的方式来处理对象间的依赖关系,避免了紧耦合的问题。
在实际项目中,合理运用这些技术和模式可以显著提高用户体验和代码的可维护性。希望本文的内容对读者有所帮助,欢迎继续探索 JavaScript 的更多可能性。无论是构建复杂的用户界面还是实现高效的数据通信,掌握这些高级编程技巧都将为开发者带来更多的便利和灵活性。