JavaScript如何将上下文分配给事件处理程序的this?

9

在阅读相关问题#1#2后,我仍然没有找到以下问题的答案:

Javascript可以使用bindcallapply来设置上下文(即设置this)。

但是当我编写一个事件处理程序时:

document.getElementById('myInput').onclick = function ()
                                                   {
                                                      alert(this.value)
                                                   }

谁/什么实际上把this附加到对象本身?

P.S. 使用jQuery的时候:

  $("#myInput").bind(function (){...})

有一个内部实现的 (bind, call 或者 apply),当我没有使用jQuery时,是谁在执行这个操作呢?


2
DOM 实现在我看来负责此事。 - wroniasty
1
http://www.w3.org/TR/DOM-Level-3-Events/#event-type-click - wroniasty
@wroniasty,你能告诉我在哪里提到了关于上下文和this的内容吗?在表格中,上下文讨论了内部属性... - Royi Namir
@wroniasty,你说的话正好表达了我的想法,这基本上只是又一种“根据定义”的答案。 - Alexander
你的#1和#2都链接到同一个问题。 - Barmar
7个回答

9
当然是使用DOM/JavaScript,据W3C规定它应该以这种方式工作。
事件处理程序在特定对象(当前事件目标)的上下文中被调用,并提供事件对象本身。 来源 具体发生的过程我们不知道。这是一个实现细节。
我们唯一知道的是,按照W3C定义的语义通过某种方式实现,但浏览器的哪个部分以及如何实现完全由浏览器开发人员决定,他们可以根据自己的意愿进行实现。

2
@MarcusEkwall [误读,应该是读不出来]:不,我们不知道怎么发生的,只知道什么发生了。这是有区别的。 - phant0m
1
实际上,我们不知道DOM实现如何调用处理程序。我们也不应该关心它。 - wroniasty
事件处理程序是在特定对象的上下文中调用的,但普通函数也是如此(它们内部使用[this]),那么有什么区别呢? - Royi Namir
@RoyiNamir 你需要区分发生了什么如何发生。毫无疑问,如果你把一个(兼容的)锅放在炉子上并打开它,它会变热。我们现在对发生了什么有了共识:它变热了。然而,如何发生还没有得到解决。有些人使用煤气,如果你比较传统,可以用木头来加热,也可以使用电炉,或者你可以使用新型的高级感应式炉灶。所使用的方法都非常不同,但结果是相同的。 - phant0m
不同之处在于,我提供的引用只告诉您onclick事件发生时会发生什么。另一方面,JavaScript规范告诉您执行x.f()时会发生什么。但它们都没有告诉您如何发生这种情况。你正在混淆两者。你看到了某些东西(x.f()部分),它指定了一个特定上下文中运行函数的情况。现在,您假设此机制是实现事件处理程序的方式。--两件事都是what,第二件事不一定是第一个whathow - phant0m
显示剩余6条评论

2
总结所有讨论:
  • 通常情况下,在调用o.x()时,JavaScript会将this绑定到函数调用中的o
  • 但是,有一些替代调用函数的方法(如f.apply()f.call())会改变这种行为。
  • onclick是一个特殊情况,调用它的方法未知并取决于DOM实现。

为什么onclick是特殊情况?它只是一个方法...请纠正我。 - Royi Namir
这是一个特殊情况,因为它是由DOM调用的。它的上下文可能通过许多方法(element.call.onclick()element.onclick()等)绑定到其调用,并且在DOM实现之间可能会有所不同。 - wroniasty
所以,仅仅因为相同的Onclick可以被不同的对象调用(不使用apply/bind/call)-这就是不同的吗? - Royi Namir
不是通过不同的对象。在某个时刻,浏览器会收到通知,鼠标已经在某个地方被点击。然后它会以某种方式评估哪个元素被“击中”。如果存在,它会以某种方式调用其onclick处理程序。但是,我们不知道以什么方式调用它。无法确定JavaScript解释器是否有效地获取了类似于element.onclick()的代码来执行,或者浏览器是否有其他更优化的特殊方法来执行此操作。 - phant0m
1
@RoyiNamir 事实是,除非他写了浏览器的那部分代码,否则没有人能够确定地回答这个问题。但是,由于不止存在一个浏览器,您可能会得到多个非常不同但都是正确答案。这就是为什么wroniasty和我试图强调,这个问题没有特定的答案,而是接受它根据规范以某种未知的方式发生。 - phant0m
抱歉,但第三点不是和第一点一样吗?o.onclick()所以是o提供了上下文? - Royi Namir

1
在你的例子中,一个 onclick 处理程序非常简单:DOM 元素就是一个对象,你正在定义 onclick 属性为一个函数。该函数有效地成为该 DOM 元素/对象的方法。
当该对象被点击时,该函数作为该元素的方法被调用,因此 this 指向其所有者,即该元素。

简而言之,函数执行的上下文与创建它的上下文相同(再次强调:在你的例子中,它是 DOM 元素的方法),除非将对函数对象的引用分配给另一个对象,或者在另一个上下文中使用 callapply 等调用该函数对象。
当然,这还有更多的内容:正如我上面暗示的那样,函数本身也是对象,并且被称为与其“所有者”松散耦合。实际上,它们没有像这样的所有者,每次调用函数时,都会确定其上下文:

var foo = someObject.someFunction;//reference to method
someObject.someFunction();//this === someObject, context is the object preceding the function
foo();//implies [window].foo(); ==> this is window, except for strict mode

正如@wroniasty所指出的,我谈论“所有权”可能会有点令人困惑。事实上,函数是对象,它们不属于任何东西。当一个对象被分配一个方法时,这个对象真正“拥有”的只是对给定函数对象的引用。当通过该引用调用该函数时,this将指向拥有调用引用的对象。
当我们将其应用到您的elem.onclick = function(){}中时,我们可以看到该元素仅拥有对在某个作用域(全局、命名空间对象等)中声明的函数表达式的引用。当单击事件触发时,该引用将用于调用处理程序,从而将元素的引用分配给this。为了澄清:
document.getElementById('foo').onclick = (function()
{//This function returns the actual handler
    var that = this;//closure var
    console.log(this);//logs window object
    //defined in global context
    return function(e)//actual handler
    {
        console.log(this === that);//false
        console.log(this);//elem
        console.log(that);//window
    };
})();//IIFE

因此,处理程序在全局上下文中声明,处理程序可以使用闭包(但这是另一个故事)通过that访问其声明的上下文。重点是,事件使用元素fooonclick属性引用处理程序。该属性是对函数对象的引用,因此函数对象将其上下文设置为调用所做的任何对象。

我希望这能澄清我在函数“所有权”方面引起的任何困惑,也许还有JS中如何确定上下文的问题。


1
匿名函数没有“所有者”。 - wroniasty
@wroniasty:this 不是指运行在哪个上下文中的窗口、文档或其他吗?即使是匿名函数,它也必须在某个上下文中运行,不是吗? - Nope
1
@FrançoisWahl 是的,据我所知,每个函数都在某个上下文中运行。但是如果您使用术语“所有者”,那么一个函数可以有多个所有者。因此,“所有者”这个术语并不是很好,因为它是一种特定关系,而不是函数固有的东西(Elias解释得很好)。 - phant0m
@phant0m:我明白了,谢谢你的解释。我自己还在学习JavaScript、闭包和上下文等方面的细节,所以有时候还是会有些困惑。 - Nope
@wroniasty: 对于匿名函数,this不会从外部作用域继承上下文。如果可以的话,您就不需要不时地声明that_self变量了。除非明确声明为方法并作为方法调用,或者显式地绑定(bind)、调用(call)或应用(apply),否则每个函数都在全局上下文中调用。就所有权而言:函数从来没有真正的所有者,这就是我说它们松散绑定且独立的原因。起初,考虑所有权可能有所帮助。也许我应该澄清一下这一点。 - Elias Van Ootegem
@wroniasty:我刚刚注意到:当我说每个函数都在全局上下文中调用时,我想说的是“每个函数都在全局上下文中定义”,但并不总是在全局范围内引用... - Elias Van Ootegem

1

说DOM是答案的都是错的。

这是 JavaScript 本身作为一种语言的一部分。DOM 只是名称所示“文档对象模型”的一部分,它只是通过使用 JavaScript 来操作 HTML 的表示方式。与 DOM 相关的对象遵循标准指定的行为,但是这是通过使用 JS 实现的。就是 JS 引擎在与正在使用的布局引擎(Gecko、Trident、WebKit、Presto 等)通信实现的。因此,如果 WebKit 检测到事件,则会将其按照 DOM 规范传递给 JS 引擎,以便 JS 程序员可以处理它(这就是你甚至问这个问题的原因,因为你可以与它一起工作)。

换句话说,如果您在编写 JavaScript 代码,那么唯一能够理解如何读取和执行该代码的引擎是 JS 引擎。这个引擎(v8、SpiderMonkey/Jugger/Trace)将从布局引擎接收数据并使用它,以便您可以与之交互。同样地,另一方面,每当您运行影响布局的代码时,更改将被布局引擎检测到,并更改布局,以便用户感知更改:即使 JS 代码可能已经启动了此更改,布局引擎也会负责布局。

当你将一个函数赋值给一个对象时,“this”代表的是函数所属的对象。因此,如果你将一个函数赋值给对象a的实例,那么在函数内部使用“this”时,“this”就会指向a。
从实现的角度来看,可以这样理解:当你调用一个方法时,首先要告诉一个实例你想调用一个带有N个参数的方法。然后这个实例调用方法并将自身添加到上下文中,作为“this”。
在Python中,这更明确,通过将所有实例方法的第一个参数设置为实例本身来实现。在这里,也是相同的,但实例是隐式地传递而不是显式地传递。
请记住,实例拥有该方法。当你执行“document.getElementById('something')”时,调用将返回一个对象(它恰好是DOM的HTMLElement对象的一部分,但这与JS如何与DOM交互无关),然后你将函数赋值给click属性。
然后,每当你调用该方法时,JavaScript引擎默认传递实例,就像传递其他变量(例如arguments也是由JS引擎生成而无需任何操作,它符合ECMAScript标准)。
建议查看第63页。

"this 关键字评估为当前执行上下文的 ThisBinding 值。"

但更重要的是,在第 68 页的"函数调用"章节。

http://www.ecma-international.org/publications/files/ECMA-ST/Ecma-262.pdf


1
这假设浏览器实际上会在 element.onclick() 等情况下评估 JavaScript 代码,这是一个没有根据的假设。浏览器没有必要这样做。 - phant0m
DOM 还负责处理点击事件,即调用处理程序。 - wroniasty
你使用的DOM是JavaScript的实现。"this"是JavaScript的一个问题。如果你想要,你也可以在其他语言中使用DOM,而且根据语言的不同,"this"可能甚至不存在。 - Mamsaac
1
不,不是这样。这取决于 DOM 实现,它会执行 element.onclick()element.onclick.apply(context) 或类似的操作。在第一种情况下,this 变成了 element,而在后一种情况下,它变成了 context - wroniasty

0

这与DOM无关,但涉及到JavaScript在调用对象内函数时的设计。

以此为例:

var myObject = {
    id: 1,
    onclick: null
}

myObject.onclick = function() {
    console.log(this.id);
}

调用myObject.onclick()将会在控制台中记录1,这意味着myObject是它的上下文。

由于onclick也是一个对象的属性,this将是父对象,在您的情况下是一个HTMLElement


他在询问是“谁/什么”实际将其附加到对象上,而不是JavaScript的工作原理。 - wroniasty
@wroniasty 我能看懂。不需要在这里重新发布他的问题。这与JavaScript的工作方式有关。 - mekwall
@MarcusEkwall,你的意思是说当点击事件发生时,实际上发生的是element.onclick()吗? - wroniasty
1
伙计们,别争了,帮我解决问题吧。让我们坚持使用JS。:-)感谢你们试图解决我的误解。 - Royi Namir
1
不!这已经走得太远了,现在停下来太晚了!!开玩笑的。抱歉@MarcusEkwall,我不是有意冒犯你。 - wroniasty
显示剩余9条评论

0

0
为了举例说明,尽管实现可能有所不同,请考虑以下函数。
 function f() { alert(this.name); } 

如下:

function f(this) { alert(this.name); } 

想象这个作为一个秘密参数,你可以通过bind、apply和call来覆盖它,但通常由浏览器设置为调用对象。

例子

var a = {},
    b = {};

a.name = "John";
b.name = "Tom";

// "this" param added secretly
function printName( ) { 
    console.log( this.name ) 
};

a.printName = printName     
b.printName = printName;

当调用printName函数时,浏览器会将“秘密”的this参数设置为调用函数。在下面的例子中,这是b,因此“Tom”被打印到控制台上。
printName( ); // global context assumed so this === window
b.printName( ); // this === b and outputs "Tom"
printName.call( a ); //this === a and outputs "John"

更多信息请点击此处


函数_f(this)_是一个很好且令人耳目一新的网站,但是像f(myObj)这样的执行在哪里进行? - Royi Namir

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接