1. document.body #

function printPrototypeChain(domObject) {
    let proto = Object.getPrototypeOf(domObject);
    while (proto !== null) {
        console.log(proto);
        proto = Object.getPrototypeOf(proto);
    }
}
printPrototypeChain(document.body);
对象 描述
HTMLBodyElement 这是 HTML 的 <body> 元素,继承自 HTMLElement。包含了与 <body> 元素相关的属性和方法,如 backgroundbgColor 等。
HTMLElement HTMLElement 接口表示所有的 HTML 元素。几乎所有的 HTML 元素都是继承自 HTMLElement,该接口包括了一些常用的属性和方法,如 click()focus()blur()innerTextinnerHTMLouterHTML 等。
Element Element 是所有 DOM 元素的基本接口,包括 HTML 元素(通过 HTMLElement 继承)和 SVG 元素。Element 提供了一些常用的方法和属性,如 getAttribute()setAttribute()removeAttribute()hasAttribute()querySelector()querySelectorAll() 等。
Node Node 接口是 DOM 中的一个主要接口,它是一个树结构中的一个节点,可以是元素节点、文本节点、注释节点等等。许多接口都从 Node 接口继承,包括 ElementNode 包括了一些关于节点本身的属性和方法,如 childNodesparentNodenextSiblingpreviousSiblingappendChild()insertBefore()removeChild()replaceChild() 等。
EventTarget EventTarget 是一个接口,实现这个接口的对象可以接收事件,并且可以创建监听器(listener)来处理这些事件。Node 接口继承了 EventTarget,所以 DOM 中的所有节点都实现了这个接口。主要的方法有 addEventListener()removeEventListener()dispatchEvent()
Object 在 JavaScript 中,几乎所有的对象都是 Object 的实例,从 Object.prototype 继承了方法和属性。这包括数组、函数、日期、正则表达式等。所有的对象都会从 Object 继承一些基本的方法,比如 toString()valueOf() 等。

2. 观察者模式 #

观察者模式是一种行为设计模式,它允许你定义一种订阅机制,可以在对象事件发生时通知多个“观察”该对象的其他对象。

在JavaScript中,你可以使用类和回调函数来实现观察者模式。以下是一个简单的示例:

class Subject {
    constructor() {
        this.observers = [];
    }
    subscribe(observer) {
        this.observers.push(observer);
    }
    unsubscribe(observer) {
        this.observers = this.observers.filter(obs => obs !== observer);
    }
    notify(data) {
        this.observers.forEach(observer => observer.update(data));
    }
}

class Observer {
    constructor(name) {
        this.name = name;
    }
    update(data) {
        console.log(`${this.name} received data: ${data}`);
    }
}

// 使用示例
let subject = new Subject();
let observer1 = new Observer('Observer 1');
let observer2 = new Observer('Observer 2');
let observer3 = new Observer('Observer 3');
subject.subscribe(observer1);
subject.subscribe(observer2);
subject.subscribe(observer3);
subject.notify('Hello, observers!');

在这个示例中,Subject类有一个observers数组,用于存储所有的观察者。subscribe方法用于添加观察者,unsubscribe方法用于移除观察者,notify方法用于通知所有的观察者。

Observer类有一个update方法,当Subject通知观察者时,会调用每个观察者的update方法。

下面是观察者模式的流程图:

Observer Pattern

在这个图中:

3. 发布订阅模式 #

在观察者模式中,观察者和主题之间存在直接关系,观察者直接订阅主题的事件。而在发布订阅模式中,发布者和订阅者之间通常通过事件总线进行通信,它们之间不存在直接关系。

// 定义事件总线
class EventBus {
    constructor() {
        // 存储事件和监听器的映射
        this.events = this.events || new Map();
    }
    // 监听事件
    addListener(type, fn) {
        // 如果该事件已经被监听,则直接在对应的数组中添加方法
        // 如果该事件没有被监听,则设置对应的数组并添加方法
        let callbacks = this.events.get(type)||[];
        callbacks.push(fn);
        this.events.set(type, callbacks);
    }
    // 触发事件
    emit(type) {
        // 获取该事件对应的监听函数队列
        let handle = this.events.get(type);
        // 执行每一个监听函数
        handle.forEach((ele) => {
            ele.apply(this);
        })
    }
}

// 定义发布者
class Publisher {
    constructor(eventBus) {
        this.eventBus = eventBus;
    }
    // 发布事件
    publish(type) {
        this.eventBus.emit(type);
    }
}

// 定义订阅者
class Subscriber {
    constructor(eventBus, type) {
        // 订阅感兴趣的事件
        eventBus.addListener(type, this.update);
    }
    // 当事件发生时,会调用这个方法
    update() {
        console.log('Subscriber received event');
    }
}

// 使用示例
let eventBus = new EventBus();
let publisher = new Publisher(eventBus);
let subscriber1 = new Subscriber(eventBus, 'event1');
let subscriber2 = new Subscriber(eventBus, 'event1');
// 发布者发布事件
publisher.publish('event1');

这个代码示例展示了一个简单的发布订阅模式的实现。在这个例子中,我们定义了一个事件总线EventBus,它负责管理事件和监听器的映射。发布者Publisher可以发布事件,订阅者Subscriber可以订阅事件。当发布者发布一个事件时,所有订阅了这个事件的订阅者都会收到通知。

4. EventTarget #

EventTarget 是一个 DOM 接口,被实现在能够触发事件的对象,或者说,能够创建和触发事件监听器的对象上。在 DOM 中,许多类型的对象,例如 DocumentHTMLElement,和 XMLHttpRequest 等都实现了 EventTarget 接口,使得它们能够注册、触发和接收事件。

EventTarget 主要提供了以下三个方法:

  1. addEventListener(type, listener): 注册一个事件监听器到 EventTarget 上。这个事件监听器在特定类型的事件发生时被触发。

    • type: 一个字符串,代表监听器要接收的事件的类型,如 'click', 'keydown' 等。
    • listener: 当事件发生时被调用的函数或者是一个实现了 EventListener 接口的对象。该函数接收一个参数,这个参数是一个 Event 对象。
  2. removeEventListener(type, listener): 移除已经注册到 EventTarget 的特定的事件监听器。

    • 参数与 addEventListener 相同。
  3. dispatchEvent(event): 发送一个事件到 EventTarget,使得 EventTarget 上的监听器能够接收这个事件

    • event: 一个 Event 对象,或者是继承自 Event 的对象。

以下是一个简单的 EventTarget 使用示例:

<body>
    <button>click</button>
    <script>
        let btn = document.querySelector("button");
        const handleClick = function (event) {
            console.log("Button is clicked!");
        }
        btn.addEventListener("click", handleClick);
        let clickEvent = new Event('click');
        btn.dispatchEvent(clickEvent);
    </script>
</body>
// 定义一个名为EventTarget的类
class EventTarget {
    // 构造函数
    constructor() {
        // 初始化一个空对象,用于存储事件和对应的回调函数
        this.listeners = {};
    }
    // 添加事件监听的方法
    addEventListener(type, callback) {
        // 如果事件类型在listeners对象中不存在,则在listeners对象中创建一个该类型的数组
        if (!(type in this.listeners)) {
            this.listeners[type] = [];
        }
        // 将回调函数添加到对应事件的数组中
        this.listeners[type].push(callback);
    }
    // 移除事件监听的方法
    removeEventListener(type, callback) {
        // 如果事件类型在listeners对象中不存在,则直接返回
        if (!(type in this.listeners)) {
            return;
        }
        // 获取对应事件类型的回调函数栈
        let stack = this.listeners[type];
        // 遍历回调函数栈,如果找到要移除的回调函数,则移除
        for (let i = 0, l = stack.length; i < l; i++) {
            if (stack[i] === callback) {
                stack.splice(i, 1);
                return;
            }
        }
    }
    // 触发事件的方法
    dispatchEvent(event) {
        // 如果事件类型在listeners对象中不存在,则直接返回true
        if (!(event.type in this.listeners)) {
            return true;
        }
        // 获取对应事件类型的回调函数栈
        let stack = this.listeners[event.type];
        // 遍历回调函数栈,依次执行每个回调函数
        for (let i = 0, l = stack.length; i < l; i++) {
            stack[i].call(this, event);
        }
    }
}