JavaScript setInterval not being properly bound to

2019-03-03 06:50发布

问题:

Problem

Hi people, I'm reasonably new to JavaScript and I come from the very object-oriented world of Python and Java, that's my disclaimer.

There are two chunks of code below, alternative implementations, one in JavaScript, one in Coffeescript. I am trying to run them on the server in a Meteor.js application. The problem I am experiencing is when calling the function "setInterval" using the bound-method "this.printSomething" as my callback, once that callback is executed, it loses scope with the instance resulting in "this.bar" being undefined! Can anyone explain to me why either the JavaScript or the coffescript code isn't working?

JavaScript Implementation

function Foo(bar) {
  this.bar = bar;

  this.start = function () {
    setInterval(this.printSomething, 3000);
  }

  this.printSomething = function() {
    console.log(this.bar);
  }
}

f = new Foo(5);
f.start();

Coffeescript Implementation

class foo
    constructor: (bar) ->
        @bar = bar

    start: () ->
        Meteor.setInterval(@printSomething, 3000)

    printSomething: () ->
        console.log @bar

x = new foo 0
x.start()

回答1:

You lose your context of Foo in the setInterval callback. You can use Function.bind to set the context to something like this to set the context for the callback function reference back to Foo instance.

setInterval(this.printSomething.bind(this), 3000);

With the call

setInterval(this.printSomething, 3000);

The callback method gets the global context (window in case of web or global in case of tenants like node) so you don't get property bar there since this refers to the global context.

Fiddle

or just

 this.printSomething = function() {
     console.log(bar); //you can access bar here since it is not bound to the instance of Foo
  }


回答2:

You could also try creating a closure to capture the this. Like this:

var self = this;
this.start = function () {
    setInterval(function(){
       self.printSomething();
    }, 3000);
}


回答3:

When you enter a function, you get a new scope in javascript. You can inherit from the parent scope, but the value of this changes. In coffeescript, you can use the fat arrow (which looks like it is going to be part of ecmascript 6) which basically retains a reference to this before going into the new scope.

class foo
    constructor: (bar) ->
        @bar = bar

    start: () =>
        Meteor.setInterval(@printSomething, 3000)

    printSomething: () =>
        console.log @bar

x = new foo 0
x.start()

The standard way to handle this kind of thing in javascript is to create a reference to this at the point you want to refer to, and then use the reference in your out of scope calls...

function Foo(bar) {

  // make reference to `this` at the point
  // where you want to use it from
  self = this;

  self.bar = bar;

  self.start = function () {
    setInterval(self.printSomething, 3000);
  }

  self.printSomething = function() {
    console.log(self.bar);
  }
}

f = new Foo(5);
f.start();