Pull to refresh

Comments 147

Всегда волновал вопрос правильной и удобной работы с контекстом, ваша статья очень помогла, спасибо.
В ExtJS у ольшинства функций есть параметр scope, указав который, к нему можно обращаться по this.
В документации jquery вообще не освещен этот вопрос.
Пример из MooTools намного удобней всех велосипедов, которые я себе для этого создавал, работая с jquery.
спасибо.
this вообще для многих — загадка. очень немногие реально чувствуют это в Javascript.
как и много других вещей)
Просто очень немногие реально знают Javascript :)
Проблема, наверное, в том, что неправильно выбрано ключевое слово — this.
В JS больше подошло бы current, или что-то в этом духе.
В jQuery есть $.proxy.
Так же можно написать свой вариант $().bind (назвав его, например, $().bind_ctx), который принимает дополнительный параметр — контекст обработчика. Я однажды так и сделал в довольно крупном проекте — было довольно удобно.
было довольно удобно

интересно, что, так же, было неудобно?
Ну, навешивание обработчика выглядело довольно громоздко.
что-то как-то похоже на перегон из пустого в порожнее, и аргументы весьма неубедительные. чем это this семантичнее self?

ваш предпоследний пример, кстати, отлично показывает преимущества сохранения контекста в переменную.

var self = this;

dataRouter.get(self.parse);

parse: function() {
// self.*
}

это не говоря о том, что «приватные» методы вообще-то хранятся в переменных внутри scope конструктора, а не в this, т.к. this публичен.

() {

var private = function() {};

self.public = function() {
setTimeout(private, 100);
}
А если контекст нужно сохранить два раза, один за другим?
В общем, посмотрите третий пример.

Да и продить сущности с одинаковыми по смыслу, но разными по написанию названиями — дурной тон. Понимание кода сильно усложняется.
пример высосан из пальца, кому может понадобится сохранять контекст лямбды?

как вы показали в примерах, смысл у this и self разный. первое — контекст исполнения (по большому счету, весьма ненадежная вещь в этом языке), второй — контекст объекта.

понимание кода не усложняется, если не пользоваться this, кроме как для сохранения контекста создаваемого объекта в кострукторе в self.

ps: .bind, кстати, намного раньше появился в prototype.js
пример высосан из пальца, кому может понадобится сохранять контекст лямбды?

Пятый пример.

второй — контекст объекта.

какого объекта?

В Javascript крайне широко распространено асинхронное программирование. При этом функции часто вызывают другие функции, передавая им функции обратного вызова.

Такие цепочки замыканий могут достигать большого уровня вложенности. Простой пример, например, здесь: Node.JS — формируем результирующий документ, используя другие HTTP-источники
большое спасибо, но я не понял к чемы Вы это.
> пример высосан из пальца, кому может понадобится сохранять контекст лямбды?

А вы правы :) Только сейчас продумал вашу фразу, и соглашусь. Контекст лямбды нужно сохранять только в случае, если она определена где-то извне, вызывается в конкретном контексте, и её автор не владеет информацией о контексте вызова. Что — крайне редко.
Мне не раз нужен был сохраненный контекст лямбды. Например тогда, когда стек замыканий вырастает до 5-6 функций и этого никак нельзя избежать — асинхронность.

Однако не понимаю, чем автору непонравился self! Вот self2 — это уже маразм, и когда требуется self2 есть 2 варианта действий:
Застрелиться либо дать нормальные имена для self и self2, как советует автор

Но вот когда один только self — я считаю — это нормально.
а почему self, а не that, this, _this? this — это конструкция языка. и она указывает на текущий контекст. а на какой контекст указывает self? родительский? родителя родителя?

вы предлагаете писать все в одной куче? у меня четкое разделение по классам-методам, что позволяет держать код структуированным и легко-читаемым.

использование scope конструктора считается дурным тоном, разве вы не знаете? а записи вида
*а записи вида
var MyClass = function () {
    var 
privateProp1 1;
    var 
privateProp2 2;
    
    var 
privateMethod = function () {};
    
    
this.publicMethod = function () {};
};

крайне не рекомендуются
не рекомендуется кем?
я прошу прощения, но Вы и в статье и в комментариях делаете какие-то абстрактные отсылки к мнению абстрактных людей («считается», «не рекомендуется», «противоречит канонам», «все давно сошлись»), давайте основываться на имеющих вес аргументах, а не на пустых словах.

т.к. могу писать лишь раз в пять минут, отвечу тут же:

> какого объекта?
контекста объекта, созданного оператором new, в котором исполняется коструктор, очевидно же.

> protected переменные
нигде, нету их. либо только если code convention.
кем не рекомендуются? на самом деле весьма выгодная практика. чуть больше расход памяти, выше скорость, удобнее разделение.
Скорость не выше кстати. А практика эта «не плоха» только в случае когда мы работаем с синглтонами.
ну я Вам тем же могу ответить: почему .bind, а не ._b?
на какой контекст указывает self и чем он отличается от this я ответил выше.

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

я назвал пятый пример высосаным из пальца вот почему:
var block = $(this);
$('button').each(function (i, button) {
$.each(users, function (i, user) {
block.append(user.init(button));
});
});

далее, я считаю Ваш предпоследний пример — контрпримером к статье, потому что в этом методе:

start: function () {
this.dataRouter.get(
this.parse.bind(this)
);
},

Вам нужно _помнить_ выполнить parse в нужном контексте (я считаю это несмоненно усложнение своей работы и, самое печальное, своих коллег), а с использованием self можно обойтись this.dataRouter.get(this.parse);

var Analizer = function(_name)
var
self = this,
_router = new DataRouter(_name),

parse = function(data) {
// self.public или приватные без префикса вообще
}

;

// полагаю, что start должен быть публичным
self.start = funciton() {
_router.get(parse);
}

// ctor
return self;
});

ругайте меня как хотите, но я считаю такой код более логичным и, главное, понятным.
я вас понял. посмотрите. в предыдущем топике я предоставляю api для работы с AI.
Оно реализуется наследованием от другого класса, который предоставляет несколько приватных методов и находится совершенно в другом месте:
Bridge.AI = new Class({
    Extends : Bridge.AIUtils,

Как применить ваш трюк с self к нему?
Я раньше тоже использовал этот способ, но со временем я понял его недостатки.
Самое главное — при использовании этого способа не называть переменную self, that, t, а давать название, которое характеризует содержимое. В данном примере —
var Analizer = function(_name) {
    var analizer = this, // analizer а не self!
    _router = new DataRouter(_name),

Это убирает большинство недостатков данного способа. А bind — приятное дополнение.
Никак конечно, потому что реальный функция-конструктор объекта хранится внутри mootools, но это проблема mootools (точнее с ним связанная), а никак не техники сохранения контекста.

я бы согласился с Вами в том, что конексту необходимо давать осмысленное имя (analizer вместо self), если бы ни тот факт, кто именованный конекст в одном объекте (классе, если хотите) по-моему должен быть всего один! по-моему, очевидно что медоты класса оперируют в контексте объекта этого класса, и указывать наэтот факт два раза (*this*.method.bind(*this*)) все же, излишне.

я все еще считаю, что каскадное сохранение конекстов никогда не требуется. в приведенном Вами примере контекст используется де-факто для передачи параметра, таков стиль jquery, это, возможно, удобно, но я не считаю это правильным, да и jquery предлагает «стандартные» механизмы как альртернативу (см. тот же .each).
я раньше долго использовал self = this и потом я испытал на практике способ с .bind. И сообщаю сообществу, что это очень удобно (а с JS я работаю много). Сообщество прочитало, проанализировало и приняло свое мнение. Тем не менее, я рекомендую вам попробовать — не пожалеете со временем. Код получается более структуированным и читабельным. Мое субъективное мнение
я собственно, потому и вступил в спор, если хотите, что у меня зеркальная ситуация: я пользовался .bind, когда работал с prototype, а потом отошел от этой практики в пользу self.

в любом случае, спасибо за мнение.
возможно, зависит от стиля кодирования и от приложения. любой подход в программировании надо применять с умом
ребят ну self реально не семантический, ровно как и переопределение контекста. Там и там нада смотреть и помнить. мне кажется самое лучшее — это давать внятные названия как предложенное первое решение.
Вообще я рад что вы раскрыли тему, а не затроли ее =) спасиб обоим.
А заставлять своих коллег помнить названия объектов и методов Вы, случайно, усложнением не считаете? )

Выполнение методов в произвольном контексте — как в естественном, так и в кастомном — является неотъемлемым приёмом программирования на JS. Факт наличия «родных» методов apply и call у функции уже говорит об этом. JS функциональный язык, жонглирование контекстом исполнения есмь часть его парадигмы.
если Вы говорите о js-парадигме, то, простите, о каких-таких «методах» речь? :)
и в чем тогда отличие функции от метода?

это, конечно, лишь вопрос терминологии, но, по-моему, в js метод как раз-таки и есть функция, привязанная к определенному контексту — объекту, чьим «методом» она является.

если же мы не говорим о классическом ооп, то давайте не упоминать методы и тогда Ваш комментарий совершенно верен, но не уместен в контексте разговора.
>> и в чем тогда отличие функции от метода?

Если говорить формально
A function stored in a property of an object is called a method.

javascript.ru/ecma/part4#a-4.3.3
В JS любая функция это метод какого-то контекста. Если контекст не указан явно, то используется глобальный. Но в функциональных языках программирования нету жёсткой привязки к контексту: объявление метода в одном контексте это не привязка, а просто декларация его «места хранения», если можно так выразиться.

Абсолютно нормально написать что-то типа:

MegaContext.MilliContext.itsMethod.apply(GigaContext.MicroContext);

Тем самым исполняя метод одного контекста в другом контексте. В JS это так же естественно как в классическом ООП инициализировать объект класса с помощью конструктора родственного класса.
хорошо, с терминологией определились: любая функция — это метод чего либо (с этим я не спорю).

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

писать так не «абсолютно нормально», а «допустимо».

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

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

Если совсем коротко сформулировать, то в JS методы определяют объект, а не объект методы.

Ну и отвечаю на последний Ваш вопрос: я вступил в этот спор потому, что очень люблю Javsscript и, соответственно, не люблю когда в него начинают привносить привычные наработанные приёмы из других языков, которые нарушают его собственную логику.
я, честно, больше ждал ответа на первый :)
тогда, думаю, все встало бы на свои места, я полагаю.

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

Лично я уже не представляю себе программирования на JS без жонглирования контекстами. Что если контекст это просто абстрактный интерфейс хранящий функцию для другого контекста? А значит и называть и конструировать его следует как хранилище, а не как управляющий объект. А также использовать и описывать в документации.

Поправьте, если я ошибаюсь, но вы рассматриваете контекст исключительно как програмную сущность, а его методы, соответственно, как функционал сущности. Но в JS способов употребления контекстов на порядок шире: я выше перечислил некоторые.

Как это связано с темой поста? Очень просто: представьте, что функция-обработчик разрослась и было принято решение сделать её методом контекста. Такое случается сплошь и рядом. Правильное привязывание контекста гарантирует безболезненный рефакторинг. И это только один пример.

Очевидно, что лучшим решением было бы использовать оба способа — как замыкание, так и привязку контекста — разумно взвешивая необходимость их употребления.
с технической точки зрения в Ваших словах для меня ничего нового нет, но я готов признать, что «мыслю» в js не так как Вы и допускаю, что это не правильно.

<зануда>
язык … есмь часть
есмь — форма наст. времени 1-го лица ед. ч. от быть.
то естЬ — я есмь, но язык … есть

</зануда>
Ага, спасибо. Со мной иногда бывает.
<зануда><зануда>
уж если есмь, то тогда уж и азаз есмь
</зануда></зануда>
более того, а где хранить protected переменные. все давно уже сошлись на мысли, что в ЖС не стоит парить себе мозг и создавать все свойства в паблик, регулируя область видимости комментариями и соглашениями
запись .bind(this) означает примерно следующее: «контекст сохраняется и дальше»
и в итоге получаем такой неуклюжий код:

this.parse_binded= this.parse.bind( this )

element.attachEvent( 'click', this.parse_binded )



element.detachEvent( 'click', this.parse_binded )
А кто мешает сделать более красиво?

this.bound = {
open: this.open.bind(this),
close: this.close.bind(this)
}
...
el.addEvent('click', this.bound.open);
...
el.removeEvent('click', this.bound.open);
Спасибо за чудесную стилистическую идею var $this = $(this);! И как сам не догадался :)
Более того, часто удобно все переменные-кортежи jQuery называть $xxx.
Я вот слышал фразу «что это за PHP в яваскрипте???». Но тем не менее имхо: это достаточно удобно и наглядно, сам так делаю.
UFO just landed and posted this here
«Такой страницы не существует.»
UFO just landed and posted this here
UFO just landed and posted this here
Ну. Я себе не присваиваю авторство метода bind, прямо заявил, что взял идею у MooTools. А содержимое статьи совершенно про другое, чем написано в xpoint
TheShock, ты, конечно, клевый чувак, но это все ненужное наживание геморроя.

asyncFunc(function () {
    this.callMethod();
}.bind(this));

А если внутри функции требуются оба this? А как быть с параметрами? Все что нужно давать осмысленные имена для внешних this.
homm, спасибо.
хочу спросить. а ты статью читал?)
я про это в статье и говорю:
1. Надо давать осмысленные имена переменным
2. Если мы просто хотим продолжить работать с текущим контекстом — используем bind и не плодим лишних, никому не нужных сущностей.
и пример с двумя this я тоже рассматриваю. а что с параметрами?
Читал ровно до процитированного кода. Про параметры понял, arguments есть.

Просто на пункте «давать осмысленные имена» нужно было остановиться. Потому что сегодня нужен один контекст, завтра оба, послезавтра внутри колбака нужно будет сделать еще один колбак. И зачем выделять случай когда нам еще пока не нужен внутренний контекст, не понятно.
можно просто использовать this и не искать геморроев на свою голову
спасибо, Кеп! А ведь еще можно использовать Javascript и NetBeans и не искать геморроев на свою голову!
всегда пожалуйста!
;)

но при чём здесь ide? o_O
та вот и я про то же. статью еще раз прочтите, может дойдет;)
var a = 'global';

var ctor = function() {

this.a = '';

this.setA = function(value) {
this.a = value;
}

this.getA = function() {
return this.a;
}

}

var o = new ctor();

o.setA('test');

alert(o.getA()); // test
alert(a); // 'global';

var setter = o.setA;
var getter = o.getA;

setter('wow');

alert(getter()); // wow
alert(a); // wow — wtf??
alert(o.getA()); // test — wtf??
Ну так а вы что хотели контекст то меняется и это не повод везде использовать замыкание :)
95% wtf code

ошибки в днк ещё не повод хвастаться кривизной рук
это пример для прочтения и понимания сути проблемы, почему нельзя использовать this. написан на javascript для тех, кто не умеет читать по-русски (топик).

еще раз, без wtf, раз у вас такие обширные проблемы.

var message = function() {
this.message = '';

this.setMessage(msg) {
this.message = msg;
}

this.showMessage() {
alert(this.message);
}
}

var msg = new message();
msg.setMessage('testing');

setTimeout(msg.showMessage, 1000); // undefined
если в не умеете использовать this в javascript — то это ведь ваши проблемы, правда?
эти два примера показывают непонимание вами принципов экранирования переменных
ru.wikipedia.org/wiki/Область_видимости
с чего вы взяли, что я чего-то не понимаю?
наполните хотя бы один свой комментарий смыслом.
я сужу по приведённым примерам бесполезного неработающего кода
ясно. приятного аппетита.
Еще один плюс к использованию var self = this; — при сжатии кода зарезирвированное слово «this» остается без изменений, т.е. не сжимается. А переменная «self» или любая другая — может быть сжата.
В статье — ни слова про замыкания… а ведь var self = this; перед моментом смены контекста работает только потому что в js есть механизм замыканий. Этот механизм является очень красивым, но и довольно «дорогим». Злоупотребление им (а var self = this — это явное злоупотребление) в больших количествах приводит к утечкам памяти.
Без замыкания можно обойтись только в том случае, если функция, принимающая callback, принимает и контекст для него. Иначе что self, что .bind, всё равно замыкание создаётся.
В случае bind замыкание идёт только на один уровень вверх, и не вылезает в ваш код. Замыкается только сама функция, которую мы вызываем, а не сам контекст, который может быть сколь угодно большим объектом.
Постоянно это использую. Ну и есть мнение, что не стоит трогать прототипы, в таком случае можно пользоваться underscore.js
Мое мнение: если приходится сохранять два контекста — с кодом что-то не так и надо еще раз его пересмотреть. На моей памяти не было ни одного случая, когда приходилось бы запоминать более одного контекста.
Про осмысленные имена: они несомненно нужны, если по какой-либо причине сохраняется более одного контекста. В случае с одним — self вполне достаточно и понятно.
putCardSmart: function (card) {
this.putCard( card,
// Этот метод вызовется только когда карта долетит, но он сохранит контекст.
this.finishSmart.bind(this)
);
},

В каком контексте будет выполнен метод this.finishSmart? :)

.bind(this) в данном примере явно лишний.
не лишний. без него контекст будет глобальным.
Больше похоже на навязывание своего мнения.
Как по мне, bind особо не отличается от self, использую оба способа в разных ситуациях.
По производительности не понятно что лучше, создание анонимной функции (на вызов bind) или замыкание (self)
self использую в своих библиотеках, т.к. bind определяют чуть ли не в каждом framework'e и везде по разному, а self удобно использовать когда нужно передать дополнительные параметры, например

var cl = {
    test: function() {
        var a = 1;
        var b = 2;
        var self = this;

        setTimeout(function() {
            self.callMethod(a);
        }
        setTimeout(function() {
            self.callMethod(b);
        }
    }
}
self.callMethod(a).delay()
ну или
method(a).bind(this).delay();

В принципе все тот же, описанный выше, mootools
В Mootools всё проще, там можно сделать так:

method(a).delay(200, this);

Т. е. методы delay и periodical принимают ссылку на контекст исполнения как параметр. За что я и люблю Mootools, так это за правильную работу с контекстами.
Я предпочитаю var that = this. Всё-таки используется «тот» контекст, а не «этот». Да и self — предопределённая переменная в JS, которая впрочем, как правило, не используется.
Немножко расширим прототип Function


Если ваш браузер поддерживает ES5, то вы его не расширяете, а напротив «портите». Потому что в ES5 уже есть Function.bind со своей семантикой. В MooTools даже баг есть связанный с этим: их bind неправильно себя ведёт в браузерах где уже есть Function.bind c ES5 семантикой, потому что они пытаются его использовать в качестве своего bind. И вот решение:

For MooTools Core 1.3 we are going to adopt the ES5 version of Function.prototype.bind.
Да, попутно добавлю
dmitrysoshnikov.com/notes/note-1-ecmascript-bound-functions/

То есть делать, как написано в этой статье, вредно. Надо назвать метод иначе хотя бы, и расширять системные прототипы — вообще вредно.

Подход $.proxy мне нравится больше.
Расширять системные прототипы не вредно, а нормально и естественно. Просто для этого нужен аккуратный подход и постоянный интерес к развитию языка и используемых им стандартов.

Метод bind в ES5 был внесён именно из библиотек Prototype и Mootools. Так часто бывает, что удачные реализации из либ становятся частью стандарта. Мы не можем при разработке решения предвидеть все будущие ходы разработчиков стандарта, которые в будущем станут конфликтовать с нашим решением. Следовательно, правильный подход: забить на предсказания будущего и разрабатывать как тебе удобно, но при этом, повторюсь, живо следить за развитием стандартов и языка. Ошибка Motools в несоблюдении последнего, а никак не в расширении системных прототипов.
Расширение системных прототипов, — дорога в прототип ада :)

Хотя соглашусь, если вы пишете приложение (контролируете окружение), а не компоненты, то это нормально.
> Расширение системных прототипов, — дорога в прототип ада :)

Не согласен абсолютно. Волков бояться — в лес не ходить. Опасность расширения родного прототипа это малообоснованное суеверие. Надо просто уметь расширять и, ещё раз, всегда следить за развитием используемого инструмента.

Я лет шесть програмлю регулярно расширяя прототипы для JS, как системные, так и Motools'овские. И, представьте, до сих пор ни единого разрыва )

Ничего личного, всё общее )
Лично мне тоже очеть нравится етот подход. Но как участник закрытой рассылки для разработчиков mootools, должен сказать, что в mootools 2 планируется перейти от расширения прототипов к врапингу… Лично я против.
Поддержу критиков по ключевым моментам.

1) Случай, когда нужно сохранять разные контексты, возможен, но это уже повод задуматься — может что-то здесь не так? Может стоит что-то упросить и переделать, а не городить огород? А с одним контекстом и self/that/me/<что_нравится> сойдет. Наоборот — будет единообразие в коде и не требуется каждый раз вспоминать, как замыкание на себя назвал.

2) Уже сказали, что злоупотребление биндом чревато утечками памяти. Все-таки плодить замыкания внутри анонимных функций лишний раз, ИМХО, не стоит — это даже не только обсуждаемого случая касается, а вообще. Одно дело, когда мы в конструкторе сделали одно замыкание и потом его используем в асинхронной работе, приватных методах и т.п., а другое — когда все вызовы тех же приватных методов у нас обернуты в анонимную функцию с замыканием в ней контекста. Брррр… Мне аж жалко становится несчастных разработчиков интерпретаторов Javascript… :-)
Вообще-то как раз наоборот — bind приводит к снижению риска появления утечек памяти, а замыкания в конструкторе и в своих больших методах, когда замыкания могут достигать нескольких уровней вложенности, приводят к бОльшему расходу ресурсов и затрудняют работу сборщика мусора.
о каком расходе ресурсов Вы говорите?
Хранение объектов в памяти для обеспечения замыкания: при использовании bind, хранится ссылка на вызываемую функцию, при использовании var self = this; — ссылка на сам объект, контекст для которого нам нужен. Функция занимает мало места в памяти, объект обычно — намного больше. Функция не ссылается ни на кого. Объект может ссылаться на любое количество взаимодействующих с ним объектов.

Если сборщик мусора ошибется (а bind гарантирует вам, что он не ошибется) и не уберет уже ненужные для замыкания объекты из памяти — то в первом случае утечка будет совсем крохотной, а во втором — всё зависит от сложности вашего приложения. Если вы пишите маленький скриптик на jquery на 100-500 строк, то вы, наверное, даже и не почувствуете разницу. Если вы пишите монстра, например, что-то типа ExtJS или огромный интерфейс на самом ExtJS, то скоро команда QA заведёт в вашем багтрекере issue, что ваше приложение в стареньком ИЕ течёт со страшной силой :) Но если вы прочитали этот комментарий, то вы будете знать в чём дело и замените все var self = this; на .createDelegate (это так в ExtJS называется .bind)
вы указали расход ресурсов *и* затруднение работы сборщика мусора, а теперь свели это к одному. так это одно или нет? я вот почему спрашиваю.
Понял вашу мысль и в целом согласен, но между ссылкой на функцию и ссылкой на объект принципиальной разницы не вижу — функция все равно и есть объект, а ссылка — это именно ссылка, а не копия же объекта в памяти…
При этом, если мы сделали замыкание на себя в конструкторе и используем его в приватных методах, то сборщику мусора вообще убирать по большому счету нечего, в отличие от создания на каждый чих анонимных функций, которые по любому действительно придется из памяти вычищать (и не важно, что они там замыкают — только функцию или объект здоровый)…
Мне, слава Богу, в своей работе смотреть на IE6 не надо, но и у него, если память не изменяет, утечки тоже как раз именно на анонимных функциях больше все проявляются.
В общем, респект вам за ваше подробное объяснение, но мне все-таки кажется, что одно замыкание на большой объект лучше, чем 1000 замыканий на функции внутри анонимных функций. :-)
при использовании bind, хранится ссылка на вызываемую функцию, при использовании var self = this; — ссылка на сам объект, контекст для которого нам нужен.


вы хотите сказать, что если вы сделаете func.bind(this) то вам не придется хранить ссылку на this? =)
$('input').bind('keydown', function () {
        var $this = $(this);
        $this.css({
            background : $this.val()
        });
    });


В примере выше вообще нет переключения контекста.

bind использовать стоит однозначно. Тем более он уже вошел в новый в javascript 1.8.5

Как именовать переменные — это наверно от команды больше зависит.
да. в том примере было написано о том, что $this — плохое название переменной.
Мне кажется кроме bind и call не стоит ничего использовать, они на много удобней.
О да, а расширять прототипы базовых объектов — это офигенно правильно.
да, такая идеология языка
Как-то никогда не замечал такой идеологии в JS.
Писать во всех for (… in ...) проверку на hasOwnProperty — настолько мощный бред, что никакие удобства от расширения стандартных объектов его не оправдывают.
Вы часто делаете for-in по функциям?
Вполне себе, а что? Почему у функции не может быть полей?
> перечивайтесь на какой-нибудь arr.each, пока не поздно
звучит как угроза
Стоит отметить что использование closure (замыкание) или bind — зависит от архитектуры. Вы не упомянули в статье, что Function.prototype.bind внесен в стандарт ECMAscript 5, и последние версии браузеров его поддерживают (даже IE9). По стандарту можно передавать не только контекст но и статичные параметры, то есть myfunc.bind(someObject, 1, 2) вернет функцию которая будет вызываться с контекстом someObject а первые два параметра будут 1 и 2 соответственно, не важно с какими параметрами она вызвана, и с каким контекстом (для call и apply игнорируется первый аргумент).
Если bind поддерживается браузером нативно, то его использование гораздо эффективнее замыкания, так как в случае замыкания создается экземпляр функции, которая «помнит» переменные своей области видимости, что ведет к затратам по памяти. В случае с bind создается обертка для функции, то есть это отдельная специальная структура, которая содержит ссылку на функцию, контекст и список значений для статичных параметров (если такие есть). В последнем случае получается экономичнее в плане памяти.
К тому же closure ни разу не эквивалентно bind и есть ряд ситуаций, когда нельзя использовать одно или другое. Разница проявляется в области видимости. Все дело в том что когда вы делаете closure то у созданной функции будет та область видимости, где она объявлена (создана). Когда вы делаете bind, то область видимости у функции сохраняется (там где она была объявлена), меняется только контекст. Это нельзя увидеть в примерах статьи, так как в них область видимости перекрывается, и сами примеры весьма просты.
Вот пример:
function example(){
return this.value + privateValue;
}

function exampleClass(value){
var provateValue = value;

// делаем метод со статичным контестом
var self = this;
this.methodWithFixedContext = function(){
return self.value + privateValue;
}
// либо же
this.methodWithFixedContext = (function(){
return this.value + privateValue;
}).bind(this);

// но никак не
this.methodWithFixedContext = example.bind(this);
}

Не смотря на то, что текст второй анонимной функции совпадает с текстом функции example — у них разная область видимости, и результат выполнения будет разный. Во втором случае по сути мы делает замыкание плюс делаем bind, который в случае эмуляции (для браузеров которые не поддерживают его нативно), создает еще одно замыкание. В такой ситуации второй вариант весьма затратный, к тому же нагроможденный. Так что предпочтение лучше отдать первому варианту (менее накладно и проще в восприятии).
Другой пример:
var globalCounter = 0;
function setMethodTimeout(someObject){
globalCounter++;

// замыкание
setTimeout(function(){
someObject.method(globalCounter);
}, 10)

// почти то же самое, но через bind
setTimeout(someObject.method.bind(someObject, globalCounter), 10);
}

var objectA = { method: function(){ console.log(arguments) } };
var objectB = { method: function(){ console.log(arguments) } };
setMethodTimeout(objectA);
setMethodTimeout(objectB);

В случае использования замыкания по сути будут вызовы:
// замыкание
objectA.method(2);
objectB.method(2);
// bind
objectA.method(1);
objectB.method(2);

Что совсем не одно и то же, а какой способ использовать зависит от задачи.
Придумать пример когда нельзя использовать замыкание и подойдет только bind не получилось.

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


Поддерживать таким образом bind мало удовольствия — получается во всей среде исполнения надо быть очень аккуратным, потому что есть полноценные функции, а есть какие-то куцые обертки, которые с одной стороны требуют особых преплясываний при вызове, а с точки зрения языка вроде бы являются «полноценными функциями». Если вы посмотрите, например, в файлик v8natives.js, то обнаружите, что реализация bind в V8 создаёт обычную функцию.
Речь шла о спецификации ECMAscript 5th Edition. Полез посмотреть еще раз, прямого упоминания отдельной структуры не нашел, но видимо так отложилось примечание к разделу 15.3.4.5 Function.prototype.bind (thisArg [, arg1 [, arg2, …]]):
NOTE Function objects created using Function.prototype.bind do not have a prototype property or the [[Code]], [[FormalParameters]], and [[Scope]] internal properties.

И еще в Table 9 (Internal Properties Only Defined for Some Objects) говорится про свойства [[TargetFunction]], [[BoundThis]] и [[BoundArguments]] которые есть только у «function object created using the standard built-in Function.prototype.bind method».
То есть, да это функция, но не совсем обычная — у нее нет определенных свойств, которые есть у обычных функций и есть свои специфические. Так что больше выглядит как отдельная структура. А как это реализуется в браузерах — дело самих браузеров, спецификация в большей степени носит рекомендательный характер.
Так же если взгляните на тест от мелкомягких samples.msdn.microsoft.com/ietestcenter/Javascript/ES15.3.html то можете заметить что не так все хорошо с bind в том же Сhrome (в плане соответствия ES5th) и видимо там это своего рода «костыль».
Так же если взгляните на тест от мелкомягких


Все уже починили :-)

Total tests: 27 Passed: 27 Failed: 0 Could not load: 0
Я что-то не понял смысла статьи… Вы вообще с JS знакомы?

this указывает текущий контекст исполнения. var self = this в конструкторе помогает функциям найти «родной» контект в случае если они выполняются в другом (что есть основа функциональщины и прототипирования) и это нормальная конструкция. Встроенная функция apply позволяет изменять конекст исполнения под свои нужды. А что вы предлагаете я не понял, если честно… Вы ещё один человек, пытающийся сделать из прототипированного ООП классовое? Если да, то до свидания, нам, прототипщикам, ваши советы не только не полезны, но и вредны. Если вы не понимаете как работает фукнциональщина + прототипирование, то учитесь — это будет полезно в вашей карьере.

bind — это корявый костыль вокруг apply. Вы либо используете apply, либо не используете. Зачем bind?

Возможно я чего-то не понимаю, может уделите мне пару минут обьяснить в чём смысл написанного в посте?
не хочу объяснять. думаю, поможет просто еще раз прочитать статью. там есть две чётких мысли. к ООП они никак не относятся и кричать к любой статье о JS «нет ооп», даже если она говорит совершенно о другом — это верх троллинга.
Причём тут троллинг? Конструкция var self = this используется исключительно при работе с ООП возможностями JS. Зачем вы её не любите? Из поста вообще ничего не ясно.
>Зачем bind?

чтобы писать

a(b.bind©);

вместо

a(function() {
b.apply(c, arguments);
});

короче и понятнее.

почему автор (и многие другие) против self — достаточно разъяснено в комментариях.
Я сначала думал пройтись по комментам и написать где какие ошибки в мыслях, а потом всё стёр и вот что напишу.

Ни вы, ни товарищ TheShock не понимаете что такое Javascript. Это нормально, у вас опыт работы с классовыми ООП языками типа PHP и Java, поэтому Javascript для вас что-то совсем непонятное. К тому же в JS есть фундаментальная ошибка, которая вносит кучу беспорядка — авторы зачем-то решили, что язык должен быть более дружелюбен для классово-ориентированных программистов и добавили оператор new. Epic Fail!

В прототипных языках у вас всё — обьекты. У обьектов есть какие-то аттрибуты, методы и свойства. Описывая эти свойства вы, в нормально прототипной модели, описываете их сразу у обьектов: Object.attr = 'hello'. Инициация нового обьекта делается с помощью clone/copy: var newObj = Object.copy. Данная операция делает клон обьекта Object.

А в JS ВСЁ НЕ ТАК! new вызывает конструктор обьекта и клонирует… Не сам обьект, а его ПРОТОТИП! То есть new Object() сначала делает Object.prototype.copy, а потом в контексте этого обьекта вызывает Object(). Понимаете? Офигеть логика, не правда ли?

Чтобы сделать нормальный clone/copy, необходимо прочитать статью Крокфорда и понять что он там говорит — javascript.crockford.com/prototypal.html

Поэтому new на самом деле мешает программистам с опытом классового программирования влиться в ряды прототипщиков.

Так вот, перейдём к контекстам. this — ещё одна ошибка в ДНК JS. Опять же такое название выбрано для большей «привычности» для новичков в мире JS. this в JS — это не тоже самое, что this в PHP. this стоило бы назвать context, тогда бы было меньше путаницы. this в JS в традицонном смысле вообще нет. Поэтому если вам нужен традиционный для других языков this, то в конструкторе вы делаете var self = this — добротная эмуляция необходимого. И да, не путайте контексты с привычным вам this из PHP, оок? Это совсем разные штуки. И этой штукой надо уметь пользоваться.

Что такое bind? bind — это curry. Ещё одна фишка из мира функциональщины. С помощью bind вы можете подготовить функцию с нужными параметрами и контекстом для отложенного выполнения или для подготовки набора параметров для последующей работы с меньшим набором переменных. bind — это не способ борьбы с тем, что вы не понимаете контекстов, это curry — en.wikipedia.org/wiki/Currying.

А вся статья построенна на том, что автор хочет делать классы и не понимает что такое контекст выполнения. Если вам что-то конкретно непонятно — спрашивайте, буду обьяснять.
и чо?

«он старается в Javascript сделать такую модель ООП, как в Java» в сообществе ЖС программистов считается страшным оскорблением, считается, что сказавший такую штуку сразу же становится на две головы выше своего опонента и такие тролли, как вы применяют её к месту и не к месту, просто чтобы выебнуться.

а ситуация в том, что ВЫ не понимаете Javascript и просто сейчас крутите словами, чтобы потроллить. как всегда, сообственно
Что конкретно я не понимаю? Посмотрите на код YUI для начала.
> все давно уже сошлись на мысли
> перечивайтесь на какой-нибудь arr.each, пока не поздно
> в сообществе ЖС программистов считается
хватит уже популизма, так и до политики недалеко :)
Вы не читали мои комментарии, похоже. я вообще-то оппонент автора.
ну и стоило бы быть вежливее.
это тролль, не обращайте внимания
Вы тут всех несогласных троллями считаете. Молодец! Так держать! Поставьте ещё мне минусик в карму и можете спать спокойно.
Извините, попутал. Тут столько всего понаписато, что аж я даже не знаю…

Насчёт bind. bind — это curry, а не замена self = this или чего-либо ещё. Возможно я сегодня дурак, но я действительно не понимаю, зачем нести в JS классовое ООП и карить всё на свете. С таким подходом мы получаем некоторый оверхед и усложняем поддержку кода.
к моему сожалению, всем этим мы упрощаем поддержку кода.
Вас окружают специалисты js и только они поддерживают Ваш код? *зависть*.
new вызывает конструктор обьекта и клонирует… Не сам обьект, а его ПРОТОТИП!
Ничего он не клонирует. Новосозданный объект связывается с прототипом. Это не то же самое, что клонирование.
Вы правы, спасибо за поправку.
Если вы не понимаете как работает фукнциональщина


во многих функциональных языках есть такие штуки как «каррирование» (currying) и «частичное применение» (partial application). Function.bind — это фактически удобный способ делать частичное применение в Javascript.
Я знаю, но карри — это не способ борьбы с собственным непониманием контекстов и возможностей языка.
А кто вам сказал, что Function.bind это способ борьбы «с собственным непониманием контекстов и возможностей языка.»?

p.s. «карри» — это вообще приправа на основе куркумы =)

в общем-то в нормальных фреймворках это реализовано (см. scope в ExtJS или context в YUI).
Похоже, тут начинает действовать закон en.wikipedia.org/wiki/Sayre%27s_Law: «In any dispute the intensity of feeling is inversely proportional to the value of the stakes at issue.»

Какая разница, bind — не bind, по большому счету-то, а комментарии за сотню уже перевалили)
$('.some').click(function(){
// нам здесь нужен бы контекст, но хочется взять того, на ком горит событие...
  this..... // <-- в таком случае как лучше поступать, как считаете?

}.bind(ctx));
в таком случае достаточно давать правильные имена переменным, в которых вы запоминаете контексты.
Спасибо. Давно не читал таких очевидных вещей + копипасту из документации.
Вы бы подумали о сборщике мусора в том же ИЕ6. Он с ума сойдет от такого количества контекстов, которые ему придется хранить.
можно для тупых вкратце: что такое контекст и его захват?
контекст исполнения. Это то куда ссылается this например в обработчиках используемых в JQuery this ссылается на тот элемент для которого сработал обработчик (вроде как, нужно поглядеть конечно в доку)
правильная реализация BIND в соответствии стандарту есть нечто вроде:
Function.prototype.bind = function() {
    if (arguments.length < 1 && typeof arguments[0] != "undefined") return this;
    var __method = this, args = [];
    for(var i=0;i<arguments.length;i++){ args.push(arguments[i]);}
 
    var object = args.shift();
    return function() {
      var args_to_apply = []
 
      for(var i=0;i<args.length;i++){ args_to_apply.push(args[i]);}
      for(var i=0;i<arguments.length;i++){ args_to_apply.push(arguments[i]);}
      return __method.apply(object, args_to_apply);
    }
};
ну и для полноты картины демо взятое из Prototype
var obj = {
  name: 'A nice demo',
  fx: function() {
    var s='';
    for(var i=0;i<arguments.length;i++){= s+(i==0?'':',')+arguments[i];}
    alert(s);
  }
};
var fx2 = obj.fx.bind(obj, 1, 2, 3);
fx2(4,5);
ой, алерт правильный такой: alert(this.name + '\n' +s);
Sign up to leave a comment.

Articles