【问题标题】:Why does Javascript require event listeners to be functions that return functions?为什么 Javascript 要求事件监听器是返回函数的函数?
【发布时间】:2021-04-22 10:05:30
【问题描述】:

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

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

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

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

    _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;
    }

}

【问题讨论】:

  • 可能与stackoverflow.com/questions/750486/…有关,但需要多贴代码。还要确保了解 IIFE 的工作原理(并且您不一定必须将其放在 addEventListener 参数列表中)。
  • 确实是^的骗子。最好的解决方案是使用constlet 而不是var,然后你只需要做.addEventListener('mouseenter', () =&gt; click_callback(cell,r,c,i))
  • 我已经添加了完整的代码,很抱歉!此外,在@Bergi 评论中的链接中,我注意到下面的示例使用了一个我认为有效的匿名函数。为什么匿名函数或箭头函数在指向另一个对象时不起作用?这真的是范围问题吗?

标签: javascript callback scope


【解决方案1】:

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

下面的行为完全相同:

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

【讨论】:

    【解决方案2】:

    这最终通过在单独的方法中添加箭头函数得到解决:

        _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 不是调用事件的元素,但现在在定义箭头函数的对象范围内。This post 帮助我理解了发生了什么,当其他帖子没有这样做时。这更多是我缺乏理解能力的反映,而不是别人向我解释的努力。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2020-11-25
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多