Primitive Values Are Equal by Value
Primitives can be compared to see if their values are literally the same. As logic would suggest, if you compare a variable containing the numeric value 10 with another variable containing the numeric value 10, JavaScript will consider these equal because 10 is the same as 10 (i.e.
10 === 10
). The same, of course, would apply if you compare the primitive string 'foo' to another primitive string with a value of 'foo'. The comparison would say that they are equal to each other based on their value (i.e. 'foo' === 'foo'
).
In the following code, I demonstrate the "equal by value" concept using primitive numbers, as well as contrast this with a complex number object.
Sample: sample15.html
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
| <!DOCTYPE html><html lang= "en" ><body><script> var price1 = 10; var price2 = 10; var price3 = new Number( '10' ); // A complex numeric object because new was used. var price4 = price3; console.log(price1 === price2); // Logs true. /* Logs false because price3 contains a complex number object and price 1 is a primitive value. */ console.log(price1 === price3); // Logs true because complex values are equal by reference, not value. console.log(price4 === price3); // What if we update the price4 variable to contain a primitive value? price4 = 10; console.log(price4 === price3); // Logs false: price4 is now primitive rather than complex. </script></body></html> |
The concept to take away here is that primitives, when compared, will check to see if the expressed values are equal. When a string, number, or Boolean value is created using the
new
keyword (e.g., new Number('10')
), the value is no longer primitive. As such, comparison does not work the same as if the value had been created via literal syntax. This is not surprising, given that primitive values are stored by value (i.e. does10 === 10
), while complex values are stored by reference (i.e. does price3 and price4 contain a reference to the same value).The String, Number, and Boolean Primitive Values Act Like Objects When Used Like Objects
When a primitive value is used as if it were an object created by a constructor, JavaScript converts it to an object in order to respond to the expression at hand, but then discards the object qualities and changes it back to a primitive value. In the code that follows, I take primitive values and showcase what happens when the values are treated like objects.
Sample: sample16.html
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
| <!DOCTYPE html><html lang= "en" ><body><script> // Produce primitive values. var myNull = null ; var myUndefined = undefined; var primitiveString1 = "foo" ; var primitiveString2 = String( 'foo' ); // Did not use new, so we get primitive. var primitiveNumber1 = 10; var primitiveNumber2 = Number( '10' ); // Did not use new, so we get primitive. var primitiveBoolean1 = true ; var primitiveBoolean2 = Boolean( 'true' ); // Did not use new, so we get primitive. /* Access the toString() property method (inherited by objects from object.prototype) to demonstrate that the primitive values are converted to objects when treated like objects. */ // Logs "string string" console.log(primitiveString1.toString(), primitiveString2.toString()); // Logs "number number" console.log(primitiveNumber1.toString(), primitiveNumber2.toString()); // Logs "boolean boolean" console.log(primitiveBoolean1.toString(), primitiveBoolean2.toString()); /* This will throw an error and not show up in Firebug Lite, as null and undefined do not convert to objects and do not have constructors. */ console.log(myNull.toString()); console.log(myUndefined.toString()); </script></body></html> |
In this code example, all of the primitive values (except
null
and undefined
) are converted to objects, so as to leverage the toString()
method, and then are returned to primitive values once the method is invoked and returned.Complex (aka Composite) Values
The native object constructors
Object()
, Array()
, Function()
, Date()
, Error()
, and RegExp()
are complex because they can contain one or more primitive or complex values. Essentially, complex values can be made up of many different types of JavaScript objects. It could be said that complex objects have an unknown size in memory because complex objects can contain any value and not a specific known value. In the following code, we create an object and an array that houses all of the primitive objects.
Sample: sample17.html
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
| <!DOCTYPE html><html lang= "en" ><body><script> var object = { myString: 'string' , myNumber: 10, myBoolean: false , myNull: null , myUndefined: undefined }; var array = [ 'string' , 10, false , null , undefined]; /* Contrast this to the simplicity of the primitive values below. In a primitive form, none of the values below can be more complex than what you see while complex values can encapsulate any of the JavaScript values (seen above). */ var myString = 'string' ; var myNumber = 10; var myBoolean = false ; var myNull = null ; var myUndefined = undefined; </script></body></html> |
The concept to take away here is that complex values are a composite of values and differ in complexity and composition to primitive values.
The term "complex object" has also been expressed in other writings as "composite objects" or "reference types.” If it's not obvious, all these names describe the nature of a JavaScript value excluding primitive values. Primitive values are not "referenced by value" and cannot represent a composite (i.e. a thing made up of several parts or elements) of other values, while complex objects are "referenced by value" and can contain or encapsulate other values.
How Complex Values Are Stored/Copied in JavaScript
It is extremely important to understand that complex values are stored and manipulated by reference. When creating a variable containing a complex object, the value is stored in memory at an address. When you reference a complex object, you’re using its name (i.e. variable or object property) to retrieve the value at that address in memory. The implications are significant when you consider what happens when you attempt to copy a complex value. In the next sample, we create an object stored in the variable
myObject
. The value in myObject
is then copied to the variable copyOfMyObject
. Really, it is not a copy of the object—more like a copy of the address of the object.
Sample: sample18.html
01
02
03
04
05
06
07
08
09
10
11
12
13
| <!DOCTYPE html><html lang= "en" ><body><script> var myObject = {}; var copyOfMyObject = myObject; // Not copied by value, just the reference is copied. myObject.foo = 'bar' ; // Manipulate the value stored in myObject. /* If we log myObject and copyOfMyObject, they will have a foo property because they reference the same object. */ console.log(myObject, copyOfMyObject); // Logs 'Object { foo="bar"} Object { foo="bar"}' </script></body></html> |
What you need to realize is that, unlike primitive values that would copy a value, objects (aka complex values) are stored by reference. As such, the reference (aka address) is copied, but not the actual value. This means that objects are not copied at all. Like I said, what is copied is the address or reference to the object in the memory stack. In our code example,
myObject
and copyOfMyObject
point to the same object stored in memory.
The idea to take away here is that when you change a complex valuebecause it is stored by referenceyou change the value stored in all variables that reference the complex value. In our code example, both
myObject
and copyOfMyObject
are changed when you update the object stored in either variable.
When the values
String()
, Number()
, and Boolean()
are created using the new keyword, or converted to complex objects behind the scenes, the values continue to be stored/copied by value. So, even though primitive values can be treated like complex values, they do not take on the quality of being copied by reference.
To truly make a copy of an object, you have to extract the values from the old object and inject them into a new object.
Complex Objects Are Equal by Reference
When comparing complex objects, they are equal only when they reference the same object (i.e. have the same address). Two variables containing identical objects are not equal to each other since they do not actually point at the same object.
In the following sample,
objectFoo
and objectBar
have the same properties and are, in fact, identical objects, but when asked if they are equal via ===
, JavaScript tells us they are not.
Sample: sample19.html
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
| <!DOCTYPE html><html lang= "en" ><body><script> var objectFoo = { same: 'same' }; var objectBar = { same: 'same' }; // Logs false, JS does not care that they are identical and of the same object type. console.log(objectFoo === objectBar); // How complex objects are measured for equality. var objectA = { foo: 'bar' }; var objectB = objectA; console.log(objectA === objectB); // Logs true because they reference the same object. </script></body></html> |
The concept to take away here is that variables that point to a complex object in memory are equal only because they are using the same "address.” Conversely, two independently created objects are not equal even if they are of the same type and possess the exact same properties.
Complex Objects Have Dynamic Properties
A new variable that points to an existing complex object does not copy the object. This is why complex objects are sometimes called reference objects. A complex object can have as many references as you want, and they will always refer to the same object, even as the object being referenced changes.
Sample: sample20.html
01
02
03
04
05
06
07
08
09
10
11
12
13
| <!DOCTYPE html><html lang= "en" ><body><script> var objA = { property: 'value' }; var pointer1 = objA; var pointer2 = pointer1; // Update the objA.property, and all references (pointer1 and pointer2) are updated. objA.property = null ; // Logs 'null null null' because objA, pointer1, and pointer2 all reference the same object. console.log(objA.property, pointer1.property, pointer2.property); </script></body></html> |
This allows for dynamic object properties because you can define an object, create references, update the object, and all of the variables referring to the object will "get" that update.
The typeof
Operator Used On Primitive and Complex Values
The
typeof
operator can be used to return the type of value you are dealing with. But the values returned from it are not exactly consistent or what some might say, logical. The following code exhibits the returned values from using the typeof
operator.
Sample: sample21.html
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
| <!DOCTYPE html><html lang= "en" ><body><script> // Primitive values. var myNull = null ; var myUndefined = undefined; var primitiveString1 = "string" ; var primitiveString2 = String( 'string' ); var primitiveNumber1 = 10; var primitiveNumber2 = Number( '10' ); var primitiveBoolean1 = true ; var primitiveBoolean2 = Boolean( 'true' ); console.log( typeof myNull); // Logs object? WHAT? Be aware... console.log( typeof myUndefined); // Logs undefined. console.log( typeof primitiveString1, typeof primitiveString2); // Logs string string. console.log( typeof primitiveNumber1, typeof primitiveNumber2); // Logs number number console.log( typeof primitiveBoolean1, typeof primitiveBoolean2); // Logs boolean boolean. // Complex values. var myNumber = new Number(23); var myString = new String( 'male' ); var myBoolean = new Boolean( false ); var myObject = new Object(); var myArray = new Array( 'foo' , 'bar' ); var myFunction = new Function( "x" , "y" , "return x * y" ); var myDate = new Date(); var myRegExp = new RegExp( '\\bt[a-z]+\\b' ); var myError = new Error( 'Darn!' ); console.log( typeof myNumber); // Logs object. console.log( typeof myString); // Logs object. console.log( typeof myBoolean); // Logs object. console.log( typeof myObject); // Logs object. console.log( typeof myArray); // Logs object. console.log( typeof myFunction); // Logs function? WHAT? Be aware... console.log( typeof myDate); // Logs object. console.log( typeof myRegExp); // Logs function? WHAT? Be aware... console.log( typeof myError); // Logs object. </script></body></html> |
When using this operator on values, you should be aware of the potential values returned given the type of value (primitive or complex) that you are dealing with.
Dynamic Properties Allow for Mutable Objects
Complex objects are made up of dynamic properties. This allows user-defined objects, and most of the native objects, to be mutated. This means that the majority of objects in JavaScript can be updated or changed at any time. Because of this, we can change the native pre-configured nature of JavaScript itself by augmenting its native objects. However, I am not telling you to do this; in fact I do not think you should. But let's not cloud what is possible with opinions.
This means its possible to store properties on native constructors and add new methods to the native objects with additions to their prototype objects.
In the following code, I mutate the
String()
constructor function andString.prototype
.
Sample: sample22.html
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
| <!DOCTYPE html><html lang= "en" ><body><script> // Augment the built-in String constructor Function() with the augmentedProperties property. String.augmentedProperties = []; if (!String.prototype.trimIT) { // If the prototype does not have trimIT() add it. String.prototype.trimIT = function () { return this .replace(/^\s+|\s+$/g, '' ); } // Now add trimIT string to the augmentedProperties array. String.augmentedProperties.push( 'trimIT' ); } var myString = ' trim me ' ; console.log(myString.trimIT()); // Invoke our custom trimIT string method, logs 'trim me'. console.log(String.augmentedProperties.join()); // Logs 'trimIT'. </script></body></html> |
I want to drive home the fact that objects in JavaScript are dynamic. This allows objects in JavaScript to be mutated. Essentially, the entire language can be mutated into a custom version (e.g.,
trimIT
string method). Again, I am not recommending thisI am just pointing out that it is part of the nature of objects in JavaScript.
Careful! If you mutate the native inner workings of JavaScript, you potentially have a custom version of JavaScript to deal with. Proceed with caution, as most people will assume that JavaScript is the same wherever its available.
No comments:
Post a Comment