Comments 45
Перешедшие на кофескрипт от неправильного контекста не страдают, используя при объявлении функции => вместо -> мы получаем bind на текущий объект :)
ага. и контекст зависит от того, есть чёрточка или нету. А на самом деле под капотом всё тот же Бинд.
Коллеги, а вам не кажется, что жирная стрелка — не лучший символ, т.к. путается с обычной стрелкой? Я лично путаю периодически при чтении кода. Мб лучше была бы «жирная и длинная» — ==>?

Но больше всего путает, конечно, чередование of/in при итерировании объекта/массива — довольно часто путается, глупые затыки из-за этого происходят.
Про стрелки не соглашусь, сложности отличия -> от => никогда не замечал, а вот то, что of для хешей, а in для массивов, запомнилось не сразу.
Присоединяюсь к мнению как coffee адепт :), пропихиваю его везде где могу. А я вот эта фича с сохранением контекста вообще бомба — никаких бинд-функций и засорения прототипов нативных классов.
Автору респект, за то что поделился с теми, кто еще не изучил поведение вокруг контекта — такие знания относятся к фундаментальным и знать их нужно. Я, пользуясь coffee, был бы не так уверен в том, что делаю если б не имел крепких знаний по чистому JS.
Так ведь понимание, когда нужно сохранять контекст, в кофе всё равно нужно, если только вы всегда не используете =>, что совсем неправильно :)
Это да, понимание нужно, согласен. Прокидывать контекст там где этого делать не надо или ошибка (например, при рефакторинге убрали this, а стрелку забыли), или просто непонимание. В кофе важна внимательность к тому, что пишется.
> В кофе важна внимательность к тому, что пишется.

Я думаю, это к любому языку программирования (и не только) применимо :)
:) согласен, все бы этому следовали, было бы счастье. Бывает откроешь чей-то код или свой старый и думаешь «бааа, по рукам бы надавать»
Я обычно вот так это дело решаю

Function.prototype.bind = function(bind) {
	var self = this;
	return function(){
		var args = Array.prototype.slice.call(arguments);
		return self.apply(bind || null, args);
	};
};
UFO landed and left these words here
Тогда не стоит забывать о том, что
15.3.4.3: In Edition 3, a TypeError is thrown if the second argument passed to Function.prototype.apply is neither an array object nor an arguments object. In Edition 5, the second argument may be any kind of generic array-like object that has a valid length property.


К примеру, чтобы вот этот пример работал везде:

Math.max.apply(null, {length: 2, 0: 0, 1: 1}); // 1


Нужно подправить Function.prototype.apply:

(function(apply) {
  try {
    Function.apply(null, {length: 0});
  }
  catch (error) {
    Function.prototype.apply = function(context, object)
      {
        if (Object.prototype.toString.call(object) !== '[object Array]')
          object = Array.prototype.slice.call(object);
        
        return apply.call(this, context, object);
      }
  }
}(Function.prototype.apply));

UFO landed and left these words here
А вот к чему здесь альтернативная реализация apply (которые, в отличие от bind, о котором говорится в топике, поддерживается уже повсюду), честно говоря, не совсем понял.

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

Вот пример вашей реализации с каррингом:
Function.prototype.bind = function(bind) {
    var slice = Array.prototype.slice, self = this, args = slice.call(arguments,1);
    return function(){
        return self.apply(bind || null, args.concat(slice.call(arguments)));
    };
};
Спасибо, действительно лучше было бы написать частичное применение, но эта функция может быть использована и для каррирования.
У меня такой вопрос:
var fn = $.proxy(object.f, object);
setTimeout(fn, 3000);


зачем использовать дополнительную объект-обёртку, если можно так:
var fn = function(){ object.f() };
setTimeout(fn, 3000);


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

var fn = $.proxy(object.f, object);
window.addElementListener('load', fn, 3000);

// vs

var fn = function(){ object.f() };
window.addElementListener('load', fn, 3000);


Функция может возвращать результат — надо добавить ретурн:

var fn = function(){ return object.f() };
window.addElementListener('load', fn, 3000);


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

var fn = function(){ return object.f.apply(object, argument) };
window.addElementListener('load', fn, 3000);


В этом месте приходит понимание, что стоит всё это вынести в метод:

var fn = object.f.bind( object );
window.addElementListener('load', fn, 3000);

Понял, спасибо, «новое понимание в мозг успешно добавлено». У меня обычно проще связи на практике.
Что за add<strong>Element</strong>Listener? Java? Судя по аргументам, вроде, нет. Но тогда что́?!
Парсер Хабрахабра наносит внезапный удар. Чем ему кошерный strong-то не угодил?
В теге <code> можно использовать любые символы, и они не будут восприниматься как html.
Зачастую, когда это возможно, второй вариант и используют. Дело привычки.
Но написанный вами код не всегда будет работать:
— когда в f передаётся параметр
— когда в f передаётся неизвестное число параметров
— когда f объявлена не в object, а где-то ещё, и надо просто вызвать f в контексте object
— когда f объявлена не в object, а где-то ещё, и надо просто вызвать f в контексте object, при этом передав туда параметры
Конечно, на каждый из этих случаев можно написать обёртку для f в 1-2 строки, но порой проще вызывать proxy/bind.
Понятно, спасибо. На практике jQuery юзать приходится не так часто.
Ну на самом деле дело в jQuery. Я вот не использую жЗапрос вообще, но каррингом играю часто — вв крупных приложениях за счёт этого код сокращается.
Ошибочка:
> bounded(17); // распечатает 25 (3 + 5 +27)
а нужно:
bounded(17); // распечатает 25 (3 + 5 + 17)
Добавлю, что в Underscore.js есть свой метод bind.
Кроме того, есть очень удобный метод bindAll, при помощи которого можно перманентно привязать контекст для всех (или выбранных) функций объекта.

var object = {
    x: 3,
    f: function() {
        console.log(this.x);
    }
}
_.bindAll(object);

Всё, теперь функция f всегда будет выполняться в контексте объекта object.
Я бы не стал начинать с jQuery объяснение таких базовых особенностей языка. Их надо освоить сперва в голом виде без всяких библиотек, чтобы понимать, как оно работает внутри. А уже потом можно про красивые обёртки.
Использовал такой вариант, писал давно, сейчас не нужен
Полифилл
Function.prototype.bind = function(thisArg) {
 function boun() {
  var arg = part.concat(Array.prototype.slice.call(arguments));
  return this instanceof boun
   ? Function("_", "return new _(" + arg + ")")(self)
   : self.apply(thisArg, arg);
 }
 var part = Array.prototype.slice.call(arguments, 1), self = this;
 return boun;
};

/* Привязывание функций */
function bind_fun() {
 function add($A, $B, $C) {
  var $D = $A + $B + $C;
  console.log('Аргументы: %s\nРезультат: %s\n',
               Array.prototype.slice.call(arguments), $D);
  return $D;
 }
 bind = add.bind(null, 3, 5);   //
 bind(7);                       // ==> 15 (3 + 5 + 7)
 bind(17);                      // ==> 25 (3 + 5 + 17)
};


// https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/Function/bind#Example:_Partial_Functions
/* частичные функции */
function _Partial_Functions() {
 function Point(a, b) {this.x = a;this.y = b;}
 Point.prototype.toString = function() {return this.x + "," + this.y;};
 var bind = Point.bind(null, 0), boun = new bind(5);
 console.log(boun.toString());  // ==> 0,5
};

// https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/Function/bind#Example:_Creating_a_bound_function
/* Пример: частичные функции */
function _Creating_a_bound_function() {
 this.x = 9;
 var module = {x: 81, getX: function() { return this.x; }};
  $A = module.x                 // ==> 81 Значение свойства 'x' module
  $B = module.getX();           // ==> 81 Значение метода 'getX' module
  getX = module.getX;           // ссылка на функцию module.getX
  $C = getX();                  // ==>  9 this === глобальный объект
  boundGetX = module.getX.bind(module); // bind функции this === module
 console.log($A, $B, $C, boundGetX());  // ==> 81 81 9 81 
}
_Creating_a_bound_function()
_Partial_Functions()

 


Function("_", "return new _(" + arg + ")")(self)


Здесь же ж аргумент в строку кастится. То есть такое будет падать:
Point.bind(null, { my: 'object' })


А такое просто работать неправильно:

function Point(depend, a, b) {
 this.depend = depend;
 this.x = a;
 this.y = b;
}
Point.prototype.toString = function() {return this.x + "," + this.y;};
var bind = Point.bind(null, [6, 5, 4]);
var boun = new bind(1, 2);
console.log(boun.toString()); // какой результат? (5,4)
Спасибо за внимание, я не заметил.

Об объектах, как об аргументах раньше и не думал.

Строку:
Function("_", "return new _(" + arg + ")")(self)
На:
Function('$,_', 'return new $(' + arg.map(function (a, b) { return '_[' + b + ']'}) + ')')(self, arg)
Попробуйте так.
Function.prototype.bind = function(thisArg) {
 function boun() {
  var arg = part.concat(Array.prototype.slice.call(arguments));
  return this instanceof boun
   ? Function('$,_', 'return new $(' + arg.map(function (a, b) { return '_[' + b + ']'}) + ')')(self, arg)
   : self.apply(thisArg, arg);
 }
 var part = Array.prototype.slice.call(arguments, 1), self = this;
 return boun;
};


Я часто пользуюсь ими как-то так:
for (var i = 0; i < this.buttons.length; i++) {
 this.buttons[i].onclick = this.clickHandler.bind(this, this.buttons[i]);
}


Скажите, зачем усложнять и делать через `eval`, если на mdn есть полифил без него?
Доброго времени суток
@TheShock 5 июня 2016 в 00:11
Скажите, зачем усложнять и делать через `eval`, если на mdn есть полифил без него?
@XGuest 14 июня 2016 в 02:56
Использовал такой вариант, писал давно, сейчас не нужен

Писал давно, зачем и когда не помню может, MDM тогда и не было ;)

== > если на mdn есть полифил
Частичная реализация

Почему код с evel, раньше не понимал — «Конструктор у функции».
Нашел в старых файлах, copy ==> past и все, переписывать для комментария было лень, но для понимания полных стандартных возможностей хватало.

Не использую потому, что bind встроен, если нет, быстрей и проще написать 'apply' к имени-телу функции или конструктора.

Переписал полифил без evel.
Function.prototype.bind = function(thisArg) {
 function boun() {
  Array.prototype.unshift.apply(arguments, part);    // unshift arguments
  return self.prototype.constructor                  // constructor для {} и ()
   .apply(thisArg || this, arguments);               // в __proto__
 }
 Array.prototype.shift.apply(arguments);             // shift arguments
 var part = arguments, self = this;
 boun.prototype = self.prototype;                    // Для {} 
 return boun;                                        // Если () то лишняя команда
};
Но как минимум лишние:
— два вызова функции — полифила и обработчика.
— две обработки аргументов.
— одно условие.

P.S. Читаю ваши статьи, спасибо.

С наилучшими пожеланиями
XGuest
==>Писал давно, зачем и когда не помню может, MDM MDN тогда и не было ;)

Извините, забыл об основных конструкторах, исправил.


Function.prototype.bind = function(thisArg) {
 function boun() {
  Array.prototype.unshift.apply(arguments, part);
  return self.apply(thisArg || this, arguments);
 }
 Array.prototype.shift.apply(arguments);
 var part = arguments, 
  self = this.prototype
   ? this.prototype.constructor
   : thisArg;
 boun.prototype = self.prototype;
 return boun;
};
Only those users with full accounts are able to leave comments. Log in, please.