为什么 Javascript 要求事件侦听器是 return 起作用的函数?

Why does Javascript require event listeners to be functions that return functions?

我正在尝试生成响应点击和鼠标悬停事件的网格。我有一个 tile 管理器对象,其中包含 tile 对象,每个对象都有自己的事件侦听器。目前,实际调用的方法在管理器对象中,但我更希望它们存在于 tile 对象中。

我想找到一个比我目前知道的解决方案更优雅的解决方案,或者至少理解为什么这个解决方案在其他解决方案不起作用时仍然有效:

为了参考和清晰起见,cell 指的是 javacript 对象,cell_node 指的是单元格中的 DOM 对象,另外 _handleClickclick_callback是同一个函数

目前,为了查看回调是否发生,我将回调定义为:

    _handleClick(el,r,c,i){
        console.log("element:" + el)
        console.log("row:" + r)
        console.log("col:" + c)
        console.log("index:" + i)
    }

其中 _handleClick(el,r,c,i) 位于管理器对象中,而不是磁贴中。

addEventListener 方法如下所示:

cell_node.addEventListener('click',
                (function(el,r,c,i){
                    return function(){
                        click_callback(el,r,c,i);
                    }
                })(cell,r,c,i),false);

我什至无法开始理解为什么这是必要的,或者为什么在以下尝试时它有效:

cell_node.addEventListener('click', cell.some_clicked_function_with_no_arguments)

不工作。如果我用 this 关键字定义 cell.some_clicked_function_with_no_arguments,所有内容都打印为未定义,让我更加困惑。

两种方法全文如下

class gridTile{
    constructor(row, col, tile_id){
        this.text= row + ", " + col;
        this.child_text_div = this._make_child_text_div(this.text);
        this.element = this._make_root_element()
        this.row = row;
        this.col = col;
        this.id = tile_id;
    }
    _get_root_style(){
        var randomColor = '#'+(Math.random() * 0xFFFFFF << 0).toString(16).padStart(6, '0');
        return `
            width: 100%;
            padding-top: 100%;
            //height: ${this.height}%;
            background-color: ${randomColor};
            position: relative;
            `;
    }
    _make_child_text_div(text){
        var cssobject =`
        width: 100%;
        height: 100%;
        top: 0;
        left: 0;
        position: absolute;
        `;
        var obj = document.createElement("div");
        obj.setAttribute("style", cssobject);
        obj.innerHTML = text;
        return obj;
    }
    _make_root_element(){
        var root_grid_div = document.createElement("div");
        root_grid_div.setAttribute("style", this._get_root_style());
        root_grid_div.appendChild(this.child_text_div);
        return root_grid_div;
    }
    get_element(){return this.element;}

    clicked_callback(cell){
        return function(cell){
            console.log("element:" + cell.element);
            console.log("row:" + cell.row);
            console.log("col:" + cell.col);
            console.log("index:" + cell.tile_id);
        }

    };

}

class Grid{
    constructor(rows, columns){
        this.grid_div = this._generate_grid_div(rows, columns, this._handleClick);
    }

    _generate_grid_div(rows, cols, click_callback){
        // generate styling row and columns
        var row_percent = String(1/rows * 100) + "% ";
        var col_percent = String(1/cols * 100) + "% ";
        console.log(row_percent + ", " + col_percent);

        var rowstyle = "grid-template-rows: ";
        for (var i=0; i<rows; i++){
            rowstyle += row_percent
        }
        var colstyle = "grid-template-columns: ";
        for (var i=0; i<cols; i++){
            colstyle += col_percent
        }
        var style = `
        display: grid;
        ${rowstyle};
        ${colstyle};
        `
        var grid = document.createElement('div');
        grid.className = 'grid';
        grid.setAttribute("style", style)

        var i=0;
        for (var r=0;r<rows;++r){
            for (var c=0;c<cols;++c){

                var cell = new gridTile(r, c, i);
                var cell_node = grid.appendChild(cell.get_element())

                cell_node.addEventListener('click',
                (function(el,r,c,i){
                    return function(){
                        click_callback(el,r,c,i);
                    }
                })(cell,r,c,i),false);

                cell_node.addEventListener('mouseenter', click_callback(cell,r,c,i));

                ++i;
            }
        }
        return grid;
    }

    _handleClick(el,r,c,i){
        console.log("element:" + el)
        console.log("row:" + r)
        console.log("col:" + c)
        console.log("index:" + i)
    }

    get_grid_element(){
        return this.grid_div;
    }

}

addEventListener() 调用包装在 IIFE 中可能会让您更容易阅读。正如上面评论中提到的,没有足够的证据表明是否可以重构以删除 IIFE

以下行为完全相同:

(function(el, r, c, i) {
  cell_node.addEventListener('click', function() {
    click_callback(el, r, c, i);
  }, false);
})(cell, r, c, i)

最后通过在单独的方法中添加箭头函数解决了这个问题:

    _make_root_element(){
        var root_grid_div = document.createElement("div");
        root_grid_div.setAttribute("style", this._get_root_style());
        root_grid_div.appendChild(this.child_text_div);
        this._add_listeners_to_root_element(root_grid_div);
        return root_grid_div;
    }

    get_element(){return this.element;}

    _add_listeners_to_root_element(el){
        el.addEventListener('click', () => this._clicked_callback());
    }

    _clicked_callback(){
        console.log("element:" + this.element);
        console.log("row:" + this.row);
        console.log("col:" + this.col);
        console.log("index:" + this.id);
    };

这一次,this 不是调用事件的元素,而是现在在定义箭头函数的对象范围内。 对我帮助很大当其他帖子没有这样做时,了解发生了什么。这更多是我理解能力不足的反映,而不是其他人为我解释的努力。