O que é contexto em _.each (lista, iterador, [contexto])?

Respostas:

220

O parâmetro context apenas define o valor de thisna função do iterador.

var someOtherArray = ["name","patrick","d","w"];

_.each([1, 2, 3], function(num) { 
    // In here, "this" refers to the same Array as "someOtherArray"

    alert( this[num] ); // num is the value from the array being iterated
                        //    so this[num] gets the item at the "num" index of
                        //    someOtherArray.
}, someOtherArray);

Exemplo de trabalho: http://jsfiddle.net/a6Rx4/

Ele usa o número de cada membro da matriz que está sendo iterado para obter o item no índice de someOtherArray, que é representado porthis desde que passamos como o parâmetro de contexto.

Se você não definir o contexto, thisfará referência ao windowobjeto.

user113716
fonte
7
Qual a vantagem disso? Por que não apenas se referir ao someOtherArray[num]invés de this[num]?
precisa saber é o seguinte
3
@ csjacobs24: É comum ter um conjunto de funções reutilizáveis ​​que não teriam acesso ao escopo da variável local. Aqui está um exemplo simples: jsfiddle.net/a6Rx4/745
1
Essa resposta responde à pergunta, mas seria melhor se desse exemplos de como isso pode ser útil.
temporary_user_name
50

contexté onde thisse refere na sua função de iterador. Por exemplo:

var person = {};
person.friends = {
  name1: true,
  name2: false,
  name3: true,
  name4: true
};

_.each(['name4', 'name2'], function(name){
  // this refers to the friends property of the person object
  alert(this[name]);
}, person.friends);
Harmen
fonte
7

O contexto permite que você forneça argumentos no momento da chamada, permitindo fácil personalização de funções auxiliares pré-criadas genéricas.

alguns exemplos:

// stock footage:
function addTo(x){ "use strict"; return x + this; }
function pluck(x){ "use strict"; return x[this]; }
function lt(x){ "use strict"; return x < this; }

// production:
var r = [1,2,3,4,5,6,7,8,9];
var words = "a man a plan a canal panama".split(" ");

// filtering numbers:
_.filter(r, lt, 5); // elements less than 5
_.filter(r, lt, 3); // elements less than 3

// add 100 to the elements:
_.map(r, addTo, 100);

// encode eggy peggy:
_.map(words, addTo, "egg").join(" ");

// get length of words:
_.map(words, pluck, "length"); 

// find words starting with "e" or sooner:
_.filter(words, lt, "e"); 

// find all words with 3 or more chars:
_.filter(words, pluck, 2); 

Mesmo nos exemplos limitados, você pode ver o quão poderoso pode ser um "argumento extra" para criar código reutilizável. Em vez de criar uma função de retorno de chamada diferente para cada situação, geralmente você pode adaptar um auxiliar de baixo nível. O objetivo é ter sua lógica personalizada agrupando um verbo e dois substantivos, com um mínimo de clichê.

É certo que as funções de seta eliminaram muitas das vantagens do "código de golfe" das funções puras genéricas, mas as vantagens semânticas e de consistência permanecem.

Eu sempre adiciono "use strict"aos auxiliares para fornecer [].map()compatibilidade nativa ao transmitir as primitivas. Caso contrário, eles são coagidos a objetos, o que geralmente ainda funciona, mas é mais rápido e seguro ser específico ao tipo.

dandavis
fonte
5

Uso simples de _.each

_.each(['Hello', 'World!'], function(word){
    console.log(word);
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore-min.js"></script>

Aqui está um exemplo simples que pode ser usado _.each:

function basket() {
    this.items = [];
    this.addItem = function(item) {
        this.items.push(item);
    };
    this.show = function() {
        console.log('items: ', this.items);
    }
}

var x = new basket();
x.addItem('banana');
x.addItem('apple');
x.addItem('kiwi');
x.show();

Resultado:

items:  [ 'banana', 'apple', 'kiwi' ]

Em vez de ligar addItemvárias vezes, você pode usar o sublinhado desta maneira:

_.each(['banana', 'apple', 'kiwi'], function(item) { x.addItem(item); });

que é idêntico a chamar addItemtrês vezes sequencialmente com esses itens. Basicamente, itera sua matriz e para cada item chama sua função de retorno de chamada anônima que chama x.addItem(item). A função de retorno de chamada anônima é semelhante à addItemfunção de membro (por exemplo, leva um item) e é meio inútil. Então, em vez de passar por uma função anônima, é melhor _.eachevitar esse indireto e chamar addItemdiretamente:

_.each(['banana', 'apple', 'kiwi'], x.addItem);

mas isso não funcionará, pois a addItemfunção de membro da cesta thisnão se refere à xcesta que você criou. É por isso que você tem a opção de passar sua cesta xpara ser usada como [context]:

_.each(['banana', 'apple', 'kiwi'], x.addItem, x);

Exemplo completo que usa _.each e context:

function basket() {
    this.items = [];
    this.addItem = function(item) {
        this.items.push(item);
    };
    this.show = function() {
        console.log('items: ', this.items);
    }
}
var x = new basket();
_.each(['banana', 'apple', 'kiwi'], x.addItem, x);
x.show();
<script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore-min.js"></script>

Em resumo, se a função de retorno de chamada que você passar de _.eachalguma forma usar this, será necessário especificar o que thisdeve estar se referindo dentro da sua função de retorno de chamada. Pode parecer que xé redundante no meu exemplo, mas x.addItemé apenas uma função e pode ser totalmente sem relação com xou basket ou qualquer outro objeto, por exemplo :

function basket() {
    this.items = [];
    this.show = function() {
        console.log('items: ', this.items);
    }
}
function addItem(item) {
    this.items.push(item);
};

var x = new basket();
_.each(['banana', 'apple', 'kiwi'], addItem, x);
x.show();
<script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore-min.js"></script>

Em outras palavras, você vincula algum valor a thisseu retorno de chamada ou pode usar o bind diretamente assim:

_.each(['banana', 'apple', 'kiwi'], addItem.bind(x));

como esse recurso pode ser útil com alguns métodos diferentes de sublinhado?

Em geral, se algum underscorejsmétodo usa uma função de retorno de chamada e se você deseja que essa chamada seja chamada em alguma função membro de algum objeto (por exemplo, uma função que use this), você poderá vincular essa função a algum objeto ou passar esse objeto como [context]parâmetro e isso é a intenção primária. E na parte superior da documentação do underscorejs, é exatamente o que eles afirmam: o iterado é vinculado ao objeto de contexto, se um for passado

Pavel P
fonte
4

Conforme explicado em outras respostas, contexté o thiscontexto a ser usado dentro do retorno de chamada passado para each.

Vou explicar isso com a ajuda do código fonte de métodos relevantes a partir do código fonte de sublinhado

A definição de _.eachou _.forEaché a seguinte:

_.each = _.forEach = function(obj, iteratee, context) {
  iteratee = optimizeCb(iteratee, context);

  var i, length;
  if (isArrayLike(obj)) {
    for (i = 0, length = obj.length; i < length; i++) {
      iteratee(obj[i], i, obj);
    }
  } else {
    var keys = _.keys(obj);
    for (i = 0, length = keys.length; i < length; i++) {
      iteratee(obj[keys[i]], keys[i], obj);
    }
  }
  return obj;
};

Segunda declaração é importante notar aqui

iteratee = optimizeCb(iteratee, context);

Aqui, contexté passado para outro método optimizeCbe a função retornada é atribuída à iterateequal é chamada mais tarde.

var optimizeCb = function(func, context, argCount) {
  if (context === void 0) return func;
  switch (argCount == null ? 3 : argCount) {
    case 1:
      return function(value) {
        return func.call(context, value);
      };
    case 2:
      return function(value, other) {
        return func.call(context, value, other);
      };
    case 3:
      return function(value, index, collection) {
        return func.call(context, value, index, collection);
      };
    case 4:
      return function(accumulator, value, index, collection) {
        return func.call(context, accumulator, value, index, collection);
      };
  }
  return function() {
    return func.apply(context, arguments);
  };
};

Como pode ser visto na definição do método acima optimizeCb, se contextnão for passado, funcserá retornado como está. Se contextfor aprovada, a função de retorno de chamada é chamada como

func.call(context, other_parameters);
          ^^^^^^^

funcé chamado com o call()qual é usado para chamar um método, definindo o thiscontexto dele. Então, quando thisé usado dentro func, ele se refere a context.

// Without `context`
_.each([1], function() {
  console.log(this instanceof Window);
});


// With `context` as `arr`
var arr = [1, 2, 3];
_.each([1], function() {
  console.log(this);
}, arr);
<script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore-min.js"></script>

Você pode considerar contextcomo o último parâmetro opcional forEachno JavaScript.

Tushar
fonte