Uses var self = is this a good way to synchronize between class and events?

Take a look at this simple code example (it is written in angularjs for simplicity, but this happens all the time in JavaScript):

angular.module('app',[]). directive('myDir', function(){ this.state = {a:1, b:2}; return { link: function(scope, elem, attrs){ elem.on('click', function(){ // "this" is not the class but the element this.state.a++; this.state.b++; console.log(this.state); }); } } }); 

When the call onclick call is called, "this" will not be a function of directives, but the element itself.

So, we all know the trick here, and we create a closure and use var self = this to complete the job.

 angular.module('app',[]). directive('myDir', function(){ // create a closure for the rescue var self = this; this.state = {a:1, b:2}; return { link: function(scope, elem, attrs){ elem.on('click', function(){ self.state.a++; self.state.b++; console.log(self.state); }); } } }); 

Ok - this works, and I have done it many times, but I ask myself, is this the best way to do something like this?
It always seemed like a bad design to me.
Is there a better way to synchronize between class and user events?

+5
source share
4 answers

It is difficult to say objectively that this is a bad design. But I think there are more elegant ways to solve the problem.

  • Does it create extra variables in your area? Yes
  • Perhaps this confuses other developers? Yes

You do not need to ask many more questions than before we can agree that there must be a better way.

Two other answers offer alternatives. The first uses Function.prototype.bind , which allows us to set the context for this within another function.

Conceptually, it is elegant, but it can be heavy syntactically and mentally. I find this often leads to huge overflows when you have to write a lot of .bind event handler calls.

The second is the alias self = this , and then use it appropriately depending on what area you are in.

I think this is a bad experience in itself, since code-supporting developers should think about which version they should use, and they need to remember to make the self = this alias at the top of each new context block.

I think the main problem is the fact that Javascript has the this . This is a pretty huge source of general confusion for Javascript programmers, largely because it has been added to make Javascript more comfortable for object-oriented programmers.

Once you start using Javascript in a purely prototype way ( forget new and this ), all these problems go away.

Instead, create a bank class with the usual old objects.

 function BankAccount(name, balance) { var account = {}; account.name = name || 'Anonymous Benefactor'; account.balance = balance || 1000; account.withdraw = function(amount) { return (account.balance -= amount); }; account.deposit = function(amount) { return (account.balance += amount); }; return account; } // we don't need a new keyword! var account = BankAccount('Prototype', 310); // this means we can use bind, call and apply! account = BankAccount.apply(null, ['Prototype', 310]); 

No matter where we want to refer to the context object ( account ) inside this function, we can do this because Javascript has links and closures. There is no risk that the context object will ever change to window or event .

The inspiration for this style came from a conversation by Doug Crockford .

You can change your code:

 angular.module('app',[]). directive('myDir', function(){ var myDir = {}; myDir.state = {a:1, b:2}; return { link: function(scope, elem, attrs){ elem.on('click', function(){ myDir.state.a++; myDir.state.b++; console.log(myDir.state); }); } } }); 
  • You do not need to worry about .bind .
  • You do not need to worry about how your context is changing.

However, consider this as a guide. Not like a law. There are a few examples where this definitely acceptable (Angular services spring). Sometimes you have no choice, some libraries and functions will ensure that you change the value of this .


Last thing. If you can use ES6, either initially (Firefox, IO.js, etc.), or through the transpiler (traceur, babel), then you can use the thick arrow syntax.

 function foo() { this.bar = 'baz'; element.on('click', event => { console.log(this.bar); // 'baz' }); } 

It associates the current value of this with the context of the function block. It works similarly to .bind(this) , but is syntactically lighter.

+1
source

In your specific example, I would rate this as bad, but maybe not for the reason you are thinking about. If you see a modified version at http://plnkr.co/edit/K66o8tmRtnnfk8NZ8YPf?p=preview

 app.directive('myDir', function(){ // create a closure for the rescue var self = this; this.state = {a:1, b:2}; return { link: function(scope, elem, attrs){ elem.on('click', function(){ self.state.a++; self.state.b++; // Using global scope (seen as a bad thing) console.log(self === window); }); } } }); 

then the self you define is actually equal to window , and therefore you are actually saving state in the global scope. This is because the function defining the directive is not the target of the new operator, so this has its default value, which is equal to window .

If the function is the target of a new operator, such as controller or service , then setting self = this fine. However, in directive or factory , then this does not change from the default window . In these cases, I would simply define a local variable

 var state = {a:1, b:2}; 

and access it from closures.

An alternative is to use something like bind for each event handler to change what this refers to. However, sometimes a default binding may be required inside the event handler, so sometimes you need to use bind , and sometimes not, I suspect that this can lead to increased debugging time, since you will not have a consistent method for writing this.

0
source

This is acceptable in services and controllers, but within directory nested function areas it looks messy - and also wrong. this defined only in compile (and refers to DDO), but neither in the factory directive nor in pre / postlink.

The best and cleanest way is to provide the context of the callback function, so it knows (and does so) what context it works with.

  elem.on('click', angular.bind(state, function(){ this.a++; this.b++; console.log(this); })); 

An alternative (native to ES5) method is

  elem.on('click', function(){ this.a++; this.b++; console.log(this); }.bind(state)); 

This can be considered recommended if the lack of support for IE8 is not a problem.

0
source

Although the approach you are using is also good, but yes, there is another better way that you can write in any javascript code using the bind() method.

 angular.module('app',[]). directive('myDir', function(){ // create a closure for the rescue var self = this; this.state = {a:1, b:2}; return { link: function(scope, elem, attrs){ elem.on('click', function(){ self.state.a++; console.log(this.state.a == self.state.a); }.bind(self)); } } }); 

bind changes the context of the code. You can pass something for context. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind

-1
source

All Articles