简体   繁体   中英

Javascript function of object as argument

Let's say we have an object defined as

var object = {counter: 0; increment: function() { this.counter++; } };

Now, calling the increment function of the object will increment the counter as expected.

object.increment(); //object.counter will now equal to 1

What bugs me is that I would have a function that calls an argument function instead, the counter will not be incremented since the this variable is pointing to the closure of the calling function instead.

function call(func) { func(); }; call(object.increment); //object.counter still 1

So my questions is if there is any way of making the caller function to keep the this variable untouched, so that the desired effect of object.counter to be incremented when called?

EDIT: Changed accepted answer because of deeper explanation.

One way to think about it is that the this keyword in JavaScript is not the reference to the object where it is defined , but a reference to the current invoker – the object that is doing the invoking. Consider your example:

var counter = {counter: 0; increment: function() { this.counter++; } };

If we were to construct another object that has a similar form, we can "borrow" the increment function:

var startAt100 = {counter:100, increment: counter.increment }
startAt100.increment()
// startAt100.counter == 101

Here, because the startAt100 object is doing the invoking, then inside the function this refers to startAt100 .

One way to get around this is to use the bind() function. The bind() function takes a function and returns a version of that function where this always points at the same invoker.

Note: bind() also has the ability to perform partial applications of function parameters, which can also be useful.

So, we can create a bound function of increment by doing:

var boundIncrement = counter.increment.bind(counter);

Now, when we invoke boundIncrement() – no matter whom the actual invoker is – this inside the function will point at the counter object.

boundIncrement() // counter.counter == 1
call(boundIncrement) // counter.counter == 2

Understanding bind()

To understand bind() in a little more detail, we can implement a bind function purely in javascript. It would look something like this:

function bind(fn, invoker) {
  return function() {
    return fn.apply(invoker, arguments);
  }
}

Binding returns a function that will invoke the supplied fn in a specific context, and always in that context.

Note: once a function has been bound to a context/invoker, you cannot "undo" that binding. Using .call() or .apply() on a bound function will not change this .

Another approach: Object construction

Another approach would be to change the way that you are constructing your counter object. We can use an anonymous function to create a stable scope, then create a closure that references a variable in that scope. By doing this, rather than using this , you can create a function that has a stable environment to execute in. Consider the following code:

var counter = (function() {
  var state = { counter: 0 }
  state.increment = function() { state.counter += 1; };
  return state;
})();

counter.increment() // counter.counter == 1
call(counter.increment); // counter.counter == 2

Now when you invoke counter.increment() , because state has been when the function was created, will always point at, and mutate, that specific object.

Not that this is better or worse, just a different way to understand the same underlying concept of how functions work in JavaScript.

You can use .bind() :

  call(object.increment.bind(object));

If you have to support old runtime environments that don't have .bind() you can just wrap it in a function:

  call(function() { object.increment(); });

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM