Javascript-форум (https://javascript.ru/forum/)
-   Общие вопросы Javascript (https://javascript.ru/forum/misc/)
-   -   Как работает Function.prototype.call.call ? (https://javascript.ru/forum/misc/51616-kak-rabotaet-function-prototype-call-call.html)

xaser 12.11.2014 16:26

Как работает Function.prototype.call.call ?
 
Читаю документацию и не могу вкурить принцип работы. Например (1):
Function.prototype.call.call(function(){ alert('how it works?') })

Если взять просто call применительно к какому либо методу, то вроде бы все понятно:
например (2):
(function(){ alert(this) }).call({})

согласно документации для call (п. 15.3.4.4):
При вызове методом call объекта func с аргументом thisArg и необязательными аргументами arg1, arg2, и т.д.,выполняются следующие шаги:
...
4.Вернуть результат вызова внутреннего метода [[Call]] для func, передавая thisArg в качестве значения this и argList в качестве списка аргументов.

В (2) объектом func является анонимная ф-ия,
call вызывает [[Call]] у анонимной ф-ии, подменяя this пустым объектом.

в (1) объектом func является другой call,
call вызывает [[Call]] у call, подменяя this анонимной функцией. И вот что тут дальше происходит непонятно.

В п. 13.2.1
сказано:

При вызове внутреннего метода [[Call]] для объекта Function, называемого F, со значением this и списком аргументов выполняются следующие шаги:
...
2. Пусть result будет результатом вычисления FunctionBody, представляющим собой значение внутреннего свойства [[code]] для F. Если у F нет внутреннего свойства [[code]], или если его значение представляет собой пустое тело функции FunctionBody, то result равен (normal, undefined, empty).
...

Для (2) все понятно - тупо отрабатывает тело нашей функции.

Для (1) - ???

Aetae 12.11.2014 16:49

Всё просто же. func.call() это вызов call в котором this(для call) == func. Вторым call вы подменяете this для первого call на новую функцию.
Всё точно также как и с обычным кодом:
obj = {
    mehod: function(){
        alert(this.prop)
    },
    prop: 'foo'
}
obj.mehod()
obj.mehod.call({prop: 'bar'})
только this не объект, а функция.

Яростный Меч 12.11.2014 16:50

Понять (1) несложно, главное разложить его по косточкам.

var func = Function.prototype.call;
var thisObj = function(){ alert('how it works?') };


тогда получается
func.call(thisObj)


что эквивалентно thisObj.func(), где под "func" в нашем случае подразумевается call, который есть у каждой функции

krutoy 12.11.2014 17:54

Цитата:

Сообщение от Aetae
Всё просто же. func.call() это вызов call в котором this(для call) == func. Вторым call вы подменяете this для первого call на новую функцию.

Я прошу прощения, но, все же давайте проясним.

func.call -- это функция, this которой -- это объект func, который является экземпляром Function
func.call.call -- это функция, this которой -- это объект call, который является экземпляром Function.

Обе они -- экземпляр Function, однако, call, которая вызывается в контексте call, ведет себя иначе, чем call, вызванный в контексте другой функции. Очевидно, что объект call, имеет в себе что-то, помимо того, что он наследует от Function

Так вот, интересно как раз то, что такого есть в call, чего нет в обычном объекте функции. То есть, это что-то, это должно быть тем, что вызывает эффект вызова функции, поданной в качестве аргумента func.call.call(function(){})

Octane 12.11.2014 18:03

Последний call меняет this первого, ничего необычного не вижу.

xaser 12.11.2014 18:07

Все равно не догоняю.
В 15.3.4.4 сказано про "При вызове методом call объекта func"
Тут под вызовом метода имеется ввиду наличие property accessor, ведь так? То бишь func.call || func['call'].

Ок. Имеем Function.prototype.call - объект func + Function.prototype.call.call - собственно сам метод.

Итак, приступим.
1. IsCallable = true, едем дальше
2, 3. формируем список аргументов, у нас он пустой
4. Вызываем [[Call]] у объекта func (первый call в нашем случае), куда передаем this, равный анонимной функции и пустой список аргументов.
Далее по 13.2.1:
5. Формируем новый execution context, где this будет указывать на анонимную ф-ию
6. Вычисляем FunctionBody (для (2) - это код ф-ии, для (1) - видимо, внутренний код call)

Так вот на этом этапе для (2) - все предельно понятно.
Тогда как для (1) - какая-то магия заставила первый call вызвать [[Call]] у this. Т.е это уже не property accessor, когда мы call через точку присоединяем к функции.
Тут call вдруг "посчитал", что нужно переданный this выполнить в качестве функции.

В общем, либо я сильно торможу, либо не вижу, где этот момент в стандарте описан, либо и то и другое)

xaser 12.11.2014 18:10

Цитата:

Сообщение от krutoy (Сообщение 340695)
call, которая вызывается в контексте call, ведет себя иначе, чем call, вызванный в контексте другой функции.

Вот и я о том же.

Octane 12.11.2014 18:12

чтобы представить, что внутри call, можно перезаписать его так:
Function.prototype.call = function (thisObj) {
    return this.apply(thisObj, Array.from(arguments).slice(1));
};

тоесть call вызывает свой this
func1.call(…) //внутри call this → func1
func1.call.call(func2) //последний call меняет this первого на func2


никакого специального поведения для call.call нет

krutoy 12.11.2014 18:43

Цитата:

Сообщение от Octane
func1.call(…) //внутри call this → func1
func1.call.call(func2) //последний call меняет this первого на func2

Нет. По Вашему определению:
func1.call(…) //внутри call this → func1
func1.call.call(func2) //внутри последнего call this --> call

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

Octane 12.11.2014 18:46

прочитай внимательно, я не писал про this внутри последнего call

Octane 12.11.2014 19:24

может быть так понятнее будет:
func1.call1.call2(func2)

1) вызов call2
2) call2 меняет thisObj для call1 на func2
3) call2 вызывает свой thisObj – call1
4) call1 меняет thisObj для func2 на undefined (или window)
5) call1 вызывет вой thisObj – func2

krutoy 12.11.2014 21:29

xaser,
Все, до меня дошло. Это, конечно, не точно, но смысл такой
myCall=function(){return this()}
myCall.call(function(){alert("foo")})

То есть call в этом смысле, напоминает вот этот myCall.

xaser 13.11.2014 00:25

Ну мне сначала было примерно ясно как это работает, но четкой картины до сих пор нет.

Цитата:

Сообщение от Octane
3) call2 вызывает свой thisObj – call1
...
5) call1 вызывет вой thisObj – func2

Вот откуда тут thisObj появляется?

В спецификации call и [[Call]] насчет этого ничего не сказано. Или сказано в других пунктах? Это какое-то общее правило для взаимодействия метода с объектом?

Aetae 13.11.2014 01:07

Это какое-то общее правило для взаимодействия метода с объектом.

//Если совсем уж тупо: this'ом у метода является то, что идёт перед точкой.

krutoy 13.11.2014 08:32

Цитата:

Сообщение от Aetae
this'ом у метода является то, что идёт перед точкой.

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

xaser 13.11.2014 13:33

Цитата:

Сообщение от Aetae (Сообщение 340723)
Это какое-то общее правило для взаимодействия метода с объектом.

Где об этом в спецификации почитать можно?

devote 17.11.2014 19:27

принцип работы call довольно прост.
Если подумать что делает этот вызов:
func1.call(obj);
Правильно, выполняет функцию func1 так как в качестве контекста у метода call указана функция func1, и соответственно он выполняет эту функцию, при этом передав в нее в качестве контекста объект obj
Пример:
function func1() {
  // здесь this это контекст
  alert(this.ololo);
}
var obj = {ololo: "ololo"};
func1.call(obj); // выполняем функцию передав контекст obj
И так, из примера выше видно что call просто выполняет функцию в контексте которой он был вызван, при этом в качестве аргумента call принимает объект в контексте которого выполнит функцию...

А теперь что же делает вариант с:
func1.call.call(func2);
в этом примере, второй call будет выполнять первый call, передав в нее необходимый контекст. А так как функция call должна принять в качестве контекста функцию, то тут второй call просто меняет контекст (функцию) у первого call и вызывает его. После срабатывает первый call и манипулирует уже с функцией переданной вторым call в качестве контекста.

PS. Что-то сдается мне, что я тоже не понятно объяснил.

Aetae 18.11.2014 00:02

devote, а понятно объяснить как-то не получается.:)
Это как "очевидно" у препода матана. Либо ты в теме и это действительно очевидно, либо не в теме и надо работать пока в голове картина не сложится. Именно "простыми словами" хрен объяснишь.)

xaser 25.11.2014 21:32

Ага, для всех все очевидно, но ссылку на раздел спеки, где про это почитать можно, никто дать не может:)

Цитата:

Сообщение от devote (Сообщение 341397)
в этом примере, второй call будет выполнять первый call, передав в нее необходимый контекст. А так как функция call должна принять в качестве контекста функцию, то тут второй call просто меняет контекст (функцию) у первого call и вызывает его.

Интересная терминология, "передать контекст" и "принять в качестве контекста функцию" - ну тут видимо thisObj имеется ввиду.


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