Object Methods and Utilities
Object Methods and Utilities
Section titled “Object Methods and Utilities”Objects are the fundamental building blocks of JavaScript. This comprehensive guide covers all object methods, property manipulation techniques, and advanced patterns for working with objects effectively.
Object Creation and Construction
Section titled “Object Creation and Construction”Object Creation Patterns
Section titled “Object Creation Patterns”// Object literal (most common)const person = { name: 'Alice', age: 30, city: 'New York', greet() { return `Hello, I'm ${this.name}`; }};
// Object constructorconst user = new Object();user.name = 'Bob';user.age = 25;
// Object.create() - specify prototypeconst protoObject = { greet() { return `Hello from ${this.name}`; }};
const child = Object.create(protoObject);child.name = 'Charlie';
// Object.create(null) - no prototype chainconst pureObject = Object.create(null);pureObject.data = 'value';console.log(pureObject.toString); // undefined
// Factory function patternfunction createPerson(name, age, city) { return { name, age, city, greet() { return `Hello, I'm ${this.name} from ${this.city}`; }, getAge() { return this.age; }, haveBirthday() { this.age++; return this.age; } };}
const alice = createPerson('Alice', 30, 'New York');const bob = createPerson('Bob', 25, 'Los Angeles');
// Constructor function patternfunction Person(name, age, city) { this.name = name; this.age = age; this.city = city;}
Person.prototype.greet = function() { return `Hello, I'm ${this.name}`;};
Person.prototype.haveBirthday = function() { this.age++; return this.age;};
const person1 = new Person('David', 35, 'Chicago');const person2 = new Person('Eve', 28, 'Seattle');
// Class syntax (ES6+)class ModernPerson { constructor(name, age, city) { this.name = name; this.age = age; this.city = city; }
greet() { return `Hello, I'm ${this.name}`; }
haveBirthday() { this.age++; return this.age; }
static species() { return 'Homo sapiens'; }}
const modern1 = new ModernPerson('Frank', 40, 'Miami');
Property Definition and Descriptors
Section titled “Property Definition and Descriptors”const obj = {};
// Simple property assignmentobj.simpleProperty = 'value';
// Object.defineProperty() - detailed controlObject.defineProperty(obj, 'configuredProperty', { value: 'configured value', writable: true, // Can be changed enumerable: true, // Shows in for...in and Object.keys() configurable: true // Can be deleted or reconfigured});
// Read-only propertyObject.defineProperty(obj, 'readOnly', { value: 'cannot change', writable: false, enumerable: true, configurable: false});
// Hidden property (non-enumerable)Object.defineProperty(obj, 'hidden', { value: 'secret', writable: true, enumerable: false, // Won't appear in Object.keys() configurable: true});
// Getter and setter propertiesObject.defineProperty(obj, 'computed', { get() { return this._computed || 'default'; }, set(value) { this._computed = value.toUpperCase(); }, enumerable: true, configurable: true});
// Object.defineProperties() - define multiple propertiesObject.defineProperties(obj, { prop1: { value: 'value1', writable: true }, prop2: { value: 'value2', writable: false }, prop3: { get() { return this._prop3; }, set(value) { this._prop3 = value * 2; } }});
// Get property descriptorconst descriptor = Object.getOwnPropertyDescriptor(obj, 'readOnly');console.log(descriptor);// { value: 'cannot change', writable: false, enumerable: true, configurable: false }
// Get all property descriptorsconst allDescriptors = Object.getOwnPropertyDescriptors(obj);console.log(allDescriptors);
Object Inspection and Enumeration
Section titled “Object Inspection and Enumeration”Property Enumeration Methods
Section titled “Property Enumeration Methods”const obj = { own1: 'value1', own2: 'value2', own3: 'value3'};
// Add non-enumerable propertyObject.defineProperty(obj, 'hidden', { value: 'hidden value', enumerable: false});
// Add property to prototypeObject.prototype.inherited = 'inherited value';
// Object.keys() - own enumerable string keysconst keys = Object.keys(obj);console.log(keys); // ['own1', 'own2', 'own3']
// Object.values() - own enumerable valuesconst values = Object.values(obj);console.log(values); // ['value1', 'value2', 'value3']
// Object.entries() - own enumerable key-value pairsconst entries = Object.entries(obj);console.log(entries); // [['own1', 'value1'], ['own2', 'value2'], ['own3', 'value3']]
// Object.getOwnPropertyNames() - all own string keys (including non-enumerable)const allKeys = Object.getOwnPropertyNames(obj);console.log(allKeys); // ['own1', 'own2', 'own3', 'hidden']
// Object.getOwnPropertySymbols() - own symbol keysconst sym1 = Symbol('symbol1');const sym2 = Symbol('symbol2');obj[sym1] = 'symbol value 1';obj[sym2] = 'symbol value 2';
const symbolKeys = Object.getOwnPropertySymbols(obj);console.log(symbolKeys); // [Symbol(symbol1), Symbol(symbol2)]
// Reflect.ownKeys() - all own keys (strings and symbols)const allOwnKeys = Reflect.ownKeys(obj);console.log(allOwnKeys); // ['own1', 'own2', 'own3', 'hidden', Symbol(symbol1), Symbol(symbol2)]
// for...in loop - enumerable properties (including inherited)for (const key in obj) { console.log(key); // own1, own2, own3, inherited}
// for...in with hasOwnProperty checkfor (const key in obj) { if (obj.hasOwnProperty(key)) { console.log(key); // own1, own2, own3 }}
// Clean up prototype pollution exampledelete Object.prototype.inherited;
Property Testing and Existence
Section titled “Property Testing and Existence”const obj = { prop1: 'value1', prop2: null, prop3: undefined, prop4: 0, prop5: false, prop6: ''};
// hasOwnProperty() - check own propertyconsole.log(obj.hasOwnProperty('prop1')); // trueconsole.log(obj.hasOwnProperty('toString')); // false (inherited)
// Object.hasOwn() - ES2022 alternative to hasOwnPropertyconsole.log(Object.hasOwn(obj, 'prop1')); // trueconsole.log(Object.hasOwn(obj, 'toString')); // false
// in operator - check property (including inherited)console.log('prop1' in obj); // trueconsole.log('toString' in obj); // true (inherited from Object.prototype)
// propertyIsEnumerable() - check if property is enumerableconsole.log(obj.propertyIsEnumerable('prop1')); // true
Object.defineProperty(obj, 'nonEnum', { value: 'hidden', enumerable: false});console.log(obj.propertyIsEnumerable('nonEnum')); // false
// Difference between undefined and non-existentconsole.log(obj.prop3); // undefinedconsole.log(obj.nonExistent); // undefinedconsole.log('prop3' in obj); // trueconsole.log('nonExistent' in obj); // false
// Safe property access patternsfunction safeGet(obj, path, defaultValue = undefined) { const keys = path.split('.'); let current = obj;
for (const key of keys) { if (current == null || !(key in current)) { return defaultValue; } current = current[key]; }
return current;}
const nested = { user: { profile: { name: 'Alice', settings: { theme: 'dark' } } }};
console.log(safeGet(nested, 'user.profile.name')); // 'Alice'console.log(safeGet(nested, 'user.profile.age', 'unknown')); // 'unknown'console.log(safeGet(nested, 'user.invalid.path', 'default')); // 'default'
// Optional chaining (ES2020)console.log(nested.user?.profile?.name); // 'Alice'console.log(nested.user?.profile?.age); // undefinedconsole.log(nested.user?.invalid?.path); // undefined
Object Manipulation and Transformation
Section titled “Object Manipulation and Transformation”Copying and Merging Objects
Section titled “Copying and Merging Objects”// Shallow copyingconst original = { name: 'Alice', age: 30, hobbies: ['reading', 'swimming'], address: { city: 'New York', zip: '10001' }};
// Object.assign() - shallow copyconst copy1 = Object.assign({}, original);
// Spread operator - shallow copyconst copy2 = { ...original };
// Both shallow copies share nested objectscopy1.hobbies.push('cycling');console.log(original.hobbies); // ['reading', 'swimming', 'cycling']
// Deep copying solutionsfunction deepClone(obj) { if (obj === null || typeof obj !== 'object') return obj; if (obj instanceof Date) return new Date(obj.getTime()); if (obj instanceof Array) return obj.map(deepClone); if (obj instanceof Object) { const cloned = {}; for (const key in obj) { if (obj.hasOwnProperty(key)) { cloned[key] = deepClone(obj[key]); } } return cloned; }}
// Modern deep cloning (where supported)const deepCopy = structuredClone(original);
// JSON method (limited - no functions, dates become strings, etc.)const jsonCopy = JSON.parse(JSON.stringify(original));
// Object merging with Object.assign()const defaults = { theme: 'light', language: 'en', notifications: true, volume: 50};
const userPrefs = { theme: 'dark', volume: 75};
const settings = Object.assign({}, defaults, userPrefs);// { theme: 'dark', language: 'en', notifications: true, volume: 75 }
// Spread operator mergingconst spreadSettings = { ...defaults, ...userPrefs };
// Deep merging functionfunction deepMerge(target, ...sources) { if (!sources.length) return target; const source = sources.shift();
if (isObject(target) && isObject(source)) { for (const key in source) { if (isObject(source[key])) { if (!target[key]) Object.assign(target, { [key]: {} }); deepMerge(target[key], source[key]); } else { Object.assign(target, { [key]: source[key] }); } } }
return deepMerge(target, ...sources);}
function isObject(item) { return item && typeof item === 'object' && !Array.isArray(item);}
const obj1 = { a: 1, b: { c: 2, d: 3 }};
const obj2 = { b: { d: 4, e: 5 }, f: 6};
const merged = deepMerge({}, obj1, obj2);// { a: 1, b: { c: 2, d: 4, e: 5 }, f: 6 }
Object Transformation Utilities
Section titled “Object Transformation Utilities”// Transform object keysfunction transformKeys(obj, transformer) { return Object.keys(obj).reduce((result, key) => { result[transformer(key)] = obj[key]; return result; }, {});}
// Transform object valuesfunction transformValues(obj, transformer) { return Object.keys(obj).reduce((result, key) => { result[key] = transformer(obj[key], key); return result; }, {});}
// Pick specific propertiesfunction pick(obj, keys) { return keys.reduce((result, key) => { if (key in obj) { result[key] = obj[key]; } return result; }, {});}
// Omit specific propertiesfunction omit(obj, keys) { const omitSet = new Set(keys); return Object.keys(obj).reduce((result, key) => { if (!omitSet.has(key)) { result[key] = obj[key]; } return result; }, {});}
// Compact object (remove falsy values)function compact(obj) { return Object.keys(obj).reduce((result, key) => { if (obj[key]) { result[key] = obj[key]; } return result; }, {});}
// Invert object (swap keys and values)function invert(obj) { return Object.keys(obj).reduce((result, key) => { result[obj[key]] = key; return result; }, {});}
// Group object entries by valuefunction groupBy(obj, grouper) { return Object.keys(obj).reduce((result, key) => { const group = grouper(obj[key], key); if (!result[group]) { result[group] = {}; } result[group][key] = obj[key]; return result; }, {});}
// Usage examplesconst data = { firstName: 'Alice', lastName: 'Johnson', age: 30, city: 'New York', country: 'USA', isActive: true, score: null, notes: ''};
// Convert to camelCase keysconst camelCased = transformKeys(data, key => key.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase()));
// Double numeric valuesconst doubled = transformValues(data, (value, key) => typeof value === 'number' ? value * 2 : value);
// Pick only name-related fieldsconst nameOnly = pick(data, ['firstName', 'lastName']);// { firstName: 'Alice', lastName: 'Johnson' }
// Omit location fieldsconst withoutLocation = omit(data, ['city', 'country']);
// Remove falsy valuesconst compacted = compact(data);// { firstName: 'Alice', lastName: 'Johnson', age: 30, city: 'New York', country: 'USA', isActive: true }
// Invert simple objectconst statusCodes = { ok: 200, notFound: 404, error: 500 };const codeToStatus = invert(statusCodes);// { '200': 'ok', '404': 'notFound', '500': 'error' }
// Group by data typeconst grouped = groupBy(data, (value) => typeof value);// { string: { firstName: 'Alice', ... }, number: { age: 30 }, boolean: { isActive: true }, object: { score: null, notes: '' } }
Advanced Object Patterns
Section titled “Advanced Object Patterns”Object Immutability
Section titled “Object Immutability”// Object.freeze() - prevent all modificationsconst frozen = Object.freeze({ name: 'Alice', age: 30, hobbies: ['reading', 'swimming']});
// These operations will fail silently in non-strict mode, throw in strict mode// frozen.name = 'Bob'; // Cannot modify// frozen.city = 'New York'; // Cannot add// delete frozen.age; // Cannot delete
console.log(Object.isFrozen(frozen)); // true
// Note: freeze is shallowfrozen.hobbies.push('cycling'); // This works! Array is not frozen
// Deep freeze implementationfunction deepFreeze(obj) { Object.getOwnPropertyNames(obj).forEach(prop => { if (obj[prop] && typeof obj[prop] === 'object') { deepFreeze(obj[prop]); } }); return Object.freeze(obj);}
const deepFrozen = deepFreeze({ name: 'Alice', hobbies: ['reading', 'swimming'], address: { city: 'New York', coordinates: { lat: 40.7128, lng: -74.0060 } }});
// Object.seal() - prevent property addition/deletion, allow modificationconst sealed = Object.seal({ name: 'Bob', age: 25});
sealed.name = 'Robert'; // This works// sealed.city = 'Boston'; // Cannot add new properties// delete sealed.age; // Cannot delete properties
console.log(Object.isSealed(sealed)); // true
// Object.preventExtensions() - prevent property addition onlyconst extensionPrevented = Object.preventExtensions({ name: 'Charlie', age: 35});
extensionPrevented.name = 'Charles'; // This worksdelete extensionPrevented.age; // This works// extensionPrevented.city = 'LA'; // Cannot add new properties
console.log(Object.isExtensible(extensionPrevented)); // false
// Immutable update patternsfunction updateObject(obj, updates) { return { ...obj, ...updates };}
function updateNested(obj, path, value) { const keys = path.split('.'); const [head, ...tail] = keys;
if (tail.length === 0) { return { ...obj, [head]: value }; }
return { ...obj, [head]: updateNested(obj[head] || {}, tail.join('.'), value) };}
const user = { name: 'Alice', profile: { settings: { theme: 'light', notifications: true } }};
const updatedUser = updateNested(user, 'profile.settings.theme', 'dark');// Original user object is unchangedconsole.log(user.profile.settings.theme); // 'light'console.log(updatedUser.profile.settings.theme); // 'dark'
Object Proxies and Meta-programming
Section titled “Object Proxies and Meta-programming”// Basic Proxy exampleconst target = { name: 'Alice', age: 30};
const proxy = new Proxy(target, { get(obj, prop) { console.log(`Getting property: ${prop}`); return obj[prop]; },
set(obj, prop, value) { console.log(`Setting property: ${prop} = ${value}`); obj[prop] = value; return true; },
has(obj, prop) { console.log(`Checking property: ${prop}`); return prop in obj; }});
proxy.name; // Logs: "Getting property: name"proxy.city = 'NY'; // Logs: "Setting property: city = NY"'age' in proxy; // Logs: "Checking property: age"
// Validation proxyfunction createValidatedObject(schema) { return new Proxy({}, { set(obj, prop, value) { const validator = schema[prop]; if (validator && !validator(value)) { throw new Error(`Invalid value for property ${prop}: ${value}`); } obj[prop] = value; return true; } });}
const userSchema = { name: value => typeof value === 'string' && value.length > 0, age: value => typeof value === 'number' && value >= 0, email: value => typeof value === 'string' && value.includes('@')};
const validatedUser = createValidatedObject(userSchema);validatedUser.name = 'Alice'; // OKvalidatedUser.age = 30; // OK// validatedUser.age = -5; // Throws error// validatedUser.email = 'invalid'; // Throws error
// Default values proxyfunction withDefaults(target, defaults) { return new Proxy(target, { get(obj, prop) { return prop in obj ? obj[prop] : defaults[prop]; } });}
const config = withDefaults({ port: 3000}, { host: 'localhost', port: 8080, ssl: false});
console.log(config.port); // 3000 (from target)console.log(config.host); // 'localhost' (from defaults)console.log(config.ssl); // false (from defaults)
// Observable proxyfunction createObservable(target, onChange) { return new Proxy(target, { set(obj, prop, value) { const oldValue = obj[prop]; obj[prop] = value; onChange(prop, value, oldValue); return true; } });}
const observable = createObservable({ count: 0 }, (prop, newVal, oldVal) => { console.log(`${prop} changed from ${oldVal} to ${newVal}`);});
observable.count = 1; // Logs: "count changed from 0 to 1"observable.count = 2; // Logs: "count changed from 1 to 2"
// Revocable proxyconst { proxy: revocableProxy, revoke } = Proxy.revocable(target, { get(obj, prop) { return obj[prop]; }});
console.log(revocableProxy.name); // 'Alice'revoke();// console.log(revocableProxy.name); // Throws TypeError: Cannot perform 'get' on a proxy that has been revoked
Object Pattern Matching and Destructuring
Section titled “Object Pattern Matching and Destructuring”// Advanced destructuring patternsconst user = { id: 1, name: 'Alice Johnson', email: 'alice@example.com', profile: { bio: 'Software developer', location: 'New York', skills: ['JavaScript', 'React', 'Node.js'], social: { github: 'alicejohnson', twitter: '@alice_codes' } }, preferences: { theme: 'dark', notifications: { email: true, push: false, sms: true } }};
// Basic destructuring with renaming and defaultsconst { name: fullName, email: emailAddress = 'no-email@example.com', phone = 'No phone provided'} = user;
// Nested destructuringconst { profile: { bio, location, skills: [primarySkill, ...otherSkills], social: { github, twitter } }} = user;
// Deep nested destructuringconst { preferences: { theme, notifications: { email: emailNotifs, push: pushNotifs } }} = user;
// Rest propertiesconst { id, name, ...userWithoutIdAndName } = user;
// Destructuring in function parametersfunction greetUser({ name, profile: { location } }) { return `Hello ${name} from ${location}!`;}
console.log(greetUser(user)); // "Hello Alice Johnson from New York!"
// Destructuring with computed property namesconst propName = 'email';const { [propName]: userEmail } = user;
// Pattern matching utilityfunction matchPattern(obj, pattern) { function match(obj, pattern, path = '') { if (pattern === obj) return true; if (typeof pattern !== 'object' || pattern === null) return false; if (typeof obj !== 'object' || obj === null) return false;
for (const key in pattern) { const currentPath = path ? `${path}.${key}` : key; if (!(key in obj)) return false; if (!match(obj[key], pattern[key], currentPath)) return false; } return true; }
return match(obj, pattern);}
// Usageconst pattern1 = { name: 'Alice Johnson' };const pattern2 = { profile: { location: 'New York' } };const pattern3 = { profile: { skills: ['JavaScript'] } }; // This won't match exactly
console.log(matchPattern(user, pattern1)); // trueconsole.log(matchPattern(user, pattern2)); // trueconsole.log(matchPattern(user, pattern3)); // false (arrays don't match exactly)
// Extract values by patternfunction extractByPattern(obj, pattern) { const result = {};
function extract(obj, pattern, target, path = '') { for (const key in pattern) { const currentPath = path ? `${path}.${key}` : key; if (key in obj) { if (typeof pattern[key] === 'object' && pattern[key] !== null && !Array.isArray(pattern[key])) { target[key] = {}; extract(obj[key], pattern[key], target[key], currentPath); } else { target[key] = obj[key]; } } } }
extract(obj, pattern, result); return result;}
const extracted = extractByPattern(user, { name: true, profile: { bio: true, skills: true }});// { name: 'Alice Johnson', profile: { bio: 'Software developer', skills: [...] } }
This comprehensive guide covers the vast majority of object operations and patterns in JavaScript. Objects are central to JavaScript programming, and mastering these techniques will greatly enhance your ability to work with complex data structures and implement sophisticated programming patterns.
Practice these object methods and patterns to become proficient in JavaScript object manipulation and advanced programming techniques.