JQuery函数的持久性

我正在尝试为HTML设置一个点击回调,导致另一个节点变得可见。 在此过程中,我惊讶地发现以下两个陈述不相同:

$("#title").click($("#content").toggle); $("#title").click(function() { $("#content").toggle(); } 

最后点击元素时,第一个语句最终会导致TypeError,并显示“未定义不是函数”的消息,我推测这表明我分配给onclick回调的任何内容最终都是未定义的,不知何故不是坚持记忆。

解决方法很简单(只使用第二种forms的语句),但我真正想要理解的是为什么将toggle函数作为对象传递最终被调用时不起作用。 我可以看到两者在语义上是不同的:第一个在绑定事件时执行$("#content")调用,另一个在事件发生时执行它,但我不明白为什么这应该重要。

如果它与答案相关,则所讨论的代码位于函数内部(可能在用户点击任何内容时返回)。

jQuery函数,就像在这个 – > $() ,只是一个函数,将其视为

 var $ = function(selector, context) { // do stuff with selector etc } 

这真的很简单,但是当你用一个有效的选择器调用jQuery函数(如在$() )时,它会得到DOM节点并返回这样的东西。

 [ 0 : 
, context : document, selector : "#title", jquery : "1.11.0", ..... etc ]

这是jQuery返回的类数组对象,你可以看到0是本机DOM节点,这就是我们可以用$('#title')[0]来获取本机DOM节点的原因。

然而,有一个人从一个简单的console.log中看不到的东西,那就是原型化到类似数组的对象上的方法,但我们可以使用for..in循环在控制台中查看它们。

 var title = $('#title'); for (var key in title) console.log(key) 

小提琴

这将返回此对象上可用的所有原型和非原型方法的长列表

 get each map first last eq extend find filter not is has closest .... etc 

请注意,这些是使用$.prototype添加到$()函数的所有jQuery方法,但jQuery使用较短的名称$.fn ,但它执行相同的操作。

因此,我们知道的所有jQuery函数都作为属性添加到主$()函数中,并且new关键字在内部用于返回带有这些原型属性的$()函数的新实例,这就是我们可以使用点表示法的原因,或者对于$()函数的方法的括号表示和链接,就像这样

 $().find() // or $()[find]() 

当使用像这样的prototyped属性扩展对象时,也会在方法内部设置this的值,所以现在我们了解它的工作方式,我们可以重新创建一个非常简单的jQuery版本

 var $ = function(selector, context) { if (this instanceof $) { this.context = context || document; this[0] = this.context.querySelector(selector); return this; }else{ return new $(selector, context); } } 

这与jQuery的工作方式相比有很多简化,但原则上它是相同的,当调用$()时,它会检查它是否是自身的实例,否则它会使用new关键字创建一个新实例并再次将其自身称为新实例实例。
它是一个新实例时,它获取它需要的元素和其他属性,并返回它们。

如果我们要对该实例的方法进行原型设计,我们可以像jQuery一样链接它,所以让我们尝试一下

 $.prototype.css = function(style, value) { this[0].style[style] = value; } 

现在我们可以做到这一点

 $('#title').css('color', 'red'); 

我们差不多创建了jQuery,只需要10000行代码。

小提琴

注意我们如何使用this[0]来获取元素,当我们使用click这样的东西时,我们不必在jQuery中这样做,我们可以使用this ,那么它是如何工作的呢?

让我们简化一下,因为理解为什么问题中的代码不起作用至关重要

 $.prototype.click = function(callback) { var element = this[0]; // we still need [0] to get the element element.addEventListener('click', callback.bind(element), false); return this; } 

我们在那里做的是使用bind()在回调函数中设置它的值,所以我们不必使用this[0] ,我们可以简单地使用this

小提琴

现在这很酷,但现在我们不能再使用我们创建的任何其他方法并将其原型化为对象,因为this不再是对象,而是DOM节点,因此失败

  $('#element').click(function() { this.css('color', 'red'); // error, 

它失败的原因是因为我们现在拥有本机DOM节点,而不是jQuery对象。

所以最后回答问题。
这有效的原因......

 $("#title").click(function() { $("#content").toggle(); }); 

...是因为你正在调用toggle()函数,并且设置了正确的值,在这种情况下它将是包含#content的jQuery对象,因为toggle()没有使用bind()回调,它只需传递jQuery对象,这个对象类似于我们在这个答案的顶部看到的对象

内部toggle()确实如此

 $.prototype.toggle = function() { this.animate(); } 

看看除了调用另一个jQuery函数之外它是如何直接使用它的,除了调用另一个jQuery函数之外,它要求 this是一个jQuery对象, 而不是本机DOM元素。

让我们再说一遍, toggle()要求函数内部的这个是jQuery对象 ,它不能是jQuery对象以外的任何东西。

-

现在让我们再继续click ,当你这样做时

 $("#title").click(function() { console.log(this) }); 

控制台将显示本机DOM元素,如

现在我们可以引用一个命名函数

 $("#title").click(myClickHandler); function myClickHandler() { console.log(this) }); 

结果将完全相同,我们将在控制台中获取原生DOM元素 - >

,这并不令人惊讶,因为这与上面使用的一个完全相同匿名function。

你正在做的是像这样引用toggle()函数

 $("#title").click($("#content").toggle); 

它与上面的示例完全相同,但现在你引用了toggle() ,并且在调用它时将使用this set的值调用click函数中的本机DOM元素 ,它会像这样

  $("#title").click($("#content").toggle); $.prototype.toggle = function() { console.log(this); // would still be 
this.animate(); // fails as
has no animate() }

这就是正在发生的事情, toggle()期望this是一个jQuery对象,但它获得了click处理程序中元素的本机DOM节点。

再次阅读, thistoggle()函数内部将是原生的#title元素 ,它甚至不是正确的元素,因为这是javascript和jQuery的工作方式,请参阅上面的长解释,了解如何在原型方法中设置它等等

在第一个示例中,您将传递jQuery的toggle函数作为事件处理程序执行。

但是,当jQuery 执行事件处理程序时,它将this的值设置为触发事件的DOM元素。

然而, toggle期望它是jQuery对象(如果正常调用它将会是这样),因为它在其实现中使用this.animate()

这就是为什么你看到“undefined不是一个函数”,因为this.animate是“未定义的”,你试图把它称为函数。


重要的是要理解函数内部的解析延迟到函数执行之前。 这意味着单个函数可以在调用之间看到不同的this值。 可以使用bind()newcall()apply()或以不同方式引用对象来更改this的值; 有关详细信息,请参阅此处 ,或“this”关键字如何工作?

在JS函数中使用this时,它指的是当前调用函数的任何对象 ,而不是它定义的位置。 例如,您可以定义一个函数并将其复制到另一个对象上,如下所示:

 foo = {'name': 'foo'}; bar = {'name': 'bar'}; foo.test= function() { console.log(this.name); } bar.test= foo.test; foo.test(); // logs 'foo' bar.test(); // logs 'bar' 

当你运行foo.test()this被设置为指向foo ; 但是当你运行与bar.test()相同的function时, this被设置为bar 。 函数中没有任何东西知道它原本是foo一部分,所以你基本上有两个独立但相同的函数,如下所示:

 foo.test = function() { console.log(this.name); } bar.test = function() { console.log(this.name); } 

当你运行$("#title").click($("#content").toggle); ,类似的事情发生了 – 你获得了对toggle函数的引用, 并将该函数复制到jQuery的事件处理程序列表中。 当回调运行时, $("#content")部分被遗忘,就像上面的foo一样,所以当jQuery中的实现看到this以查看你要切换的内容时,它会发现错误的东西。

它确切地发现它有一个额外的小怪癖:jQuery将this点击处理程序设置为被点击的DOM元素(在JS中有各种方式明确告诉函数它应该使用什么作为this )。 确切的错误是因为toggle的实现期望jQuery对象,而不是本机DOM对象,但即使将jQuery对象设置为this ,它也会是错误的节点 :你点击了$('#title') ,但想要切换$('#content') ,jQuery无法知道这一点。

为了完整性,解释为什么$("#title").click(function() { $("#content").toggle(); } 确实有效:这里保存在jQuery中的函数是一个匿名函数,它没有不要使用this ,因此不关心当回调最终触发时它被设置为什么。当事件运行时(当你点击时)它调用具有显式上下文的toggle$('#content')返回的对象) $('#content') lookup),这正是它所期待的。