阿里云主机折上折
  • 微信号
Current Site:Index > Property descriptors

Property descriptors

Author:Chuan Chen 阅读数:63473人阅读 分类: JavaScript

Property Descriptors

In JavaScript, object properties are not just simple key-value pairs. Each property is actually associated with a property descriptor object. Property descriptors define the behavioral characteristics of properties, including whether they are writable, enumerable, configurable, and more. Understanding property descriptors is key to mastering the JavaScript object model.

Types of Property Descriptors

Property descriptors are divided into two main types: data descriptors and accessor descriptors. Data descriptors directly define a property's value and its characteristics, while accessor descriptors control property access through getter and setter functions.

Data descriptors include the following optional keys:

  • value: The value of the property
  • writable: Whether the property can be modified
  • enumerable: Whether the property is enumerable
  • configurable: Whether the property is configurable

Accessor descriptors include:

  • get: A function to get the property value
  • set: A function to set the property value
  • enumerable: Whether the property is enumerable
  • configurable: Whether the property is configurable
// Data descriptor example
const obj1 = {};
Object.defineProperty(obj1, 'name', {
  value: 'John',
  writable: true,
  enumerable: true,
  configurable: true
});

// Accessor descriptor example
const obj2 = {};
let _age = 25;
Object.defineProperty(obj2, 'age', {
  get() { return _age; },
  set(newVal) { _age = newVal > 0 ? newVal : _age; },
  enumerable: true,
  configurable: false
});

Retrieving Property Descriptors

You can use the Object.getOwnPropertyDescriptor() method to retrieve the descriptor of a specific property of an object:

const person = { name: 'Alice' };
const descriptor = Object.getOwnPropertyDescriptor(person, 'name');
console.log(descriptor);
/*
{
  value: "Alice",
  writable: true,
  enumerable: true,
  configurable: true
}
*/

To retrieve descriptors for all own properties of an object, use Object.getOwnPropertyDescriptors():

const car = {
  brand: 'Toyota',
  year: 2020
};
const descriptors = Object.getOwnPropertyDescriptors(car);
console.log(descriptors);
/*
{
  brand: {
    value: "Toyota",
    writable: true,
    enumerable: true,
    configurable: true
  },
  year: {
    value: 2020,
    writable: true,
    enumerable: true,
    configurable: true
  }
}
*/

Detailed Explanation of Property Attributes

Writable Attribute

When writable is false, the property value cannot be reassigned. In strict mode, attempting to modify it will throw an error:

const obj = {};
Object.defineProperty(obj, 'readOnly', {
  value: 'initial',
  writable: false
});

console.log(obj.readOnly); // "initial"
obj.readOnly = 'new value'; // Silently fails in non-strict mode
console.log(obj.readOnly); // Still "initial"

'use strict';
obj.readOnly = 'new value'; // TypeError: Cannot assign to read only property

Enumerable Attribute

enumerable controls whether the property appears in for...in loops and Object.keys():

const obj = {};
Object.defineProperties(obj, {
  visible: { value: 1, enumerable: true },
  hidden: { value: 2, enumerable: false }
});

console.log(Object.keys(obj)); // ["visible"]
for (let key in obj) {
  console.log(key); // Only outputs "visible"
}

Configurable Attribute

When configurable is false, the property cannot be deleted, cannot be changed to an accessor property, and cannot modify attributes other than writable and value:

const obj = {};
Object.defineProperty(obj, 'x', {
  value: 1,
  configurable: false
});

// Attempt to delete
delete obj.x; // Silently fails
console.log(obj.x); // 1

// Attempt to modify attributes
Object.defineProperty(obj, 'x', {
  enumerable: false
}); // TypeError: Cannot redefine property: x

// Only allowed modification
Object.defineProperty(obj, 'x', {
  writable: false
}); // Succeeds

Use Cases

Creating Immutable Properties

function createImmutableObject(properties) {
  const obj = {};
  for (const [key, value] of Object.entries(properties)) {
    Object.defineProperty(obj, key, {
      value,
      writable: false,
      configurable: false
    });
  }
  return obj;
}

const constants = createImmutableObject({
  PI: 3.14159,
  E: 2.71828
});

Implementing Private Property Patterns

function createPrivateProperties(obj, privateData) {
  const privateStore = new WeakMap();
  privateStore.set(obj, privateData);
  
  for (const key in privateData) {
    Object.defineProperty(obj, key, {
      get() {
        return privateStore.get(this)[key];
      },
      set(value) {
        privateStore.get(this)[key] = value;
      },
      enumerable: true
    });
  }
  
  return obj;
}

const user = {};
createPrivateProperties(user, { 
  _password: 'secret123',
  _token: 'abc123'
});

console.log(user._password); // "secret123"
user._password = 'newSecret';
console.log(user._password); // "newSecret"

Prototype Chain and Property Descriptors

Property descriptors exhibit special behavior in the prototype chain. When accessing inherited properties through the prototype chain, their descriptor attributes affect how the properties are accessed:

const proto = {
  inheritedProp: 'proto value'
};
Object.defineProperty(proto, 'readOnlyProp', {
  value: 'cannot change',
  writable: false
});

const child = Object.create(proto);
child.ownProp = 'child value';

console.log(child.inheritedProp); // "proto value"
child.inheritedProp = 'new value'; // Actually creates a new property on child
console.log(child.inheritedProp); // "new value"
console.log(proto.inheritedProp); // Still "proto value"

child.readOnlyProp = 'attempt change'; // Silently fails
console.log(child.readOnlyProp); // "cannot change"

Batch Property Definition

Object.defineProperties() allows defining multiple properties at once:

const config = {};
Object.defineProperties(config, {
  apiUrl: {
    value: 'https://api.example.com',
    writable: false,
    enumerable: true
  },
  maxRetries: {
    value: 3,
    writable: true
  },
  logLevel: {
    get() {
      return this._logLevel || 'info';
    },
    set(value) {
      if (['debug', 'info', 'warn', 'error'].includes(value)) {
        this._logLevel = value;
      }
    },
    enumerable: true
  }
});

Combining with Class Syntax

In ES6 classes, you can use accessor properties and static methods with property descriptors:

class Temperature {
  constructor(celsius) {
    this._celsius = celsius;
  }

  get fahrenheit() {
    return this._celsius * 9/5 + 32;
  }

  set fahrenheit(value) {
    this._celsius = (value - 32) * 5/9;
  }

  static createReadOnly(name, value) {
    const temp = new Temperature(0);
    Object.defineProperty(temp, name, {
      value,
      writable: false,
      configurable: false
    });
    return temp;
  }
}

const temp = Temperature.createReadOnly('unit', 'Celsius');
console.log(temp.unit); // "Celsius"
temp.unit = 'Fahrenheit'; // Silently fails

Limitations of Property Descriptors

Certain operations are restricted by property descriptors:

  1. Cannot change a non-configurable property from a data property to an accessor property, or vice versa
  2. Cannot modify the enumerable attribute of a non-configurable property
  3. For data properties, if writable is false and configurable is false, the value cannot be modified
  4. If an accessor property's configurable is false, the get and set functions cannot be modified
const obj = {};
Object.defineProperty(obj, 'x', {
  value: 1,
  writable: false,
  configurable: false
});

// The following operations will all throw TypeError
Object.defineProperty(obj, 'x', {
  get() { return 2; }
});

Object.defineProperty(obj, 'x', {
  enumerable: false
});

Object.defineProperty(obj, 'x', {
  value: 2
});

Combining with Proxy

Property descriptors can be combined with Proxy to implement advanced property control:

const handler = {
  defineProperty(target, prop, descriptor) {
    if (prop.startsWith('_')) {
      throw new Error(`Cannot define private property "${prop}"`);
    }
    // Force all properties to be non-enumerable
    return Reflect.defineProperty(target, prop, {
      ...descriptor,
      enumerable: false
    });
  }
};

const target = {};
const proxy = new Proxy(target, handler);

proxy.publicProp = 'value'; // Succeeds
console.log(Object.keys(proxy)); // []
try {
  proxy._privateProp = 'secret'; // Error: Cannot define private property "_privateProp"
} catch (e) {
  console.error(e.message);
}

本站部分内容来自互联网,一切版权均归源网站或源作者所有。

如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn

上一篇:对象创建方式

下一篇:对象基础

Front End Chuan

Front End Chuan, Chen Chuan's Code Teahouse 🍵, specializing in exorcising all kinds of stubborn bugs 💻. Daily serving baldness-warning-level development insights 🛠️, with a bonus of one-liners that'll make you laugh for ten years 🐟. Occasionally drops pixel-perfect romance brewed in a coffee cup ☕.