Javascript classes and variable references

I am trying to solve this cryptic Javascript OOP problem.

So, I have the following class:

var ClassA = function() { this.initialize(); } ClassA.prototype = { methods : ['alpha','beta','gama'], initialize : function() { for ( var i in this.methods ) { this[this.methods[i]] = function() { console.log(this.methods[i]); } } } } var a = new ClassA(); 

When I call each method, I expect to print its name, right? But here is what I get:

 a.alpha(); // returns gama ?!? a.beta(); // returns gama ?!? a.gama(); // returns gama 

But when my class looks like this:

 var ClassB = function() { this.initialize(); } ClassB.prototype = { methods : ['alpha', 'beta', 'gama'], initialize: function() { for ( var i in this.methods ) { this.addMethod(this.methods[i]); } }, addMethod: function(method) { this[method] = function() { console.log(method); } } } var b = new ClassB(); b.alpha(); // returns alpha b.beta(); // returns beta b.gama(); // returns gama 

Why is this happening?

+6
source share
4 answers
 for ( var i in this.methods ) { this[this.methods[i]] = function() { console.log(this.methods[i]); } } 

Your problem is here. When this cycle completes, i is the last element. Each function uses the same i , so they are all the last element.

When you use addMethod , you do a closure to “capture” the correct value.

EDIT: When you call addMethod , you “copy” the value instead of using the i value, which changes with each iteration of the loop.

+6
source

In your first version:

 initialize : function() { for ( var i in this.methods ) { this[this.methods[i]] = function() { console.log(this.methods[i]); } } } 

The methods that you create in initialize refer to the same variable i from initialize - and after initialize i works, it has the value "gama" , so no matter which method you call the value i , which they will write to the console. JS does not save the current value of i during method creation.

JS creates a “closure” for each function - the variables declared in your initialize function (i.e. i ) are still in the scope for nested functions (s) even after initialize completed.

The second version calls addMethod to add each method:

 addMethod: function(method) { this[method] = function() { console.log(method); } } 

... and therefore, when they are launched, they will refer to their own "copy" of the method parameter, because then there is a separate closure for each method.

Edit: See also this question: How do JavaScript locks work? (several answers there explain this more clearly than I do).

+3
source

You can fix your first example by adding an anonymous close:

 initialize : function() { for ( var i in this.methods ) { (function (i) { // anonymous closure this[this.methods[i]] = function() { console.log(this.methods[i]); } }).call(this, i); // use .call() if you need "this" inside } } 

Now it will work just like your second example. “Anonymous” means that closing is done by a function that does not have a name and is called instantly because it is “created”.

Sideways note: use .call(this, ...) to save this inside the called function or you can do var that = this , use that instead of this and call the function normally:

 for ( var i in this.methods ) { var that = this; (function (i) { // anonymous closure that[that.methods[i]] = function() { console.log(that.methods[i]); } })(i); // Called normally so use "that" instead of "this"! } 
+1
source

Well, first of all, stop using array loops for arrays (a property in an object). All this is fun and games, until someone prototypes an Array object, which is a perfectly reasonable and very useful / popular task. This will cause custom methods to be added to your x for array loops.

As for the problem, she does exactly what you told her to do in version 1. The problem is that by the time you get around to shoot at it, I’m the last thing I was, gamma. When you pass a link to a function as an argument, the function goes into a state of value as it is passed.

0
source

Source: https://habr.com/ru/post/912265/


All Articles