The Participant Pattern

The participant pattern in JavaScript refers to executing a given function within a specific scope and passing the parameters as-is. It does not belong to the category of the generally defined 23 design patterns, but it is usually regarded as a broad-spectrum technical design pattern.

Description

The participant pattern involves executing a given function within a specific scope and passing the parameters as they are, fundamentally including function binding and function currying. When it comes to function binding, it involves passing the function as a function pointer (function name) so that the function can be executed within the scope of the bound object. As a result, the function can smoothly access the internal data of the object during execution. Since function binding is complex to construct and consumes more memory during execution, the speed of execution may be slightly slower. Nevertheless, considering the problems it addresses, this consumption is worthwhile. Thus, it is commonly used in events, setTimeout, or setInterval as asynchronous logic in callback functions.

Example

Suppose we need to add an event to an element; we can encapsulate a compatible method.

let A = { event: {} }

A.event.on = function(dom, type, fn) {
    if(dom.addEventListener) {
        dom.addEventListener(type, fn, false);
    }else if(dom.attachEvent) {
        dom.attachEvent("on" + type, fn);
    }else{
        dom["on"+ type] = fn;
    }
}

In this way, event binding is completely possible, but we cannot pass in certain parameters to maintain the integrity of lexical scope data within the callback function. Therefore, we make some adjustments within the callback function.

A.event.on = function(dom, type, fn, data) {
    if(dom.addEventListener) {
        dom.addEventListener(type, function(e){
            fn.call(dom, e, data)
        })
    }
}

This leads to a new issue: the added event callback function cannot be removed. Hence, it needs further improvement by using bind to modify the binding function's this. In essence, it forms a closure. bind is now generally implemented on Function.prototype, but it still requires manual implementation in some older browsers. Let's implement a bind function manually:

function bind(fn, context) {
    return function() {
        return fn.apply(context, arguments)
    }
}

Try using bind to bind this, and then we can pass the function bound with bind in event binding.

var obj = {
    title: "test",
}
function test() {
    console.log(this.title)
}
var bindTest = bind(test, obj);

bindTest(); // test

We can further enhance bind. In fact, bind can save partial parameters. At this point, we can separate the parameters using the concept of function currying. Of course, directly using the ... operator in ES6 is also feasible. Currying is the technique of transforming a function that accepts multiple parameters into a function that accepts a single parameter and returns a new function that accepts the remaining parameters and returns the result, which is an application of functional programming.

// For reference on currying, please check: https://blog.touchczy.top/#/JavaScript/Js%E4%B8%ADCurrying%E7%9A%84%E5%BA%94%E7%94%A8
function bind(fn, context) {
    var slice = Array.prototype.slice;
    var args = slice.call(arguments, 2);
    return function() {
        var addArgs = slice.call(arguments);
        var allArgs = addArgs.concat(args);
        return fn.apply(context, allArgs);
    }
}

var obj = {
    title: "test",
}
function test(name) {
    console.log(this.title, name)
}
var bindTest = bind(test, obj, 1);

bindTest(); // test 1
function bind(fn, context, ...args) {
    return function() {
        return fn.apply(context, args);
    }
}


```javascript
var obj = {
    title: "test",
}
function test(name) {
    console.log(this.title, name)
}
var bindTest = bind(test, obj, 1);

bindTest(); // test 1

Actually, not only do we use the bind to bind the scope when binding events, but also when subscribing in the process of using the observer pattern, we usually pass a bound function.

function PubSub() {
    this.handlers = {};
}

PubSub.prototype = {
    constructor: PubSub,
    on: function(key, handler) { // subscribe
        if (!(key in this.handlers)) this.handlers[key] = [];
        if (!this.handlers[key].includes(handler)) {
            this.handlers[key].push(handler);
            return true;
        }
        return false;
    },

    once: function(key, handler) { // one-time subscription
        if (!(key in this.handlers)) this.handlers[key] = [];
        if (this.handlers[key].includes(handler)) return false;
        const onceHandler = (args) => {
            handler.apply(this, args);
            this.off(key, onceHandler);
        }
        this.handlers[key].push(onceHandler);
        return true;
    },

    off: function(key, handler) { // unload
        const index = this.handlers[key].findIndex(item => item === handler);
        if (index < 0) return false;
        if (this.handlers[key].length === 1) delete this.handlers[key];
        else this.handlers[key].splice(index, 1);
        return true;
    },

    commit: function(key, ...args) { // trigger
        if (!this.handlers[key]) return false;
        console.log(key, "Execute");
        this.handlers[key].forEach(handler => handler.apply(this, args));
        return true;
    },

}

const obj = {
    name: "me",
    say: function(){
        console.log(this.name);
    }
}

const eventBus = new PubSub();

eventBus.on("say", obj.say);
eventBus.on("say", obj.say.bind(obj));

eventBus.commit("say");

// say Execute // This is the log for triggering this event
// undefined   // Unbound trigger
// me          // Bound trigger

Daily Challenge

https://Github.com/WindrunnerMax/EveryDay

Reference

https://blog.csdn.net/zxl1990_ok/article/details/110095794 https://blog.csdn.net/Forever201295/article/details/104032369 https://stackoverflow.com/questions/2236747/what-is-the-use-of-the-javascript-bind-method