Fading Coder

One Final Commit for the Last Sprint

Home > Tech > Content

Understanding the 'in' Operator and Property Enumeration in JavaScript

Tech May 19 2

The in operator in JavaScript offers two primary use cases: standalone checks and iteration with in for-in loops. When used independently, in returns true if a specified property is accessible through an object, regardless of whether that property resides directly on the instance or is inherited from its prototype chain.

Consider the following example:


function Person() {
   this.instanceProp = "Instance Value";
}

Person.prototype.inheritedProp = "Prototype Value";
Person.prototype.showProp = function() {
   console.log(this.instanceProp || this.inheritedProp);
};

let personInstance1 = new Person();
let personInstance2 = new Person();

// Initially, 'inheritedProp' is only on the prototype
console.log(personInstance1.hasOwnProperty("inheritedProp")); // false
console.log("inheritedProp" in personInstance1); // true

// Adding 'inheritedProp' to the instance
personInstance1.inheritedProp = "Overridden Value";
console.log(personInstance1.inheritedProp); // "Overridden Value" (from instance)
console.log(personInstance1.hasOwnProperty("inheritedProp")); // true
console.log("inheritedProp" in personInstance1); // true

// 'personInstance2' still accesses 'inheritedProp' from the prototype
console.log(personInstance2.inheritedProp); // "Prototype Value"
console.log(personInstance2.hasOwnProperty("inheritedProp")); // false
console.log("inheritedProp" in personInstance2); // true

// Removing the instance property allows access to the prototype property again
delete personInstance1.inheritedProp;
console.log(personInstance1.inheritedProp); // "Prototype Value" (from prototype)
console.log(personInstance1.hasOwnProperty("inheritedProp")); // false
console.log("inheritedProp" in personInstance1); // true
   

In the scenario above, the inheritedProp is always accessible via personInstance1, either directly or through inheritance, hence "inheritedProp" in personInstance1 consistently returns true. To specifically identify properties located on the prototype chain, you can combine hasOwnProperty() with the in operator.

A helper function can determine if a property exists solely on the prototype:


function hasPrototypeProperty(object, propertyName) {
   return !object.hasOwnProperty(propertyName) && (propertyName in object);
}
   

The in operator returns true for any accessible property, while hasOwnProperty() returns true only for properties defined directly on the instance. Therefore, if in evaluates to true and hasOwnProperty() returns false, the property is confirmed to be an inherited one.


// Using the Person constructor and its prototype from the previous example
let person = new Person();

console.log(hasPrototypeProperty(person, "inheritedProp")); // true (initially)

person.inheritedProp = "Greg"; // Overriding the prototype property
console.log(hasPrototypeProperty(person, "inheritedProp")); // false (now it's an instance property)
   

Initially, "inheritedProp" exists only on the prototype, so hasPrototypeProperty returns true. After overriding it on the instance, the instance property takes precedence, causing hasPrototypeProperty to return false, even though the prototype still holds the property.

When used in a for-in loop, the in operator enumerates all property accessible through the object that are also enumerable, including both instance and prototype properties. Instance properties that shadow non-enumerable prototype properties will also be included in the for-in loop, as developer-defined properties are typically enumerable by default.

To retrieve only the enumerable own properties of an object, the Object.keys() method is useful. It accepts an object and returns an array of strings representing the names of its enumerable own properties.


// Using the Person constructor and its prototype
let prototypeProperties = Object.keys(Person.prototype);
console.log(prototypeProperties); // ["inheritedProp", "showProp"] (order might vary)

let p1 = new Person();
p1.name = "Rob"; // Add an instance property
p1.age = 31;    // Add another instance property

let p1InstanceKeys = Object.keys(p1);
console.log(p1InstanceKeys); // ["name", "age"] (order might vary)
   

In this example, prototypeProperties contains an array of the enumerable properties of Person.prototype. When called on an instance p1, Object.keys(p1) returns only the enumerable properties directly defined on p1.

For a comprehensive list of all own properties, regardless of enumerability, use Object.getOwnPropertyNames():


// Continuing with Person.prototype
let allPrototypeProps = Object.getOwnPropertyNames(Person.prototype);
console.log(allPrototypeProps); // ["constructor", "inheritedProp", "showProp"] (constructor is non-enumerable)
   

Note that the result includes the non-enumerable constructor property. Both Object.keys() and Object.getOwnPropertyNames() serve as effective alternatives to for-in loops in specific scenarios.

With the introduction of Symbol types in ECMAScript 6, a companion method to Object.getOwnPropertyNames() was needed for properties keyed by Symbols, as these lack a traditional name. This led to the creation of Object.getOwnPropertySymbols().


let sym1 = Symbol('key1');
let sym2 = Symbol('key2');

let objWithSymbols = {
   [sym1]: 'Symbol Value 1',
   [sym2]: 'Symbol Value 2',
   regularProp: 'Regular Value'
};

console.log(Object.getOwnPropertySymbols(objWithSymbols));
// Expected output: [Symbol(key1), Symbol(key2)]
   

Property Enumeration Order

There are significant differences in property enumeration order among for-in, Object.keys(), Object.getOwnPropertyNames(), Object.getOwnPropertySymbols(), and Object.assign(). The enumeration order for for-in and Object.keys() is generally not guaranteed and can vary across JavaScript engines and browsers.

Conversely, Object.getOwnPropertyNames(), Object.getOwnPropertySymbols(), and Object.assign() adhere to a deterministic order: numeric keys are enumerated in ascending order first, followed by string keys and Symbol keys in the order they were inserted. For object literals, keys are inserted in the order they appear, separated by commas.


let symA = Symbol('symA');
let symB = Symbol('symB');

let obj = {
   10: 'ten',
   'beta': 'b',
   [symA]: 'symbol A',
   'alpha': 'a',
   0: 'zero',
   [symB]: 'symbol B',
   2: 'two',
   'gamma': 'g'
};

obj[1] = 'one'; // Inserted based on insertion order

console.log(Object.getOwnPropertyNames(obj));
// Expected output: ["0", "1", "2", "10", "alpha", "beta", "gamma"]

console.log(Object.getOwnPropertySymbols(obj));
// Expected output: [Symbol(symA), Symbol(symB)] (order depends on insertion)
   

Related Articles

Comprehensive Guide to SSTI Explained with Payload Bypass Techniques

Introduction Server-Side Template Injection (SSTI) is a vulnerability in web applications where user input is improper handled within the template engine and executed on the server. This exploit can r...

SBUS Signal Analysis and Communication Implementation Using STM32 with Fus Remote Controller

Overview In a recent project, I utilized the SBUS protocol with the Fus remote controller to control a vehicle's basic operations, including movement, lights, and mode switching. This article is aimed...

Comprehensive Guide to Hive SQL Syntax and Operations

This article provides a detailed walkthrough of Hive SQL, categorizing its features and syntax for practical use. Hive SQL is segmented into the following categories: DDL Statements: Operations on...

Leave a Comment

Anonymous

◎Feel free to join the discussion and share your thoughts.