【问题标题】:thisArg in Array Methods数组方法中的 thisArg
【发布时间】:2020-06-20 08:26:04
【问题描述】:
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);

...没有按预期工作。

我的理解,

在这里,我们关心以{age:16} 作为参数调用army.canJoin 函数。

当执行到canJoin(user)时, 它检查 this.minAge ,现在我已经了解了 this is the method called before the dot 的值 IE。 army.Canjoin(Canjoin 被称为军队)因此它应该以军队为这个。

不过好像没有军队这样的

为什么?

【问题讨论】:

    标签: javascript filter this


    【解决方案1】:

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

    【讨论】:

    • 你能否详细说明一下,绑定上下文是什么意思。我是Js新手,刚开始学习
    • 就像this 变量(不完全) 的值在运行时绑定。更多信息请查看here
    【解决方案2】:

    因为没有thisArg 的Array#filter 看起来像这样:

    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 中定义

    【讨论】:

      【解决方案3】:

      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 在每次拨打canJoin 时为您做同样的bind 工作。

      【讨论】:

      • 我无法理解您答案中的“......而不是方法......”部分。可能是您对某些事情有误 - 我邀请您阅读我的答案的“更新”部分并发表评论。
      【解决方案4】:

      你买了一把勺子,你的邻居也买了同一个勺子。你 5 岁的孩子从抽屉里拿出你的勺子,走出家门,问一个外星人“那是爸爸的勺子吗?” - 外星人耸了耸肩。下一个场景,外星人在你家,你5岁的孩子打开抽屉,又问; “是的”外星人说。

      勺子上没有你的姓名首字母。它不知道它是你的勺子。它不能也不能。好吧,直到你在上面写上你的姓名首字母,然后,它就不再是“你邻居买的同一把勺子”了。

      你的函数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 不会修改('put initials on')canJoin 函数,kindergarten.canJoin=canJoin 也不会 - 就像将某物放入抽屉中不会在其上添加首字母一样。在institution.canJoin(...) 中,我们知道this 是institution - 函数调用发生在这里。另一方面,如果括号不存在,则不会发生函数调用。 勺子从抽屉里出来了。

      正如在其他答案中指出的那样,您可以使用 bind 来“将您的姓名首字母放在勺子上”。就个人而言,我从不使用bind。我支持将整个this JS 装置(this = 'the you-know-what-I-mean-object'(除非你不知道))视为语。我更喜欢构造闭包,如下所述(以下代码将使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 类,不会 使该错误消失,并且 不会(如您所见)以您可能认为的方式绑定。如果你使用这样的 ES6 类,你仍然需要第二个参数来过滤函数。

      【讨论】:

        猜你喜欢
        • 2014-06-22
        • 2011-11-06
        • 1970-01-01
        • 2021-12-18
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多