JavaScript 链式调用 | 设计模式 笔记

系列 - 前端八股文基础
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
import { createApp } from 'vue';

// 创建一个 Vue 应用实例
const app = createApp({
  data() {
    return {
      message: 'Hello, Vue!'
    };
  },
  methods: {
    handleClick() {
      alert('Button clicked!');
    }
  }
});

// 链式调用:配置应用并绑定事件
app
  .component('MyButton', {
    template: `<button @click="handleClick">Click Me</button>`,
    methods: {
      handleClick() {
        this.$emit('custom-click');
      }
    }
  })
  .directive('highlight', {
    mounted(el) {
      el.style.backgroundColor = 'yellow';
    }
  })
  .mixin({
    created() {
      console.log('Mixin created hook');
    }
  })
  .mount('#app'); // 挂载到 DOM

createApp:创建一个 Vue 应用实例。传入一个根组件配置对象,包含 data 和 methods。

链式调用

  • .component(): 注册一个全局组件 MyButton,组件中定义了一个按钮,点击按钮时会触发 custom-click 事件。
  • .directive(): 注册一个全局指令 highlight,当元素挂载时,背景颜色会变为黄色。
  • .mixin(): 添加一个全局混入,在组件的 created 生命周期钩子中输出日志。
  • .mount(): 将应用挂载到 DOM 中,挂载点为 #app

事件绑定

MyButton 组件中,通过 @click 绑定了一个点击事件,触发时会调用 handleClick 方法,并通过 $emit 触发 custom-click 事件。

在父组件中,可以通过监听 custom-click 事件来处理按钮点击逻辑。

use  方法是 Vue 应用实例的一个方法,用于安装插件。插件可以是一个对象(必须提供  install  方法),也可以是一个函数(会被直接调用)。Vue 会调用插件的  install  方法,并将 Vue 应用实例作为参数传递给它。

1
app.use(plugin, options)
  • plugin: 要安装的插件,可以是一个对象或函数。
  • options: 可选的配置对象,传递给插件的  install  方法。
  1. use 的逻辑
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
function use(plugin, options) {
  if (typeof plugin.install === 'function') {
    // 如果插件是一个对象,并且提供了 install 方法
    plugin.install(this, options);
  } else if (typeof plugin === 'function') {
    // 如果插件是一个函数
    plugin(this, options);
  } else {
    throw new Error('Plugin must be a function or an object with an install method.');
  }
  return this; // 返回应用实例,支持链式调用
}
  1. 使用
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
// 自定义插件
const myPlugin = {
  install(app, options) {
    console.log('My plugin is installed with options:', options);
    // 添加全局方法或属性
    app.config.globalProperties.$myMethod = () => {
      console.log('Hello from my plugin!');
    };
    // 注册全局组件
    app.component('my-component', {
      template: '<div>My Custom Component</div>'
    });
  }
};

// 使用插件
createApp(App)
  .use(myPlugin, { someOption: true }) // 安装自定义插件
  .mount('#app');

PromiseA+规范详细描述了 JavaScript 中 Promise 的行为标准,确保不同的 Promise 实现可以相互兼容。

promise:先是一个对象或函数,其具有,然后是方法,其行为符合本规范。 thenable:先用一个具有,再用方法的对象或函数。 value:任何合法的脚本值(包括未定义,一个已启用,或一个 Promise)。 exception:一个使用抛出语句抛出的值。 reason:一个表示 Promise 被拒绝的原因

Promise 状态

一个 promise 必须处于以下三种状态之一:pending(等待态),fulfilled(执行态),或 rejected(拒绝态)。

pending:可以迁移到 fulfilled 或 rejected 状态。 fulfilled:不可迁移到其他状态,必须有一个 value。 rejected:不可迁移到其他状态,必须有一个 reason。

then 方法 一个 promise 必须提供一个 then 方法来访问其当前或最终的 value 或 reason。 promise.then(onFulfilled, onRejected)

  • onFulfilled 和 onRejected 都是可选参数。
  • 如果 onFulfilled 不是函数,必须忽略它。
  • 如果 onRejected 不是函数,必须忽略它。

onFulfilled 的执行

  • onFulfilled 必须在 promise 完成后被调用,且 promise 的 value 作为其第一个参数。
  • onFulfilled 不得在 promise 完成前被调用。
  • onFulfilled 必须只被调用一次。

onRejected 的执行

  • onRejected 必须在 promise 被拒绝后被调用,且 promise 的 reason 作为其第一个参数。
  • onRejected 不得在 promise 被拒绝前被调用。
  • onRejected 必须只被调用一次。

then 方法必须返回一个 promise

1
promise2 = promise1.then(onFulfilled, onRejected);

promise2 必须是一个新的 promise。

处理返回的值

  • 如果 onFulfilled 或 onRejected 返回一个值,则运行 Promise 解决程序Resolve(promise2, x)
  • 如果 onFulfilled 或 onRejected 抛出一个异常,则 promise2 必须以作为拒绝原因。
  • 如果 onFulfilled 不是一个函数且 promise1 完成,promise2 必须以 promise1 的 value 作为其 value。
  • 如果 onRejected 不是一个函数且 promise1 被拒绝,promise2 必须以 promise1 的 reason 作为其 reason。
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';

function isFunction(fn) {
  return Object.prototype.toString.call(fn) === '[object Function]';
}

function myPromise(fn) {
  if (!this || this.constructor !== myPromise) {
    throw new TypeError('Promise must be called with new');
  }
  if (!isFunction(fn)) {
    throw new TypeError('Promise resolver undefined is not a function');
  }

  this.status = PENDING;
  this.value = void 0;
  this.reason = void 0;
  this.onFulfilledCallbacks = [];
  this.onRejectedCallbacks = [];

  const resolve = value => {
    if (this.status === PENDING) {
      this.status = FULFILLED;
      this.value = value;

      // 发布
      this.onFulfilledCallbacks.forEach(fn => fn());
    }
  };

  const reject = reason => {
    if (this.status === PENDING) {
      this.status = REJECTED;
      this.reason = reason;

      // 发布
      this.onRejectedCallbacks.forEach(fn => fn());
    }
  };

  try {
    fn(resolve, reject);
  } catch (e) {
    reject(e);
  }
}

// 添加 then 原型方法
myPromise.prototype.then = function (onFulfilled, onRejected) {
  // 处理边界
  onFulfilled = isFunction(onFulfilled) ? onFulfilled : value => value;
  onRejected = isFunction(onRejected) ? onRejected : reason => { throw reason };

  // 返回一个新的 Promise
  const promise2 = new myPromise((resolve, reject) => {
    if (this.status === FULFILLED) {
      setTimeout(() => {
        try {
          const x = onFulfilled(this.value);
          resolve(x);
        } catch (e) {
          reject(e);
        }
      }, 0);
    } else if (this.status === REJECTED) {
      setTimeout(() => {
        try {
          const x = onRejected(this.reason);
          resolve(x);
        } catch (e) {
          reject(e);
        }
      }, 0);
    } else if (this.status === PENDING) {
      // 订阅
      this.onFulfilledCallbacks.push(() => {
        setTimeout(() => {
          try {
            const x = onFulfilled(this.value);
            resolve(x);
          } catch (e) {
            reject(e);
          }
        }, 0);
      });

      this.onRejectedCallbacks.push(() => {
        setTimeout(() => {
          try {
            const x = onRejected(this.reason);
            resolve(x);
          } catch (e) {
            reject(e);
          }
        }, 0);
      });
    }
  });

  return promise2;
};


// 测试
const promise = new myPromise((resolve, reject) => {
  setTimeout(() => {
    resolve('success');
  }, 1000);

});

promise.then(value => {
  console.log(value);
  return 'msg from then1';
}).then(value => {
  console.log(value);
  return 'msg from then2';
}).then(value => {
  console.log(value);
});

限制用 JavaScript 实现:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
Student("Alice"); // 输出 I am Alice

Student("Alice").rest(10).learn("computer");
/* 输出
I am Alice
经过10秒后
After 10 seconds
Learning computer
*/

Student("Alice").restFirst(5).learn("Math");
/* 输出
I am Alice
经过5秒后
After 5 seconds
Learning Math
*/
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
class Student {
    constructor(name) {
        this.name = name;
        this.tasks = []; // 任务队列
        this.tasks.push(() => {
            console.log(`I am ${this.name}`);
            this.next(); // 执行下一个任务
        });
        setTimeout(() => this.next(), 0); // 异步启动任务队列
    }

    next() {
        const task = this.tasks.shift(); // 取出队列中的第一个任务
        task && task(); // 如果任务存在,则执行
    }

    rest(seconds) {
        this.tasks.push(() => {
            setTimeout(() => {
                console.log(`After ${seconds} seconds`);
                this.next(); // 执行下一个任务
            }, seconds * 1000);
        });
        return this; // 返回this以支持链式调用
    }

    restFirst(seconds) {
        this.tasks.unshift(() => {
            setTimeout(() => {
                console.log(`After ${seconds} seconds`);
                this.next(); // 执行下一个任务
            }, seconds * 1000);
        });
        return this; // 返回this以支持链式调用
    }

    learn(subject) {
        this.tasks.push(() => {
            console.log(`Learning ${subject}`);
            this.next(); // 执行下一个任务
        });
        return this; // 返回this以支持链式调用
    }
}

// 测试用例
new Student("Alice"); // 输出 I am Alice

new Student("Alice").rest(10).learn("computer");
/* 输出
I am Alice
经过10秒后
After 10 seconds
Learning computer
*/

new Student("Alice").restFirst(5).learn("Math");
/* 输出
I am Alice
经过5秒后
After 5 seconds
Learning Math
*/

发布-订阅模式(Pub-Sub)和观察者模式(Observer)是两种常见的设计模式,它们都用于处理对象之间的通信和事件处理。

观察者模式定义了一种一对多的依赖关系,当一个对象(称为Subject,主题)的状态发生变化时,所有依赖于它的对象(称为Observers,观察者)都会收到通知并自动更新。

Subject

  • 维护一个观察者列表。
  • 提供注册(attach)和注销(detach)观察者的方法。
  • 在状态变化时通知所有观察者(notify)。

Observer(观察者)

  • 定义一个更新接口(update),用于接收主题的通知。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
class Subject {
    constructor() {
        this.observers = [];
    }

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

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

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

class Observer {
    constructor(name) {
        this.name = name;
    }

    update() {
        console.log(`${this.name} received an update!`);
    }
}

// 使用
const subject = new Subject();
const observer1 = new Observer('Observer 1');
const observer2 = new Observer('Observer 2');

subject.attach(observer1);
subject.attach(observer2);

subject.notify(); // Observer 1 received an update! Observer 2 received an update!

发布-订阅模式通过一个事件中心(Event Bus)来解耦发布者和订阅者。发布者将消息发布到事件中心,订阅者从事件中心订阅感兴趣的消息。发布者和订阅者之间没有直接的依赖关系。

Publisher(发布者): 负责发布消息到事件中心。

Subscriber(订阅者): 向事件中心订阅感兴趣的消息。

Event Bus(事件中心):维护一个消息队列和订阅者列表;负责将消息分发给订阅者。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
class EventBus {
  constructor() {
    this.events = {};
  }

  subscribe(event, callback) {
    if (!this.events[event]) {
      this.events[event] = [];  // 初始化事件队列
    }
    this.events[event].push(callback);
  }

  publish(event, data) {
    // 边界检查
    if (!this.events[event]) {
      return;
    }
    if (this.events[event]) {
      this.events[event].forEach(callback => callback(data));
    }
  }
}

// 使用
const eventBus = new EventBus();

// 订阅者
eventBus.subscribe('news', data => {
  console.log(`Subscriber 1 received news: ${data}`);
});

eventBus.subscribe('news', data => {
  console.log(`Subscriber 2 received news: ${data}`);
});

// 发布者
eventBus.publish('news', 'Breaking news!');
// 输出:
// Subscriber 1 received news: Breaking news!
// Subscriber 2 received news: Breaking news!
特性观察者模式发布-订阅模式
通信方式直接通信(主题直接通知观察者)间接通信(通过事件中心)
耦合度强耦合(观察者和主题直接关联)松耦合(发布者和订阅者解耦)
角色主题(Subject)和观察者(Observer)发布者(Publisher)、订阅者(Subscriber)、事件中心(Event Bus)
适用场景简单的对象间通信复杂的系统,需要解耦的场景
实现复杂度简单相对复杂
同步/异步通常是同步的可以是异步的
  • GUI 框架中的事件处理(如按钮点击事件)。
  • 数据模型和视图之间的绑定(如 MVC 模式)。
  • 简单的对象间通信场景。
  • 消息队列系统(如 RabbitMQ、Kafka)。
  • 事件驱动架构(如 Node.js 的 EventEmitter)。
  • 需要解耦的复杂系统(如微服务之间的通信)。

相关内容