Avoiding Pass-By-Reference Using getValue() & setValue()
Coercion and Typing
Server-side GlideRecord objects contain an element for each field in the table that the record is in. These elements are not primitive values (strings, numbers, booleans), but Objects. The type of object is GlideElement, and the GlideElement API is available. However, since JavaScript is a "loosely typed" language, you can reassign a variable of one data-type (such as an Object) to another type (such as a string) without any hassle. This is what it means to be "loosely typed": A variable's type is whatever the interpreter thinks it ought to be, when it is accessed.
The process of treating a variable as one type when it may have originally been set to another type, is called coercion. The following line of code for example, is coercing the myInt variable to a string. It doesn't modify the original myInt variable, but it sets the coercedString variable to a string, rather than attempting to do math on it.
In "typed" languages, the above code would result in a type error, and would not run unless you explicitly cast myInt to a String, or myString to a numerical datatype. Metaphorically speaking, you might say that JavaScript being loosely typed means that it is a bit more "presumptuous" than strongly typed languages, insofar as it makes assumptions about what you're trying to do, when dealing with mixed-type operations.
This type-coercion is especially common when dealing with boolean values. JavaScript will coerce certain values to true, and certain values to false. These are called truthy and falsy values respectively. Any positive or negative integer (except zero) for example, is a truthy value. If you attempt to use the number 3 for example, in an if() condition, that condition will fire, because 3 is truthy. This is important. Consider the following code:
This code will print out the word "test" to the console, because the string 'Hello' is truthy. However, a blank string is not truthy.
Since zero is falsy, but -1 is truthy, consider how unintuitive the following code might be:
Recall that the Array .indexOf() method returns the zero-based index of an element that's passed into it in the array it's called on, or - if the passed argument is not found, it returns -1. But -1 is a truthy value, and 0 is a falsy value.
This means that when an element is not found, as with folks.indexOf('Bob'), the returned -1 will be coerced to true, and the statement "Bob exists!" will be logged to the console, despite not being a true statement about the array.
By the same token, folks.indexOf('Steve') returns the number 0, corresponding to the index of the value "Steve" in the folks array. Since 0 is a falsy value, that condition does not fire, despite being a true statement about the array!
If you want to know more, here is a list of truthy and falsy values.
Primitive vs. Object
Because JavaScript doesn't explicitly link a variable to a type, setting a GlideRecord element's value to a string will overwrite the reference to the GlideElement object itself. This isn't a good idea. Here's an example of what that would look like:
Note: ServiceNow's back-end JavaScript implementation, Mozilla Rhino, actually prevents us from overwriting the GlideElement property in this way. However, both as a matter of best practice, and in order to avoid major issues with methods like updateMultiple(), you should still aim to always use getters and setters where possible.
Primitive values are stored on the stack, and are accessed directly by variable name. You might say that they are "contained within" a variable. A reference value on the other hand, is merely a pointer (or, you might say, a reference!) to an object which is stored on the heap. The difference between the heap and stack isn't important here, but what is important, is that an object variable in JavaScript does not technically contain an object. Consider what might print out, if we run the following in a background script:
In the above example, we declare an object: parent, with a property: prop1, set to a value: 'old value'.
On the next line, we then declare child, and set it's value to the same object as parent. In doing this, we might think that we are setting it to an instance of parent, but that is not the case! The two variables refer to the same object!
On line three then, we set prop1 in the child object to 'new value', and then finally we print out the same property of the parent object (parent.prop1).
So, what actually prints out? One might expect the prop1 property of the parent object to remain unchanged, but due to the fact that both variables are references to the same object, modifying child also modified parent! Thus, on line four, we print out the new value... "new value".
This applies just the same to GlideRecord objects on the server, since their properties are not primitive values like a string (as they would be in most client-side scripts), but GlideElement objects. Consider the following code:
Above, we set a limit of 2 records to return, and order them by number. This should give us INC0000001 and INC0000002. In this code, we've specifically set a condition so that we will only set the num variable's value, if the number we get ends in 0001. However, the next time the loop is run, we change the value of the GlideElement object that resides at gr.number, which the variable num references. When we print the contents of num, we're really printing out whatever object resides at "gr.number" when the line of code that references it runs. Therefore, we print out "INC0000002" instead of the expected "INC0000001". This is called "pass-by-reference".
One final example of the issues that not using .getValue() can cause, just to drive the point home.
Imagine you're building an array consisting of some value derived from a GlideRecord like so:
The above code populates two arrays: one using pass-by-reference, and the other with a primitive value. One might expect the above code to result in two identical arrays, but when we print out the contents of each, we get the following:
That is why it's important to always use getter (.getValue()) and setter (.setValue()) methods when accessing GlideElement (or other object) values. This retrieves the primitive (non-reference) value that corresponds to the field's actual value, safely.
There are a few exceptions to using .getvalue(), and those include things like dot-walking and accessing variables. However, it is still important to ensure you're getting a primitive value rather than an Object reference. Getting primitive values is how we avoid PBR (pass-by-reference) issues. So if we want to dot-walk to a field, we might be inclined to do something like this:
However, this would not get us what we're looking for; but we can't use getValue() here, so what's the solution? In this case, we should use .toString(), like so:
Note: It is also possible to use the global object constructor String(), as you can see below, but this is not preferred. There is almost never a reason to use the below syntax over the .toString() method.
Another exception to this rule, is that you should not use .setValue() when setting a journal field (like work_notes or additional_comments), as ServiceNow has special handlers for these properties.
Got questions about pass-by-reference, objects vs. primitives, or pretty much anything else about ServiceNow and coding? Leave a comment below, and we'll do our best to answer!