阿里云主机折上折
  • 微信号
您当前的位置:网站首页 > 属性描述符

属性描述符

作者:陈川 阅读数:63472人阅读 分类: JavaScript

属性描述符

JavaScript中的对象属性不仅仅是简单的键值对,每个属性实际上都关联着一个属性描述符对象。属性描述符定义了属性的行为特征,包括是否可写、可枚举、可配置等。理解属性描述符是深入掌握JavaScript对象模型的关键。

属性描述符的类型

属性描述符分为两种主要类型:数据描述符和存取描述符。数据描述符直接定义属性的值及其特性,而存取描述符则通过getter和setter函数控制属性的访问。

数据描述符包含以下可选键:

  • value:属性的值
  • writable:是否可修改
  • enumerable:是否可枚举
  • configurable:是否可配置

存取描述符包含:

  • get:获取属性值的函数
  • set:设置属性值的函数
  • enumerable:是否可枚举
  • configurable:是否可配置
// 数据描述符示例
const obj1 = {};
Object.defineProperty(obj1, 'name', {
  value: 'John',
  writable: true,
  enumerable: true,
  configurable: true
});

// 存取描述符示例
const obj2 = {};
let _age = 25;
Object.defineProperty(obj2, 'age', {
  get() { return _age; },
  set(newVal) { _age = newVal > 0 ? newVal : _age; },
  enumerable: true,
  configurable: false
});

获取属性描述符

可以使用Object.getOwnPropertyDescriptor()方法获取对象某个属性的描述符:

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

要获取对象所有自身属性的描述符,可以使用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
  }
}
*/

属性特性详解

writable特性

writablefalse时,属性值不能被重新赋值。在严格模式下尝试修改会抛出错误:

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

console.log(obj.readOnly); // "initial"
obj.readOnly = 'new value'; // 非严格模式下静默失败
console.log(obj.readOnly); // 仍然是"initial"

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

enumerable特性

enumerable控制属性是否出现在for...in循环和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); // 只输出"visible"
}

configurable特性

configurablefalse时,不能删除属性,也不能将其改为存取器属性,且不能修改除writablevalue外的特性:

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

// 尝试删除
delete obj.x; // 静默失败
console.log(obj.x); // 1

// 尝试修改特性
Object.defineProperty(obj, 'x', {
  enumerable: false
}); // TypeError: Cannot redefine property: x

// 唯一允许的修改
Object.defineProperty(obj, 'x', {
  writable: false
}); // 成功

使用场景

创建不可变属性

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
});

实现私有属性模式

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"

原型链与属性描述符

属性描述符在原型链中表现出特殊行为。当通过原型链继承的属性被访问时,其描述符特性会影响属性的访问方式:

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'; // 实际上会在child上创建新属性
console.log(child.inheritedProp); // "new value"
console.log(proto.inheritedProp); // 仍然是"proto value"

child.readOnlyProp = 'attempt change'; // 静默失败
console.log(child.readOnlyProp); // "cannot change"

批量定义属性

Object.defineProperties()允许一次性定义多个属性:

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
  }
});

与类语法结合

在ES6类中,可以使用访问器属性和静态方法配合属性描述符:

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'; // 静默失败

属性描述符的限制

某些操作会受到属性描述符的限制:

  1. 不能将不可配置的属性从数据属性改为存取器属性,反之亦然
  2. 不能修改不可配置属性的enumerable特性
  3. 对于数据属性,如果writablefalseconfigurablefalse,则不能修改value
  4. 如果访问器属性的configurablefalse,则不能修改getset函数
const obj = {};
Object.defineProperty(obj, 'x', {
  value: 1,
  writable: false,
  configurable: false
});

// 以下操作都会抛出TypeError
Object.defineProperty(obj, 'x', {
  get() { return 2; }
});

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

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

与Proxy结合

属性描述符可以与Proxy结合,实现更高级的属性控制:

const handler = {
  defineProperty(target, prop, descriptor) {
    if (prop.startsWith('_')) {
      throw new Error(`Cannot define private property "${prop}"`);
    }
    // 强制所有属性不可枚举
    return Reflect.defineProperty(target, prop, {
      ...descriptor,
      enumerable: false
    });
  }
};

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

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

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

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

上一篇:对象创建方式

下一篇:对象基础

前端川

前端川,陈川的代码茶馆🍵,专治各种不服的Bug退散符💻,日常贩卖秃头警告级的开发心得🛠️,附赠一行代码笑十年的摸鱼宝典🐟,偶尔掉落咖啡杯里泡开的像素级浪漫☕。‌