Изменение нескольких элементов и вызов цепочки методов. Attribute span window - Цепочка вызовов.bind () в JavaScript. Неожиданный результат

Одной из особенностей, которые делают jQuery таким четким и выразительным, является то, что вызов метода для jQuery объекта, как правило, меняет все элементы, которые содержатся в данном объекте. Я сказал "как правило", потому что некоторые методы совершают операции, выполнение которых для множества элементов не имеет смысла; вы это увидите в следующих главах. В показано, насколько сильно jQuery облегчает жизнь по сравнению с базовым DOM API.

Листинг 5-21: Работа с несколькими элементами ... ; var labelElems = document.getElementsByTagName("label"); for (var i = 0; i < labelElems.length; i++) { labelElems[i].style.color = "blue"; } }); ...

В этом примере я выбираю в документе все элементы label и меняю значение свойства color на blue . В jQuery я делаю это в одном выражении, а при использовании DOM API на это потребуется больше усилий. Также мне кажется, что смысл выражения jQuery очевиден, но это мое личное мнение.

Другой очень хорошей особенностью объекта jQuery является то, что он реализует текучий интерфейс . Это обозначает, что где бы вы ни вызвали метод, который меняет содержание объекта, результатом этого метода будет другой jQuery объект. Это может показаться простым, но это позволяет использовать цепочку методов, как показано в .

Листинг 5-22: Применение цепочки методов для jQuery объекта ... $(document).ready(function () { $("label").css("color", "blue").css("font-size", ".75em"); var labelElems = document.getElementsByTagName("label"); for (var i = 0; i < labelElems.length; i++) { labelElems[i].style.color = "blue"; labelElems[i].style.fontSize = ".75em"; } }); ...

В этом примере я создал jQuery объект, используя $ функцию, вызвал метод css и установил значение для свойства color , а затем снова вызвал метод css , на этот раз чтобы установить значение для свойства font-size . Также я продемонстрировал эквивалентное решение, используя DOM API. Вы видите, что тут не требуется намного больше работы, чтобы получить тот же результат, потому что есть цикл for , который перечисляет выбранные элементы.

Вы получите реальную выгоду тогда, когда будете использовать цепочку методов, которые вносят более существенные изменения в коллекцию элементов, содержащихся в jQuery объекте. В показан пример.

Листинг 5-23: Более сложный пример использования цепочки методов $(document).ready(function () { $("label").css("color", "blue").add("input") .filter("").css("font-size", ".75em"); var elems = document.getElementsByTagName("label"); for (var i = 0; i < elems.length; i++) { elems[i].style.color = "blue"; if (elems[i].getAttribute("for") != "snowdrop") { elems[i].style.fontSize = ".75em"; } } elems = document.getElementsByTagName("input"); for (var i = 0; i < elems.length; i++) { if (elems[i].getAttribute("name") != "rose") { elems[i].style.fontSize = ".75em"; } } });

В этом примере хорошо продемонстрирована гибкость jQuery. Давайте рассмотрим каждый метод по отдельности, чтобы понять, как это работает. Начнем с этого:

$("label").css("color", "blue")

Это хорошее и простое начало. Я выбрал в документе все элементы label и для них всех CSS свойству color установил значение blue . Следующий шаг:

$("label").css("color", "blue").add("input")

Метод add добавляет элементы, которые соответствуют указанному селектору в объект jQuery. В данном случае я выбрал все элементы input , значение атрибута name которых не является rose . Эти элементы объединяются с предыдущими выбранными элементами, и у меня есть комплекс из элементов label и input . О методе add подробнее будет рассказано в главе 6. Вот следующее дополнение:

$("label").css("color", "blue").add("input").filter("")

Метод filter убирает все элементы из jQuery объекта, которые не соответствуют указанному условию. Более подробно об этом методе я расскажу в главе 6, а на данный момент достаточно знать, что это позволяет мне удалить любой элемент из jQuery объекта, значением атрибута for которого является snowdrop .

$("label").css("color", "blue").add("input") .filter("").css("font-size", ".75em") ;

И завершающим шагом является вызов метода css снова, на этот раз для указания значения.75em свойству font-size . И вот конечный результат этой работы:

  • Для всех элементов label для CSS свойства color установлено значение blue .
  • Для всех элементов label кроме одного, значение атрибута for которого равно snowdrop , присвоено значение.75em CSS свойству font-size .
  • Для всех элементов input , у которых значение атрибута name не равно rose , присвоено значение.75em CSS свойству font-size .
  • Намного сложнее получить такой же результат при использовании DOM API, и я столкнулся с некоторыми сложностями, пока писал этот скрипт. Например, я думал, что могу использовать метод document.querySelectorAll , описанный во второй главе , чтобы выбрать input элементы, используя селектор input , но выяснилось, что атрибутивный фильтр такого рода не работает с этим методом. Потом я пытался избежать дублирования вызова метода для указания значения для font-size , объединив результаты двух getElementsByTagName , но этот опыт оказался довольно болезненным. Я не хочу мучить вас подробностями, тем более если у вас есть устойчивый интерес к jQuery и прочтению данной книги. Но хочу сказать, что jQuery обеспечивает тот уровень гибкости и выразительности, которого невозможно достичь, используя базовый DOM API.

    Method chaining is a common pattern in the JavaScript world. This tutorial will provide a brief explanation of what method chaining is, give a real world example of how jQuery uses method chaining, and teach you how to add method chaining to your own classes. Let’s get started.

    What is Method Chaining?

    Method chaining is a technique that can be used to simplify code in scenarios that involve calling multiple functions on the same object consecutively. This is an example of how you can use method chaining when using jQuery.

    /** Example of method chaining in jQuery */ ////////////////////////// // WITHOUT METHOD CHAINING var $div = $ ("#my-div" ); // assign to var $div . css ("background" , "blue" ); // set BG $div . height (100 ); // set height $div . fadeIn (200 ); // show element /////////////////////// // WITH METHOD CHAINING $ ("#my-div" ). css ("background" , "blue" ). height (100 ). fadeIn (200 ); // often broken to multiple lines: $ ("#my-div" ) . css ("background" , "blue" ) . height (100 ) . fadeIn (200 );

    As you can see, using method chaining can tidy up code quite a bit, however some developers dislike the visual style of method chaining and choose not to use it.

    Understanding Method Chaining

    For our example, we will define a custom class with a few methods to call. Let’s create a Kitten class:

    // define the class var Kitten = function () { this . name = "Garfield" ; this . color = "brown" ; this . gender = "male" ; }; Kitten . prototype . setName = function (name ) { this . name = name ; }; Kitten . prototype . setColor = function (color ) { this . color = color ; }; Kitten . prototype . setGender = function (gender ) { this . gender = gender ; }; Kitten . prototype . save = function () { console . log ( "saving " + this . name + ", the " + this . color + " " + this . gender + " kitten..." ); // save to database here... };

    Now, let’s instantiate a kitten object from our class and call its methods.

    var bob = new Kitten (); bob . setName ("Bob" ); bob . setColor ("black" ); bob . setGender ("male" ); bob . save (); // OUTPUT: // >

    Wouldn’t it be better if we could get rid of some of this repetition? Method chaining would be perfect for this. The only problem is that currently this won’t work. Here is why:

    var bob = new Kitten (); bob . setName ("Bob" ). setColor ("black" ); // ERROR: // >

    To better understand why this doesn’t work, we will rearrange the code above slightly.

    var bob = new Kitten (); var tmp = bob . setName ("Bob" ); tmp . setColor ("black" ); // ERROR: // > Uncaught TypeError: Cannot call method "setColor" of undefined

    This returns the same error. This is because the setName() function doesn’t return a value, so tmp is assigned the value of undefined . The typical way to enable method chaining is to return the current object at the end of every function.

    Implementing Method Chaining

    Let’s rewrite the Kitten class with the ability to chain methods.

    // define the class var Kitten = function () { this . name = "Garfield" ; this . color = "brown" ; this . gender = "male" ; }; Kitten . prototype . setName = function (name ) { this . name = name ; return this ; }; Kitten . prototype . setColor = function (color ) { this . color = color ; return this ; }; Kitten . prototype . setGender = function (gender ) { this . gender = gender ; return this ; }; Kitten . prototype . save = function () { console . log ( "saving " + this . name + ", the " + this . color + " " + this . gender + " kitten..." ); // save to database here... return this ; };

    Now, if we rerun the previous snippet, the variable tmp will reference the same object as the variable bob , like so:

    var bob = new Kitten (); var tmp = bob . setName ("Bob" ); tmp . setColor ("black" ); console . log (tmp === bob ); // OUTPUT: // > true

    To shorten this even more, we do not even need to create the variable bob . Here are two examples with and without method chaining on our new class:

    /////////////////// // WITHOUT CHAINING var bob = new Kitten (); bob . setName ("Bob" ); bob . setColor ("black" ); bob . setGender ("male" ); bob . save (); // OUTPUT: // > saving Bob, the black male kitten... /////////////////// // WITH CHAINING new Kitten () . setName ("Bob" ) . setColor ("black" ) . setGender ("male" ) . save (); // OUTPUT: // > saving Bob, the black male kitten...

    By using method chaining we end up with much cleaner code that is easier to understand.

    Conclusion

    That’s it! Method chaining can be a very useful technique to have in your bag of programming tools. If you have any questions, let me know in the comments below.

    Предисловие

    Статья рассчитана на пользователей, хорошо знакомых с объектно-ориентированным программированием.

    Сейчас я расскажу о новой полезной возможности, которая была введена с ветки 5 в качестве развития ООП в PHP (по сравнению с веткой 4). Она называется «цепочки вызовов» (Method Chaining) и выглядит следующим образом:

    $object->method_a()->method_b()->method_c();

    Что же здесь на самом деле происходит? Начнем с того, что одно из (многих) изменений между PHP4 и PHP5 заключается в том, что теперь методы могут возвращать объекты. Это небольшое изменение позволяет по-новому использовать ООП. Итак, для того, чтобы пользоваться цепочками вызовами у вас должна быть возможность возвращать из метода объекты. Причем не существенно что это будет за объект — текущего класса, или совершенно другого.

    Обычное использование класса
    Начнем с самого обыкновенного класса и его использования.

    Class Person { private $m_szName; private $m_iAge; public function setName($szName) { $this->m_szName = $szName; } public function setAge($iAge) { $this->m_iAge = $iAge; } public function introduce() { printf("Hello my name is %s and I am %d years old.", $this->m_szName, $this->m_iAge); } }

    Теперь мы можем создать экземпляр класса Person и передать в объект имя и возраст. После чего, воспользовавшись методом introduce вывести на экран информацию о человеке:

    $peter = new Person(); $peter->setName("Peter"); $peter->setAge(23); $peter->introduce();

    В результате выполнения данного примера, на экран будет выведена строка:

    Hello my name is Peter and I am 23 years old.

    Использование цепочных вызовов
    На самом деле, отличий в коде будет совсем немного. Мы добавим всего 2 строки. Как же это будет работать? Очень просто! Нам нужно возвращать объект класса Person из каждого метода. Наш исходный класс будет выглядеть так:

    Class Person { private $m_szName; private $m_iAge; public function setName($szName) { $this->m_szName = $szName; return $this; //Возвращаем объект текущего класса } public function setAge($iAge) { $this->m_iAge = $iAge; return $this; //Возвращаем объект текущего класса } public function introduce() { printf("Hello my name is %s and I am %d years old.", $this->m_szName, $this->m_iAge); } }

    Пример использования модифицированного класса:

    $peter = new Person(); $peter->setName("Peter")->setAge(23)->introduce();

    На экран будет выведено абсолютно тоже самое, что и в изначальном примере.

    Как это работет
    Сначала мы выполняем $peter->setName(‘Peter’). Этот метод заполнит имя и вернет $this — текущий объект. Теперь нам нужно заполнить возраст. Следующее действие ->setAger(23). Поскольку мы записали вызов в цепочку, PHP интерпретирует это как «запустить метод setAge, принадлежащий тому, что вернул предыдущий метод». В данном случае Person ($peter).

    Абсолютно тоже самое происходит при вызове introduce. PHP запустит метод, обратившись к объекту возвращенному из предыдущей операции.

    Записывать методы в цепочку можно в любой последовательности и с любыми повторениями:

    $peter->setAge(23) ->setName("Peter") ->setName("Winifred") ->setAge(72) ->introduce();

    В данном случае на экран будет выведена строка

    Hello my name is Winifred and I am 72 years old.

    Отличный ответ torazaburo дал мне идею. Было бы возможно, чтобы функция, подобная связыванию, вместо того, чтобы выпекать приемник (этот) в вызове внутри замыкания, чтобы поместить его как свойство в объект функции, а затем использовать его при вызове. Это позволит повторной попытке обновить свойство до того, как будет выполнен вызов, эффективно предоставив ожидаемые результаты повторной обработки.

    Например,

    function original_fn() { document.writeln(JSON.stringify(this)); } Function.prototype.rebind = function(obj) { var fn = this; var bound = function func() { fn.call(func.receiver, arguments); }; bound.receiver = obj; bound.rebind = function(obj) { this.receiver = obj; return this; }; return bound; } var bound_fn = original_fn.rebind({foo: "bar"}); bound_fn(); var rebound_fn = bound_fn.rebind({fred: "barney"}); rebound_fn();

    Или вывод из node.js выглядит следующим образом.

    { foo: "bar" } { fred: "barney" }

    Обратите внимание, что первый вызов для rebind вызывает тот, который был добавлен в Function.prototype поскольку он вызывается в обычной функции original_fn , но второй вызов вызывает rebind которая была добавлена ​​как свойство связанной функции (и любой последующий вызовы вызовут это тоже). Эта rebind просто обновляет receiver и возвращает тот же объект функции.

    Можно было получить доступ к свойству receiver в связанной функции, сделав его именованным функциональным выражением .

    2018-12-04T00:00Z

    Заманчиво думать о bind как о какой-то модификации функции, чтобы использовать это новое. В этой (неправильной) интерпретации люди думают о bind добавляя какой-то волшебный флаг в функцию, говорящую ему использовать другое в следующий раз, когда он вызывается. Если это так, тогда должно быть возможно «переопределить» и изменить волшебный флаг. И тогда спросите, в чем причина произвольного ограничения возможности сделать это?

    Но на самом деле это не так, как это работает. bind создает и возвращает новую функцию, которая при вызове вызывает первую функцию с конкретным значением. Поведение этой вновь созданной функции, чтобы использовать указанное для вызова исходной функции, записывается при создании функции. Он не может быть изменен больше, чем внутренние функции любой другой функции, возвращаемой функцией, могут быть изменены после факта.

    Это может помочь взглянуть на реальную простую реализацию bind:

    // NOT the real bind; just an example Function.prototype.bind = function(ctxt) { var fn = this; return function bound_fn() { return fn.apply(ctxt, arguments); }; } my_bound_fn = original_fn.bind(obj);

    Как вы можете видеть, нигде в bound_fn функция, возвращаемая из bind , ссылается на this с которой была вызвана связанная функция. Он игнорируется, так что

    My_bound_fn.call(999, arg) // 999 is ignored

    Obj = { fn: function () { console.log(this); } }; obj.fn = obj.fn.bind(other_obj); obj.fn(); // outputs other_obj; obj is ignored

    Поэтому я могу связать функцию, возвращенную из bind «снова», но это не отталкивает исходную функцию; это просто привязка внешней функции, которая не влияет на внутреннюю функцию, поскольку она уже настроена для вызова базовой функции с контекстом (this значение), переданным для bind . Я могу связывать снова и снова, но все, что я делаю, это создание более внешних функций, которые могут быть связаны чем-то, но в конечном итоге вызывают самую внутреннюю функцию, возвращаемую из первого bind .

    Поэтому несколько неверно утверждать, что bind «нельзя переопределить».

    Если я хочу «восстановить» функцию, тогда я могу просто создать новое связывание с исходной функцией. Поэтому, если я связал его один раз:

    Function orig() { } my_bound_fn = orig.bind(my_obj);

    а затем я хочу, чтобы моя оригинальная функция была вызвана с некоторыми другими, тогда я не перепровергаю связанную функцию.



    Есть вопросы?

    Сообщить об опечатке

    Текст, который будет отправлен нашим редакторам: