參與者模式
JavaScript
中的參與者模式,就是在特定的作用域中執行給定的函數,并将參數原封不動的傳遞,參與者模式不屬于一般定義的
23
種設計模式的範疇,而通常将其看作廣義上的技巧型設計模式。
描述
參與者模式實作的在特定的作用域中執行給定的函數,并将參數原封不動的傳遞,實質上包括函數綁定和函數柯裡化。
對于函數綁定,它将函數以函數指針(函數名)的形式傳遞,使函數在被綁定的對象作用域中執行,是以函數的執行中可以順利地通路到對象内部的資料,由于函數綁定構造複雜,執行時需要消耗更多的記憶體,是以執行速度上要稍慢一些,不過相對于解決的問題來說這種消耗還是值得的,是以它常用于事件,
setTimeout
或
setInterval
等異步邏輯中的回調函數。
示例
當我們需要為元素添加事件時,可以封裝一個相容的方法。
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;
}
}
這樣對于事件綁定是完全可以的,但是我們不能傳入一些參數以保持回調函數内部的詞法作用域的資料的完整性,是以我們在回調函數中下點文章。
A.event.on = function(dom, type, fn, data) {
if(dom.addEventListener) {
dom.addEventListener(type, function(e){
fn.call(dom, e, data)
})
}
}
這樣又導緻了新問題,添加的事件回調函數不能移除了,是以還需要對其改進,使用
bind
改變綁定函數的
this
,實際上就是形成了一個閉包,現在
bind
普遍都在
Function.prototype
上實作了,但是在一些低版本浏覽器還是需要自行實作,我們自行實作一個
bind
。
function bind(fn, context) {
return function() {
return fn.apply(context, arguments)
}
}
嘗試使用
bind
綁定
this
,接下來我們在事件綁定中就可以傳遞使用
bind
綁定的函數了。
var obj = {
title: "test",
}
function test() {
console.log(this.title)
}
var bindTest = bind(test, obj);
bindTest(); // test
我們可以繼續完善
bind
,
bind
實際上是可以儲存部分參數的,這時我們可以借助函數柯裡化的思想将參數分開實作,當然直接使用
ES6
的
...
操作符也是可以直接實作的。柯裡化
Currying
是把接受多個參數的函數變換成接受一個單一參數的函數,并且傳回接受餘下的參數且傳回結果的新函數的技術,是函數式程式設計應用。
// 柯裡化可以參考 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);
}
}
var obj = {
title: "test",
}
function test(name) {
console.log(this.title, name)
}
var bindTest = bind(test, obj, 1);
bindTest(); // test 1
實際上不光在事件綁定的時候我們使用到
bind
綁定作用域,在使用觀察者模式時訂閱的過程通常也是傳遞了一個綁定好的函數。
function PubSub() {
this.handlers = {};
}
PubSub.prototype = {
constructor: PubSub,
on: function(key, handler) { // 訂閱
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) { // 一次性訂閱
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) { // 解除安裝
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) { // 觸發
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 // 這是觸發這個事件的log
// undefined // 未綁定this的觸發
// me // 綁定this的觸發
每日一題
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