javascript addEventListenerで引数渡したいしremoveもしたい！

環境
初書：2020/09/22
PC：macOS 10.15.6

# 前置き
javascriptで、addEventListenerを使いたい時に、引数を渡したいと思ったが、同時にremoveEventListenerで削除もしたい時（タイトルの通り）にどうするのか迷ったのでメモ。

# 前提
IEは知らない

# addEventListenerで引数を渡す
これは調べたら比較的すぐに出てくるので、簡単に実装できる（と思う）
参考サイト：[【JavaScript】addEventListenerで関数に引数をわたす｜北の南｜note](https://note.com/yamanoborer/n/n2e4cc40328b7)

```html:index.html
<div id="youso">div要素</div>
<script>
const id = document.querySelector("#youso");
id.addEventListener('click', {arg: "aa", handleEvent: fuc});
handleEvent: fuc});
function fuc(){
    console.log(this.arg); //"aa"
}
</script>
```
・・・引数という表現でいいのか知らないがとりあえず渡すことはできた。
addEventListenerの第二引数に、呼び出す関数を入れた`handleEvent`と、その他引数名と値を入れたオブジェクトを渡すことによって、呼び出した関数に値を渡すことが可能。
注意点は、関数内の`this`が、addEventListenerの第二引数に全て置き換わる。

# removeEventListenerでイベントを消す
普通にやる場合は特に難しいことはなく、`removeEventListener`を呼ぶだけでできる。
ただ、上記の方法で追加した場合は、

```html:index.html
<div id="youso">div要素</div>
<script>
const id = document.querySelector("#youso");
id.addEventListener('click', {arg: "aa", handleEvent: fuc});
function fuc(){
    console.log(this.arg); // "aa"
    id.removeEventListener('click', {arg: "aa", handleEvent: fuc});
}
</script>
```
この方法では削除ができない。
理由は単純で、第二引数のオブジェクトが、`{arg: "aa", handleEvent: fuc}==={arg: "aa", handleEvent: fuc}` =falseになるからである。
ということは、第二引数のオブジェクトがtrueになるようにすればいい。
無名関数ではないのだが、[似たような方法がこちら](https://webkatu.com/archives/remove-eventlistener/)に記載されていたので、参考にしながら付け足してみる。（そして関数化する）

```html:index.html
<div id="youso">div要素</div>
<script>
let counter = 0;
const eventlist = [];

const num = addlistener("#youso", "click", fuc, {arg :"aa"});
function fuc() {
    console.log(this.arg); // "aa"
    removelistener(num);
}

function addlistener(selectors, type, handleEvent, arg = {}, option = null) {
    counter++;
    const eventlistner = {handleEvent, ...arg};
    const array = {counter, selectors, type, eventlistner, option};
    eventlist.push(array);
    const docs = document.querySelectorAll(selectors);
    for (const doc of docs) {
        doc.addEventListener(type, eventlistner, option);
    }
    return counter;
}
function removelistener(num) {
    const find_in = (e) => e.counter === num;
    const value = eventlist.findIndex(find_in);
    const {selectors, type, eventlistner, option} = eventlist[value];
    const docs = document.querySelectorAll(selectors);
    for (const doc of docs) {
        doc.removeEventListener(type, eventlistner, option);
    }
}
</script>
```
このように、addEventListenerの第二引数を保存しておき、removeEventListenerに同じオブジェクトを渡すことで、削除できる。

<details><summary>関数の使い方について</summary>
addlistener関数でイベントを追加し、addlistenerの戻り値で得た数値をremovelistenerに渡すことで削除できる。
addlistener関数の引数は、
`selectors = querySelectorAllの引数`,
` type = addEventListenerの第一引数`,
`handleEvent = addEventListenerの第二引数`,
`arg = 渡したい引数`,
`option = addEventListenerの第三引数`
</details>

# 引数は引数として渡したい
一応上記コードで表題はクリアしているのだが、一つ気になるとすれば、引数が引数として受け取れないこと。
現状引数を受け取るのは、this.xxxという形を取らないといけないので、これをクリアしたい。

ということで、addEventListenerと呼び出す関数の間にワンクッション挟むことで、これを実現していこうと思う。

```html:index.html
<div id="youso">div要素</div>
<script>
let counter = 0;
const eventlist = [];

const num = addlistener("#youso", "click", fuc, ["aa"]);
function fuc(e,arg) {
    console.log(this); //windowオブジェクト
    console.log(e); // mouseeventオブジェクト
    console.log(arg); // "aa"
    removelistener(num);
}

function receivelistener(e) {
    let arg = this.arg_eve.arg;
    let func = this.arg_eve.handleEvent;
    if (!Array.isArray(arg)) {
        arg = [];
    }
    return func.call(window,e, ...arg);
};

function addlistener(selectors, type, handleEvent, arg = [], option = null) {
    counter++;
    let eventlistner = {
        arg_eve: { arg, handleEvent },
        handleEvent: receivelistener,
    };
    const array = {counter, selectors, type, eventlistner, option};
    eventlist.push(array);
    var docs = document.querySelectorAll(selectors);
    for (const doc of docs) {
        doc.addEventListener(type, eventlistner, option);
    }
    return counter;
}
function removelistener(num) {
    const find_in = (e) => e.counter === num;
    const value = eventlist.findIndex(find_in);
    const {selectors, type, eventlistner, option} = eventlist[value];
    var docs = document.querySelectorAll(selectors);
    for (const doc of docs) {
        doc.removeEventListener(type, eventlistner, option);
    }
}
</script>
```
ワンクッションとしてreceivelistener関数を用意。
thisとして受け取らないといけなかった引数を、この中で変換し引数として渡す。
また、addEventListener関数の第二引数に関数を直接渡す方法だと、受け取った関数の`this`は`window`オブジェクトを返すので、`.call`を使うことでthisに`window`オブジェクトを渡している。（あと第一引数は勿論mouseevent）

使い方としては、`const num = addlistener("#youso", "click", fuc, ["aa"]);`のように、第一引数にイベントを追加する要素のCSSセレクター、第二引数にイベントの種類、第三引数に呼び出す関数、第四引数に渡したい引数の配列、第五引数にaddEventListenerのオプションを渡す。
イベントを消したい時は、addlistenerの戻り値をremovelistenerに渡すだけ。
また、呼び出された側の関数は、第一引数にmouseevent、第二引数以降に渡された引数がそのまま入る。

# 終わりに
唯一の問題は、removeをする際に一つずつは消せないこと
あと型チェックは入れていないので、その辺は注意かもしれない

# 参考サイト
[【JavaScript】addEventListenerで関数に引数をわたす｜北の南｜note](https://note.com/yamanoborer/n/n2e4cc40328b7)
[【JavaScript】addEventListenerの無名関数をremoveEventListenerで消す方法 | Web活](https://webkatu.com/archives/remove-eventlistener/)