Object.defineProperty

235 阅读3分钟

在 JavaScript 中,Object.defineProperty 是一个强大的方法,它允许你精确地定义或修改对象的属性,并控制这些属性的行为。这个方法提供了比简单的属性赋值更多的功能和灵活性,包括属性描述符(descriptors)的精细控制。本文将深入探讨 Object.defineProperty 的用法及其在实际开发中的应用。

一、基本语法

Object.defineProperty 的基本语法如下:

Object.defineProperty(obj, prop, descriptor)
  • obj:要定义属性的对象。

  • prop:要定义或修改的属性的名称。

  • descriptor:属性描述符,是一个对象,可以包含以下属性:

    • value:属性的值。
    • writable:布尔值,表示属性的值是否可以被修改。
    • configurable:布尔值,表示属性的描述符是否可以被修改或属性是否可以被删除。
    • enumerable:布尔值,表示属性是否能在 for...in 循环和 Object.keys 方法中被枚举。
    • get:一个给属性提供 getter 的方法,如果没有 value 属性,则必须指定。
    • set:一个给属性提供 setter 的方法,如果没有 value 属性,则可以选择性指定。

二、属性描述符详解

(一)数据描述符
  1. value:属性对应的值,可以是任意合法的 JavaScript 数据类型,如数字、字符串、对象、函数等。例如:
let person = {};
Object.defineProperty(person, 'age', {
  value: 25
});
console.log(person.age); // 输出 25
  1. writable:一个布尔值,决定属性值是否可被修改。默认值为false,意味着一旦定义,属性值就是只读的。若要允许修改,需显式设置为true,像这样:
let car = {};
Object.defineProperty(car,'speed', {
  value: 100,
  writable: true
});
car.speed = 120;
console.log(car.speed); // 输出 120,因为 speed 属性是可写的
  1. enumerable:同样是布尔值,用于控制属性是否可被for...in循环或Object.keys()等枚举方法遍历到。默认false,当设为true时,属性会暴露在枚举操作下:
let book = {};
Object.defineProperty(book, 'title', {
  value: 'JavaScript高级编程',
  enumerable: true
});
for (let key in book) {
  console.log(key); // 输出 'title',因为 title 属性可枚举
}
  1. configurable:此布尔值决定属性描述符能否被修改,以及属性能否从对象上删除。默认false,设置为true才具备后续调整的灵活性:
let configObj = {};
Object.defineProperty(configObj, 'debugMode', {
  value: false,
  configurable: true
});
(二)存取描述符
  1. get:是一个函数,当读取属性值时会被自动调用,常被用于动态计算属性值、懒加载数据等场景。比如:
let rectangle = {
  width: 10,
  height: 5
};
Object.defineProperty(rectangle, 'area', {
  get: function() {
    return this.width * this.height;
  }
});
console.log(rectangle.area); // 输出 50,每次读取 area 都是实时计算得出
  1. set:也是函数,在属性值被修改时触发,可用于校验新值、同步更新关联数据等复杂操作。例如:
let temperature = {
  _celsius: 0
};
Object.defineProperty(temperature, 'fahrenheit', {
  get: function() {
    return (this._celsius * 9 / 5) + 32;
  },
  set: function(newValue) {
    this._celsius = (newValue - 32) * 5 / 9;
  }
});
temperature.fahrenheit = 50;
console.log(temperature._celsius); // 输出约 10,通过 fahrenheit 设置同步更新内部 _celsius 值

三、应用场景大揭秘

(一)数据校验与安全防护

在处理用户输入或与外部接口交互获取的数据时,可利用Object.defineProperty对关键属性设限,防止非法赋值。例如,构建一个用户信息对象,确保年龄只能是正整数:

let user = {};
Object.defineProperty(user, 'age', {
  value: 0,
  writable: true,
  enumerable: true,
  configurable: true,
  set: function(newValue) {
    if (Number.isInteger(newValue) && newValue > 0) {
      this.value = newValue;
    } else {
      console.log('年龄必须是正整数');
    }
  }
});
user.age = 20; // 正常赋值
user.age = -5; // 控制台输出提示,赋值失败
(二)数据双向绑定
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>双向数据绑定示例</title>
  <style>
    body {
      font-family: Arial, sans-serif;
      padding: 20px;
    }

    input {
      padding: 5px;
      font-size: 16px;
    }

    span {
      font-weight: bold;
    }
  </style>
</head>

<body>

  <h1>双向数据绑定示例</h1>

  <!-- 输入框 -->
  <input id="inputElement" type="text" placeholder="输入内容...">

  <!-- 显示输入内容 -->
  <p>你输入的内容是:<span id="displayText"></span></p>

  <script>
    // 双向数据绑定函数
    function bindData(model, element) {
      // 初始化 UI 与模型的绑定
      element.value = model.value.toString();  // 强制将数字转为字符串
      document.getElementById('displayText').textContent = model.value; // 显示模型的初始值

      // 使用 Object.defineProperty 为 model 对象的 value 属性定义 getter 和 setter
      Object.defineProperty(model, 'value', {
        get() {
          return this._value;
        },
        set(newValue) {
          this._value = newValue;
          // 数据更新时同步更新 UI
          element.value = newValue;
          document.getElementById('displayText').textContent = newValue; // 更新展示文本
        }
      });

      // 监听 UI 的 input 事件,数据变化时更新 model
      element.addEventListener('input', (e) => {
        model.value = e.target.value;  // 更新 model 的值
      });

      return model;
    }

    // 初始化数据模型
    const model = { value: 1 };

    // 获取 DOM 元素
    const inputElement = document.getElementById('inputElement');

    // 将模型与页面元素进行双向绑定
    const boundModel = bindData(model, inputElement);
  </script>

</body>

</html>

参考链接:developer.mozilla.org/zh-CN/docs/…