RunningFromShia
RunningFromShia

Reputation: 610

Javascript - how do primitives really work

I have the following code:

String.prototype.isLengthGreaterThan = function(limit){
    return this.length > limit;
}

console.log("John".isLengthGreaterThan(3));

Number.prototype.isPositive = function(){
    return this > 0;
}

console.log(5.isPositive(3)); //error

The 1st example work, the other doesn't. Why?

From my understanding, primitives are not objects – though they do have access to their function constructor's prototype (Number, String, etc.). So, you can add methods directly to the prototype. In my example above it didn't work in one instance.

I am looking for "under the hood" answer, to understand what happens when you do, for example:

var a = 1;

How does it really have an access to its prototype if it isn't an object?

Upvotes: 1

Views: 56

Answers (1)

T.J. Crowder
T.J. Crowder

Reputation: 1074475

The dot thing isn't to do with primitives, it's just the syntax of writing numeric literals. In a number, the . is a decimal point, whereas of course that's not the case for a string. To use a method on a literal number, you have two choices:

  1. Parens:

    console.log((5).isPositive(3));
    
  2. Two dots (yes, really):

    console.log(5..isPositive(3));
    

In the second case, it works because the first dot is a decimal point; this means that the dot following can't be a decimal point, and so it's the property accessor operator.

How does it really have an access to it's prototype if it isn't an object.

It's promoted to an object automatically by the JavaScript engine when you do the propety access. In the case of numbers, it's as though you called new Number(5) or similar. In the case of strings, it's as though you called new String("the string"). When the property access is complete, the temporary object is then discarded immediately when the expression is complete. Naturally, the JavaScript engine can optimize the object allocation out, but conceptually that's what happens.

So conceptually:

  1. We do (say) var n = 1; The variable a contains the primitive number value 1.

  2. We do n.isPositive(3); to create a string from it using Number.prototype.toFixed. That's a property access operation:

    1. The engine evaluates the left-hand side, n, and gets the result 1 (a primitive number); this is the base it'll use for the property access operation.

    2. The engine evaluates the right-hand side (isPositive) to determine the property key (name) to look up. In this case it's a literal, so the key is the string "isPositive". This is the property key for the property access operation.

    3. The engine goes to look up the property on the base. Since the base we have is a primitive, the JavaScript engine promotes it (coerces it) to an equivalent Number object.

    4. Since that object doesn't have a "isPositive" property, the engine looks at its prototype, Number.prototype, and finds it; it's a reference to a function.

    5. Various things happen that aren't really germane, and ultimately isPositive is called with this being an object coerced from the primitive value 1. It does its work and generates its return value.

    6. Since the temporary object isn't referenced by anything anymore, it's eligible for garbage collection.

The mechanism by which this happens is a bit scattered in the specification:

  • The runtime semantics of the property accessor operator return something called a Reference specification type, which basically is a purely abstract specification holding object that will get evaluated later. It says that the base will be the result of evaluating the left-hand side of the property accessor, and the property anme will be the result of evaluting the right-hand side.

  • The GetValue operation on the Reference type, which says (step 5) that if it's a property reference and the base is primitive, it's coerced via the specification's ToObject operation.

  • ToObject, which defines the rules for doing that.

Upvotes: 3

Related Questions