Вызов JavaScript - я могу просто получить доступ к свойствам объекта напрямую

Я пытаюсь понять цель использования call(). Я прочитал этот пример о том, для чего он используется, и собрал примеры из Mozilla, а именно:

var animals = [
  { species: 'Lion', name: 'King' },
  { species: 'Whale', name: 'Fail' }
];

for (var i = 0; i < animals.length; i++) {
  (function(i) {
    this.print = function() {
      console.log('#' + i + ' ' + this.species
                  + ': ' + this.name);
    }
    this.print();
  }).call(animals[i], i);
}

Я знаю, что их пример предназначен только для демонстрации, но я мог бы легко просто получить доступ к свойствам объекта непосредственно в цикле как таковом:

        var animals = [
          { species: 'Lion', name: 'King' },
          { species: 'Whale', name: 'Fail' }
        ];

        for (var i = 0; i < animals.length; i++) {
            console.log('#' + i + ' ' + animals[i].species  + ': ' + animals[i].name);        
        }

От этого я в замешательстве. Я до сих пор не вижу причины, по которой мне нужно было бы передавать другой this контекст для call().


Кроме того, с этим примером Mozilla в функции .call(animals[i], i); у меня есть два вопроса:

  • this здесь - это массив animals. Я предполагаю, что вам нужно вызвать здесь, потому что animals в противном случае выходит за рамки внутренней анонимной функции?

  • какова цель передачи индекса i в качестве второго аргумента в .call(animals[i], i);?


Этот вопрос вызван следующим сегментом кода, который я пытался понять. Этот код предназначен для сбора всех внутренних значений из определенных интервалов в документе и их объединения. Почему мне нужно выполнить call здесь, на карте? Это потому, что document.querySelectorAll иначе не соответствует контексту?

        console.log([].map.call(document.querySelectorAll("span"), 
            function(a){ 
                return a.textContent;
            }).join(""));

person Growler    schedule 26.02.2015    source источник
comment
Да, этот пример действительно надуманный. Он просто передал животное для this и i для параметра i. Было бы разумнее, если бы .print() не вызывался напрямую, как в этом надуманном примере.   -  person Bergi    schedule 26.02.2015
comment
@NetaMeta: за исключением того, что querySelectorAll не возвращает массив. И именно поэтому вам НУЖНО call там.   -  person Bergi    schedule 26.02.2015
comment
Берги, трепло удалили комментарий, ошибся   -  person Neta Meta    schedule 26.02.2015


Ответы (4)


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

    console.log([].map.call(document.querySelectorAll("span"), 
        function(a){ 
            return a.textContent;
        }).join(""));

Итак, вот в чем проблема. Что действительно хотел сделать этот фрагмент кода, так это использовать метод map для массива. Однако document.querySelectorAll возвращает NodeList, а не массив. Что наиболее важно, в NodeList нет функции сопоставления. Таким образом, вы застряли бы с классическим циклом for.

Однако оказывается, что Array.map на самом деле работает со всем, что имеет свойство длины и поддерживает числовое индексирование (например, nodes[i]). Однако реализация карты использует специально this.length для внутреннего использования.

Итак, используя здесь call, вы можете позаимствовать реализацию Array.map, передать ей ссылку this, которая на самом деле не является массивом, но работает достаточно, чтобы работать для этих целей.

Итак, в основном, это вызов:

document.querySelectorAll("span").map( 
  function(a){ 
    return a.textContent;
  }).join("");

за исключением того, что на самом деле return не имеет метод map, поэтому мы заимствовали его из Array. Это часто называют «утиной печатью».

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

Таким образом, call наиболее полезен для заимствования методов одного типа для использования в другом.

person Chris Tavares    schedule 26.02.2015
comment
просто так я понимаю... 1) в идеале вы должны просто использовать [].map для создания нового массива внутренних текстовых значений соединенных диапазонов, найденных querySelector, но вам нужен call, чтобы map мог обрабатывать возвращенный nodeList как массив? 2) Я попробовал ваш второй пример, document.querySelectorAll("span").map( function(a){ etc... без [].map.call, и он сказал undefined is not a function, почему? и 3) я так и не понял, какие аргументы принимает call? Всегда ли первый аргумент возвращает массив? Какие аргументы идут после первых аргументов? Всегда ли они являются анонимными функциями? - person Growler; 26.02.2015
comment
Пара вещей. 1) [].map не вызывает карту. Он извлекает объект функции для метода карты, чтобы вы могли вызвать для него call. 2) Второй терпит неудачу, потому что вы вызываете функцию, которая возвращает объект (в данном случае querySelectorAll). Затем вы получаете доступ к свойству результата (в данном случае map). Объект результата не имеет свойства map, поэтому вы получаете значение undefined. Затем вы пытаетесь вызвать его (используя оператор ()), но вы не можете вызвать функцию для undefined. Таким образом ошибка. Надеюсь это поможет. - person Chris Tavares; 26.02.2015
comment
It's retrieving the function object for the map method so that you can call call on it. Меня это очень смущает. Array.prototype.map создает новый массив с результатами вызова предоставленной функции для каждого элемента в этом массиве, верно? Что такое function object, который я получаю в этом случае, и почему мне нужно, чтобы объект функции вызывал для него call? - person Growler; 26.02.2015
comment
Помните: функции Javascript ЯВЛЯЮТСЯ объектами. Метод — это просто свойство, значение которого оказывается функцией. Когда вы делаете [].map (обратите внимание, что в этом выражении нет ()), вы не вызываете map, вы извлекаете свойство карты из массива. call — это метод для функциональных объектов. Итак, вы извлекаете свойство объекта (карты) и вызываете для него метод (вызов). Было бы легче понять, если бы вместо [].map было Array.prototype.map (это тоже работает)? - person Chris Tavares; 26.02.2015
comment
Хорошо, последнее, что я приму, обещаю :). Мне нужно это понять... поэтому функция Array.prototype.call( 1) получает все интервалы с тегом, скрытым от querySelectorAll, 2) передает значения селектора запроса в качестве аргументов (a) анонимной функции (где a = диапазон), 3) создает новый массив с возвращаемыми результатами анонимной функции, 4) когда массив всех hidden диапазонов заполнен, используйте join, чтобы преобразовать массив в строку, 5) call возвращает эту строку - person Growler; 27.02.2015
comment
Почти. Я не уверен, откуда взялась спрятанная вещь? Мы просто запрашиваем диапазоны. На самом деле здесь два вызова функций. map.call является первым - его возвращаемое значение является результатом операции карты, которая представляет собой массив, содержащий возвращаемые значения из анонимной функции. Второй вызов функции — это вызов join, который представляет собой метод для массивов, который объединяет каждый элемент в массиве, для которого он вызывается, в строку. - person Chris Tavares; 27.02.2015
comment
Итак, я извлекаю свойство карты из объекта-прототипа массива и вызываю для него метод (call() ). Думаю, я запутался, потому что в документах MDN в разделе свойств Array.prototype нет свойства map, а есть только функция map(), которая создает новый массив. - person Growler; 27.02.2015
comment
map — это имя свойства, которое содержит объект функции карты. Это просто основной javascript. - person Chris Tavares; 28.02.2015

Тот пример правильный, но беден тем, что совершенно не нужен для достижения их результата.

Кроме того, с этим примером Mozilla в функции .call(animals[i], i); у меня есть два вопроса:

  • this здесь - это массив animals. Я предполагаю, что вам нужен вызов здесь, потому что animals в противном случае выходит за рамки внутренней анонимной функции?

На самом деле, this — это каждый элемент. В .call(animals[i], i); первый элемент — это объект, который будет сопоставлен с this, поэтому this внутри функции — это текущий элемент животного, а не массив животных.

Кроме того, внутренняя функция может получить доступ к animals в текущем закрытии. Опять же, доказывая, что их пример плохой.

  • какова цель передачи индекса i в качестве второго аргумента в .call(animals[i], i);?

Второй параметр — это call — это первый аргумент, передаваемый вызываемой функции (а третий параметр — это второй аргумент и т. д.). Это, в отличие от apply, которое принимает массив в качестве второго параметра и применяет его в качестве отдельных аргументов. Но вызываемая ими функция имеет доступ к текущему i, что еще раз показывает, что их пример не нужен.


Теперь вторая часть. Вам нужно использовать call, потому что document.querySelectorAll это NodeList, а не Array и, к сожалению, NodeList не имеет метода map, а Array есть.

Для очень упрощенной версии предположим, что мы определили Array.prototype.map примерно так:

Array.prototype.map = function(fn) {
  var copy = [];
  for (var i = 0; i < this.length; i++) {
    copy.push(fn(this[i]));
  }
  return copy;
};

Вы можете видеть здесь, что когда мы вызываем:

var timesTwo = [1,2,3].map(function(n) {
  return n * 2;
});
// timesTwo is [2,4,6];

Вы можете видеть в нашем определенном Array.prototype.map, что мы ссылаемся на наш экземпляр массива как this. Теперь вернемся к нашему NodeList сверху: у нас нет доступного метода map, но мы все еще можем вызвать Array.prototype.map, используя call, и заставить this ссылаться на наш NodeList. Это нормально, потому что у него есть свойство length, которое мы используем.

Итак, мы можем сделать это, используя:

var spanNodeList = document.querySelectorAll('span');
Array.prototype.map.call(spanNodeList, function(span) { /* ... */ });

// Or, as MDN's example, use an array instance as "[]"
[].map.call(spanNodeList, function(span) { /* ... */ });

Надеюсь, это поможет.

person rgthree    schedule 26.02.2015
comment
Отличное описание. Спасибо, что углубились в Array.prototype. Итак, я извлекаю свойство карты из объекта-прототипа массива и вызываю для него метод (call() ). Думаю, я запутался, потому что в документах MDN раздел свойств Array.prototype не имеет свойства карты, а только функцию map(), которая создает новый массив. - person Growler; 27.02.2015
comment
@Гроулер Ага. Array.prototype.map — это метод, а не свойство. Когда мы выполняем Array.prototype.map.call и передаем наш NodeList в качестве привязки, все, что мы говорим: Эй, пожалуйста, вызовите метод карты в прототипе Array, но используйте этот NodeList, когда он ссылается на this. call на самом деле не имеет значения, вызывается ли он для прототипа, метода экземпляра, глобальной или анонимной функции и т. д. Он просто говорит, что this ссылается на мой первый параметр внутри вызова функции. - person rgthree; 27.02.2015

document.querySelectorAll("span") возвращает NodeList, а не массив, поэтому, если вы попытаетесь document.querySelectorAll("span").map(...), вы получите сообщение об ошибке о том, что map не определено (то же самое, если вы попытаетесь .join)

[].map.call(document.querySelectorAll("span"), 
    function(a){ 
        return a.textContent;
    }
)

Это просто использование call, чтобы сделать так, чтобы NodeList использовался как массив для функции map


for (var i = 0; i < animals.length; i++) {
  (function(i) {
    this.print = function() {
      console.log('#' + i + ' ' + this.species
                  + ': ' + this.name);
    }
    this.print();
  }).call(animals[i], i);
}
  1. здесь this — это массив животных. Я предполагаю, что вам нужно вызвать здесь, потому что в противном случае животные выходят за рамки внутренней анонимной функции?

    На самом деле this здесь будет ссылаться на объект в массиве животных с индексом i. Но animals не выходит за рамки, просто проще сделать this.species, чем animals[i].species. Они могли бы так же легко сделать )(i) и сделать animals[i].species вместо ).call(...) и this.species.

  2. какова цель передачи индекса i в качестве второго аргумента в .call(animals[i], i);?

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

person Patrick Evans    schedule 26.02.2015

Ваш пример отличается от примера Mozilla. В Mozilla это создает функцию. Без анонимной функции в цикле создания этой функции вы столкнетесь с проблемой закрытия. . Фактически это означает, что эта функция не будет вести себя так, как вы могли бы ожидать. Например, попробуйте воссоздать функцию без анонимной функции и вызовите animals[0].print()

это вот массив животных. Я предполагаю, что вам нужно вызвать здесь, потому что в противном случае животные выходят за рамки внутренней анонимной функции?

Это значение this представляет собой отдельный объект животного. Массив animals НЕ выходит за рамки внутренней анонимной функции - это природа JavaScript, у вас всегда будет доступ к переменным внешней функции, если только другая переменная затеняет ее (имеет такое же имя и, таким образом, покрывает внешнюю переменную).

Рассмотрим пример без метода call:

var animals = [
    { species: 'Lion', name: 'King' },
    { species: 'Whale', name: 'Fail' }
];

for (var i = 0; i < animals.length; i++) {
    (function(i) {
        this.print = function() {
            console.log('#' + i + ' ' + this.species
              + ': ' + this.name);
        }
        this.print();
    })(i);
}

В этом примере переменная this будет ссылаться на объект window или null в зависимости от некоторых факторов, таких как использование use strict. Но мы хотим присоединить метод print к объекту животного, поэтому нам нужно использовать метод call.

какова цель передачи индекса i в качестве второго аргумента в .call(animals[i], i);?

Как и выше, из-за проблемы закрытия вы не хотите ссылаться на переменную i во внешнем закрытии, но хотите локализовать ее внутри анонимной функции, чтобы метод print был вменяемым.

person sahbeewah    schedule 26.02.2015