3

Given ES6 code like

let a = new SomeClass();
let b = new AnotherClass();

let x = a.someMethod.bind(b, 1, 2, 3);
let y = () => a.someMethod.call(b, 1, 2, 3);

is there any meaningful difference between x and y? I know that bind() is a lot older function but is there any need to use it over arrow functions nowadays?

At least for me the arrow function syntax is much easier to read than the bind() syntax, especially because you can often avoid using call() in practice when this still has the normal meaning from the lexical context. For example, will bind() have better performance (CPU or RAM) in practice?

Mikko Rantalainen
  • 14,132
  • 10
  • 74
  • 112
  • 1
    For me, I think x is clear. However, if I need to bind them dynamically, I will use y approach with some args passing in it like `y = (a,b) => a.someMethod.call(b, 1, 2, 3);` – ikhvjs Feb 03 '22 at 14:45
  • "*is there any need to use it over arrow functions nowadays?*" uh, that seems like a different question. Did you intended is such? Because I partial application is handled out of the box with `.bind()` and you can still do with another function but I'm not convinced you *have to* or that it'd be easier. No real need to reinvent the wheel. At any rate, point is that *in this case you have shown* the answer to the question is different than to the more general case. – VLAZ Feb 03 '22 at 14:51
  • 1
    @customcommander I assume `someMethod` doesn't exist on `AnotherClass`. – VLAZ Feb 03 '22 at 14:53
  • @ikhvjs Since you can do `y = (a,b) => a.someMethod.call(b, 1, 2, 3);` and it will be readable, is there any reason to not use `() => ...call()` for the simple case, too? – Mikko Rantalainen Feb 03 '22 at 14:54
  • 1
    @VLAZ You're right that the partial application would be one example to use `bind()`. Even for that case, I find `const addTwo = y => add(2, y);` easier to read than `const addTwo = add.bind(null, 2);`. – Mikko Rantalainen Feb 03 '22 at 15:04
  • @MikkoRantalainen what if there are 5 parameters that I'd like to partially fill in up to five different places? Maybe one or two in one place, maybe one or zero or another. Yes, I *could* keep making functions but it starts to obscure the meaning of *why* I'm making them. That being partial application. And yes, I know this scenario I outlined is not very typical. However, I find it's not really fair to focus on just one example and declare it fit for generalising a conclusion from. – VLAZ Feb 03 '22 at 15:07

2 Answers2

1

let a = function(){};
let b = function(){};

a.m = 1
b.m = 2
a.someMethod = function(x, y, z){ return this.m + x + y + z }

let x = a.someMethod.bind(b, 1, 2, 3);
let y = () => a.someMethod.call(b, 1, 2, 3)

console.log( x(1,2,3) )
console.log( y(1,2,3) )

function goBind() {
    for (var i = 0; i < 1000000; i++) {
        x(1,2,3)
    }
}

function goArrow() {
    for (var i = 0; i < 1000000; i++) {
        y(1,2,3)
    }

}

function race() {
  var start = performance.now();
  goBind();
  console.log('bind: ' + (performance.now() - start));
  start = performance.now();
  goArrow()
  console.log('arrow: ' + (performance.now() - start));
  start = performance.now();
  goBind();
  console.log('bind: ' + (performance.now() - start));
  start = performance.now();
  goArrow()
  console.log('arrow: ' + (performance.now() - start));
  console.log('------');
}
<button onclick="race()">RACE!</button>

Based on this: Are arrow functions faster (more performant, lighter) than ordinary standalone function declaration in v8?

adiian
  • 1,382
  • 2
  • 15
  • 32
  • Yeah, there seems to be pretty high performance difference at least in microbenchmark. Note that you don't need `b.someMethod` definition for anything here. – Mikko Rantalainen Feb 03 '22 at 15:23
  • changed from `let y = () => a.someMethod.call(b, 1, 2, 3)` to `let y = () => a.someMethod.call` then to `let y = () => a.someMethod`, which yields closer performances, but still different – adiian Feb 03 '22 at 15:29
  • 1
    I think you you cannot do that optimization in generic case because `someMethod()` could modify `this` and that would mean the properties of `b`. Doing `y = () => a.someMethod` calls correct method but it will have wrong `this`. – Mikko Rantalainen Feb 03 '22 at 15:35
  • 1
    you're right, but I think performance wise the arrow is wrapping the function while bind is invoking it directly – adiian Feb 03 '22 at 15:44
  • So to summarize It seems bind is much faster than an arrow embedding a call. Just for fun I tried to add a wrapping arrow `let x = () => a.someMethod.bind(b, 1, 2, 3); in this case we are comparing arrow+bind to arrow+call and the first seems to be much faster, so it appears the performance difference comes from bind vs call – adiian Feb 03 '22 at 15:50
  • Yes, logically arrow function syntax might create one more stack frame per call. However, I would expect that the JS engine could easily optimize that out which is why I was asking about meaningful difference. I think your answer gives a good enough reason use `bind()` in performance critical code no matter the readability. – Mikko Rantalainen Feb 03 '22 at 15:54
  • 1
    On second thought, JS engine may not be able to optimize away the lambda stack frame because there's difference in the meaning of those functions. If `a.someMethod` is modified later, the variant with `bind()` keeps using the old definition whereas the lambda method will redirect to new definition. – Mikko Rantalainen Feb 03 '22 at 15:55
1

Regardless of the performance, for some use cases, arrow function cannot represent the same logic. For example, when you use Promises you can have something like this (source for this example):

function CommentController(articles) {
    this.comments = [];

    articles.getList()
        .then(function (articles) {
            return Promise.all(articles.map(function (article) {
                return article.comments.getList();
            }));
        })
        .then(function (commentLists) {
            return commentLists.reduce(function (a, b) {
                return a.concat(b);
            });
        })
        .then(function (comments) {
            this.comments = comments;
        }.bind(this));
}

Note the difference if the last bind(this) were removed. There's no simple way to use arrow function notation to change this without modifying the code a lot. I personally prefer to use closure with variable name other than this for a code like this, though.

In addition, bind() can be used for partial application which may be easier to read for people with functional programming background.

On the other hand, if a.someMethod is modified later, the version that used bind() will not see the change because it took the reference to the function during the binding. The variant with lambda function will see current value of a.someMethod when y() is called.

Additional example about needing .bind(this):

let f  =
{
    x: "bind data",
  test: function()
  {
    console.log("1. this=", this);
    setTimeout(function() {
      console.log("2. this=", this);
    }, 0);
    setTimeout(function() {
      console.log("3. this=", this);
    }.bind(this), 0);
  }
}

f.test();
Mikko Rantalainen
  • 14,132
  • 10
  • 74
  • 112
  • I don't get the example. In which case is `function (comments) { this.comments = comments; }.bind(this)` not the same as `comments => { this.comments = comments; }`? – VLAZ Feb 03 '22 at 16:24
  • Depending on the other code, the `this` may refer to Promise or an Event. You need the `bind` if you want to make sure that the `this` refers to the `this` in `function CommentController`. – Mikko Rantalainen Feb 04 '22 at 10:11
  • In both cases - with an arrow function or with `.bind()`, the `this` will be the same. It will be exactly equal to the value of `this` when `CommentController` is executed. I don't see how `promise.then(() => this)` would ever refer to a promise. Or to anything else that's *not* the value of `this` in the surrounding scope. The surrounding scope here being `CommentController`. The only way to change `this` is to execute `CommentController` with a different value *and that would affect both types of functions*. https://jsbin.com/poresuh/edit?js,console What's an example where that is not true? – VLAZ Feb 04 '22 at 10:19
  • Note that the above example doesn't use arrow function notation on purpose. If you use arrow functions only, obviously `this` doesn't change. – Mikko Rantalainen Feb 04 '22 at 10:20
  • 1
    "*There's no simple way to use arrow function notation to change this without modifying the code a lot.*" is what you said about the example. I am unable to see how `comments => { this.comments = comments; }` is 1. not the same. 2. not simple. – VLAZ Feb 04 '22 at 10:27
  • I added an additional example. Note the difference between output for `2. this=` vs `3. this=`. Now, imagine a case where you have multiple async event handlers nested. Referring from the innermost handler all the way to the outmost `this` is hard without `bind()`. – Mikko Rantalainen Feb 04 '22 at 10:35
  • 1
    The premise was for using *arrow functions* instead. Your point was that it's not possible to replace the function with `.bind()` with a regular function easily. Yet `setTimeout(() => { console.log("2. this=", this); }, 0);` is the equivalent. Yes, I agree that doing something different entirely, will have a entirely different result. But that's neither here nor there. A regular function working differently without `.bind()` does not explain to me why you cannot easily use arrow functions in this instance. Again, which was the quote I showed. – VLAZ Feb 04 '22 at 10:42
  • The point I'm trying to make about `bind()` at the end of this answer is that you cannot replace the `bind()` usage here with arrow notation just for that part. You're correct that without nesting different callbacks, you can use the arrow function notation for the single callback in the example. However, when you have multiple nested functions, you'd need to change *every one of those* to arrow notation to get correct `this` down the stack. However, if any of those actually needs real function `this`, you cannot use arrow function notation for every part. – Mikko Rantalainen Feb 04 '22 at 12:47