Сохранение функций JQuery

Я пытаюсь настроить обратный вызов на клик для HTML, который вызывает видимость другого node. Попутно я был удивлен, узнав, что следующие два утверждения не эквивалентны:

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

Первое утверждение в конечном итоге приводит к типу TypeError, когда элемент наконец-то нажат, сообщение "undefined не является функцией", которое, как я предположил, указывает, что все, что мне было присвоено вызову onclick, оказалось undefined и как-то не сохраняется в памяти.

Обходной путь прост (просто используйте инструкцию второй формы), но я действительно хочу понять, почему передача функции переключения в качестве объекта не работает, когда она наконец вызвана. Я вижу, что эти два семантически отличаются: первый выполняет вызов $("#content") при привязке события, а другой выполняет его, когда происходит событие, но я не понимаю, почему это имеет значение.

В случае, если это имеет отношение к ответу, соответствующий код находится внутри функции (которая предположительно возвращается к моменту, когда пользователь может нажать что-либо).

Ответ 1

Функция jQuery, как в этом → $(), является просто функцией, думайте об этом как

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

Это действительно упрощено, но когда вы вызываете функцию jQuery (как в $()) с допустимым селектором, он получает DOM node и возвращает что-то вроде этого.

[
    0        : <div id="title"></div>, 
    context  : document, 
    selector : "#title", 
    jquery   : "1.11.0",
    .....
    etc
]

это массив, похожий на объект jQuery, и, как вы можете видеть, 0 является родным DOM node, и это причина, по которой мы можем сделать $('#title')[0], чтобы получить собственный DOM node.

Однако есть что-то, что действительно невозможно увидеть из простого console.log, и что методы, прототипированные на этот объект, похожий на массив, мы могли бы использовать цикл for..in, чтобы увидеть их в консоли.

var title = $('#title');

for (var key in title) 
    console.log(key)

FIDDLE

Это вернет длинный список всех прототипированных и не прототипированных методов, доступных на этом объекте

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

Обратите внимание, что это все методы jQuery, добавленные в функцию $() с помощью $.prototype, но jQuery использует более короткое имя $.fn, но делает то же самое.

Итак, все функции jQuery, которые мы знаем, добавлены к основной функции $() в качестве свойств, а ключевое слово new используется внутренне, чтобы вернуть новый экземпляр функции $() с этими прототипированными свойствами, и поэтому мы можем использовать точечную нотацию или, в этом случае, обозначение и цепочку скобок по методам функции $(), как этот

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

Когда объекты расширены с помощью прототипированных свойств, подобных этому, в методах также устанавливается значение 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 строк кода.

FIDDLE

Обратите внимание, как нам нужно использовать this[0] для получения элемента, нам не нужно делать это в 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 внутри функции обратного вызова, поэтому нам не нужно использовать this[0], мы можем просто использовать this.

FIDDLE

Теперь, когда классный, но теперь мы больше не можем использовать какие-либо другие методы, которые мы создали и прототипировали для объекта, поскольку this больше не является объектом, это DOM node, поэтому это не удается

 $('#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';
 });

Причина, по которой это не удается, состоит в том, что теперь у нас есть собственный DOM node, а не объект jQuery.

Итак, наконец, чтобы ответить на заданный вопрос.
Причина, по которой это работает...

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

... заключается в том, что вы вызываете функцию toggle(), и задано правильное значение this, в этом случае это будет объект jQuery, содержащий #content, поскольку toggle() не имеет обратного вызова, который использует bind(), он просто передает объект jQuery, объект, аналогичный тому, что мы видим в верхней части этого ответа.

Внутренне toggle() делает

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

посмотрите, как он использует this напрямую, не выполняя ничего, кроме вызова другой функции jQuery, требует, что this - это объект jQuery, не собственный DOM элемент.

Давайте повторим, что toggle() требует, чтобы this внутри функции был объектом jQuery, он не может быть чем-то другим, кроме объекта jQuery.

-

Теперь вернемся к click снова, когда вы делаете

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

консоль покажет собственный элемент DOM, что-то вроде <div id="title"></div>

Теперь мы можем ссылаться на именованную функцию вместо

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

function myClickHandler() {
     console.log(this)
});

и результат будет точно таким же, мы бы получили собственный DOM-элемент в консоли → <div id="title"></div>, что не удивительно, так как это точно такое же, как и выше, с использованием анонимной функции.

Что вы делаете, ссылаясь на функцию toggle(), такую ​​как

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

Это точно так же, как в примере выше, но теперь вы ссылаетесь на toggle(), а при вызове он будет вызываться со значением this, установленным в собственный элемент DOM в функции click, это будет похоже на это

    $("#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()
    }

Это то, что происходит, toggle() ожидает, что this будет объектом jQuery, но вместо этого он получает собственный DOM node для элемента в обработчике кликов.

Снова прочитайте, что this внутри функции toggle() будет нативным #title элементом, что даже не является правильным элементом, как это работает с javascript и jQuery. длинное объяснение выше для того, как this задано в прототипированных методах и т.д.

Ответ 2

В первом примере вы передаете функцию toggle для jQuery для выполнения в качестве обработчика события.

Однако, когда jQuery выполняет обработчик события, он устанавливает значение this как элемент DOM, на котором было запущено событие.

toggle, однако, ожидает, что он будет объектом jQuery (который он будет, если будет вызван нормально), поскольку он использует this.animate() в своем реализация.

Вот почему вы видите, что "undefined не является функцией", поскольку this.animate - "undefined", и вы пытаетесь назвать его как функцию.


Важно понимать, что разрешение this внутри функции отложено до тех пор, пока функция не будет выполнена. Это означает, что одна функция может видеть другое значение this между вызовами. Значение this может быть изменено с помощью bind(), new, call(), apply() или путем ссылки на объект по-разному; для получения дополнительной информации см. здесь или Как работает ключевое слово "his" ?

Ответ 3

Когда вы используете this в JS-функции, он ссылается на любой объект, на который в данный момент вызывается функция, а не там, где он был определен. Например, вы можете определить функцию и скопировать ее на другой объект, например:

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(), 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, это было бы неправильно node: вы нажали на $('#title'), но вы хотите переключить $('#content'), а jQuery не знает этого.

Для полноты объяснения, почему работает $("#title").click(function() { $("#content").toggle(); }: здесь функция, сохраненная в jQuery, является анонимной функцией, которая не использует this, поэтому не важно, к чему она привязана, когда обратный вызов, наконец, срабатывает. Когда событие запускается (когда вы нажимаете), он вызывает toggle с явным контекстом (объект, возвращаемый поиском $('#content')), что именно то, что он ожидает.