Что такое контекст в _.each (список, итератор, [контекст])?

Ответы:

220

Параметр context просто устанавливает значение thisв функции итератора.

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

Рабочий пример: http://jsfiddle.net/a6Rx4/

Он использует число от каждого члена массива, для которого выполняется итерация, чтобы получить элемент с таким индексом someOtherArray, который представлен, thisпоскольку мы передали его в качестве параметра контекста.

Если вы не установите контекст, то thisбудете ссылаться на windowобъект.

user113716
источник
7
В чем преимущество этого? Почему бы просто не обратиться к, someOtherArray[num]а не this[num]?
csjacobs24
3
@ csjacobs24: Распространено иметь набор многократно используемых функций, которые не имеют доступа к области локальных переменных. Вот простой пример: jsfiddle.net/a6Rx4/745
1
Этот ответ действительно отвечает на вопрос, но было бы лучше, если бы он предоставил примеры того, как это может быть полезно.
временное_имя_пользователя
50

contextгде thisссылка в вашей функции итератора. Например:

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
источник
7

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

Некоторые примеры:

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

Даже из ограниченных примеров вы можете увидеть, насколько мощным может быть «дополнительный аргумент» для создания кода, который можно использовать повторно. Вместо того, чтобы делать разные функции обратного вызова для каждой ситуации, вы обычно можете адаптировать помощника низкого уровня. Цель состоит в том, чтобы ваша пользовательская логика связывала глагол и два существительных с минимальным образцом.

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

Я всегда добавляю "use strict"к помощникам для обеспечения нативной [].map()совместимости при передаче примитивов. В противном случае они преобразуются в объекты, которые обычно все еще работают, но быстрее и безопаснее быть привязанными к типу.

dandavis
источник
5

Простое использование _.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>

Вот простой пример, который можно использовать _.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();

Вывод:

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

Вместо addItemнескольких вызовов вы можете использовать подчеркивание следующим образом:

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

что идентично addItemтрехкратному последовательному вызову с этими предметами. По сути, он повторяет ваш массив и для каждого элемента вызывает вашу анонимную функцию обратного вызова, которая вызывает x.addItem(item). Функция анонимного обратного вызова похожа на addItemфункцию-член (например, она принимает элемент) и является бессмысленной. Таким образом, вместо прохождения анонимной функции лучше _.eachизбегать этого косвенного обращения и вызывать addItemнапрямую:

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

но это не сработает, так как внутри addItemфункции члена корзины не thisбудет ссылаться на xсозданную вами корзину. Вот почему у вас есть возможность передать свою корзину xдля использования в качестве [context]:

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

Полный пример, который использует _.each и 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>

Короче говоря, если функция обратного вызова, которую вы передаете _.eachкаким-либо образом, использует, thisто вам нужно указать, что thisдолжно ссылаться внутри вашей функции обратного вызова. Может показаться , что xявляется излишним в моем примере, но x.addItemэто просто функция и может быть совершенно не связан с xили basket или любого другого объекта, например :

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>

Другими словами, вы привязываете некоторое значение к thisсвоему обратному вызову, или вы можете использовать привязку напрямую, например так:

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

Как эта функция может быть полезна с некоторыми другими методами подчеркивания?

В общем, если какой-то underscorejsметод принимает функцию обратного вызова и если вы хотите, чтобы этот обратный вызов вызывался для некоторой функции-члена некоторого объекта (например, функции, которая использует this), то вы можете связать эту функцию с каким-либо объектом или передать этот объект в качестве [context]параметра, и это основное намерение. И в верхней части документации underscorejs, это именно то, что они заявляют: итерируемый связывается с объектом контекста, если он был передан

Павел П
источник
4

Как объяснялось в других ответах, contextявляется thisконтекст для использования внутри обратного вызова передается each.

Я объясню это с помощью исходного кода соответствующих методов из подчеркивания исходного кода

Определение _.eachили _.forEachследующее:

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

Второе утверждение важно отметить здесь

iteratee = optimizeCb(iteratee, context);

Здесь contextпередается другой метод, optimizeCbи затем присваивается возвращаемая ему функция, iterateeкоторая вызывается позже.

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

Как видно из приведенного выше определения метода optimizeCb, если contextне передано, то funcвозвращается как есть. Если contextпередано, функция обратного вызова вызывается как

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

funcвызывается с помощью call()которого используется для вызова метода путем установки его thisконтекста. Таким образом, когда thisиспользуется внутри func, это будет относиться к 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>

Вы можете считать contextпоследним необязательным параметром forEachв JavaScript.

Tushar
источник