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.
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.
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
https://Github.com/WindrunnerMax/EveryDay
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