1

I want to create some object members based on a constructor argument, but it's not working as expected. No members are created. How can I achieve this?

function Foo(parameters = []) {
    parameters.forEach(function(e) {
        this[e.name] = e.initial;
    });
};

const p = [{
    "name": "sensitivity",
    "initial": 50
}];

const f = new Foo(p);
Mihai Alexandru-Ionut
  • 41,021
  • 10
  • 77
  • 103
ziggystar
  • 26,526
  • 9
  • 63
  • 117

3 Answers3

4

You are using this keyword within the callback function provided by forEach method, but the this of that function is just a reference to the window object.

You can read more about the scope of this in javascript in this answer.

1. Use bind method.

The bind() method creates a new function that, when called, has its this keyword set to the provided value, with a given sequence of arguments preceding any provided when the new function is called.

function Foo(parameters = []) {
    parameters.forEach(function(e) {
        this[e.name] = e.initial;
    }.bind(this));
};

const p = [{
    "name": "sensitivity",
    "initial": 50
}];

const f = new Foo(p);
console.log(f);

2. Use arrow functions.

Until arrow functions, every new function defined its own this value.

For instance, this can be a new object in the case of a constructor.

function Person(age){
  this.age=age;
  console.log(this);
}
let person=new Person(22);

Or this can points to the base object if the function created can be accessed like obj.getAge().

let obj={
  getAge:function(){
    console.log(this);
    return 22;
  }
}
console.log(obj.getAge());

An arrow function does not create its own this, it's just used the this value of the enclosing execution context. In the other hand, arrow function uses this of parent scope.

function Foo(parameters = []) {
    parameters.forEach((e) => {
        this[e.name] = e.initial;
    });
};

const p = [{
    "name": "sensitivity",
    "initial": 50
}];

const f = new Foo(p);
console.log(f);
Mihai Alexandru-Ionut
  • 41,021
  • 10
  • 77
  • 103
1

You are having the classic "this is not what you think it is" problem. Using an arrow function is a way of solving the problem, as it retains the this reference from parent scope.

function Foo(parameters = []) {
    parameters.forEach(e => this[e.name] = e.initial);
};

const p = [{
    "name": "sensitivity",
    "initial": 50
}];

const f = new Foo(p);

console.log(f);
nicooga
  • 7,046
  • 23
  • 42
1

The problem you are experiencing here is a problem with this.

You are referencing this inside of a function in a callback but the this of that inner function refers to the parameters object rather than the Foo instance.

For example if you were to add after your loop this:

console.log(parameters)

I suspect you would find that the parameters object itself was modified to have a name property with the value '50'.

There are a couple of ways to solve this and they all have to do with preserving the correct this that you are expecting.

Use a lambda function

Alter your loop function to be a lambda function instead of a basic function like so:

parameters.forEach((e) => {
    this[e.name] = e.initial;
});

The primary difference between a lambda function and a standard function is that a lambda function automatically preserves the this of its parent scope.

Bind your function

You can also bind the function to use a specific this rather than the default, which is the caller object.

parameters.forEach((function (e) {
    this[e.name] = e.initial;
}).bind(this));

Calling .bind(obj) on a function will return a new function where obj is always the this when it is called.

Capture the outer scope explicitly

Its also common to just set the this from the outer-scope into its own variable and reference that directly.

var self = this;
parameters.forEach(function (e) {
    self[e.name] = e.initial;
});

These are the basic ways to capture and use this appropriately.

Alternative

I also want to add that other than the this bug you are experiencing you may want to consider a different approach to your goal entirely. Specifically you may want to consider using Object.assign to get the behavior you are looking for. This may look like this:

const f = new Foo(p)
Object.assign(f, { sensitivity: 50 })
justin.m.chase
  • 11,241
  • 6
  • 42
  • 84
  • The solutions appear to be correct, but `this` refers to the global window object, as stated by Alexandru. I've just checked this. – ziggystar Oct 10 '17 at 21:04
  • That shouldn't matter, you should be able to add properties onto the global window object as well. It should have the same behavior in this case. – justin.m.chase Oct 10 '17 at 21:09