thisArg в методах массива - PullRequest
1 голос
/ 20 июня 2020
let army = {
  minAge: 18,
  maxAge: 27,
  canJoin(user) {
    return user.age >= this.minAge && user.age < this.maxAge;
  }
};

let users = [
  {age: 16},
  {age: 20},
  {age: 23},
  {age: 30}
];

let soldiers = users.filter(army.canJoin, army);

alert(soldiers.length); // 2
alert(soldiers[0].age); // 20
alert(soldiers[1].age); // 23

Приведенный выше код работает нормально. Но это:

let soldiers = users.filter(army.canJoin);

... работает не так, как задумано.

Насколько я понимаю,

Здесь мы заботимся о вызове функции army.canJoin с {age:16} как a аргумент.

Когда выполнение достигает canJoin(user), он проверяет this.minAge. Теперь, когда я узнал значение , это метод, вызываемый перед точкой ie . army.Canjoin (Canjoin был вызван с армией) Следовательно, он должен взять армию как это.

Но похоже, что он не берет армию, как это

Почему?

Ответы [ 4 ]

0 голосов
/ 08 июля 2020

Вы покупаете ложку, и ваш сосед покупает такую ​​же ложку. Ваш пятилетний ребенок достает вашу ложку из вашего ящика, выходит из дома и спрашивает инопланетянина: «Это папина ложка?» - пожимает плечами инопланетянин. Следующая сцена, инопланетянин в вашем доме, ваш пятилетний ребенок открывает ящик и снова спрашивает; «да» говорит инопланетянин.

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

Ваша функция canJoin в том, что ложка, у вашего соседа детский сад, а инопланетянин - это JS время выполнения.

let kindergarten = {
    minAge: 2,
    maxAge: 6
};

function canJoin(user) {
    return user.age >= this.minAge && user.age < this.maxAge;
}

army.canJoin = canJoin; kindergarten.canJoin = canJoin;

Вы говорите в своем вопросе «canJoin был вызван с армией» - это ошибка.

Если вы сделали army.canJoin({user: 9999}), то canJoin вызвали с армией - ложка у вас в ящике. Если вы сделали kindergarten.canJoin({user: 9999}), то canJoin звонили с детского сада - ложка лежит в ящике вашего соседа. И если вы вызовете какую-то функцию, например Array.filter, и передадите в качестве аргумента либо army.canJoin, либо kindergarten.canJoin, либо просто canJoin, то ложка окажется вне ящика. Иначе и быть не могло. Наберите kindergarten.canJoin === army.canJoin в консоль. Получается true. Если вы хотите, чтобы инопланетяне не разводили руками, вы просите невозможного. Подумай об этом. иначе и быть не могло - информации нет.

army.canJoin=canJoin не изменяет (' ставит инициалы на ') canJoin не работает и kindergarten.canJoin=canJoin - точно так же, как кладя что-то в ящик, на нем не ставятся инициалы. В institution.canJoin(...) мы знаем, что this равно institution - здесь происходит вызов функции. Если, с другой стороны, скобки нет, вызов функции не выполняется. ложка извлечена из ящика .

Как указано в других ответах, вы можете использовать bind, чтобы «нанести свои инициалы на ложку». Лично я никогда не использую bind. Я сторонник того лагеря, который рассматривает все this хитроумное изобретение JS (this = «сам-знаешь-что-я-имею в виду-объект» (кроме случаев, когда вы этого не сделаете)) как слабое место. языка. Я предпочитаю конструировать закрытие, как описано ниже (следующий код заставит Array.filter работать без второго аргумента)

function createAgeFilter(min,max){
    return function(item){
        return item.age>=min && item.age<max;
    };
}

army.canJoin = createAgeFilter(army.minAge, army.maxAge);
kindergarten.canJoin = createAgeFilter(kindergarten.minAge, kindergarten.maxAge);

С помощью приведенного выше, так сказать, вы делаете ложки с инициалами на их с самого начала, вместо того, чтобы надевать их позже, с помощью bind.

Вероятно, вы можете подтвердить, что новички считают, что намного легче читать по сравнению с чем-либо с использованием bind.

ОБНОВЛЕНИЕ

Использование классов ES6 не не меняет характер проблемы .

class Army {
  constructor(t){
    this.tankCount=t;this.minAge=18;this.maxAge=27
  }
  canJoin(user){
    return user.age >= this.minAge && user.age < this.maxAge;
  }
}
class Kindergarten {
  constructor(t){
    this.toyCount=t;this.minAge=2;this.maxAge=6
  }
}
Kindergarten.prototype.canJoin = Army.prototype.canJoin;

var myArmy = new Army(5);
var myKG = new Kindergarten(5);

var cast=[{name:'Susie',age:3},{name:'Agamemnon',age:5},{name:'Salome',age:18},{name:'Herod',age:40}]

function getName(u){ return u.name; }

var e1 = document.getElementById('one');
var e2 = document.getElementById('two');
var e3 = document.getElementById('three');
var e4 = document.getElementById('four');

e1.textContent = cast.filter(Army.prototype.canJoin, myArmy).map(getName).join(',');
e2.textContent = cast.filter(Army.prototype.canJoin, myKG).map(getName).join(',');
e3.textContent = cast.filter(Kindergarten.prototype.canJoin, myArmy).map(getName).join(',');
e4.textContent = cast.filter(Kindergarten.prototype.canJoin, myKG).map(getName).join(',');
<div id='one'></div>
<br>
<div id='two'></div>
<br>
<div id='three'></div>
<br>
<div id='four'></div>
Salome = {name:'Salome',age:18};
myArmy.canJoin(Salome);  // true
myKG.canJoin(Solame); // false
f = myArmy.canJoin;
f(Salome);             // error

обратите внимание на эту ошибку. f равно по-прежнему не привязан к классу Army! Просто потому, что вы использовали классы ES6, не устраняет эту ошибку go, а не связывает (как вы видите) так, как вы могли подумать. И вам все равно нужен второй аргумент для функции фильтрации, если вы используете такие классы ES6.

0 голосов
/ 20 июня 2020

Поскольку Array#filter без thisArg будет выглядеть так:

function filter(cb){
  const output = []
  for(let i = 0; i < this.length; i++)
    if(cb(this[i], i, this))
      output.push(this[i])
  return output
}

Здесь army.canJoin становится cb, и там, где вызывается функция, нет контекста: cb( /* ... */).

Здесь появляется thisArg. Array#filter был разработан для применения переданного ему значения this:

function filter(cb, thisArg){
  const output = []
  for(let i = 0; i < this.length; i++)
    if(cb.call(thisArg, this[i], i, this))
      output.push(this[i])
  return output
}

В этом случае cb - это call -ed, что позволяет установить пользовательское значение this, Array#filter применяется thisArg, и он начинает работать.

Примечание! Array#filter - это собственный метод, поэтому на самом деле его код не определен в JS -land

0 голосов
/ 20 июня 2020

TLTR: даже если вы вызываете army.canJoin() с точкой, это не вызов метода, потому что canJoin был определен как обычная функция (внутри объекта), а не как метод (как определено как часть класса ). Поэтому при вызове она рассматривается как простая функция, без какой-либо привязки к army, несмотря на точечную нотацию. Внутри canJoin, this по умолчанию относится к глобальному объекту и никак иначе.

Чтобы указать вызову функции, что this вы хотите, чтобы он видел, вы используете стандарт bind метод:

const canJoinUser = army.canJoin.bind(army);
canJoinUser(user)   // should give expected result for this user

Когда вы передаете thisArg в filter вот так:

 let soldiers = users.filter(army.canJoin, army);   

вы фактически просите filter сделать то же самое bind задание за вас на каждый звонок canJoin.

0 голосов
/ 20 июня 2020

filter() по умолчанию связывает контекст с элементом массива. Вы можете изменить это поведение следующими способами:

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this

let army = {
  minAge: 18,
  maxAge: 27,
  canJoin(user) {
    return user.age >= this.minAge && user.age < this.maxAge;
  }
};

let users = [
  {age: 16},
  {age: 20},
  {age: 23},
  {age: 30}
];

let soldiers1 = users.filter(army.canJoin, army);
let soldiers2 = users.filter(army.canJoin.bind(army));
let soldiers3 = users.filter(user => army.canJoin(user));

console.log(soldiers1); 
console.log(soldiers2);
console.log(soldiers3);
...