Javascript-форум (https://javascript.ru/forum/)
-   Общие вопросы Javascript (https://javascript.ru/forum/misc/)
-   -   Объясните, почему функция работает именно так. (https://javascript.ru/forum/misc/31048-obyasnite-pochemu-funkciya-rabotaet-imenno-tak.html)

Имя 24.08.2012 16:33

Объясните, почему функция работает именно так.
 
Всем привет.
Есть вопрос:

function addThree(x) {
    return x + 3;
}

function composed(func) {
    return function(x) {
        return func(func(x))
    }
}

var answer = composed(addThree)(4);


И не могу понять, что за странная передача аргумента в функцию - composed(addThree)(4) ??
Почему не вот так composed(addThree(4)) - я уже понял, что так нельзя, попробовал. Просто еще не сталкивался с подобным способом написания. Что в таком случае происходит? Ведь функция composed принимает только один параметр - func? Тогда что за странная запись (addThree)(4) ?

Новичок. Не судите строго :)
Спасибо.

melky 24.08.2012 16:36

первая функция передаётся как аргумент, и возвращается новая функция, которая вызовет первую, передав ей первый аргумент.

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

почему запоминается первая функция? это называется замыкание.

Имя 24.08.2012 19:30

спасибо

Kivi 30.08.2012 19:15

Вопрос не по этой теме, но по примеру из данного раздела раздела "Декораторы" сайта learn.javascript.ru:

01 function doublingDecorator(f) {
02 return function() {
03 return 2*f.apply(this, arguments); // (*)
04 };
05 }
06
07 // Использование:
08
09 function sum(a, b) {
10 return a + b;
11 }
12
13 sum = doublingDecorator(sum);
14
15 alert( sum(1,2) ); // 6
16 alert( sum(2,3) ); // 10

в строке 13 мы записываем в переменную sum результат, и теперь данная переменная не должна указывать на функцию function sum(a, b) {...}
Кто может указать (где на сайте или в книге Флэнагана) этот момент можно понять, можно и самостоятельно объяснить.

melky 30.08.2012 21:02

Цитата:

Сообщение от Kivi (Сообщение 201764)
Вопрос не по этой теме, но по примеру из данного раздела раздела "Декораторы" сайта learn.javascript.ru:

01 function doublingDecorator(f) {
02 return function() {
03 return 2*f.apply(this, arguments); // (*)
04 };
05 }
06
07 // Использование:
08
09 function sum(a, b) {
10 return a + b;
11 }
12
13 sum = doublingDecorator(sum);
14
15 alert( sum(1,2) ); // 6
16 alert( sum(2,3) ); // 10

в строке 13 мы записываем в переменную sum результат, и теперь данная переменная не должна указывать на функцию function sum(a, b) {...}
Кто может указать (где на сайте или в книге Флэнагана) этот момент можно понять, можно и самостоятельно объяснить.

Она и не указывает на функцию sum - она указывает на новую функцию, которая будет вызывать sum (она находится в переменной "f").

Она сохраняется благодаря замыканию.

Статья, по которой Я понял замыкания ( я перечитал их около 10 штук), находится на хабре.

Aetae 30.08.2012 21:34

melky, хорошо начинать с js - такие вещи как замыкания и прототипы кажутся очевидными и элементарными с самого начала.)
С другой стороны если берёшся за другой язык - уже "нормальное" ООП кажется тёмным лесом и вообще непонятно нафига козе баян.))

melky 30.08.2012 22:24

Цитата:

Сообщение от Aetae (Сообщение 201804)
melky, хорошо начинать с js - такие вещи как замыкания и прототипы кажутся очевидными и элементарными с самого начала.)
С другой стороны если берёшся за другой язык - уже "нормальное" ООП кажется тёмным лесом и вообще непонятно нафига козе баян.))

я помню, как я на C# пересел как-то раз ... я пытался передать в функцию два аргумента, а она требовала три - и я гуглил, что же это за фигня такая :) в общем, через месяц я вернулся к JS :)

Kivi 30.08.2012 23:12

Может и так, но я еще не до конца въехал.

Замыкание - Объект с переменными внешней функции можно использовать из внутренней функции. (Илья Кантор)

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


Если, здесь это все используется, то может кто-нибудь терминами глобальных объектов, областей видимости и этапами инициализации это все объяснить.

И еще, это нормальный вариант вот так делать или это пример "индусского кода" (надеюсь поданного специально чтоб лучше запомнилось, когда закипит мозг) ? ( сразу уточню, что не хотел сказать, что Илья Кантор выдает "индусский код", просто для новичков опытные говорят на "индусском") :).

Aetae 30.08.2012 23:19

Нормально. Но дело вкуса. Я, например, люблю замыкания и "замыкаю" всё подряд, а кто-то не любит.))

Kivi 30.08.2012 23:40

Я люблю и обожаю, но еще не все примеры разобрал.

Может, объясниш этот случай.

nerv_ 30.08.2012 23:48

Цитата:

Сообщение от Kivi
Если, здесь это все используется, то может кто-нибудь терминами глобальных объектов, областей видимости и этапами инициализации это все объяснить.

смысл замыкания в том, чтобы оставить сборщик мусора не у дел :)

melky 31.08.2012 00:34

Цитата:

Сообщение от nerv_ (Сообщение 201858)
смысл замыкания в том, чтобы оставить сборщик мусора не у дел :)

бла, это самое лаконичное определение термина "замыкание", которое я видел :victory:

Aetae 31.08.2012 00:58

Что там разбирать, всё элементарно.
function doublingDecorator(f) {
  return function() {
    return 2*f.apply(this, arguments); // (*)
  };
}

// Использование:

function sum(a, b) {
  return a + b;
}

sum = doublingDecorator(sum);

alert( sum(1,2) ); // 6
alert( sum(2,3) ); // 10


Вызываем: doublingDecorator(sum)

function doublingDecorator(f) {
	//,,,
}

В данном случае f = sum,
т.е. условно
f = function(a, b) {
	return a + b;
}
Собсно эта f находится в scope(области видимости) текущего вызова данной функции.

//...
return function() {
	return 2*f.apply(this, arguments); // (*)
}
это по сути то же самое что:
function temp() {
	return 2*f.apply(this, arguments); // (*)
}
return temp
просто не создаётся лишних переменных, а идёт возврат на ходу созданной функции.

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

Последним действием:
sum = doublingDecorator(sum);
затирается первоначальная ссылка на sum, т.е. sum = temp. В простом случае это заставило бы сборщик мусора стереть саму память о функции на которую раньше ссылалась sum, но у нас сохранилась ещё одна ссылка на оную - переменная f, замкнутая внутри новосозданной функции temp и, соответственно, вполне работоспособная.


f.apply(this, arguments);
.apply же просто вызывает нашу функцию f с теми аргументами, что переданы в temp(+подменяет this, но это отдельная тема).
В данном же случае можно было поступить и так:
function temp(a,b) {
	return 2*f(a, b); // (*)
}
но изначальный вариант универсальнее, ибо не привязан к конкретной функции sum и может использоваться с другими.


Ещё небольшой примерчик пользы замыканий:
function act( num ){
	return {
		minus : function( n ){ return num - n },
		plus : function( n ){ return num + n },
	}
}

a = 10;
a = act(a);

alert( a.plus( 3 ) ); //13
alert( a.minus( -1 ) ); //12


Это работает, т.к. обе функции видят одну и ту же переменную num, что при вызове стала равной a,
и, т.к. act уже отработала, эта переменная больше никому недоступна.


Не знаю, помог ли я или ещё сильнее запутал, но пускай будет.)

Kivi 31.08.2012 10:16

спасибо, прояснилось
а есть возможность описать как обратиться к f чтоб посмотреть что там (полный путь типа window.-%:";&+%%/!!.#*/&/.f () {return...} (в моем и своем примере)
этот момент меня и смущает - неявное создание каких-то промежуточных объектов или непонятное место нахождение замкнутых переменных.
Надеюсь я понятно выразился?
Хочется понять весь механизм, потому что понять код - я пойму, но сам не "замкну".

Aetae 31.08.2012 10:20

В этом суть замыкания. Эта переменная больше никому не доступна, кроме созданных внутри функций.

nerv_ 31.08.2012 13:33

Цитата:

Сообщение от Aetae
В этом суть замыкания. Эта переменная больше никому не доступна, кроме созданных внутри функций.

это ограничение области видимости. Кому я это говорю. Вы и так все знаете лучше меня )
Цитата:

Сообщение от melky
бла, это самое лаконичное определение термина "замыкание", которое я видел

:) еще короче: смысл замыкания сохранить состояние

Kivi 01.09.2012 21:27

Хочется материться.
Такое впечатление, как будто теорию относительности или Фрейда пытаешься понять, половину приходится принять на веру - раз работает, значит так оно и есть.
Но, понял как это все в данном мной примере работает.
Самая большая сложность - это учитывать время и ход событий. Типа прошла инициализация (интерпритация / компиляция / (?*%*?-ция) и сложились вот такие объедки, которые находятся в обновленных старых ссылках. Типа в строке 13 происходит перезапись в ссылку sum новой функции которая есть "самостоятельной", а к функции описанной
09	function sum(a, b) {
10	  return a + b;

обращений производиться уже не будет никогда.
:help:
Если не лень, можете над моим примером поизвращаться - функционал оставить, а код еще "перезамкнуть" и сделать лаконичнее, используя самые извращенные способы?

Aetae 01.09.2012 21:37

Javascript просто выполняется потоком по порядку, потому не надо ни о каких сложностях париться, как написано так и сработает.)
(исключая некоторые заморочки с var, setTimeout - но это отдельная тема)

Цитата:

а к функции описанной
09	function sum(a, b) {
10	  return a + b;

обращений производиться уже не будет никогда.
:help:
sum - это ссылка на функцию(вообще все объекты в js передаются только по ссылке, а функция тоже объект, да), соотвественно при исполнении
doublingDecorator(sum)
- в функцию doublingDecorator передаётся ссылка на функцию
function sum(a, b) {
	  return a + b;
}
и внутренняя переменная f тоже становится ссылкой на оную. После чего действием
sum = doublingDecorator(sum)
, sum становится ссылкой уже на новую функцию (temp) созданную внутри doublingDecorator, при этом ссылка на оригинальную всё ещё остаётся во внутренней переменной f.

nerv_ 02.09.2012 00:44

Kivi, думаю, тебе стоит почитать про замыкания.

Aetae 02.09.2012 00:49

Цитата:

Сообщение от nerv_ (Сообщение 202451)
Kivi, думаю, тебе стоит почитать про замыкания.

Вообще думаю ему стоит начать не с замыканий, а с описания языка и его особенностей и отличий от иных.))

Kivi 02.09.2012 02:39

Цитата:

Сообщение от nerv_ (Сообщение 202451)
Kivi, думаю, тебе стоит почитать про замыкания.

Учу Javascript по учебнику этого сайта, иду по порядку. Все нормально, но вот в этом месте зацепился за эту перезапись ссылочной переменной.
Но уже разобрался, зацените.
Я думал, что каждый раз, когда происходит что-то подобное:
15	alert( sum(1,2) ); // 6
16	alert( sum(2,3) ); // 10

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

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

Kivi 02.09.2012 20:28

Вот еще хрень.
Источник комментарий на habrahabr.ru
var y = 5;
 var x = function(){
     return y;
 };
 var z = function(t){
     var y = 10;
     return t();
 }
 z(x);

Почему в строке 7 return t();?
Что это значит?
Где можно об этом прочитать?

nerv_ 02.09.2012 21:13

Цитата:

Сообщение от Kivi
Почему в строке 7 return t();?
Что это значит?
Где можно об этом прочитать?

смотри внимательней: у тебя есть
1. y - number
2. x - function
3. z - function

z(x); -> вызывается функция z, в которую в качестве аргумента передается другая фунция (x), имя которой (имя аргумента) задается как t.

Лучше скажи, почему y = 5 :)

var y = 5;
 var x = function(){
     return y;
 };
 var z = function(t){
     var y = 10;
     return t();
 }
 alert( z(x) );

Kivi 03.09.2012 00:18

По-моему у в
3 return y; - есть window.y=5,
а в
6 var y = 10; - это function Lexical Environment.
Сначала х(), а потом уже t(), используют window.y=5.

На этом примере, можешь показать как ты анализируешь области видимости. Так как это было (дословно), когда просматривал этот код, используешь ли какие-то наборы правил типа этих для this (в разделе "Теория").
Может схемку какую рисуешь?

nerv_ 03.09.2012 00:34

Цитата:

Сообщение от Kivi
как ты анализируешь области видимости

Также, как и все :)
// 1

function() {
	// 2
	
	function() {
		// 3
	
	}
}

Достаточно запомнить одно:

Дэвид Флэнаган:
Цитата:

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

Kivi 03.09.2012 01:16

Встречал где-нибдь описание такого
var some_function = new Function('console.log(my_var)'); (именно 'console.log(my_var)') ?
Источник - комментарий на habrahabr.ru

nerv_ 03.09.2012 01:29

Цитата:

Сообщение от Kivi
Встречал где-нибдь описание такого
var some_function = new Function('console.log(my_var)'); (именно 'console.log(my_var)') ?

именно 'console.log(my_var)'? Нет, не встречал :D

конструктор Function
[[Scope]] для new Function

Kivi 03.09.2012 15:56

Дэвид Флэнаган:

Инструкция return может также использоваться без выражения, тогда она про
сто прерывает исполнение функции, не возвращая значение. Например:

function display_object(obj) {
[I]    // Сначала убедимся в корректности нашего аргумента
    // В случае некорректности пропускаем остаток функции[/I]
    if (obj == null) return;
[I]    // Здесь находится оставшаяся часть функции...[/I]
}


И еще пример

function foo() { 
    if (false) { 
        var x = 1; 
    } 
    return; 
    var y = 1; 
}

Вопрос:
Почему return не в конце?
Если поменять местами строки 5 и 6 что-то изменится?

nerv_ 04.09.2012 00:05

Цитата:

Сообщение от Kivi
Почему return не в конце?

потому, что выполнение функции можно прервать в любом месте.

Цитата:

Сообщение от Kivi
Если поменять местами строки 5 и 6 что-то изменится?

а это мне ты скажи :)

Kivi 04.09.2012 01:31

Для возврата значения используется директива return. Она может находиться в любом месте функции.
Как только до нее доходит управление — функция завершается и значение передается обратно.


Есть совет как быть внимательнее, отличный от "Будь внимательнее!" ?:)

Kivi 04.09.2012 02:18

Опять хрень!!! Только, кажется, уже все - разобрался и опять...
Здесь (на http://learn.javascript.ru Использование замыканий есть пример:
function makeArmy() {
 
  var shooters = [];
 
  for(var i=0; i<10; i++) {
    var shooter = function() { // функция-стрелок
      alert(i); // выводит свой номер
    };
    shooters.push(shooter);
  }
 
  return shooters;
}
 
var army = makeArmy();
 
army[0](); // стрелок выводит 10, а должен 0
army[5](); // стрелок выводит 10...
// .. все стрелки выводят 10 вместо 0,1,2...9
ну и объяснение
К моменту вызова army[0](), функция makeArmy уже закончила работу. Цикл завершился, последнее значение было i=10.
Все понятно.

А вот чуть ниже чем тут. Еще пример:
for(var i = 0; i < 10; i++) {
    setTimeout(function() {
        console.log(i);
    }, 1000);
}
и тоже объясняется:
Данный код не будет выводить числа с 0 до 9, вместо этого число 10 будет выведено десять раз.

Анонимная функция сохраняет ссылку на i и когда будет вызвана функция console.log, цикл for уже закончит свою работу, а в i будет содержаться 10.


Моё объяснение (по методу прогиба подреальность):
скрипт прошел - все ссылки определились, и не смотря, что в сценарии вывод идет параллельно циклу, на самом деле все не так - вывод в консоль идет в конце.
Вопрос:
Где почитать когда события по коду идут "одновременно" а когда "по-особенному"?

Kivi 04.09.2012 02:22

Nerv
Чувствую, что нужно переходить на ru-board.com - там движение побольше, а не один ты будешь за всех отдуваться.

Aetae 04.09.2012 02:46

function top(){
	for(var i = 0; i < 10; i++) {
		setTimeout(function() {
			console.log(i);
		}, 1000);
	}
}

Тут всё просто, присмотритесь внимательнее:
function() {
    console.log(i);
}
console.log(i) выводит не конкретное число, а внутреннюю переменную i из области видимости функции top(или глобальной если цикл находится вне функции). А поскольку это происходит через 1000мс(1с), когда функция top уже отработала, то обращаясь к i функция получает значение внутренней переменной i, которое та приобрела к концу работы функции top, т.е. - 10.

Разложить это можно ещё так:
for(var i = 0; i < 10; i++) {}

//спустя 1000мс

console.log(i);
console.log(i);
console.log(i);
console.log(i);
console.log(i);
console.log(i);
console.log(i);
console.log(i);
console.log(i);
console.log(i);



По поводу ru-board.com не скажите, жабаскрипетров нам не так много, а компетентных и подавно.)

Kivi 04.09.2012 13:22

А почему так с кодом
for(var i = 0; i < 10; i++) {}
 
//спустя 1000мс
 
console.log(i);
console.log(i);
console.log(i);
console.log(i);
ведь (из моего примера) он в теле цикла в {}.

Разве не должно быть так:
for i=1 console.log(1);
for i=2 console.log(2);
...
for i=10 console.log(10);
поледовательно ?

nerv_ 04.09.2012 13:54

Kivi, потому, что значение i нигде не фиксируется. Это можно прочитать как: вывести значение переменной i через n миллисекунд. А через n миллисекунд оно будет равно чему?

Цитата:

Сообщение от Kivi
Чувствую, что нужно переходить на ru-board.com - там движение побольше, а не один ты будешь за всех отдуваться.

не то, чтобы я отдувался :) Отвечаю тогда, когда мне удобно. Не хочу, не отвечаю. :no:
Переходить или нет, решать тебе. Могу только отметить, что здесь js мозги очень даже неплохие (это я не о себе).

Kivi 04.09.2012 16:23

Так вот я и хочу узнать механизм.
Внес некоторые изменения в код:
for ( var k = 0; k < 10999; k++ ) {
    document.write( k )
    document.write( "<br>" );
    setTimeout(function() {
        document.write( k );
    }, 2000 );
}
чтоб цикл не успел закончиться, а например document.write(1) должен бы уже напечататься. А все равно ничего.

ведь setTimeout идет как тело цикла и пока оно не выполнится не k не должно меняться.
Или какое k<XXXXXXX нужно поставить чтоб заработало?

Aetae 04.09.2012 17:30

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

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

Т.е. document.write можно использовать даже так:
<script>
document.write('<in');
document.write('pu');
document.write('t');
document.write(' valu');
document.write('e="t');
document.write('ext');
document.write('">');
</script>
в отличие от innerHTML который работает уже на живую, а потому каждая выводимая часть обрабатывается сразу как полноценная:
<script>
function appendHTML( html, node ){
	var d = document,
		fragment = d.createDocumentFragment(),
		container = d.createElement('div');
	container.innerHTML = html;
	while( container.hasChildNodes() ) fragment.appendChild( container.firstChild );
	(node || d.body || d.documentElement).appendChild( fragment );
	return node
}
appendHTML('<in');
appendHTML('pu');
appendHTML('t');
appendHTML(' valu');
appendHTML('e="t');
appendHTML('ext');
appendHTML('">');
</script>


Цитата:

Сообщение от Kivi (Сообщение 202949)
Или какое k<XXXXXXX нужно поставить чтоб заработало?

Я так понимаю в хотите подобрать такое k, чтобы на момент прошествия 1000мс цикл всё ещё продолжал исполнение и соответственно значение k было взято из текущей итерации?
Так вот - это не возможно, т.к. асинхронность в js виртуальна и всё исполняется в одном потоке. Вызов же функции по событию или по таймеру, в случае если другой код ещё выполняется, ставит эту функцию в очередь выполнения сразу после окончания текущего непрерывного блока кода. Циклы же относятся как раз тем блокам, что нельзя прерывать.
Соответственно сколько бы не итерировался цикл, функция по таймеру не будет вызвана пока оный не закончится. По этому в описании setTimeout всегда пишут не: "вызывает функцию через n", а "вызывает функцию не раньше чем через n", или типа того.

Цитата:

Сообщение от Kivi (Сообщение 202926)
ведь (из моего примера) он в теле цикла в {}.

Нет не в теле. В описание setTimeout написано.(покрайней мере должно быть), что он исполняете передаваемую функцию в глобальной области видимости. А внутреннюю переменную i она видит только потому, что сама передаваемая функция была создана в внутри другой и замкнула на себя ссылки на внутренние переменные. И на момент исполнения i уже давно равна 10.

Также возможно вас смущает сам синтаксис for? Чтоб было понятнее:
function top(){
    for(var i = 0; i < 10; i++) {
        //...
    }
}
это то же самое что:
function top(){
    var i = 0;
    while(i < 10) {
        i++;
        //...
    }
}
Т.е. i в цикле не имеет никаких особых свойств, это такая же обычная переменная.


Часовой пояс GMT +3, время: 18:45.