1

First question on SO and I hope I’m not duplicating anything; I’ve looked at other questions and think mine is different enough to warrant asking.

Basically, is there a way to have the this that’s in the method body of a method written using the shorthand notation to be either lexical or bound to a specific value?

The motivation for this comes from wanting to use the ES6 method shorthand when I was implementing the iterator protocol, in which the @@iterator method, when called, returns an iterator object. That iterator object has to have a next() method, which when called itself returns another object. It’s in this 2nd object that I want a reference to the original (in my case, Graph) instance.

Note that in the below code snippet, which does not work as intended due to the this binding, next() is using the ES6 method definition shorthand.

class Graph {
  constructor(initialNodes) {
    this.data = [...initialNodes];
  }

  [Symbol.iterator]() {
    let nextIndex = 0;
    // `this` binding is fine here, as expected
    return {
      // look at that beautiful shorthand
      next() {
        return nextIndex < this.data.length
          // `this` binding is not fine here
          ? { done: false, value: this.data[nextIndex++] }
          : { done: true };
      }
    };
  }
}

I’m aware of defining the function above the object return and then assigning that function as a property of the object being returned, but again I was wondering if a way with the method shorthand syntax was possible.

Defining next() above the return object as a regular function then binding it in the object property assignment:

class Graph {
  // constructor

  [Symbol.iterator]() {
    let nextIndex = 0;
    // I know you can do this
    const next = function() {
      return nextIndex < this.data.length
        ? { done: false, value: this.data[nextIndex++] }
        : { done: true };
    };
    return {
      next: next.bind(this)
    };
  }
}

Defining next() above the return object as an arrow function means no need for bind, but still no method syntax shorthand:

class Graph {
  // constructor

  [Symbol.iterator]() {
    let nextIndex = 0;
    // I know you can also do this
    const next = () => {
      return nextIndex < this.data.length
        ? { done: false, value: this.data[nextIndex++] }
        : { done: true };
    };
    return {
      next
    };
  }
}

Icepickle brought up a good point in the comments that I could just use store the this context and refer to that in the shorthand method body. I’m assuming this is the closest I can get?

class Graph {
  // constructor

  [Symbol.iterator]() {
    let nextIndex = 0;
    const self = this;
    return {
      next() {
        return nextIndex < self.data.length
          ? { done: false, value: self.data[nextIndex++] }
          : { done: true };
      }
    };
  }
}
Community
  • 1
  • 1
abigsmall
  • 67
  • 6
  • `next: () =>` isn't that much longer than `next()`. I'd just go with that. – Ry- Feb 19 '17 at 07:11
  • 3
    The whole point of the arrow syntax is that it inherits the lexical `this`, not a `this` from how it's called. People seem to want to only use the arrow syntax now and somehow make it do everything that the original syntax does. That's just not how it is designed. If you're not going to use the lexical `this`, then you probably shouldn't use the arrow syntax. Use the longer syntax and then you can `.bind()` to your hearts content. – jfriend00 Feb 19 '17 at 07:21
  • @jfriend00: They're asking for the opposite, I think: lexical `this`, but still with the shorthand method notation. – Ry- Feb 19 '17 at 07:34
  • @Ryan - Very confusing question then - just look at the title. Arrow syntax should usually NOT be used for method definitions because you WANT `this` to come from how it is called `obj.method()`. I guess I don't follow what they're asking for if it's not what the title of the question says. I give up here. – jfriend00 Feb 19 '17 at 07:38
  • Sorry my question’s confusing, @jfriend00, I didn’t intend for it to be so. @Ryan has it right, in that I want to keep the method shorthand but have lexical `this`. I’ll try to edit my question to reflect this. In case you don’t fully understand where I’m coming from, the method in question for my code above is of the iterator object: when `iterator.next()` is called, I want `this` inside `next()` to refer to the `Graph` instance. Perhaps that’s subpar code, though I pretty much cribbed it from [MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols). – abigsmall Feb 19 '17 at 07:51
  • So, the whole intention of your question is that you want to keep the shorthand because you find it nicer? If that would be the case, why not define a variable in the scope that gets a reference to this, and then refer from your next method to this new variable? Also, if you like shorthand functions, you can also think that there is no need to write nextIndex twice when the property name and the variableName are the same ;) – Icepickle Feb 19 '17 at 08:03
  • The intention of my question is to find out if it’s possible, in case I didn’t know about something in ES6. The impetus for that question is that, yes, my preference for the shorthand. Oh yeah, I did consider the `const self = this` option when I was writing; for some reason I totally forgot about that when writing the question! And on that note, that `nextIndex: nextIndex` wasn’t meant to be included ><. ’Twas leftover from when my code wasn’t working and I wasn’t thinking too hard about how the code looks, just trying to debug it. I’ll remove it right now :) – abigsmall Feb 19 '17 at 08:12
  • The shortest would be to replace `let nextIndex = 0;` with `let it = this.data[Symbol.iterator]();`, and work with that. Of course, if in the example you push that idea to the limit, you end up with just `[Symbol.iterator]() { return this.data[Symbol.iterator]() }`. I understand your real case would not be that reducible. – trincot Feb 19 '17 at 08:40
  • @abigsmall: I'm really pretty sure `next: () =>` is as small as it gets without restructuring, though. – Ry- Feb 19 '17 at 10:49

1 Answers1

1

The ideal solution to your problem would be to use the proper tool for the job: a generator function. They too can be used with a shorthand syntax, and they will properly bind this.

Generator functions return objects which conform to the iterator protocol, and can use yield* to delegate the output of the iterator to another iterable object (like an array).

class Graph {
  constructor(initialNodes) {
    this.data = [...initialNodes];
  }

  * [Symbol.iterator]() {
    yield* this.data;
  }
}

const graph = new Graph([1, 2, 3]);
for (const node of graph) {
  console.log(node);
}
4castle
  • 32,613
  • 11
  • 69
  • 106
  • Wow, thanks! I hadn’t considered generators because I hadn’t fully grokked them, but I suppose they’re not too difficult to understand. This is pretty cool, especially with the shorthand. I noticed in the MDN shorthand generator method link that there’s a space between the `*` and the name of the generator, whereas in your code snippet there is not. I looked up airbnb’s style guide for their thoughts on this and saw that they in fact don’t even recommend the use of either iterators or generators. Any thoughts on that? https://github.com/airbnb/javascript#iterators-and-generators – abigsmall Feb 20 '17 at 06:59
  • @abigsmall The reason they don't like iterators is because they encourage using non-functional syntax. You can use generators in a functional way if you create some small helper functions. I personally find the generator syntax super powerful, and converting to an array is as easy as `[...graph]`. I'd didn't know there was a standard style for the shorthand notation, thanks! – 4castle Feb 20 '17 at 14:48
  • Thanks for the answer, @4castle :) – abigsmall Feb 27 '17 at 02:44