Привязка области видимости к элементу в каждом цикле

#javascript #node.js #typescript #ecmascript-6

#javascript #node.js #typescript #ecmascript-6

Вопрос:

Мне чрезвычайно сложно объяснить это в названии, поэтому я просто приведу пример, чтобы лучше объяснить:

Я хочу передать такую функцию, как ()=>{console.log(this.operatorValue)} , в качестве параметра рекурсивной traverseCriteria функции.

Функция обхода критерия

   function traverseCriteria(arr, parameters,fn) {
        for (const item of arr) {
          if (Array.isArray(item))
          this.traverseCriteria(item, parameters)
          else if (typeof item === 'object'){
              item.operatorValue = parameters[item.Property]
             console.log(item.operatorValue)  ///<-- REPLACE THIS WITH fn()
            //fn()
          }
        }
        return arr
      }
  

В приведенной выше функции я хочу иметь возможность заменить console.log(item.operatorValue) на fn();

     const criteria =  [
            "and",
            {
              "Collection": "persons",
              "Property": "phone",
              "operator": "eq",
              "operatorValue": "23138213"
            },
            {
              "Collection": "persondetails",
              "Property": "country",
              "operator": "eq",
              "operatorValue": "Russia"
            }
          ]
    const parameters = { phone: "23138213", "country": "Russia" };
  

вызов функции:

   traverseCriteria(criteria,parameters,()=>{console.log(this.operatorValue)}); 
  

Ответ №1:

Вы можете использовать .call или .apply для установки this значения функции:

 fn.call(item)
  

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

Комментарии:

1. Я думаю, что это отвечает на мой вопрос. Однако, из любопытства, что, если бы я также хотел получить доступ к другому параметру внутри функции, например, скажем … к самому массиву. Или может быть переменной, которая была объявлена непосредственно перед for (const item of arr)

2. Вы можете передать любое количество аргументов в функцию, например, fn.call(item, arr) установить this значение item и передать arr в качестве первого аргумента функции. Однако в этом случае, возможно, было бы лучше забыть об этом this и использовать более распространенный «интерфейс», например, тот, который используется forEach методом массива (где обратный вызов передается текущему элементу, индексу элемента и массиву в качестве аргументов).

3. Большое вам спасибо, ваш комментарий был действительно полезен.

Ответ №2:

Значение this внутри функции со стрелкой остается неизменным на протяжении всего жизненного цикла функции и всегда привязано к значению this в ближайшей родительской функции без стрелки.

Функция Arrow не может использоваться везде, не в этом случае, передача обычной функции будет работать!.

Отличная статья: https://medium.com/better-programming/difference-between-regular-functions-and-arrow-functions-f65639aba256#:~:text=Unlike regular functions, arrow functions,closest non-arrow parent function .

Ответ №3:

Вы просто передаете item.operatorValue в качестве аргумента своей функции

   function traverseCriteria(arr, parameters, fn) {
        for (const item of arr) {
          if (Array.isArray(item))
          this.traverseCriteria(item, parameters)
          else if (typeof item === 'object'){
              item.operatorValue = parameters[item.Property]
            fn(item)
          }
        }
        return arr
      }
  

а затем вызовите его

 traverseCriteria(criteria, parameters,(item) => { console.log(item.opValue) }); 
  

Комментарии:

1. Да, но дело в том, что я не всегда могу захотеть выполнять действие с operatorValue я могу захотеть что-то сделать с operatorValue и каким-то другим свойством в другой раз. Я не хочу жестко кодировать имя свойства внутри traverseCriteria

2. @SamuraiJack: Вы можете просто передать все item в качестве аргумента для обратного вызова.

3. @SamuraiJack похоже, что вы выравниваете массив с помощью рекурсивной функции. Возможно, вы захотите просто использовать Array.prototype.flat , чтобы удалить рекурсию и упростить свой код

Ответ №4:

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

Таким образом, объем traverseCriteria остается небольшим, и можно сосредоточиться на реальной задаче обработки. Реализация последнего затем может быть более легко реорганизована, например, для целей OP.

Поскольку было не совсем ясно, каковы эти цели на самом деле, подход предоставляет два решения для того, что, возможно, требовалось. Во-первых, этот дополнительно переданный обработчик можно понимать как метод, который работает с элементом критериев. В этом случае к этому обработчику просто применяется определенный элемент критериев через call . Во-вторых, и это то, что я бы предложил, следует рассматривать этот обработчик как наиболее общую форму обратного вызова для конкретного элемента. Таким образом, один из них, подобный моментальному снимку, передает ему все соответствующие данные. Как реализовать такой обработчик обратного вызова, тогда остается на усмотрение того, кто использует traverseCriteria метод.

 function processContextualItem(item, idx, arr) {
  const { target, params, itemHandler, genericCallback } = this;
  if (Array.isArray(item)) {

    target.traverseCriteria(item, params, itemHandler, genericCallback);

  } else if (item amp;amp; (typeof item === 'object')) {

    item.operatorValue = params[item.Property];

    // I want to be able to replace
    // console.log(item.operatorValue)
    // with fn();

    // e.g. *item specific* with bound item reference.
    itemHandler.call(item/*, idx, arr, .., ...*/);

    // e.g. *most generic* for any kind of "callback".
    genericCallback(item, idx, arr, params, target);
  }
}


function traverseCriteria(arr, params, itemHandler, genericCallback) {
  arr.forEach(processContextualItem, {
    target: this,
    params,
    itemHandler,
    genericCallback
  });
  return arr;
}
const testType = {
  foo: "foo",
  bar: "bar",
  traverseCriteria
};


function itemMethod() {
  console.log('n this.operatorValue :', this.operatorValue);
}
function genericCallback(item, idx, arr, params, target) {
  console.log('n item.operatorValue :', item.operatorValue);
  console.log('n idx, arr, params, target:', idx, arr, params, target);
}
const params = { phone: "23138213", "country": "Russia" };

const criteria =  [
  "foo", {
    "Collection": "persons",
    "Property": "phone",
    "operator": "eq",
    "operatorValue": "23138213"
  }, {
    "Collection": "persondetails",
    "Property": "country",
    "operator": "eq",
    "operatorValue": "Russia"
  }, [
    "bar", {
      "Collection": "persons",
      "Property": "phone",
      "operator": "eq",
      "operatorValue": "23138213"
    },
    "baz"
  ]
];


testType.traverseCriteria(criteria, params, itemMethod, genericCallback);  
 .as-console-wrapper { min-height: 100%!important; top: 0; }