1

Inspired by this article, I've been refactoring some old code.

However, I'm running into problems with passing arguments using Array.prototype.filter since the second parameter of Array.prototype.filter(callback, thisArg) binds the this-object in the callback but arrow functions don’t bind this.

In my example, I’m getting the keys from an associative array (yeah I know, technically not available in JavaScript) by using Object.keys(), then filtering that array by a property of their object in the associative array this[item].property, but that fails since this binding isn’t available.

So, embracing arrow functions, how does one pass parameters to the callback in filter()?

const arr = {
    a: {
      property: true,
      otherProp: false
    },
    b: {
      property: true,
      otherProp: false
    },
  },
  hasProperty = item => this[item].property,
  getMatchingKeys = object => Object.keys(object).filter(hasProperty, object);
getMatchingKeys(arr);
Edric
  • 18,215
  • 11
  • 68
  • 81
Hessius
  • 1,394
  • 1
  • 11
  • 30
  • 1
    arrow function dont bind to this on there own https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions – TimCodes Aug 31 '17 at 21:36
  • 2
    Why does it need to involve `this` in the first place? `.filter(v => arr[v].property)` extremely readable and short already. – loganfsmyth Aug 31 '17 at 21:41
  • Are `hasProperty` and `getMatchingKeys` really supposed to be inside the `arr` object literal? – Barmar Aug 31 '17 at 22:20
  • Don't use an arrow function if you need `this` to be the method context. – Barmar Aug 31 '17 at 22:21
  • 1
    "*So, embracing arrow functions*" - that's clearly the wrong attitude. One should only use arrows where they are useful, and one should not try to use them where they don't work. – Bergi Aug 31 '17 at 22:53
  • @Bergi Hmm I'm not sure you've grasped the question. The task at hand is not finding out whether or not arrow functions should be used but plainly a coding exercise, can they be used in this fashion? Similarly I don't agree with the tagging of this question as a dupe, the question clearly states an understanding that this-binding isn't available (thus knowing they're not interchangeable) and it's not asking if it this is recommended practice but rather, if one were to adhere to this style of coding, how would one do it – Hessius Sep 01 '17 at 04:52
  • @Bergi the solution to this proved to be currying a function, see solution here https://medium.com/@joelthoms/okay-i-see-what-you-are-trying-to-do-you-are-very-close-b09619da9184?source=linkShare-160f62d1b3c4-1504246051 I strongly disagree with closing this issue without providing a solution – Hessius Sep 01 '17 at 06:08
  • @Hessius I think loganfsmyth already provided a good solution. Of course you can always use higher order functions to abstract out a value. – Bergi Sep 01 '17 at 10:16
  • @bergi Obviously several solutions applicable, none of those provided by the questions you provided as duplicates though? – Hessius Sep 01 '17 at 10:36
  • @Hessius Maybe you're right, they're the wrong targets. (Originally I even pointed to [Methods in ES6 objects: using arrow functions](https://stackoverflow.com/questions/31095710/methods-in-es6-objects-using-arrow-functions) as I thought the arrow function was part of the object literal). How about [pass more parameters into callback](https://stackoverflow.com/q/939032/1048572) or [How do I pass an extra parameter to the callback function in Javascript .filter() method?](https://stackoverflow.com/q/7759237/1048572) instead? – Bergi Sep 01 '17 at 10:42
  • @Bergi Yes! Those are actually helpful. Posted an answer with the solution I went with along with your links (to help visibility, as most people don't bother with long comment threads). Thanks – Hessius Sep 01 '17 at 10:57

2 Answers2

1

You can use Object.entries. It provides both the key and the value, so that you don't need the reference to the object itself:

const arr = {
        a: {
            property: true,
            otherProp: false
        },
        b: {
            property: true,
            otherProp: false
        },
        c: {
            property: false, // to be excluded
            otherProp: true
        },
    },
    hasProperty = ([key, value]) => value.property,
    first = ([key]) => key,
    getMatchingKeys = object => Object.entries(object).filter(hasProperty).map(first);

console.log(getMatchingKeys(arr));
.as-console-wrapper { max-height: 100% !important; top: 0; }

You could also use bind -- not to bind this, but a first argument:

const arr = {
        a: {
            property: true,
            otherProp: false
        },
        b: {
            property: true,
            otherProp: false
        },
        c: {
            property: false, // to be excluded
            otherProp: true
        },
    },
    hasProperty = (object, key) => object[key].property,
    getMatchingKeys = object => Object.keys(object).filter(hasProperty.bind(null, arr));

console.log(getMatchingKeys(arr));
.as-console-wrapper { max-height: 100% !important; top: 0; }

See also some other options in my answer to another question.

trincot
  • 211,288
  • 25
  • 175
  • 211
  • I couldn’t get this to work? For me it produces the same array as using keys? – Hessius Sep 01 '17 at 06:02
  • It does not produce the same for me. I added a `c` property which has `property: false`: the output will only have `a` and `b`, because only those have `property: true`. Is this not what you wanted as output? Can you then edit your question and specify what the desired output is? – trincot Sep 01 '17 at 08:25
  • Thank you, this updated snippet does indeed produce a different result, the filtering is correct but the returned array unfortunately is not, this returns an array of the objects, without the keys whereas the wanted result is an array of the keys. – Hessius Sep 01 '17 at 10:49
  • I adapted the answer to that requirement. – trincot Sep 01 '17 at 11:33
0

The author of the article provided an answer in the comments, provided here for reference:

const arr = {
  a: {
    property: true,
    otherProp: false,
  },
  b: {
    property: true,
    otherProp: false,
  },
}
const hasProperty = object => item => object[item].property
const getMatchingKeys = object => Object.keys(object).filter(hasProperty(arr))
getMatchingKeys(arr) // = ['a', 'b']

Further reading, provided by @bergi in comments of original post (buried deep, posted here for greater visibility):

  1. jQuery pass more parameters into callback
  2. How do I pass an extra parameter to the callback function in Javascript .filter() method?
Hessius
  • 1,394
  • 1
  • 11
  • 30