【问题标题】:How does the way in which a JavaScript event handler is assigned affect its execution?分配 JavaScript 事件处理程序的方式如何影响其执行?
【发布时间】:2020-06-19 02:57:28
【问题描述】:

请考虑以下代码。

<!DOCTYPE html>
<title>Question</title>
<script>
    function report(handler, _this) {
        alert(
            "handler:\n" + handler + "\n" +
            "this: " + _this + "\n" +
            "event: " + event + "\n"
        )
    }
</script>
<input type=button id=A value=A onclick="report(A.onclick, this)">
<script>function f() {report(B.onclick, this)}</script>
<input type=button id=B value=B onclick="f()">
<input type=button id=C value=C>
<script>C.onclick = function () {report(C.onclick, this)}</script>

通过点击按钮,我看到了:

  1. A.onclickB.onclick 被包裹在 "function onclick(事件) {...}"。
  2. B.onclickthis是窗口 (而不是按钮)。

还有其他注意事项吗?

【问题讨论】:

  • 由于 JavaScript 作用域,Note 事件在 report 中定义。
  • 您想查看 javascript 中的事件循环。 javascript 中的所有事件都是异步的。因为它们是异步的,所以它们被窗口对象回调。研究 javascript 和事件循环中的异步操作来理解这个概念。
  • 在A.onclick和C.onclick中,this是一个按钮;据推测,f 是绑定到窗口的,因为它是在脚本的顶层定义的。
  • 通常事件中的回调函数绑定到触发事件的元素的对象。但是在 B 的情况下,回调函数被包装在一个函数中并且它失去了它的上下文。
  • 这能回答你的问题吗? How does the "this" keyword work?

标签: javascript eventhandler


【解决方案1】:

内联监听器没有任何好处,相反,将事件监听器添加到 HTML 元素是一种非常有缺陷的方式。

执行上的差异

#1 属性代码内的this 值使用JavaScript with 绑定到元素。在调用函数(或任何全局变量)的内联代码中,首先从元素中搜索。如果未找到(通常是这种情况),则内联侦听器会从元素的原型链中搜索该函数。如果未找到匹配的属性名称,则搜索到达window,并运行被调用的全局函数。但如果函数名与查找路径上的任何属性名冲突,则会引发错误,或执行意外函数。

内联侦听器如何找到包装表单的action 属性的示例,只需单击输入:

function action () {
  console.log('Not a function!?');
}
<form action="" method="post">
  <input onclick="console.log(action); action();">
</form>

#2 属性码的返回值实际上是用在特定的事件中(如onsubmit)。返回false 会阻止事件的默认操作。带有addEventListener 的侦听器的返回值始终被完全忽略(该值没有接收器)。

#3 处理程序中使用的所有变量和函数都必须是全局可访问的。这也可以算作缺陷。

经常被误解的行为

属性代码中调用的函数不是实际的事件处理函数,属性本身的代码是附加的处理函数。因此,事件对象和正确的this 值仅存在于属性处理程序代码中。如果您在全局函数中需要这些值中的任何一个,则必须在调用全局函数时手动传递它们。

这是任何 JavaScript 代码的正常行为,在附加 addEventListener 的事件处理程序中也是如此。如果要从事件处理程序调用另一个函数,则必须传递事件对象,并绑定/传递 this 值,如果该其他函数需要这些值。

从事件侦听器调用另一个函数的示例。

function show () {
  console.log('Called:', typeof event, this.toString());
}

const inp = document.querySelectorAll('input')[1];
inp.addEventListener('click', function (e) {
  console.log('addEventListener:', typeof e, this.toString());
  show();
});
<input onclick="console.log('Inline:', typeof event, this.toString()); show();" placeholder="Inline listener">
<input placeholder="addEventListener">

正如我们所见,在附加类型之间,事件对象和this 值的处理方式没有区别。当事件触发时,在内联侦听器中,属性中的代码是首先执行的代码,而对于其他附加类型,首先执行的代码是处理函数本身。 (示例中的事件对象部分是部分无关的,因为目前几乎所有浏览器都在实现全局事件对象。)

内联侦听器的缺陷

#1您只能将一个相同类型的侦听器附加到一个元素。

#2 内联侦听器是潜在的攻击媒介,因为属性中的侦听器代码以及从属性代码调用的任何函数都很容易被 DevTools 覆盖。

#3 在编写内联侦听器时,正确引用字符串很复杂。在服务器上编写动态标签时,引用的复杂性会增加,因为您必须处理 HTML 引用、JS 引用和服务器端语言引用。

#4 内联侦听器不能在模块和浏览器扩展中工作(这些环境不在全局命名空间中,您不能从模块或插件代码中调用编写的函数inline listener),并且不被许多框架所接受,并且它们不会通过任何安全审计。

#5 内联监听器打破了Separation of concerns 原则,混合了页面的表示层和动作层。

element.onxxxx

onxxxx 属性没有缺陷 #3、#4 和 #5,但是您不能添加多个具有 onxxxx 属性的侦听器,并且由于元素是全局的,因此侦听器很容易用 DevTools 覆盖。

addEventListener

作为结论:使用addEventListener 将事件附加到HTML 元素,它没有任何缺陷。 this 绑定正确,事件对象正确传递,可以附加多个相同类型的事件,并且没有安全风险(当处理程序不是全局可访问的函数时),因为一切都发生在封装代码中,不需要单个全局变量或函数。

作为奖励,您可以选择触发事件的阶段,将只触发一次的事件附加到元素而无需额外工作,并通过某些事件获得better performance of scrolling

【讨论】:

  • 这是一个很好的答案,但不是被接受的答案,只有 1 个赞(现在是 2 个)
【解决方案2】:

还有另一个考虑因素。考虑以下代码。

<!DOCTYPE html>
<title>Answer</title>
<script type=module>function f() {alert(1)}</script>
<input type=button value=1 onclick=f()>
<input type=button value=2 id=button>
<script type=module>button.onclick = function () {alert(2)}</script>

f 未在 onclick 中为 value=1 的按钮定义(因为 f 在模块中定义)。因此,当事件处理程序必须执行模块中的代码(例如,使用import 的代码)时,必须在模块中分配事件处理程序。

【讨论】:

    【解决方案3】:

    当从 on-event 处理程序调用代码时,其this 设置为放置侦听器的 DOM 元素:

    <!-- this will be the button -->
    <button onclick="alert(this.tagName.toLowerCase());">
        Show this
    </button>
    

    但请注意,只有外部代码以这种方式设置 this。在这种情况下,内部函数的this 未设置,因此它返回全局/窗口对象(即non–strict 模式下的默认对象,其中this 未被调用设置)。

    global contextfunction context 中检查this

    <!-- this will be the global object which is window in the browser -->
    <button onclick="alert((function() { return this; })());">
        Show inner this
    </button>
    

    另一个考虑因素是当您使用addEventListener

    使用anonymous functionarrow functionarrow functions 中的this 检查事件侦听器。

    请注意,虽然匿名函数和箭头函数相似,但它们具有不同的 this 绑定。虽然匿名(以及所有传统 JavaScript 函数)创建自己的 this 绑定,但箭头函数继承了包含上下文的 this 绑定。

    <button id="my_button" onclick="alert(this.tagName.toLowerCase());">
        Show this
    </button>
    
    <script>
        const btn = document.getElementById("my_button")
        btn.addEventListener("click", function () {
            // this will be the button
            console.log(this)
        })
        btn.addEventListener("click", () => {
            // this will be window
            console.log(this)
        })
    </script>
    

    这是我写的关于JavaScript this keyword 的简单文章,但没有提到事件处理程序。

    【讨论】:

      【解决方案4】:

      在 HTML 中设置事件监听器并没有什么特别的好处。此外,它被认为是有害的,例如CSP 禁止它https://developers.google.com/web/fundamentals/security/csp#inline_code_is_considered_harmful

      【讨论】:

        猜你喜欢
        • 2011-10-27
        • 1970-01-01
        • 1970-01-01
        • 2011-03-28
        • 1970-01-01
        • 1970-01-01
        • 2023-03-09
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多