Tuesday, December 31, 2013

Why and how the "this" keyword in JavaScript should be avoided.

The this keyword in JavaScript is a great source of confusion for newcomers to the language, particularly if they come from many other Object Oriented languages and discover the hard way that this does not behave as they would expect.  After working through the initial confusion, however, the keyword is still a wart on the language, and one that should normally be avoided.

The Problem

This has been something that has been simmering in my mind for a couple weeks now after deciding that I was swearing off of this, and now I think the underlying issue in my mind has finally resolved itself beyond vague code smells.  For anyone unaware, this in JavaScript is bound at the time of invocation rather than definition.  This, when combined with the implicit nature of it being a keywords leads to what is the potential for broken encapsulation and less controlled behavior within the body of a function where this is used.  In a collaborative environment where code is intended to be shared and re-used this is a recipe for disaster (no pun intended).

The combination that causes the issue is that this changes at invocation time and is implicitly available within the function body, thereby baking assumptions into the code (expecting that the function will be called as designed).  In other OO languages the assumption is not present because the keyword would be bound at the time of definition...but again the issue is the combination of the two factors without either being a problem in itself.  The issue with this is that it it will vary depending on how a function is called but it is not part of the signature of the function.  Signatures are the standard and proven way to express how behavior can be modified at the time of calling a function, and that is where something analogous to this belongs.


Symptoms of the Problem

Lack of Clarity

As previously mentioned a clear sign is the confusion that newcomers to the language suffer through and the amount of blog posts dedicated to explaining how to use this.  That by itself of course is solved when the concept is grasped, but using the signature analog that confusion would be at least significantly reduced.  It is far more digestible to know that a function called in a certain context has certain parameters in the signature rather than differentiating the meaning of a single inconsistent identifier: differences are more easily understood when they're...different. Also subtle dangers such as polluting the global namespace when forgetting the new keyword before a pseudo-classical constructor function would be avoided entirely if it was expected that the new instance was the first parameter rather than the magical this (or the mistake would be more obvious).  

Lack of Simplicity

call, apply, and bind are all designed to work around controlling this (in addition to further approaches provided by libraries and frameworks).  They quickly become standard tools for experienced JavaScript programmers and the immediate reaction for many of them would be "and what's the problem with them?".  The problem is that they are extra pieces to handle execution time behavior when there is something far simpler already present.  On a conceptual level these calls could also very quickly lead to the fragmentation of function arguments: there is nothing preventing using this as an operand within the body of the function and when combined with the above calls the execution path could become very muddled for no good reason.  If a signature analog were preferred over this then none of the above would be needed at all and the resultant code would be far simpler and more consistent.

Lack of Control

Another standard practice for experienced JavaScript programmers is to alias this so that objects from surrounding context can be more clearly and consistently accessed.  Again the immediate question may be "what's the problem with that?".   The problem is that this is effectively fighting the language; the utility that has been provided has proven unsuitable enough that it is consistently overwritten.  If, however, the signature analog were used then the concept of the alias would be replaced by a parameter name which could be controlled to produce the same result as the alias.  This would save the extra line of code but would also mean that you are working with the language rather than fighting against it, and it would also be more inherently readable as the parameter would be directly within the signature of the function for which it is providing the context rather than relying on a breakable convention.

The Solution

The proposed solution is not anything new and is essentially stolen from other languages which are more multi-paradigm than they are OO (such as Perl and Python).  In any case where a function is attached to an object and effectively becomes a method, the containing object is available to the function as the first parameter.  This is an elegant solution in languages where functions are optionally methods and is also very easy to understand as the language effectively just dissects a method call and passes the object receiving the message in as an argument to the method, behavior which could virtually be syntactic sugar.  

In JavaScript this makes a fantastic amount of sense since JavaScript has at least strong functional leanings (one could consider it a functional language with objects).  The concept of first class functions while also retaining some form of implicit enclosing context is an odd combination at least.  More importantly since JavaScript does provide functional approaches merging the concept of this into the standard function behavior provides access to all of the techniques developed in other functional languages.

The language as it presently stands provides support for this approach and the end result will be a simpler, more predictable, and at least as powerful platform.  Creating functions which expect their context as the first argument are able to be called with any arguments desired and without worrying about the invocation context.  Attaching those functions to become methods on an object is as simple as assigning a reference to that function that has been curried to consistently pass that object.  A method attached this way will also consistently have access to its containing object even if called through an alias, unlike one called with this which is reliant on the method call syntax.

Conclusion

So far life without this has resulted in simpler more consistent code.  This post took a bit longer than expected so code examples were skipped, but this may be revisited, subsequent posts may be added, or sample code will be referenced in relevant FOSS projects.

There may be times when this is needed since it is for better or worse part of the core language.  This should be minimized and kept out of any business code.  

No comments :

Post a Comment