There are several myths about JavaScript shortcomings when it comes to modern features of a programming language. One of them is the statement that you should declare the local variables at the top of a function because they span over all the local scope of that function. While local variables are not restricted to block level scope like in other OOP languages, they still exist only after the place where they are declared. So, the code where they appear at the top, and the code where they appear first where needed, although both valid, are not equivalent (scope of access is not the same as scope of existence). In fact, it is more readable to declare a counter that is used only after 20 lines of code, in that spot, than to clutter the beginning of the function with auxiliary variables. Even if you declare two times a local counter, the modern browsers won’t choke on that, and you can clean the final code with JSLint/JSHint/ESLint to avoid duplicates.
Another more important myth is that because variables are always passed by value, you cannot program with pointers or references in JavaScript. Although the language syntax use the dot to point to the members of an object, there is no mechanism to use their addresses (i.e. pointers). But, when passing an object to a function, the language engine makes a copy of the object, but doesn’t make copies of its members. The specification states that the members of the copy are the actual members of the original object. So, what is copied in that case?
One should look to the members of an object as being linked to their parent object, instead of being included within. When the object is copied, the links to its members are the ones to get copied, and not the members. One question might arise: what are those links? The answer is related to another feature of JavaScript: garbage collection. It is stated that when a variable is not used anymore, the allocated memory for that variable is freed by the garbage collector. Furthermore, some implementations specify that when the count of references to an object reaches zero, that object candidates for garbage collection. This is a disclosure that those links to the object members are not mere links; they are more probably the references of the pointers encountered in other OOP languages.
To exemplify the perceived shortcoming in manipulating the object members in JavaScript, below, it will be proposed two situations.
The problem
1. Suppose we want to swap two objects depending on one of their properties. The code below illustrates the classical solution.
var data = { version: '0.01', isTest: true, first: { id: 'a11', weight: 1.1, size: 2.0 }, second: { id: 'b10', weight: 0.5, size: 1.4 } }; // try a generic swap; this won't work function swap1(o1, o2, sortBy) { var o = null; if (o2[sortBy] < o1[sortBy]) { o = o1; o1 = o2; o2 = o; } } swap1(data.first, data.second, 'weight'); alert('first: ' + data.first.id + ', second: ' + data.second.id); // this will work, but is data structure dependent function swap2(parent, sortBy) { var o = null; if (parent.second[sortBy] < parent.first[sortBy]) { o = parent.first; parent.first = parent.second; parent.second = o; } } swap2(data, 'weight'); alert('first: ' + data.first.id + ', second: ' + data.second.id);
But we can see that we need to modify the initial declaration of our swap() function, and the code inside it is dependent on the structure of the parent object.
2. A list of items is received from server-side, and must be processed for display changing several fields and adding another computed field. The qr() function that does the processing needs a code like below.
// initial structure var result1 = { meta: { weightField: 'w', sizeField: 's', qrField: 'q' }, items: [ {id: 'c20', w: 2.1, s: 0.15, q: false}, {id: 'c21', w: 1.5, s: 1.05, q: false}, {id: 'c22', w: 1.8, s: 2.20, q: false} ] }; function qr1(item, weightField, sizeField, qrField) { if (item[sizeField] < 0.5) { item[qrField] = 100 * item[weightField]; } else { item[qrField] = 25 * item[sizeField] + 75 * item[weightField]; } item[weightField] = 100 * item[weightField] / item[qrField]; item[sizeField] = 100 * item[sizeField] / item[qrField]; } // changed structure var result2 = { meta: { weightField: 'we', sizeField: 'sz', qrField: 'qr', qmField: 'qm' }, items: [ {id: 'c20', dataIn: {we: 2.1, sz: 0.15}, dataOut: {qr: false, qm: false}}, {id: 'c21', dataIn: {we: 1.5, sz: 1.05}, dataOut: {qr: false, qm: false}}, {id: 'c22', dataIn: {we: 1.8, sz: 2.20}, dataOut: {qr: false, qm: false}} ] }; function qr2(itemIn, weightField, sizeField, itemOut, qrField) { if (itemIn[sizeField] < 0.5) { itemOut[qrField] = 100 * itemOut[weightField]; } else { itemOut[qrField] = 25 * itemIn[sizeField] + 75 * itemIn[weightField]; } itemIn[weightField] = 100 * itemIn[weightField] / itemOut[qrField]; itemIn[sizeField] = 100 * itemIn[sizeField] / itemOut[qrField]; }
But, if the name or the structure of the server-side data changes, one must modify the code inside the qr() function although the computing algorithm hasn’t changed a bit.
These examples show that much of JavaScript code is dependent of the structure of the processed data, and requires continuous maintenance when changing the data source. Well … not anymore
Introducing references in JavaScript
A reference in JavaScript is a pair formed by an existing JavaScript object and a string/number which is used as a key for the object.
The object is called the owner/referenced object, and the string is called the reference item/key. The key may be absent in the referenced object at the moment of defining the reference.
The simplest implementation in JavaScript is a hash (object) with two properties that hold the owner and the item, like in the code below.
var data = { group: 'widgets', control: 'Panel', style: { fonts: ['Helvetica', 'Gerorgia', 'Traffic'], borders: {inner: '1px', outer: '2px'}, visible: true } }; // creating references var p1 = {ref: data.style, key: 'visible'}; var p2 = {ref: data.style.fonts, key: 2}; var p3 = {ref: data.style.borders, key: 'inner'}; // assigning a value p1.ref[p1.key] = false; p2.ref[p2.key] = 'Verdana'; // applying delete operator delete p3.ref[p3.key]; // copying a reference var p4 = {ref: p3.ref, key: p3.key}; // changing the key p4.key = 'shadow'; p4.ref[p4.key] = '4px'; /* the resulting object will be: data = { group: 'widgets', control: 'Panel', style: { fonts: ['Helvetica', 'Georgia', 'Verdana'], borders: {outer: '2px', shadow: '4px'}, visible: false } }; ------------------------------ */
As one can see, we are able to implement all basic operations with this reference concept, But what advantage could that be to the problem of the data-dependent JavaScript code?
The solution – generics
We call a generic, a piece of code or an algorithm that doesn’t change when the passed/processed data changes structures (field names, field membership, etc.).
Using references, we can build generic functions for the two example problems mentioned before. If the data structures change, one must modify only the actual call of the generic, and not its inner code.
1.
function swap(p1, p2, sortBy) { var o = null; var o1 = p1.ref[p1.key]; var o2 = p2.ref[p2.key]; if (o2[sortBy] < o1[sortBy]) { o = o1; p1.ref[p1.key] = o2; p2.ref[p2.key] = o; } } // the call swap({ref: data, key: 'first'}, {ref: data, key: 'second'}, 'weight'); alert('first: ' + data.first.id + ', second: ' + data.second.id);
2.
function qr(pWeight, pSize, pQr) { if (pSize.ref[pSize.key] < 0.5) { pQr.ref[pQr.key] = 100 * pWeight.ref[pWeight.key]; } else { pQr.ref[pQr.key] = 25 * pSize.ref[pSize.key] + 75 * pWeight.ref[pWeight.key]; } pWeight.ref[pWeight.key] = 100 * pWeight.ref[pWeight.key] / pQr.ref[pQr.key]; pSize.ref[pSize.key] = 100 * pSize.ref[pSize.key] / pQr.ref[pQr.key]; } // let it be: var item = data.items[i]; // inside a for loop // the call in first case: qr({ref: item, key: 'w'}, {ref: item, key: 's'}, {ref: item, key: 'q'}); /// the call in second case: qr({ref: item.dataIn, key: 'we'}, {ref: item.dataIn, key: 'sz'}, {ref: item.dataOut, key: 'qr'});
A reference class In JavaScript
The JavaScript UI Language module has an implementation of a class for working with references. Using JUL.Ref class, the code for basic operations can be rewritten as below.
var p1 = new JUL.Ref(data.style, 'visible'); var p2 = new JUL.Ref(data.style.fonts, 2); var p3 = new JUL.Ref(data.style.borders, 'inner'); // assigning a value p1.val(false); p2.val('Verdana'); // applying delete operator p3.del(); // copying a reference var p4 = new JUL.Ref(p3); // changing the key p4.key('shadow'); p4.val('4px');
Conclusion
The reference concept in JavaScript fills the gap where pointers or references to variables are needed in the other programming languages. It also offers a way to implement generics in JavaScript, which leads to a possible existence of a standard algorithms library for JavaScript (like STL for C++).
Upon a closer examination, as a programming language, JavaScript lacks nothing that the other modern languages have, and it’s a winning bet for the future of cloud-platform (web, mobile, net-pc) applications.