javascript
examples
examples.js⚡javascript
/**
* =====================================================
* 7.3 PROPERTY DESCRIPTORS - EXAMPLES
* =====================================================
* Comprehensive examples of property descriptors in JavaScript
*/
// =====================================================
// EXAMPLE 1: Getting Property Descriptors
// =====================================================
console.log('=== Example 1: Getting Property Descriptors ===');
const person = {
name: 'Alice',
age: 30,
};
// Get descriptor for a single property
const nameDescriptor = Object.getOwnPropertyDescriptor(person, 'name');
console.log('Name descriptor:', nameDescriptor);
/*
{
value: "Alice",
writable: true,
enumerable: true,
configurable: true
}
*/
// Get all descriptors
const allDescriptors = Object.getOwnPropertyDescriptors(person);
console.log('All descriptors:', allDescriptors);
// =====================================================
// EXAMPLE 2: Defining Properties with defineProperty
// =====================================================
console.log('\n=== Example 2: Defining Properties ===');
const config = {};
// Define a read-only property
Object.defineProperty(config, 'VERSION', {
value: '1.0.0',
writable: false,
enumerable: true,
configurable: false,
});
console.log('VERSION:', config.VERSION); // "1.0.0"
// Try to change it (silently fails in non-strict mode)
config.VERSION = '2.0.0';
console.log('After attempted change:', config.VERSION); // Still "1.0.0"
// =====================================================
// EXAMPLE 3: Default Descriptor Values
// =====================================================
console.log('\n=== Example 3: Default Descriptor Values ===');
const obj = {};
// Object literal assignment - all defaults are true
obj.normalProp = 'I am normal';
console.log(
'Normal property:',
Object.getOwnPropertyDescriptor(obj, 'normalProp')
);
// { value: "I am normal", writable: true, enumerable: true, configurable: true }
// defineProperty - all defaults are FALSE!
Object.defineProperty(obj, 'definedProp', {
value: 'I am defined',
// writable, enumerable, configurable all default to false!
});
console.log(
'Defined property:',
Object.getOwnPropertyDescriptor(obj, 'definedProp')
);
// { value: "I am defined", writable: false, enumerable: false, configurable: false }
// =====================================================
// EXAMPLE 4: Writable Attribute
// =====================================================
console.log('\n=== Example 4: Writable Attribute ===');
const constants = {};
Object.defineProperty(constants, 'PI', {
value: 3.14159265359,
writable: false,
enumerable: true,
configurable: false,
});
Object.defineProperty(constants, 'E', {
value: 2.71828182845,
writable: false,
enumerable: true,
configurable: false,
});
console.log('PI:', constants.PI);
console.log('E:', constants.E);
// Cannot modify
constants.PI = 3;
console.log('PI after attempted change:', constants.PI); // Still 3.14159...
// =====================================================
// EXAMPLE 5: Enumerable Attribute
// =====================================================
console.log('\n=== Example 5: Enumerable Attribute ===');
const user = {
name: 'John',
email: 'john@example.com',
};
// Add a non-enumerable property
Object.defineProperty(user, '_internalId', {
value: 'usr_12345',
enumerable: false,
writable: false,
configurable: false,
});
Object.defineProperty(user, '_createdAt', {
value: new Date().toISOString(),
enumerable: false,
});
// Regular enumeration - won't show non-enumerable props
console.log('Object.keys:', Object.keys(user));
// ["name", "email"]
console.log('Object.values:', Object.values(user));
// ["John", "john@example.com"]
console.log('for...in loop:');
for (const key in user) {
console.log(` ${key}: ${user[key]}`);
}
// name: John
// email: john@example.com
console.log('JSON.stringify:', JSON.stringify(user));
// {"name":"John","email":"john@example.com"}
// But we can still access them directly
console.log('Direct access to _internalId:', user._internalId);
// "usr_12345"
// Get ALL properties including non-enumerable
console.log('getOwnPropertyNames:', Object.getOwnPropertyNames(user));
// ["name", "email", "_internalId", "_createdAt"]
// =====================================================
// EXAMPLE 6: Configurable Attribute
// =====================================================
console.log('\n=== Example 6: Configurable Attribute ===');
const secure = {};
// Configurable: true - can be modified or deleted
Object.defineProperty(secure, 'tempData', {
value: 'temporary',
writable: true,
enumerable: true,
configurable: true,
});
// Configurable: false - locked down
Object.defineProperty(secure, 'permanentId', {
value: 'perm_123',
writable: false,
enumerable: true,
configurable: false,
});
// Can modify tempData's configuration
Object.defineProperty(secure, 'tempData', {
enumerable: false, // Works - configurable is true
});
// Can delete tempData
delete secure.tempData;
console.log('After delete tempData:', secure.tempData); // undefined
// Cannot modify permanentId's configuration
try {
Object.defineProperty(secure, 'permanentId', {
enumerable: false, // Fails - configurable is false
});
} catch (e) {
console.log('Cannot reconfigure:', e.message);
}
// Cannot delete permanentId
const deleted = delete secure.permanentId;
console.log('Delete permanentId result:', deleted); // false
console.log('permanentId still exists:', secure.permanentId); // "perm_123"
// =====================================================
// EXAMPLE 7: Accessor Properties (Getters and Setters)
// =====================================================
console.log('\n=== Example 7: Accessor Properties ===');
const temperature = {
_celsius: 0,
};
Object.defineProperty(temperature, 'celsius', {
get() {
return this._celsius;
},
set(value) {
if (typeof value !== 'number') {
throw new TypeError('Temperature must be a number');
}
this._celsius = value;
},
enumerable: true,
configurable: true,
});
Object.defineProperty(temperature, 'fahrenheit', {
get() {
return (this._celsius * 9) / 5 + 32;
},
set(value) {
this._celsius = ((value - 32) * 5) / 9;
},
enumerable: true,
configurable: true,
});
Object.defineProperty(temperature, 'kelvin', {
get() {
return this._celsius + 273.15;
},
set(value) {
this._celsius = value - 273.15;
},
enumerable: true,
configurable: true,
});
// Use the accessors
temperature.celsius = 25;
console.log(`Celsius: ${temperature.celsius}°C`);
console.log(`Fahrenheit: ${temperature.fahrenheit}°F`);
console.log(`Kelvin: ${temperature.kelvin}K`);
// Set via Fahrenheit
temperature.fahrenheit = 212;
console.log(`\nAfter setting to 212°F:`);
console.log(`Celsius: ${temperature.celsius}°C`); // 100
// =====================================================
// EXAMPLE 8: Getter-Only Properties (Read-Only)
// =====================================================
console.log('\n=== Example 8: Getter-Only Properties ===');
const circle = {
_radius: 5,
};
Object.defineProperty(circle, 'radius', {
get() {
return this._radius;
},
set(value) {
if (value <= 0) throw new RangeError('Radius must be positive');
this._radius = value;
},
enumerable: true,
});
// Read-only computed properties
Object.defineProperty(circle, 'diameter', {
get() {
return this._radius * 2;
},
enumerable: true,
});
Object.defineProperty(circle, 'circumference', {
get() {
return 2 * Math.PI * this._radius;
},
enumerable: true,
});
Object.defineProperty(circle, 'area', {
get() {
return Math.PI * this._radius ** 2;
},
enumerable: true,
});
console.log('Radius:', circle.radius);
console.log('Diameter:', circle.diameter);
console.log('Circumference:', circle.circumference.toFixed(2));
console.log('Area:', circle.area.toFixed(2));
// Try to set diameter (no setter, will fail silently)
circle.diameter = 20;
console.log('Diameter after attempted set:', circle.diameter); // Still 10
// =====================================================
// EXAMPLE 9: Object.defineProperties (Multiple)
// =====================================================
console.log('\n=== Example 9: Object.defineProperties ===');
const product = {};
Object.defineProperties(product, {
_id: {
value: 'prod_001',
writable: false,
enumerable: false,
configurable: false,
},
_price: {
value: 0,
writable: true,
enumerable: false,
configurable: false,
},
name: {
value: 'Widget',
writable: true,
enumerable: true,
configurable: true,
},
price: {
get() {
return `$${this._price.toFixed(2)}`;
},
set(value) {
if (value < 0) throw new RangeError('Price cannot be negative');
this._price = value;
},
enumerable: true,
configurable: true,
},
id: {
get() {
return this._id;
},
enumerable: true,
configurable: false,
},
});
product.price = 99.99;
console.log('Product:', product);
console.log('ID:', product.id);
console.log('Price:', product.price);
console.log('Enumerable keys:', Object.keys(product));
// =====================================================
// EXAMPLE 10: Object.freeze()
// =====================================================
console.log('\n=== Example 10: Object.freeze() ===');
const frozenConfig = Object.freeze({
apiUrl: 'https://api.example.com',
timeout: 5000,
retries: 3,
});
console.log('Is frozen:', Object.isFrozen(frozenConfig)); // true
// All these fail silently (or throw in strict mode)
frozenConfig.apiUrl = 'http://new-url.com'; // Cannot change
frozenConfig.newProp = 'value'; // Cannot add
delete frozenConfig.timeout; // Cannot delete
console.log('frozenConfig after attempts:', frozenConfig);
// Still { apiUrl: "https://api.example.com", timeout: 5000, retries: 3 }
// Check property descriptors
console.log(
'apiUrl descriptor:',
Object.getOwnPropertyDescriptor(frozenConfig, 'apiUrl')
);
// writable: false, configurable: false
// =====================================================
// EXAMPLE 11: Object.seal()
// =====================================================
console.log('\n=== Example 11: Object.seal() ===');
const sealedUser = Object.seal({
name: 'Alice',
age: 30,
role: 'admin',
});
console.log('Is sealed:', Object.isSealed(sealedUser)); // true
// CAN change existing values
sealedUser.name = 'Bob';
sealedUser.age = 31;
console.log('After changes:', sealedUser); // { name: "Bob", age: 31, role: "admin" }
// CANNOT add new properties
sealedUser.email = 'bob@example.com';
console.log('Has email:', 'email' in sealedUser); // false
// CANNOT delete properties
delete sealedUser.role;
console.log('Has role:', 'role' in sealedUser); // true
// =====================================================
// EXAMPLE 12: Object.preventExtensions()
// =====================================================
console.log('\n=== Example 12: Object.preventExtensions() ===');
const limited = Object.preventExtensions({
x: 1,
y: 2,
});
console.log('Is extensible:', Object.isExtensible(limited)); // false
// CAN modify existing properties
limited.x = 100;
console.log('x:', limited.x); // 100
// CAN delete properties
delete limited.y;
console.log('y:', limited.y); // undefined
// CANNOT add new properties
limited.z = 3;
console.log('z:', limited.z); // undefined
// =====================================================
// EXAMPLE 13: Deep Freeze
// =====================================================
console.log('\n=== Example 13: Deep Freeze ===');
function deepFreeze(obj) {
// Freeze properties before freezing self
Object.getOwnPropertyNames(obj).forEach((name) => {
const prop = obj[name];
if (prop && typeof prop === 'object') {
deepFreeze(prop);
}
});
return Object.freeze(obj);
}
const shallowFrozen = Object.freeze({
level1: 'frozen',
nested: {
level2: 'not frozen with Object.freeze',
},
});
// Nested object can be modified!
shallowFrozen.nested.level2 = 'CHANGED!';
console.log('Shallow frozen nested:', shallowFrozen.nested.level2); // "CHANGED!"
// Deep freeze example
const deepFrozenObj = deepFreeze({
level1: 'frozen',
nested: {
level2: 'also frozen',
deeper: {
level3: 'frozen too',
},
},
});
deepFrozenObj.nested.level2 = 'try to change';
console.log('Deep frozen nested:', deepFrozenObj.nested.level2); // "also frozen"
// =====================================================
// EXAMPLE 14: Creating Immutable Objects with Proxies
// =====================================================
console.log('\n=== Example 14: Immutable Objects with Proxies ===');
function createImmutable(obj) {
return new Proxy(obj, {
set(target, property, value) {
throw new TypeError(
`Cannot modify property '${property}' of immutable object`
);
},
deleteProperty(target, property) {
throw new TypeError(
`Cannot delete property '${property}' of immutable object`
);
},
get(target, property) {
const value = target[property];
// Recursively wrap nested objects
if (value && typeof value === 'object') {
return createImmutable(value);
}
return value;
},
});
}
const immutableData = createImmutable({
user: {
name: 'Alice',
settings: {
theme: 'dark',
},
},
});
try {
immutableData.user.name = 'Bob';
} catch (e) {
console.log('Caught error:', e.message);
}
console.log('Name still:', immutableData.user.name); // "Alice"
// =====================================================
// EXAMPLE 15: Lazy Property Initialization
// =====================================================
console.log('\n=== Example 15: Lazy Property Initialization ===');
function createLazyObject() {
const obj = {};
Object.defineProperty(obj, 'expensiveData', {
get() {
console.log('Computing expensive data...');
const data = Array.from({ length: 5 }, (_, i) => i * i);
// Replace getter with computed value (self-modifying)
Object.defineProperty(obj, 'expensiveData', {
value: data,
writable: false,
enumerable: true,
configurable: false,
});
return data;
},
enumerable: true,
configurable: true,
});
return obj;
}
const lazy = createLazyObject();
console.log('Object created');
// First access - computes value
console.log('First access:', lazy.expensiveData);
// "Computing expensive data..." then [0, 1, 4, 9, 16]
// Second access - uses cached value
console.log('Second access:', lazy.expensiveData);
// [0, 1, 4, 9, 16] (no "Computing" message)
// =====================================================
// EXAMPLE 16: Property Validation with Setters
// =====================================================
console.log('\n=== Example 16: Property Validation ===');
function createValidatedUser() {
const user = {
_email: '',
_age: 0,
};
Object.defineProperties(user, {
email: {
get() {
return this._email;
},
set(value) {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailRegex.test(value)) {
throw new TypeError(`Invalid email: ${value}`);
}
this._email = value;
},
enumerable: true,
},
age: {
get() {
return this._age;
},
set(value) {
if (!Number.isInteger(value)) {
throw new TypeError('Age must be an integer');
}
if (value < 0 || value > 150) {
throw new RangeError('Age must be between 0 and 150');
}
this._age = value;
},
enumerable: true,
},
});
return user;
}
const validatedUser = createValidatedUser();
// Valid values
validatedUser.email = 'alice@example.com';
validatedUser.age = 30;
console.log('Valid user:', {
email: validatedUser.email,
age: validatedUser.age,
});
// Invalid values
try {
validatedUser.email = 'not-an-email';
} catch (e) {
console.log('Email validation:', e.message);
}
try {
validatedUser.age = 200;
} catch (e) {
console.log('Age validation:', e.message);
}
// =====================================================
// EXAMPLE 17: Creating Constants Object
// =====================================================
console.log('\n=== Example 17: Creating Constants Object ===');
function createConstants(constantsObj) {
const constants = {};
for (const [key, value] of Object.entries(constantsObj)) {
Object.defineProperty(constants, key, {
value: value,
writable: false,
enumerable: true,
configurable: false,
});
}
return Object.freeze(constants);
}
const HTTP_STATUS = createConstants({
OK: 200,
CREATED: 201,
BAD_REQUEST: 400,
UNAUTHORIZED: 401,
NOT_FOUND: 404,
SERVER_ERROR: 500,
});
console.log('HTTP_STATUS:', HTTP_STATUS);
console.log('OK:', HTTP_STATUS.OK);
// Cannot modify
HTTP_STATUS.OK = 999;
console.log('OK after attempted change:', HTTP_STATUS.OK); // Still 200
// =====================================================
// EXAMPLE 18: Private Properties Pattern
// =====================================================
console.log('\n=== Example 18: Private Properties Pattern ===');
function createCounter(initialValue = 0) {
const counter = {};
let count = initialValue;
Object.defineProperties(counter, {
value: {
get() {
return count;
},
enumerable: true,
},
increment: {
value() {
count++;
return this;
},
enumerable: false,
},
decrement: {
value() {
count--;
return this;
},
enumerable: false,
},
reset: {
value() {
count = initialValue;
return this;
},
enumerable: false,
},
});
return Object.freeze(counter);
}
const counter = createCounter(10);
console.log('Initial value:', counter.value);
counter.increment().increment().increment();
console.log('After 3 increments:', counter.value);
counter.reset();
console.log('After reset:', counter.value);
// Methods are hidden from enumeration
console.log('Visible properties:', Object.keys(counter));
// =====================================================
// EXAMPLE 19: Observable Properties
// =====================================================
console.log('\n=== Example 19: Observable Properties ===');
function createObservable(initial) {
const listeners = {};
const data = { ...initial };
const observable = {};
for (const key of Object.keys(initial)) {
Object.defineProperty(observable, key, {
get() {
return data[key];
},
set(newValue) {
const oldValue = data[key];
if (oldValue !== newValue) {
data[key] = newValue;
if (listeners[key]) {
listeners[key].forEach((fn) => fn(newValue, oldValue, key));
}
}
},
enumerable: true,
});
}
// Add subscribe method (non-enumerable)
Object.defineProperty(observable, 'subscribe', {
value(property, callback) {
if (!listeners[property]) {
listeners[property] = [];
}
listeners[property].push(callback);
// Return unsubscribe function
return () => {
listeners[property] = listeners[property].filter(
(fn) => fn !== callback
);
};
},
});
return observable;
}
const state = createObservable({ count: 0, name: 'Initial' });
// Subscribe to changes
const unsubscribe = state.subscribe('count', (newVal, oldVal) => {
console.log(`count changed: ${oldVal} -> ${newVal}`);
});
state.count = 1; // "count changed: 0 -> 1"
state.count = 2; // "count changed: 1 -> 2"
unsubscribe();
state.count = 3; // No log (unsubscribed)
console.log('Final count:', state.count);
// =====================================================
// EXAMPLE 20: Copying Objects with Descriptors
// =====================================================
console.log('\n=== Example 20: Copying Objects with Descriptors ===');
const original = {
visible: "I'm enumerable",
};
Object.defineProperty(original, 'hidden', {
value: "I'm not enumerable",
enumerable: false,
});
Object.defineProperty(original, 'computed', {
get() {
return "I'm a getter";
},
enumerable: true,
});
// Object.assign loses descriptors
const shallowCopy = Object.assign({}, original);
console.log('Shallow copy keys:', Object.keys(shallowCopy));
console.log(
'Shallow copy descriptors:',
Object.getOwnPropertyDescriptors(shallowCopy)
);
// Proper copy with descriptors
const properCopy = Object.create(
Object.getPrototypeOf(original),
Object.getOwnPropertyDescriptors(original)
);
console.log('\nProper copy keys:', Object.keys(properCopy));
console.log(
'Proper copy descriptors:',
Object.getOwnPropertyDescriptors(properCopy)
);
console.log('Hidden property preserved:', properCopy.hidden);
console.log('Getter works:', properCopy.computed);
console.log('\n=== All Examples Complete ===');