Deep Dive into the JavaScript new Operator and instanceof
The Mechanism of the new Operator
In JavaScript, the new operator is used to instantiate a constructor function. While it might seem like a simple keyword, it performs a sequence of specific oeprations internally:
- Object Creation: A brand new empty object is created.
- Prototype Linking: The internal
[[Prototype]](often accessible via__proto__) of the new object is linked to theprototypeproperty of the constructor function. - Execution: The constructor function is called with
thisbound to the new object. - Return Handling: If the constructor returns an object, that object is returned. Otherwise, the newly created object is returned.
We can simulate this behavior with a custom function:
function createInstance(Constructor, ...args) {
// 1. Create a new object
const instance = {};
// 2. Link the prototype
Object.setPrototypeOf(instance, Constructor.prototype);
// 3. Execute the constructor with the new object as context
const result = Constructor.apply(instance, args);
// 4. Return the object (handling explicit object returns)
return (result !== null && typeof result === 'object') ? result : instance;
}
Understanding instanceof
The instanceof operator checks whether the prototype property of a constructor exists anywhere in the prototype chain of an object.
Syntax: object instanceof Constructor
We can visualize its internal logic as a loop that traverses the prototype chain:
function checkInstance(object, Constructor) {
let proto = Object.getPrototypeOf(object);
while (proto !== null) {
if (proto === Constructor.prototype) {
return true;
}
proto = Object.getPrototypeOf(proto);
}
return false;
}
Practical Example: Properties and Prototypes
Consider the following example involving a constructor function:
function Gadget(brand) {
this.brand = brand;
}
// Static property on the constructor itself
Gadget.power = "electric";
// Method on the prototype
Gadget.prototype.start = function() {
console.log("Starting " + this.brand);
};
const phone = new Gadget("Pixel");
Let's analyze how phone accesses properties:
phone.brand: This property was defined onthisduring the constructor execution.phone.power: This returnsundefined.poweris a static property on theGadgetfunction object, not on its prototype, so its not inherited by instances.phone.start(): Thephoneobject does not have astartproperty. JavaScript looks up the prototype chain and finds it onGadget.prototype.
The constructor function Gadget is also an object. Its prototype chain is Gadget -> Function.prototype -> Object.prototype -> null. Therefore, Gadget.power works (static property), but Gadget.start() would fail because start is not defined on the constructor or its prototype chain.
The Impact of Prototype Assignment Timing
The behavior of instanceof depends heavily on when the prototype is assigned relative to object creation.
function Base() {}
function Child() {}
// Scenario 1: Prototype assigned before instantiation
Child.prototype = new Base();
const instanceA = new Child();
console.log(instanceA instanceof Child); // true
console.log(instanceA instanceof Base); // true
// Scenario 2: Prototype assigned after instantiation
function Parent() {}
function Kid() {}
const instanceB = new Kid();
Kid.prototype = new Parent();
console.log(instanceB instanceof Kid); // false
console.log(instanceB instanceof Parent); // false
In the second scenario, instanceB was created with a reference to the original Kid.prototype. Changing Kid.prototype afterwards affects future instances but not instanceB, whose internal prototype link still points to the old prototype object.
Advanced Execution Context Challenge
The following code snippet combines new, this binding, variable hoisting, and prototype chains. Analyze the execution flow:
function Processor() {
log = function() {
console.log(1);
};
return this;
}
Processor.log = function() {
console.log(2);
};
Processor.prototype.log = function() {
console.log(3);
};
var log = function() {
console.log(4);
};
function log() {
console.log(5);
}
// Execution sequence:
Processor.log(); // Output: 2
log(); // Output: 4
Processor().log(); // Output: 1
log(); // Output: 1
new Processor.log(); // Output: 2
new Processor().log(); // Output: 3
new new Processor().log(); // Output: 3
Execution Breakdown:
Processor.log(): Calls the static method attached directly to the function object. Output:2.log(): Variable hoisting moves the declarationvar logand function declarationfunction logto the top. The assignmentlog = function(){...}(outputting 4) overwrites the function declaration. Output:4.Processor().log():Processoris called as a regular function. Inside,log = function(){...}assigns a new function to the globallogvariable (outputting 1). It returnsthis, which is the global object (or undefined in strict mode). Calling.log()on the global object executes the newly assigned globallog. Output:1.log(): The globallogvariable was modified in the previous step. Output:1.new Processor.log(): Thenewoperator is applied to the static methodProcessor.log. It executes the method (outputting 2) and returns a new instance of that function object. Output:2.new Processor().log(): Creates a new instance ofProcessor. The instance inherits fromProcessor.prototype. The.log()method is found on the prototype. Output:3.new new Processor().log(): Equivalent tonew (new Processor().log)(). First,new Processor()creates an instance. Then,instance.logretrieves the prototype method (outputting 3). Finally,newinvokes that method as a constructor. Output:3.