icon
属性描述符

属性描述符

属性描述符(Property Descriptor)定义了对象属性的特性,包括值、可写性、可枚举性、可配置性等。

基本概念

每个对象属性都有描述符,可以通过 Object.getOwnPropertyDescriptor() 获取:

const obj = {
  name: 'Alice'
};

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

数据描述符

包含以下属性:

  • value:属性的值
  • writable:是否可写(默认 true)
  • enumerable:是否可枚举(默认 true)
  • configurable:是否可配置(默认 true)
const obj = {};

// 使用 Object.defineProperty 定义属性
Object.defineProperty(obj, 'name', {
  value: 'Alice',
  writable: true,
  enumerable: true,
  configurable: true
});

// 只读属性
Object.defineProperty(obj, 'readonly', {
  value: 'cannot change',
  writable: false
});

obj.readonly = 'try to change';
console.log(obj.readonly); // 'cannot change' (未改变)

// 不可枚举属性
Object.defineProperty(obj, 'hidden', {
  value: 'hidden',
  enumerable: false
});

console.log(Object.keys(obj)); // ['name'] (不包含 hidden)
console.log(obj.hidden); // 'hidden' (仍可访问)

访问器描述符

包含以下属性:

  • get:getter 函数
  • set:setter 函数
  • enumerable:是否可枚举
  • configurable:是否可配置
const obj = {
  _name: 'Alice'
};

Object.defineProperty(obj, 'name', {
  get: function() {
    return this._name;
  },
  set: function(value) {
    this._name = value.toUpperCase();
  },
  enumerable: true,
  configurable: true
});

console.log(obj.name); // 'Alice'
obj.name = 'bob';
console.log(obj.name); // 'BOB'

Object.defineProperty

定义单个属性的描述符:

const obj = {};

// 定义数据属性
Object.defineProperty(obj, 'prop1', {
  value: 42,
  writable: false,
  enumerable: true,
  configurable: true
});

// 定义访问器属性
let _value = 0;
Object.defineProperty(obj, 'prop2', {
  get: function() {
    return _value;
  },
  set: function(newValue) {
    _value = newValue * 2;
  },
  enumerable: true,
  configurable: true
});

obj.prop2 = 5;
console.log(obj.prop2); // 10

Object.defineProperties

同时定义多个属性:

const obj = {};

Object.defineProperties(obj, {
  name: {
    value: 'Alice',
    writable: true,
    enumerable: true,
    configurable: true
  },
  age: {
    value: 25,
    writable: false,
    enumerable: true,
    configurable: false
  },
  _email: {
    value: '',
    writable: true,
    enumerable: false,
    configurable: true
  },
  email: {
    get: function() {
      return this._email;
    },
    set: function(value) {
      this._email = value.toLowerCase();
    },
    enumerable: true,
    configurable: true
  }
});

obj.email = '[email protected]';
console.log(obj.email); // '[email protected]'

描述符属性详解

  1. writable
    • false 时,属性值不能被重新赋值
const obj = {};
Object.defineProperty(obj, 'readonly', {
  value: 'initial',
  writable: false
});

obj.readonly = 'changed';
console.log(obj.readonly); // 'initial'

// 严格模式下会抛出错误
'use strict';
obj.readonly = 'changed'; // TypeError
  1. enumerable
    • false 时,属性不会出现在枚举中
const obj = {
  visible: 'visible',
  hidden: 'hidden'
};

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

console.log(Object.keys(obj));        // ['visible']
console.log(Object.values(obj));      // ['visible']
for (let key in obj) {
  console.log(key);                   // 'visible'
}
console.log(obj.propertyIsEnumerable('hidden')); // false
  1. configurable
    • false 时,不能删除属性,不能修改描述符(writable 可以从 true 改为 false)
const obj = {};
Object.defineProperty(obj, 'prop', {
  value: 'value',
  configurable: false
});

// 不能删除
delete obj.prop;
console.log(obj.prop); // 'value'

// 不能修改描述符
Object.defineProperty(obj, 'prop', {
  writable: false
}); // TypeError

// 但可以将 writable 从 true 改为 false
const obj2 = {};
Object.defineProperty(obj2, 'prop', {
  value: 'value',
  writable: true,
  configurable: false
});

Object.defineProperty(obj2, 'prop', {
  writable: false
}); // 成功

实际应用

  1. 实现私有属性
function createPerson(name) {
  const _name = name;
  
  const person = {};
  Object.defineProperty(person, 'name', {
    get: function() {
      return _name;
    },
    enumerable: true,
    configurable: false
  });
  
  return person;
}

const person = createPerson('Alice');
console.log(person.name); // 'Alice'
person.name = 'Bob';      // 无效(没有 setter)
console.log(person.name); // 'Alice'
  1. 数据验证
const user = {
  _age: 0
};

Object.defineProperty(user, 'age', {
  get: function() {
    return this._age;
  },
  set: function(value) {
    if (typeof value !== 'number' || value < 0 || value > 150) {
      throw new Error('Invalid age');
    }
    this._age = value;
  },
  enumerable: true,
  configurable: true
});

user.age = 25;  // 成功
user.age = -1;  // Error: Invalid age
  1. 计算属性
const rectangle = {
  width: 10,
  height: 5
};

Object.defineProperty(rectangle, 'area', {
  get: function() {
    return this.width * this.height;
  },
  enumerable: true,
  configurable: true
});

console.log(rectangle.area); // 50
rectangle.width = 20;
console.log(rectangle.area); // 100
  1. 属性监听
function observe(obj, prop, callback) {
  let value = obj[prop];
  
  Object.defineProperty(obj, prop, {
    get: function() {
      return value;
    },
    set: function(newValue) {
      const oldValue = value;
      value = newValue;
      callback(newValue, oldValue);
    },
    enumerable: true,
    configurable: true
  });
}

const data = { count: 0 };
observe(data, 'count', (newVal, oldVal) => {
  console.log(`count changed from ${oldVal} to ${newVal}`);
});

data.count = 1; // count changed from 0 to 1
data.count = 5; // count changed from 1 to 5

Object.getOwnPropertyDescriptors

获取对象所有属性的描述符:

const obj = {
  name: 'Alice'
};

Object.defineProperty(obj, 'age', {
  value: 25,
  writable: false,
  enumerable: true,
  configurable: true
});

const descriptors = Object.getOwnPropertyDescriptors(obj);
console.log(descriptors);
// {
//   name: { value: 'Alice', writable: true, enumerable: true, configurable: true },
//   age: { value: 25, writable: false, enumerable: true, configurable: true }
// }

与 Object.create 结合

const proto = {
  greet: function() {
    return `Hello, ${this.name}`;
  }
};

const obj = Object.create(proto, {
  name: {
    value: 'Alice',
    writable: true,
    enumerable: true,
    configurable: true
  },
  age: {
    value: 25,
    writable: false,
    enumerable: true,
    configurable: false
  }
});

console.log(obj.greet()); // 'Hello, Alice'

注意事项

  1. 数据描述符和访问器描述符不能同时使用
  2. 默认值:writable、enumerable、configurable 都是 true
  3. 使用 Object.defineProperty 时,未指定的属性默认为 false
  4. 严格模式下,违反 writable 会抛出错误