您现在的位置是:网站首页 > 属性描述符文章详情

属性描述符

属性描述符的基本概念

属性描述符是JavaScript中用于定义或修改对象属性特性的对象。每个对象的属性都关联着一个属性描述符,它决定了这个属性如何工作。属性描述符分为两种类型:数据描述符和存取描述符。

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

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

存取描述符包含以下可选键值:

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

// 存取描述符示例
const obj2 = {
  _age: 30
};
Object.defineProperty(obj2, 'age', {
  get() {
    return this._age;
  },
  set(newValue) {
    this._age = newValue;
  },
  enumerable: true,
  configurable: true
});

获取属性描述符

可以使用Object.getOwnPropertyDescriptor()方法获取对象某个属性的描述符。如果要获取对象所有自身属性的描述符,可以使用Object.getOwnPropertyDescriptors()

const person = {
  name: 'Alice',
  age: 25
};

// 获取单个属性的描述符
const nameDescriptor = Object.getOwnPropertyDescriptor(person, 'name');
console.log(nameDescriptor);
/*
{
  value: 'Alice',
  writable: true,
  enumerable: true,
  configurable: true
}
*/

// 获取所有属性的描述符
const allDescriptors = Object.getOwnPropertyDescriptors(person);
console.log(allDescriptors);
/*
{
  name: {
    value: 'Alice',
    writable: true,
    enumerable: true,
    configurable: true
  },
  age: {
    value: 25,
    writable: true,
    enumerable: true,
    configurable: true
  }
}
*/

数据描述符详解

数据描述符是最常用的属性描述符类型,它直接定义了属性的值和特性。

value属性

value指定了属性的初始值,可以是任何有效的JavaScript值。

const car = {};
Object.defineProperty(car, 'brand', {
  value: 'Toyota'
});
console.log(car.brand); // "Toyota"

writable属性

writable决定属性值是否可以被修改。默认为false

const book = {};
Object.defineProperty(book, 'title', {
  value: 'JavaScript Guide',
  writable: false
});

book.title = 'New Title'; // 静默失败,严格模式下会抛出TypeError
console.log(book.title); // "JavaScript Guide"

enumerable属性

enumerable决定属性是否会在for...in循环或Object.keys()中显示。默认为false

const colors = {
  red: '#FF0000',
  green: '#00FF00'
};

Object.defineProperty(colors, 'blue', {
  value: '#0000FF',
  enumerable: false
});

console.log(Object.keys(colors)); // ["red", "green"]
for (const color in colors) {
  console.log(color); // 只输出"red"和"green"
}

configurable属性

configurable决定属性是否可以被删除或特性是否可以被修改。默认为false

const config = {};
Object.defineProperty(config, 'apiUrl', {
  value: 'https://api.example.com',
  configurable: false
});

// 尝试删除
delete config.apiUrl; // 静默失败
console.log(config.apiUrl); // "https://api.example.com"

// 尝试修改特性
Object.defineProperty(config, 'apiUrl', {
  writable: true
}); // 抛出TypeError

存取描述符详解

存取描述符通过getter和setter函数来控制属性的访问和修改。

getter函数

get是一个无参函数,当访问属性时会被调用。

const circle = {
  _radius: 5
};

Object.defineProperty(circle, 'diameter', {
  get() {
    return this._radius * 2;
  }
});

console.log(circle.diameter); // 10

setter函数

set是一个接收一个参数的函数,当设置属性时会被调用。

const circle = {
  _radius: 5
};

Object.defineProperty(circle, 'diameter', {
  get() {
    return this._radius * 2;
  },
  set(value) {
    this._radius = value / 2;
  }
});

circle.diameter = 14;
console.log(circle._radius); // 7

使用Object.defineProperties定义多个属性

可以一次性定义多个属性及其描述符。

const employee = {};

Object.defineProperties(employee, {
  firstName: {
    value: 'John',
    writable: true
  },
  lastName: {
    value: 'Doe',
    writable: true
  },
  fullName: {
    get() {
      return `${this.firstName} ${this.lastName}`;
    },
    set(name) {
      const parts = name.split(' ');
      this.firstName = parts[0] || '';
      this.lastName = parts[1] || '';
    }
  }
});

console.log(employee.fullName); // "John Doe"
employee.fullName = 'Jane Smith';
console.log(employee.firstName); // "Jane"
console.log(employee.lastName); // "Smith"

属性描述符的继承与原型链

属性描述符不会被继承,但通过原型链访问属性时,描述符的特性仍然会影响属性的行为。

function Person(name) {
  this.name = name;
}

Person.prototype = {
  get greeting() {
    return `Hello, my name is ${this.name}`;
  }
};

const person = new Person('Alice');
console.log(person.greeting); // "Hello, my name is Alice"

// 获取原型上属性的描述符
const greetingDescriptor = Object.getOwnPropertyDescriptor(
  Person.prototype, 
  'greeting'
);
console.log(greetingDescriptor.enumerable); // false

密封、冻结和防止扩展

属性描述符与对象的不可变性操作密切相关。

Object.preventExtensions

阻止对象添加新属性,但现有属性仍可修改。

const obj = { prop: 'value' };
Object.preventExtensions(obj);

obj.newProp = 'new'; // 静默失败,严格模式下抛出TypeError
console.log(obj.newProp); // undefined

Object.seal

密封对象,阻止添加新属性并将所有现有属性标记为configurable: false

const obj = { prop: 'value' };
Object.seal(obj);

delete obj.prop; // 静默失败
obj.prop = 'new value'; // 允许修改
console.log(obj.prop); // "new value"

Object.freeze

冻结对象,阻止添加新属性、删除现有属性或修改现有属性(所有属性变为writable: false)。

const obj = { prop: 'value' };
Object.freeze(obj);

obj.prop = 'new value'; // 静默失败
console.log(obj.prop); // "value"

实际应用场景

数据验证

使用setter进行数据验证。

function createUser(name, age) {
  const user = {};
  
  Object.defineProperties(user, {
    name: {
      value: name,
      writable: true
    },
    age: {
      get() {
        return this._age;
      },
      set(value) {
        if (typeof value !== 'number' || value < 0 || value > 120) {
          throw new Error('Invalid age');
        }
        this._age = value;
      }
    }
  });
  
  user.age = age;
  return user;
}

const user = createUser('Bob', 30);
user.age = 25; // 正常
user.age = 150; // 抛出错误

计算属性

使用getter创建计算属性。

const rectangle = {
  width: 10,
  height: 5,
  get area() {
    return this.width * this.height;
  }
};

console.log(rectangle.area); // 50
rectangle.width = 20;
console.log(rectangle.area); // 100

私有属性模拟

结合闭包和属性描述符模拟私有属性。

function createCounter() {
  let _count = 0;
  
  const counter = {};
  
  Object.defineProperties(counter, {
    value: {
      get() {
        return _count;
      }
    },
    increment: {
      value() {
        _count++;
      }
    },
    decrement: {
      value() {
        _count--;
      }
    }
  });
  
  return counter;
}

const counter = createCounter();
counter.increment();
counter.increment();
console.log(counter.value); // 2
counter._count = 100; // 无效
console.log(counter.value); // 2

与ES6类的结合使用

属性描述符可以与ES6类一起使用,实现更精细的属性控制。

class Temperature {
  constructor(celsius) {
    this.celsius = celsius;
  }
  
  get fahrenheit() {
    return this.celsius * 1.8 + 32;
  }
  
  set fahrenheit(value) {
    this.celsius = (value - 32) / 1.8;
  }
  
  static fromFahrenheit(value) {
    return new Temperature((value - 32) / 1.8);
  }
}

const temp = new Temperature(25);
console.log(temp.fahrenheit); // 77
temp.fahrenheit = 86;
console.log(temp.celsius); // 30

// 获取类中属性的描述符
const descriptor = Object.getOwnPropertyDescriptor(
  Temperature.prototype, 
  'fahrenheit'
);
console.log(descriptor.get); // [Function: get fahrenheit]

性能考虑

使用属性描述符可能会对性能产生轻微影响,特别是在频繁访问的属性上使用getter/setter时。在性能关键的代码中应谨慎使用。

// 简单属性访问
const obj1 = { value: 0 };
console.time('simple');
for (let i = 0; i < 1000000; i++) {
  obj1.value += 1;
}
console.timeEnd('simple');

// 使用getter/setter
const obj2 = {
  _value: 0,
  get value() {
    return this._value;
  },
  set value(v) {
    this._value = v;
  }
};
console.time('getter-setter');
for (let i = 0; i < 1000000; i++) {
  obj2.value += 1;
}
console.timeEnd('getter-setter');

浏览器兼容性

属性描述符在现代浏览器中得到了广泛支持,包括:

  • Chrome 5+
  • Firefox 4+
  • Safari 5+
  • Opera 12+
  • Edge 12+
  • IE 9+

对于旧版浏览器,可以使用polyfill或回退方案。

与其他特性的交互

属性描述符与JavaScript的其他特性如Proxy、Reflect等有良好的交互。

// 结合Proxy使用
const target = {};
const proxy = new Proxy(target, {
  defineProperty(t, prop, descriptor) {
    console.log(`Defining property ${prop}`);
    return Reflect.defineProperty(t, prop, descriptor);
  }
});

Object.defineProperty(proxy, 'newProp', {
  value: 'proxy value'
});
// 控制台输出: "Defining property newProp"
console.log(target.newProp); // "proxy value"

常见陷阱与注意事项

  1. 默认值:使用Object.defineProperty时,所有描述符特性默认为false,而普通对象字面量定义的属性默认为true
const obj1 = { prop: 'value' };
const descriptor1 = Object.getOwnPropertyDescriptor(obj1, 'prop');
console.log(descriptor1.writable); // true

const obj2 = {};
Object.defineProperty(obj2, 'prop', { value: 'value' });
const descriptor2 = Object.getOwnPropertyDescriptor(obj2, 'prop');
console.log(descriptor2.writable); // false
  1. 不可逆操作:一旦将属性设置为configurable: false,就无法再将其改回true

  2. 静默失败:在非严格模式下,违反属性描述符规则的操作通常会静默失败,不会抛出错误。

  3. 原型链上的属性Object.defineProperty只能定义对象自身的属性,不能定义原型链上的属性。

我的名片

网名:~川~

岗位:console.log 调试员

坐标:重庆市-九龙坡区

邮箱:cc@qdcc.cn

沙漏人生

站点信息

  • 建站时间:2013/03/16
  • 本站运行
  • 文章数量
  • 总访问量
微信公众号
每次关注
都是向财富自由迈进的一步