JQuery函数的持久性

Har*_*wEm 10 javascript jquery

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

$("#title").click($("#content").toggle);
$("#title").click(function() {
    $("#content").toggle();
}
Run Code Online (Sandbox Code Playgroud)

当最后点击该元素时,第一个语句最终会导致TypeError,并显示"未定义不是函数"的消息,我推测这表明我分配给onclick回调的任何内容最终都是未定义的,并且某种程度上没有坚持记忆.

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

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

Mat*_*att 6

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

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

toggle但是,它希望它是jQuery对象(如果正常调用它将会是如此),因为它this.animate()在其实现中使用.

这就是为什么你看到"undefined不是一个函数",就像this.animate"未定义"一样,并且你试图把它称为函数.


重要的是要理解this函数内部的解析延迟到函数执行之前.这意味着单个函数可以this在调用之间看到不同的值.所述的值this可使用被改变bind(),new,call(),apply()或通过不同地引用对象; 有关详细信息,请参阅此处,或"this"关键字如何工作?


ade*_*neo 3

jQuery函数,就像这个 -> 一样$(),只是一个函数,可以将其视为

var $ = function(selector, context) {
   // do stuff with selector etc
}
Run Code Online (Sandbox Code Playgroud)

这确实很简单,但是当您使用有效的选择器调用jQuery函数(如 中所示$())时,它会获取 DOM 节点并返回类似这样的内容。

[
    0        : <div id="title"></div>, 
    context  : document, 
    selector : "#title", 
    jquery   : "1.11.0",
    .....
    etc
]
Run Code Online (Sandbox Code Playgroud)

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

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

var title = $('#title');

for (var key in title) 
    console.log(key)
Run Code Online (Sandbox Code Playgroud)

小提琴

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

get
each
map
first
last
eq
extend
find
filter
not
is
has
closest
....
etc
Run Code Online (Sandbox Code Playgroud)

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

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

$().find()
// or
$()[find]()
Run Code Online (Sandbox Code Playgroud)

当使用像这样的原型属性扩展对象时, 的值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);

    }
}
Run Code Online (Sandbox Code Playgroud)

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

如果我们要为该实例的方法建立原型,我们可以像 jQuery 那样链接它,所以让我们尝试一下

$.prototype.css = function(style, value) {
    this[0].style[style] = value;
}
Run Code Online (Sandbox Code Playgroud)

现在我们可以这样做

$('#title').css('color', 'red');
Run Code Online (Sandbox Code Playgroud)

我们几乎已经创建了 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;
}
Run Code Online (Sandbox Code Playgroud)

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

小提琴

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

 $('#element').click(function() {
     this.css('color', 'red'); // error, <div id="element".. has no css()

     // however this would work, as we now have the DOM node
     this.style.color = 'red';
 });
Run Code Online (Sandbox Code Playgroud)

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

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

$("#title").click(function() {
    $("#content").toggle();
});
Run Code Online (Sandbox Code Playgroud)

...是因为您正在调用该toggle()函数,并且设置了正确的值this,在本例中它将是包含#contentas的 jQuery 对象,toggle()没有使用 的回调bind(),它只是传递 jQuery 对象,该对象类似于我们的对象可以在这个答案的顶部看到

内部toggle()确实

$.prototype.toggle = function() {
    this.animate();
}
Run Code Online (Sandbox Code Playgroud)

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

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

-

现在让我们再次回到click当你这样做时

$("#title").click(function() {
     console.log(this)
});
Run Code Online (Sandbox Code Playgroud)

控制台将显示本机 DOM 元素,例如<div id="title"></div>

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

$("#title").click(myClickHandler);

function myClickHandler() {
     console.log(this)
});
Run Code Online (Sandbox Code Playgroud)

结果是完全相同的,我们会在控制台中获得原生 DOM 元素 -> <div id="title"></div>,这并不奇怪,因为这与上面使用匿名函数的完全相同。

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

$("#title").click($("#content").toggle);
Run Code Online (Sandbox Code Playgroud)

它与上面的示例完全相同,但是现在您正在引用toggle(),并且在调用时将使用在单击函数中设置为本机 DOM 元素的值来调用它this,它会像这样

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

    $.prototype.toggle = function() {
        console.log(this); // would still be <div id="title"></div>

        this.animate(); // fails as <div id="title"></div> has no animate()
    }
Run Code Online (Sandbox Code Playgroud)

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

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