Primitives vs. Objects

  1. Primitive values are not objects. They can, however, act like objects. JavaScript does this by wrapping the primitive in an object wrapper.

     var str1 =            'not an object' ,
         str2 =     String('not an object'),
         str3 = new String('is  an object');
    
     str1.length;    // 13 - primitive acting like an object
     'hi'.length;    // 2  - primitive acting like an object
    

    NOTE: You can think of primitive values as being atomic or irreducible in relation to objects. Objects can be broken down into properties and values, while primitives are literal values themselves.

    It is, therefore, a common misconception that everything in JavaScript is an object. Not everything in JavaScript is an object, but rather, everything in JavaScript can act like an object.



  2. The String(), Number() and Boolean() constructors return an object when called with the new operator and a primitive when called without the new operator.

     var str1 =            'not an object' ,     // returns a primitive (String() not called)
         str2 =     String('not an object'),     // returns a primitive
         str3 = new String('is  an object');     // returns an object
    

    NOTE: The String() constructor is not called for str1. JavaScript creates this String literal using internal native code.



  3. When dealing with primitives (e.g. String, Number or Boolean), an object is only created when the new operator is used or when the primitive is treated like an object.

    An object is not created when a primitive value is defined using literal syntax (e.g. var str = 'hi') or a constructor without the new operator (e.g. var str = String('hi')). Instead, the JavaScript engine creates a primitive value.

    However, an object is created once the primitive is treated like an object (e.g. by invoking a method on the primitive or by adding a property to ). When this happens, JavaScript creates an object wrapper that temporarily wraps around the primitive, allowing for it to be treated as an object. The object wrapper is then discarded after the expression is complete, bringing the value back to it's original primitive state.

    Compare the following:
     // primitive
    
     var s1 = 'hi';
     typeof s1;  // "string"
            s1;  // hi
    
     // primitive
    
     var s2 = String('hi');
     typeof s2;  // "string"
            s2;  // hi
    
     // object
    
     var s3 = new String('hi');
     typeof s3;  // "object"
            s3;  // String {0: "h", 1: "i", length: 2, [[PrimitiveValue]]: "hi"}
    
    Let's see what happens when we treat a primitive like an object:
     // add a method to s1 and invoke it
    
     s1.func = function() {return 'myFunc1'};
     s1.func;    // undefined - (?) didn't we just define it?
     s1.func();  // error: [1]
    
     // add a method to s2 and invoke it
    
     s2.func = function() {return 'myFunc2'};
     s2.func;    // undefined
     s2.func();  // error: [1]
    
     // add a method to s3 and invoke it
    
     s3.func = function() {return 'myFunc3'};
     s3.func;    // function()
     s3.func();  // "myFunc3"
    
     /*  Footnotes:
         [1] At this point, the object wrapper around s1 has
             been discarded, so s1.func, the function assigned 
             to s1's object wrapper, no longer exists.
     */
    

    In the code below, all of the primitive values (except for null and undefined) are wrapped by object wrappers. The toString() method is then called on each object wrapper. The objects revert back to primitive values once the method is invoked and returned.

     // primitives
    
     var nul = null,
         undef = undefined,
         str1 = "foo",
         str2 = String('foo'),
         num1 = 10,
         num2 = Number('10'),
         bool1 = true,
         bool2 = Boolean('true');
    
     // toString() called on each primitive
    
     console.log(str1.toString(), str2.toString());   // foo foo
     console.log(num1.toString(), num2.toString());   // 10 10
     console.log(bool1.toString(), bool2.toString()); // true true
     console.log(myNull.toString());                  // error [1]
     console.log(myUndefined.toString());             // error [1]
    
     /*  Footnotes:
         [1] An error is raised because null and undefined do not 
         have constructors, and therefore, do not convert to objects.
     */
    

    NOTE: When the values String, Number, and Boolean are either created using the new keyword or converted to objects behind the scenes, the values continue to be stored and copied by value. So, although primitive values can be treated like objects, they do not take on the quality of being copied by reference.



  4. Primitives are copied-by-value.

     var bob = 'Bob',
         sam =  bob;
    
     bob;    // Bob
     sam;    // Bob
    
     sam = 'Sam';
    
     bob;    // Bob
     sam;    // Sam
    

    Changes made to bob affect sam.



  5. Objects are copied-by-reference.

     var bob = {name: 'Bob'},
         sam = bob;
    
     bob.name;   // Bob
     sam.name;   // Bob
    
     sam.name = 'Sam';
    
     bob.name;   // Sam
     sam.name;   // Sam
    

    Changes made to bob do not affect sam.



  6. An object is not stored on the stack directly—the reference to that object is stored on the stack. The object itself is stored within in the heap.
     var obj = {};
     obj;    // obj -> [memory address] -> Object {}
    
    Compare this to referencing primitives:
     var str = 'hi';
     str;    // str -> 'hi'
    
    To better understand copy-by-value vs. copy-by-reference, it is important to understand that when something is copied, whether a primitive or an object, the value literals themselves are being copied. In the case of primitives, the literal value is copied, hence copy-by-value. In the case of objects, the object's memory address (or reference) is copied, hence copy-by-reference.
     var obj1 = {},
         obj2 = obj1,
         prim1 = 'primitive',
         prim2 = prim1;
    
       key   |             value
     ------- | --------------------------------
     'obj1'  | 0x7fff9575c05f (address literal) 
     'obj2'  | 0x7fff9575c05f (address literal) 
     'prim1' | 'primitive'    (string  literal)
     'prim2' | 'primitive'    (string  literal)
    



  7. == compares value. === compares value and type.

     var n1 =            10 ,    // primitive
         n2 =     Number(10),    // primitive
         n3 = new Number(10),    // object
         n4 = n3;                // object
    
     n1 ==  n2;      // true
     n1 === n2;      // true
    
     n2 ==  n3;      // true
     n2 === n3;      // false - primitive ≠ object
    
     n3 ==  n4;      // true
     n3 === n4;      // true
    

    According to the === operator, n2 and n3 have the same value, but are of different types. n2 is a number primitive, and n3 is an Number object.



  8. Both primitives and objects are compared by value (the value stored within the variable). But wait, aren't objects compared by reference? Yes, remember that the value of object variables is the reference to that object in memory.

     // primitives compared by value
    
     var prim1 = 10,     // prim1 -> 10
         prim2 = 10;     // prim2 -> 10
    
     prim1 ==  prim2;    // true
     prim1 === prim2;    // true
    
     // objects compared by value (of reference)
    
     var obj1 = {},      // obj1       ->       [0x00000]
         obj2 = {},      // obj2       ->       [0x11111]
         obj3 = obj1;    // obj3  ->  obj1  ->  [0x00000]
    
     obj1 === obj2;      // false - [0x00000] ≠ [0x11111]
     obj3 === obj1;      // true  - [0x00000] = [0x00000]
    



  9. All objects have a constructor property somewhere in their prototype chain. This property may or may not be the object's own property. Its value is the constructor function that created the object.

     // A generic object created by the Object() constructor
    
     var foo = {};
    
     // foo's insides look something like:
    
     Object {
         __proto__: Object {
             constructor: function Object() { ... },     // constructor property
             // more properties
         }
     };
    
     // A String object created by the String() constructor
    
     var str = new String('hi');
    
     // str's insides look something like:
    
     String {
         __proto__: String {
             constructor: function String() { ... },     // constructor property
             // more properties
         },
         // more properties
     };
    
     var foo = {};
     foo.constructor;    // [1][2] function Object()
     foo.constructor();    // [3] Object {}
    
     /*  Footnotes:
         [1] Object() is the contructor that created foo.
         [2] Notice that Object is a function (contructor function 
             to be exact) that can be invoked. String, Array, and the 
             other data types are also functions.
         [3] foo's constructor is invoked and a new Object is returned.
     */
    

    The same goes for user-defined objects:

     var CustomConstructor = function CustomConstructor() {this.message = 'Wow!'},
         instanceOfCustomObject = new CustomConstructor();
    
     // true
     instanceOfCustomObject.constructor === CustomConstructor;
    
     // function CustomConstructor() {this.message = 'Wow!'}
     instanceOfCustomObject.constructor;
    

    NOTE: A JavaScript object is not necessarily an instance of Object(). An object created by the Object() constructor is a generic object. A JavaScript object, however, could be an instance of Array(), Number() or some user-defined object.

results matching ""

    No results matching ""