Thursday, March 30, 2017

Sneaky JavaScript Technics III

A ninja is a master of disguise. How do you apply the JavaScript ninjutsu to hide an object member so that nobody can see (and use) it? Here are several ways.

The context

These days, I started a new task on my gpf-js project and it implies manipulating an AST structure within NodeJS. Because the structure analysis requires to have access to the parent nodes, I augmented the AST items with a link to their parent. The result is later serialized into JSON for tracing purpose but the generated circular reference broke the conversion.

Actually, I could have used the replacer parameter of the JSON.stringify method but this circular reference also caused trouble in my own code. Hence I had to find another way.

Reflection

The JavaScript language offers some reflection mechanisms. Indeed, the for..in syntax is capable of listing all enumerable properties of any object.

I would recommend using Object.keys instead. However, the lack of support of old browsers requires that you polyfilled it with for..in combined with hasOwnProperty .

This fundamental machinery is widely used:

  • ESlint offers a rule to detect unsafe uses
  • JavaScript evolved to make the enumerable state configurable on a property

Object.defineProperty

The simplest way to add a property to an object is to simply do it: var obj = {}; obj.newProperty = "value";

This newly created property will be enumerable by default.

ECMAScript 5.1 introduced Object.defineProperty to create a property with options. This feature is implemented by most recent browsers with some limitations when using it on DOM objects.

It can be used in different ways and most of them are beyond the scope of this article. I will mostly focus on the possibility to create a non-enumerable property:

var obj = {}; Object.defineProperty(obj, "newProperty", { value: "value", writable: true, enumerable: false, configurable: false });

By setting enumerable to false, this property will not be enumerated when using the for..in syntax. By setting configurable to false, this property can't be deleted and can't be reconfigured to make it visible again.

The advantages are:

  • It is easy to implement
  • It is available since IE9

But, regarding the initial purpose, it comes with drawbacks:

To make the task harder for a hacker to figure out the property name, you may generate a complex random name and store it in a 'private' variable.

Symbol

Another simple way to add a property to an object is to use the square bracket syntax: var obj = {}; var newPropertyName = "newProperty"; obj[newPropertyName] = "value";

In that example, the newly created property will be named "newProperty" as per the value of the variable. The only difference with the dot syntax is the use of a variable to name the property.

But what if the variable is not a string?

For most standard objects (and primitive types), the variable value is converted to string. Consequently, the following syntax is valid (even if it does not make sense): var obj = {}; function any () {} obj[any] = "value";

The created property name will be "function any() {}"

This obviously means that you can use names that are not valid identifiers. Hence, it is mandatory to use the bracket syntax to access them.

However, there is one mechanism that behaves differently. It was introduced with ECMAScript 2015. Every time you call the Symbol function, it returns a unique value. This value type is primitive but it does not convert to string.

This feature is implemented only by most recent browsers and not by IE.

The advantages are:

  • It is easy to implement
  • There is no way to access the property unless you have the symbol value

No comments:

Post a Comment