web-dev-qa-db-fra.com

Quel est le contexte dans _.each (liste, itérateur, [contexte])?

Je suis nouveau sur underscore.js. Quel est le but de [context] dans _.each()? Comment devrait-il être utilisé?

161
ram

Le paramètre context définit uniquement la valeur de this dans la fonction itérateur.

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);

Exemple de travail: http://jsfiddle.net/a6Rx4/

Il utilise le numéro de chaque membre du tableau en cours d'itération pour obtenir l'élément à l'indice de someOtherArray, représenté par this, car nous l'avons passé en tant que paramètre context.

Si vous ne définissez pas le contexte, alors this fera référence à l'objet window.

219
user113716

context est l'endroit où this fait référence à votre fonction itérateur. Par exemple:

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);
49
Harmen

Le contexte vous permet de fournir des arguments au moment de l'appel, ce qui permet de personnaliser facilement des fonctions d'assistance génériques prédéfinies.

quelques exemples:

// 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); 

Même à partir d'exemples limités, vous pouvez voir à quel point un "argument supplémentaire" peut être puissant pour créer du code réutilisable. Au lieu de créer une fonction de rappel différente pour chaque situation, vous pouvez généralement adapter un assistant de bas niveau. Le but est que votre logique personnalisée comprenne un verbe et deux noms, avec un passe-partout minimal.

Certes, les fonctions fléchées ont éliminé de nombreux avantages des fonctions pures génériques "Code Golf", mais les avantages sémantiques et de cohérence demeurent.

J'ajoute toujours "use strict" aux aides pour fournir une compatibilité native [].map() lors du passage de primitives. Sinon, ils sont forcés de pénétrer dans des objets, ce qui fonctionne généralement toujours, mais il est plus rapide et plus sûr d'être spécifique à un type.

6
dandavis

Comme expliqué dans d'autres réponses, context est le contexte this à utiliser dans le rappel transmis à each.

Je vais expliquer cela avec l'aide du code source des méthodes pertinentes de code source souligné

La définition de _.each ou _.forEach est la suivante:

_.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;
};

La deuxième déclaration est importante à noter ici

iteratee = optimizeCb(iteratee, context);

Ici, context est passé à une autre méthode optimizeCb et la fonction renvoyée est assignée à iteratee qui sera appelée plus tard.

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);
  };
};

Comme le montre la définition ci-dessus de la méthode optimizeCb, si context n'est pas transmis, alors func est renvoyé tel quel. Si context est passé, la fonction de rappel est appelée comme

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

func est appelé avec call() qui est utilisé pour appeler une méthode en définissant son contexte this. Ainsi, lorsque this est utilisé dans func, il fera référence à 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>

Vous pouvez considérer context comme dernier paramètre facultatif de forEach en JavaScript.

4
Tushar

Utilisation simple 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>

Voici exemple simple qui pourrait utiliser _.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();

Sortie:

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

Au lieu d'appeler addItem plusieurs fois vous pouvez utiliser un trait de soulignement de cette façon:

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

ce qui est identique à l'appel de addItem trois fois de manière séquentielle avec ces éléments. Fondamentalement, il itère votre tableau et pour chaque élément appelle votre fonction de rappel anonyme qui appelle x.addItem(item). La fonction de rappel anonyme est similaire à la fonction membre addItem (elle prend par exemple un élément) et est en quelque sorte sans objet. Ainsi, au lieu de passer par une fonction anonyme, il est préférable que _.each évite cet indirection et appelle directement addItem:

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

mais cela ne fonctionnera pas, car la fonction membre addItem à l'intérieur du panier this ne fera pas référence à votre panier x que vous avez créé. C'est pourquoi vous avez la possibilité de transmettre votre panier x à utiliser en tant que [context]:

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

Exemple complet qui utilise _.each et le contexte:

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>

En bref, si la fonction de rappel que vous transmettez à _.each utilise de quelque manière que ce soit this, vous devez spécifier le nom this qui doit faire référence à l'intérieur de votre fonction de rappel. Il peut sembler que x soit redondant dans mon exemple, mais x.addItem n’est qu’une fonction et n’a aucun rapport avec x ou basketou n’importe quel autre objet, par exemple :

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>

En d'autres termes, vous liez une valeur à this dans votre rappel, ou vous pouvez aussi utiliser bind directement comme ceci:

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

en quoi cette fonctionnalité peut-elle être utile avec différentes méthodes de soulignement?

En général, si une méthode underscorejs prend une fonction de rappel et si vous souhaitez que ce rappel soit appelée sur une fonction membre d'un objet (par exemple, une fonction qui utilise this), vous pouvez lier cette fonction à un objet ou transmettre cet objet en tant que [context] paramètre et c'est l'intention principale. Et en haut de la documentation de underscorejs, c’est exactement ce qu’ils indiquent: L’itéré est lié à l’objet context, s’il est passé

3
Pavel