Pull to refresh

Comments 84

Я бы все таки посоветовал книгу, одна хорошая книга будет полезнее пачки статей/переводов. А в статьях и блогах было бы интересно видеть что-то не стандартное, не из разряда справочной информации.
В книгах информация бывает сгруппирована в другом порядке, по другой логике.
Или Вы имеете ввиду какую-то конкретную книгу, где информация по this в функциях сгруппирована именно так?
А группировка «именно так» претендует на единственную истину? Бывают книги, именно с подборками приемов и конструкций, для гиков, и от гиков ) книги Stefanov, Zakas, Resig и тп. Где не рассматриваются азы JS, а именно приемы, паттерны и тд. Прочитайте парочку, и на многие статьи не придется тратить время.
Группировка «именно так» претендует на понятность именно этой взаимосвязи группируемого. Вовсе не обязательно ей претендовать на «единственность». Народ плюсует, соответственно части народа подобная группировка пришлась по душе. Мне тоже.
У меня в аське сегодня как раз был вопрос на эту тему. Удивительно, что многим людям таак тяжело даётся этот участок.

Ещё топику не хватает описания bind — возможности привязать функцию к определённому контексту.

Самое главное — помнить, что у функции контекст (this) — это то, от какого объекта она вызвана, а не то, возле какого она создана.

Ну и не забываем о 'use strict'
И ещё хотел бы добавить. Прочитал в твиттере вашего товарища, что он не понимает, для чего нужны call/apply. На самом деле они очень часто используются для создания изящного интерфейса, многие трюки базируются именно на этих методах. К примеру, можно очень легко сделать из объекта arguments — массив:
var array = [].slice.call(arguments);


Магия jQuery-each применяется именно при помощи apply:
$('div').each(function () {
    $(this); // <== here
});


Конечно, в простых приложениях такая магия нужна редко. Но в фреймворках она используется очень часто и без неё было бы очень тяжело построить изящный интерфейс.
UFO landed and left these words here
В древнем приложении, где все было построено на фреймах, приходилось использовать apply. А вообще — да, редко используется. Только на собеседованиях :)
а почему по стрелочке <== here я не вижу слова apply?
Пункт 2 сформулирован неверно. Ничто не мешает вызвать функцию так: { func: function(){} }.func()

Ну сколько уже можно переводить статьи из серии «JS/PHP/Java/<любая другая технология> для чайников»?
И что? Да, переменной нету, но ссылка будет на объект:
</source.
!{ x: 42,
   func: function(){
     console.log(this.x);
   }
}.func() // 42
Ну такой способ порой бывает очень даже наруку.
var foo = {
    show: function() {
        $('body').css('color', '#F');
        return this;
    }
    /**  ...  */
}.show();

— и объект создали, и он успел уже что-то сделать;), и код чище. Почему бы не использовать?

пусть народ читает, а то развелось кодеров на «jquery» глаза б мои их кода не видели!!!

Вообще хорошо бы юзерам понимать что в js все есть объекты, каждая мелочь даже
function(){
var t,y,u;

}
t,y,u — это свойства объекта, а в качестве объекта, объект перменных данной функции.

Может написать статью по объектам в js? надо погуглить хабр есть ли тут такое
Ммм? Но есть нюансы.
var num = 123;
num.test = 42;
console.log( num.test ); // undefined
Единственное применение конструктора Number:)
Но не совсем rulez =(
var foo = 0, bar = new Number(0);

console.log( !foo, !bar ); // true, false
:(
Только так:
var bar = new Number(0);
console.log(!(bar + 0))
Вообще можно так:
var bar = new Number(0);
console.log(!+bar);

Но всё-равно не айс. По этой причине в Modernizr костыльно делается проверка на video и audio. Если они присутствуют, то хранится new Object(true), иначе — false. Плохо, если в таком случае необходимо и в отрицательном варианте хранить дополнительные свойства.
Я иногда жалею, что нет методов типа toBoolean
Категорически поддерживаю. Этим страдает что php, что js. Реализовано только String =(

ps. php __cast
Преобразование типа можно явным образом сделать через его название:
var test = Boolean(«something») // true
Ну, самый короткий вариант это !!+
console.log( !!+ new Boolean(false) ) // false


Но даже это не прикольно. Про использование конструктора для преобразования я знаю.
Теоретически, можно «по соглашению» делать — функция toBoolean, которая вызывает метод .toBoolean у аргумента. Но это не так изящно, как если бы поддерживалось языком.
да можно Object.prototype.toBoolean = function а так каст в bool вшит в язык
Тут о том и речь, что хочется иметь возможность переопределять этот каст для своих объектов.
Чем не угодил valueOf()? Проблема в том что object → boolean всегда true.
> Проблема в том что object → boolean всегда true
вот этим самым и не угодил
foo — элементарный bar — объект естественно что он не интерпретируется в false при отрицании
ну примитивы нужны, но они преобразуются автоматически в объект «Хабр».length при вызове методов
Примитивы преобразуются в объекты с помощью дот-оператора, а объекты преобразуются в примитивы при помощи операторов +, -, <, >, !, etc.

Говорить, что всё в JS объекты (или преобразуются в объекты) — это всё равно что говорить что всё числа (или преобразуются в числа).

Не смущайте новичков.
Новичков, может, это и смущает, но знать стоит.

Были времена, когда всё было числами выражено. Всё выражалось двоичном кодом, что в памяти хранилось, поэтому и имело место говорить, что всё числа. Тогда же и обрабатывали мы их таким образом. Сейчас же другая ситуация: в JS нет (на соотв. уровне абстракции) понятия памяти, ея управления и др.; вместо этого нам предоставляется мощный инструмент для работы к объектами. Обратите внимание: только null и undefined не могут быть сведены к Object (что, кстати, было бы странно). Это дает нам полное право утверждать, что всякая структура данных выражается объектом. На практике, собственно, так и бывает: со всеми структурами можно проводить характерные «объектные» манипуляции.

Я, правда, соглашусь, что примитивы нужны. Сложно было бы осваивать язык, если б числа, строки и булевы величины вели себя «более объектно». А так на данный момент на них можно смотреть как на примитивы, так и как на объекты.
Примитивы может и не нужны, во многих языках их нет и всё в порядке. Стоит понимать разницу между примитивами и объектами и не валить всё в одну кучу.
Обратите внимание, что даже null и undefined могут быть сведены к Number. Это дает нам полное право утверждать, что всякая структура данных выражается числом. То же верно и про строку и про boolean.

{valueOf: function () { return 6; }} / 2 === 3

это то же самое что

«habr».length === 4

оператор преобразовал значение в нужный нам тип чтобы разделить его на два или взять свойство length.
не с помощью дот оператор а с помощью вызова метода. Возь пример из комментария выше
var num = 123;
num.test = 42;
console.log( num.test ); // undefined

Говорить, что все объекты можно, это сделано для удобства ибо в любой момент можно сделать «ааа».length и примитив будет вести себя как объект. Вот из-за этого и возникает термин «все объекты», так как разница между объектом и примитивом, а скажем в java заметна, хотя там тоже главная идеология объекты.

я бы мог только undefined назвать костылем, так как null в данном случае является нормальным значением в мире объектов, указывая на отсутствие оного
Какого метода?
Говорить, что всё объекты — это то же самое что говорить, что киты и дельфины это рыбы. Так оно может и проще рассуждать, только это неправда и в приведённом вами коде это явно видно.
Разве это подводный камень? Это общеизвестное, задокументированное поведение.
Может написать статью по объектам в js? надо погуглить хабр есть ли тут такое

В общем, если есть что писать — пишите.
никогда не писал, а фигню писать не хочу

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

Это не справедливо для strict mode, this там будет undefined

6 способ вызова функции — косвенный вызов функции. Это когда мы вызываем функцию не напрямую someFunc(args), а через результат выполнения какой-нибудь операции, например используя запятую: (1, someFunc)(args). Такой вызов — единственный способ получить глобальный объект в strict mode, с помощью косвенного вызова eval
В последнем предложении вы что-то намудрили…
А кто нибудь знает, как вызвать функцию одновременно с new и call/apply?
Т.е. у меня есть параметры в массиве, я не знаю что там в этом массиве, и нужно вызвать функцию как конструктор с этими параметрами.
Есть хитрость на самом деле.
Можно вынести код из конструктора в прототип (например метод init) и после конструирования через new выполнить вынесенный метод с помощью apply. Конечно всё это не ручками хорошо бы делать, а написать обёртку какую-нибудь
Если вы про создание объекта по массиву аргументов конструктора, то, например (но не работает для системных конструкторов, только для пользовательских):

// create objects with arbitrary-length args to constructor
var constructor_apply = function ( constructor, args )
{
function new_const( constructor, args )
{
return constructor.apply( this, args );
};
new_const.prototype = constructor.prototype;
return new new_const( constructor, args );
};
//…
var o = constructor_apply( MyClass, [ 42 ] );

Ждём штатный способ в новых версиях JS…
В AtomJS я реализовал метод Class.factory так:
Constructor.prototype.factory = new function() {
	function Factory(args) { return Constructor.apply(this, args); }
	Factory[prototype] = Constructor[prototype];
	return function(args) { return new Factory(args || []); }
}


Потом можно вызывать метод factory:
Constructor.factory([1, 2, 3]) == new Constructor(1, 2, 3)
Ой, prototype там лишний. Там просто
Constructor.factory = new function() {
Тем не менее, это всё равно hack который не сработает для «нативных» конструкторов.
Array.factory = new function() {
	function Factory(args) { return Array.apply(this, args); }
	Factory.prototype = Array.prototype;
	return function(args) { return new Factory(args || []); }
}
console.log( Array.factory([5]).length ); // 5
Мне просто интересно, зачем оно может понадобиться для String, Number или Boolean =))
Ну я могу написать функцию которая будет по разному себя вести в зависимости от того как она вызывается, как конструктор или как функция.

Я к тому, что решение хорошее, но небезупречное. Это как if (a instanceof Array), в большинстве случаев сработает…
Ну я могу написать функцию которая будет по разному себя вести в зависимости от того как она вызывается, как конструктор или как функция.

Угу, у меня в atom.Class такое реализовано. Был вдохновлён Number

Кстати, почему оно не работает с примитивными объектами?
Что оно? И что такое «примитивные объекты»?
Понял. По ночи не до конца сообразил))
Кстати, как обмануть такую фабрику? Обычный подход с this instanceof Class — не подходит:
var Class = function () {
    if ( this instanceof Class ) {
        console.log('first');
    } else {
        console.log('second');
    }
};
Class.factory = new function() {
	function Factory(args) { return Class.apply(this, args); }
	Factory.prototype = Class.prototype;
	return function(args) { return new Factory(args || []); }
}
Class.factory();
Действительно, был неправ. Подмена прототипа закрывает все дыры. Остаются только нативы типа Number, но ими можно пренебречь. :)
Поправьте плиз
с
return [ this, arg1, arg 2];
на
return [ this, arg1, arg2 ];
Как раз на днях пришлось разбираться с поведением this. К перечисленным четырём вариантам я бы, пожалуй, добавил ещё и пятый, который не то, чтобы совсем особенный, но всё же неочевидный.

Возьмём такой код:
setTimeout(someObj.someFunc, 1000);

Даже когда я прочёл описания всех четырёх механизмов (глобальный вызов, конструктор, метод объекта, call/apply), я был уверен, что при выполнении someFunc() значением this будет someObj. На самом деле — ничего подобного, он будет равен window. Чёткого и внятного объяснения в интернетах я найти не смог, но по обрывкам рассуждений понял, что в данном случае движок JS сохраняет для отложенного вызова лишь ссылку на функцию, после чего вызывает её из глобального контекста, а информация, что хотели вызвать не просто функцию, а именно метод объекта someObj, к этому моменту уже потеряна.
Объяснение этому следующее: функции в JS являются объектами первого класса и не имеют неявной привязки к объекту. Если вы передаёте куда-то в качестве аргумента функцию — вы передаёте только функцию и ничего больше.
Все верно, представьте себе это так:

var someFunc = someObj.someFunc;
someFunc();

т.е. someFunc ссылается на ту же самую функцию, но вызывается уже в другом контексте.

Лечится например так:

setTimeout( function(){ someObj.someFunc() }, 1000 );

или так:

setTimeout( "someObj.someFunc()", 1000 );
// или так:
setTimeout( someObj.someFunc.bind(someObj) , 1000 );
// или при помощи mootools:
someObj.someFunc.delay(1000, someObj);
Это не подойдёт, если используется только чистый JS, без сторонних фреймворков. (Первый вариант — это, я так понял, для jQuery?)
Понятно. Попытка гугления вывалила лишь кучу ссылок на Prototype и jQuery, до сайта Мозиллы я не добрался. Буду знать.

PS: Наверное, всё же, не «в старых браузерах», а в неподдерживающих. Потому что даже в последнем снэпшоте Оперы такой функции нет.
Лечится например так:
Именно так в конце концов и поступил (по первому способу, второй не прокатил бы, т.к. в качестве someObj выступал локальный объект, а не глобальный). Но с точки зрения незамутнённого сознания всё-таки не совсем логично создавать новую «пустую» функцию исключительно для того, чтобы сохранить привязку к объекту…
Можно сделать функцию с параметром, в котором передаётся объект :)
В данном случае, мы как бы скопировали всю функцию, включая список аргументов и тело, и присвоили получившийся объект свойству make объекта arrayMaker. Это равносильно такому объявлению:
var arrayMaker = {
someProperty: 'Какое-то значение';
make: function (arg1, arg2) {
return [ this, arg1, arg 2];
}
};

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

Пы.Сы. Заголовок звучит как «Пять способов вызвать функцию», хотя описано только четыре (если конечно apply и call не считаются двумя принципиально разными способами).
Не описан способ вызова анонимных функций.
(function (x){ console.log(x);})('Hello world');

или
!function (x){ console.log(x);}('Hello world');


или именованных функций

(function f(x){ console.log(x); return f; })('Hello')('world');
// при этом прошу заметить
f('foo'); // exception


или для рекурсии

(function f(x){ console.log(x); if(x>0) f(x-1);})(10);


Есть ещё несколько методов из будущего (неявный вызов функции):
// 1. через ключевое слово get
var o = {
  get pewpew () {
      console.log('pass');   
  }
}
// неявно вызываем функцию pewpew
o.pewpew;

// 2. через __defineGetter__
var o = {};
o.__defineGetter__("pewpew", function blabla(){
    console.log('pass');
});

// неявно вызываем функцию blabla
o.pewpew;

// 3. через __defineSetter__
Object.prototype.__defineSetter__("pew", function blabla(a){
    console.log('pass');
});

// неявно вызываем функцию blabla
({}).pew = 152;

// 4. через Proxy (существует куча ловушек, обработчик каждой можно неявно вызвать)
var o = Proxy.create({
    get: function () {
        console.log('pass');
    }
}, {});

// неявно вызываем функцию-ловушку get
o.pewpew1;
o.pewpew2;
o.pewpew3;

С помощью __defineSetter__, кстати, можно провернуть один трюк с JSONP. Вместо функции обработчика можно передать ({}).pew= и настроить обработчик на «pew»:
Object.prototype.__defineSetter__("pew", function pewHook(a){
    console.log(a.aaa === 123);
    // снимаем обработчик "pew" ...
});

// Сервер подчинится и отправит нам код в таком формате, как будто это вызывается какая-то функция:
({}).pew=({'aaa':123});

// Дальше отработает pewHook
Ничего полезного в этом нет (просто «финт ушами»), разве что не засоряются глобалы на время ожидания JSONP. Не все сервера принимают обработчик такого формата (проверял на Flickr — не заработало).
А можно буквально двумя словами, в чём практический смысл неявного вызова функции?
Only those users with full accounts are able to leave comments. Log in, please.