For developers familiar with JavaScript, its quirks and unexpected behaviors are well-known. Issues with elements like Date.getMonth, parseInt, null, undefined, and others are widely recognized. The introduction of ES5 brought its own set of interesting characteristics, adding to both the enjoyment and challenges of working with this language. Let’s explore some of these aspects.
Years ago, the introduction of the bind function in Prototype.js was a welcome addition, especially for its lexical this functionality. However, over time, the practice of manually creating closures for binding this became so ingrained that the addition of bind to the ES5 standard went somewhat unnoticed by some developers. It wasn’t until reading about arrow functions in ES.next, specifically in [this article](this excellent article), that the distinct nature of ES5 bound functions became apparent. Unlike the wrapper functions returned by Prototype.js or ES5 shims, ES5 bound functions are a distinct type with unique internal properties, as [explained here](read more about bound functions here).
But why discuss Function.bind in this context?
Beyond binding this, Function.bind enables the binding of other parameters, a concept known as partial function application. While seemingly convenient, a limitation arises when attempting to bind parameters while preserving dynamic this binding. If you bind null or undefined for this, as in:
| |
and later invoke the bound function with:
| |
func2 receives null instead of myObj. Ideally, when a bound function has null or undefined for its bound this value, it should indicate no specific binding. However, this isn’t the case, necessitating a separate function for partial application (similar to the one, albeit misnamed as curry, in Prototype.js). Unfortunately, the standard library lacks such a function, leaving developers to create their own. While simple to implement, this omission feels like an incomplete feature.
Absence of Object.extend
The absence of Object.extend in the standard library is perplexing given its utility. While alternatives exist, such as implementing your own or using libraries like Underscore.js or Lodash, its absence from the standard remains a mystery.
New Object Functions in ES5
The new Object functions introduced in ES5 (freeze, seal, getOwnPropertyDescriptor, defineProperty, keys, etc.) were added as static methods (e.g., Object.method) rather than instance methods (e.g., Object.prototype.method). This choice might seem counterintuitive since these operations are performed on instances. However, as discussed in this StackOverflow thread, this decision stems from:
- Separating meta-operations from application logic.
- Minimizing API surface area.
While understandable, some instances remain confusing, such as:
- Handling the internal prototype (
Object.getPrototypeOf(obj), yet we haveobj.hasOwnProperty). A more consistent approach might have beenObject.prototype.getPrototype. - The existence of
Object.prototype.isPrototypeOfwhile other property-related methods are static.
This static vs. instance method debate harks back to the use of string.upper in Python. The concept of calling string.upper("my string") instead of "my string".upper() can feel strange for developers accustomed to other languages. As [explained in this article](the same idea) from a .NET perspective, the rationale for methods like ToUpper is that they are pure functions with no side effects, operating on an input expression (this) to return a result.
Similarly, the choice of having Math.sin, Math.cos, etc., rather than Number.prototype.sin, Number.prototype.cos, etc., likely stems from factors like the Single Responsibility Principle, avoiding unnecessary boxing, and potentially aligning with established conventions.
[[Prototype]] does not seem to be considered as a meta operation, as we have Object.prototype.isPrototypeOf