4

Let's say I have the following code:

class Test {
  constructor(obj) {
    this.obj = obj
  }

  change() {
    Object.keys(this.obj).forEach(function(name, index) {
      alert(this.obj[name])
    })
  }

}

objct = {
  n1: 1,
  n2: 2
}

var test = new Test(objct)
test.change()

However, when I run this, I get the following error:

Uncaught TypeError: Cannot read property 'obj' of undefined

I believe it means that this is undefined inside the object keys function. How can I access this.obj inside the Object keys and forloop?

As per this answer, I can use map, but I need the property name and the array index inside the forloop as well. And here's a fiddle of the code, if it may help you. Thank you!!!

adiga
  • 34,372
  • 9
  • 61
  • 83
toing_toing
  • 2,334
  • 1
  • 37
  • 79

4 Answers4

6

That's because the function-context (the this) of the function you're passing into forEach() is now window and it's no longer your Test instance.

If you'll use an arrow-function instead, you'll be able to preserve the lexical-scope and your this would point to the current (Test) instance:

Object.keys(this.obj).forEach((name, index) =>
{
    alert(this.obj[name])
});

See MDN

haim770
  • 48,394
  • 7
  • 105
  • 133
5

this refers to the current context of the function and hence in your example this.obj looks for obj in the context of forEach callback. You can solve this by keeping a reference to current context i.e. this OR use an arrow function instead.

OLD WAY: In this solution, we keep a reference to the current context in variable self

class Test {
  constructor(obj){
    this.obj = obj
  }

  change() {
    var self = this
    Object.keys(this.obj).forEach(function (name, index) {
    alert(self.obj[name])
  })
  }

}

objct = {
  n1: 1,
  n2: 2
}

var test = new Test(objct)
test.change()

Preferred or Shorthand Solution: By using Arrow Functions

class Test {
  constructor(obj){
    this.obj = obj
  }

  change() {
  Object.keys(this.obj).forEach((name, index) => {
    alert(this.obj[name])
  })
  }

}

objct = {
        n1: 1,
      n2:2
}

var test = new Test(objct)
test.change()
Suresh Prajapati
  • 3,991
  • 5
  • 26
  • 38
  • 1
    thank you for the answer, it was interesting to know about self, having not used it before. JS is changing fast :) – toing_toing Mar 17 '19 at 20:28
4

A classic scope lesson in Javascript.

There are two ways to do this:

Using bind() which binds the scope of your forEach to the parent function

change() {
  Object.keys(this.obj).forEach(function(name, index) {
    alert(this.obj[name])
  }.bind(this))
  }

using => which is another way of binding the scope of your forEach to the parent function

change() {
  Object.keys(this.obj).forEach((name, index) => {
    alert(this.obj[name])
  })
  }
PsyGik
  • 3,535
  • 1
  • 27
  • 44
  • thank you for the answer. bind looks like a workaround when working with older browser compatibility :) (yes, i dont like babel) – toing_toing Mar 17 '19 at 20:30
1

The anonymous functions are not bound to the context, so they doesn't have access to this; But the arrow functions are. So use that instead.

class Test {
  constructor(obj) {
    this.obj = obj
  }

  change() {
    Object.keys(this.obj).forEach((name, index) => {
      alert(this.obj[name])
    })
  }

}

objct = {
  n1: 1,
  n2: 2
}

var test = new Test(objct)
test.change()
Amir
  • 1,885
  • 3
  • 19
  • 36