it-roy-ru.com

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

Я новичок в underscore.js. Какова цель [context] в _.each()? Как это должно быть использовано?

161
ram

Параметр 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.

219
user113716

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

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

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

// 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() при передаче примитивов. В противном случае они преобразуются в объекты, которые обычно все еще работают, но быстрее и безопаснее быть привязанными к типу.

6
dandavis

Как объяснено в других ответах, 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.

4
Tushar

Простое использование _.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 внутри вашего обратного вызова, или вы также можете использовать bind прямо так:

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

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

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

3
Pavel