Object.defineProperty() 是 ES5 引入 的方法,用于在对象上精确定义或修改属性,特别是控制属性的可写性、可枚举性、可配置性 和 getter/setter。
在 ES6 中,它仍然可用,但随着 class 语法、Proxy、Reflect 的引入,一些功能可以用新的方式实现。
1. ES5 中的 Object.defineProperty()
1.1 作用
直接在对象上定义新属性或修改已有属性,并指定详细的控制行为。
1.2 语法
Object.defineProperty(obj, prop, descriptor)
- obj:目标对象
- prop:要定义或修改的属性名
- descriptor:属性描述符对象,控制该属性的行为
1.3 descriptor 详细说明
属性描述符分为 数据描述符 和 访问器描述符:
1、数据描述符
用于定义普通值属性:
const obj = {};
Object.defineProperty(obj, 'name', {
value: 'Alice',
writable: false, // 不能修改
enumerable: false, // 不能被枚举
configurable: false, // 不能被删除或修改描述符
});
console.log(obj.name); // "Alice"
obj.name = 'Bob'; // ❌ 失败(严格模式下报错)
console.log(obj.name); // "Alice"
console.log(Object.keys(obj)); // [] (不可枚举)
delete obj.name; // ❌ 失败
2、访问器描述符
使用 get / set 代替 value:
const person = {
firstName: "Alice",
lastName: "Smith",
};
Object.defineProperty(person, "fullName", {
get() {
return this.firstName + " " + this.lastName;
},
set(value) {
[this.firstName, this.lastName] = value.split(" ");
},
});
console.log(person.fullName); // "Alice Smith"
person.fullName = "Bob Johnson";
console.log(person.firstName); // "Bob"
console.log(person.lastName); // "Johnson"
1.4 ES5 限制
- 定义多个属性时,要调用多次 defineProperty()
- 无法监听对象的动态属性变化(只能通过 Object.observe(),但该方法已废弃)
- 不能拦截属性的读取和写入
2. ES6 中的 Object.defineProperty() 及相关变化
2.1 仍然支持
它在 ES6 中仍然可用,完全兼容 ES5。
2.2 新增的 Object 方法
ES6 引入了多个 Object 相关方法,增强了对象操作:
1、Object.assign() —— 用于复制对象,但无法控制 writable、enumerable 等属性:
const obj1 = { a: 1 };
const obj2 = Object.assign({}, obj1, { b: 2 });
console.log(obj2); // { a: 1, b: 2 }
2、Object.getOwnPropertyDescriptors() —— 获取对象所有属性的完整描述符:
const obj = { a: 1 };
Object.defineProperty(obj, "b", { value: 2, enumerable: false });
console.log(Object.getOwnPropertyDescriptors(obj));
// {
// a: { value: 1, writable: true, enumerable: true, configurable: true },
// b: { value: 2, writable: false, enumerable: false, configurable: false }
// }
3、Reflect.defineProperty() —— 类似 Object.defineProperty(),但返回 true/false 而不是抛出异常:
const obj = {};
console.log(Reflect.defineProperty(obj, "x", { value: 42 })); // true
console.log(obj.x); // 42
2.3 class 语法替代
ES6 提供了 class 语法,更直观地定义属性和方法,可以用 get/set 直接定义访问器:
class Person {
constructor(first, last) {
this.firstName = first;
this.lastName = last;
}
get fullName() {
return this.firstName + " " + this.lastName;
}
set fullName(value) {
[this.firstName, this.lastName] = value.split(" ");
}
}
const p = new Person("Alice", "Smith");
console.log(p.fullName); // "Alice Smith"
p.fullName = "Bob Johnson";
console.log(p.firstName); // "Bob"
区别:
- class 语法更直观,省去 Object.defineProperty() 的繁琐代码
- class 默认开启 严格模式
- 不能动态添加 get/set,但可以通过 Object.defineProperty() 修改 prototype
2.4 Proxy 替代 Object.defineProperty()
ES6 提供 proxy,可以拦截所有对象操作,更灵活:
const handler = {
get(target, prop) {
console.log(`读取 ${prop}`);
return prop in target ? target[prop] : "属性不存在";
},
set(target, prop, value) {
console.log(`设置 ${prop} 为 ${value}`);
target[prop] = value;
return true;
},
};
const obj = new Proxy({}, handler);
console.log(obj.name); // "属性不存在"
obj.name = "Alice"; // "设置 name 为 Alice"
console.log(obj.name); // "Alice"
优点:
- Proxy 可以拦截任意属性的访问和修改
- 可以监听整个对象(不像 Object.defineProperty() 需要一一定义)
- 适用于数据绑定、权限控制等场景
3. 定义属性和方法
ES5 和 ES6 在定义属性和方法方面有几个重要的区别,主要体现在对象字面量、构造函数、类(class)语法、原型方法等方面。
3.1 ES5
1、使用对象字面量定义
在 ES5 中,可以使用对象字面量直接定义属性和方法:
var person = {
name: "Alice",
age: 25,
sayHello: function () {
console.log("Hello, my name is " + this.name);
}
};
person.sayHello(); // Hello, my name is Alice
特点:方法必须使用 function 关键字定义。访问对象的属性时,使用 this 关键字指向当前对象。
2、Object.defineProperty()
如上
3、ES5 构造函数 + 原型方法
在 ES5 中,通常使用构造函数+原型来创建对象:
function Person(name, age) {
this.name = name;
this.age = age;
}
// 在原型上定义方法
Person.prototype.sayHello = function () {
console.log("Hello, my name is " + this.name);
};
var p1 = new Person("Alice", 25);
p1.sayHello(); // Hello, my name is Alice
特点:构造函数本质上是普通函数,但通过 new 关键字调用时,它的 this 绑定到新创建的对象上。方法一般添加在 prototype 上,以减少每个实例都创建相同方法的内存消耗。
⚠️ 注意
new 关键字是必须的,否则 this 指向 window(严格模式下 undefined)。
方法必须挂载在 prototype 上,否则每次创建实例都会重复创建方法,浪费内存。
3.2 ES6
1、使用对象字面量简写
ES6 允许对象方法的简写:
const person = {
name: "Alice",
age: 25,
sayHello() { // 省略 function 关键字
console.log("Hello, my name is " + this.name);
}
};
person.sayHello(); // Hello, my name is Alice
特点:代码更简洁,省去 function 关键字。this 的行为与 ES5 相同,仍然指向调用该方法的对象。
2、class 语法
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
// 直接在 class 体中定义方法
sayHello() {
console.log(`Hello, my name is ${this.name}`);
}
}
const p1 = new Person("Alice", 25);
p1.sayHello(); // Hello, my name is Alice
特点:
- 语法更加直观,符合面向对象编程思维。
- class 语法本质上是构造函数的语法糖,仍然基于原型链。
- 方法自动添加到 prototype,不需要手动 Person.prototype.sayHello = function() {}。
⚠️ 注意
1、class 不能直接调用,必须用 new 实例化,否则会报错:
Person(); // TypeError: Class constructor Person cannot be invoked without 'new'
2、class 中的方法是不可枚举的,而 ES5 原型上的方法默认是可枚举的:
console.log(Object.keys(Person.prototype)); // []
console.log(Object.keys(Person.prototype.__proto__)); // []
3、Object.defineProperty() + class
class Person {
constructor(name) {
Object.defineProperty(this, "name", {
value: name,
writable: false, // 不能修改
enumerable: true, // 可枚举
configurable: false // 不能删除
});
}
}
const p1 = new Person("Alice");
console.log(p1.name); // Alice
p1.name = "Bob";
console.log(p1.name); // 仍然是 Alice
4、get 和 set 访问器
ES6 class 允许更简洁地定义 getter 和 setter:
class Person {
constructor(name) {
this._name = name; // 用 _name 作为私有变量
}
get name() {
return this._name;
}
set name(newName) {
console.log("Name cannot be modified");
}
}
const p1 = new Person("Alice");
console.log(p1.name); // Alice
p1.name = "Bob"; // Name cannot be modified
特点:get 和 set 让代码更直观,属性像变量一样访问。
总结:现代 JavaScript 推荐使用 ES6 class,但了解 ES5 的原理有助于理解 JS 底层机制!
4. 举个 🌰
1. ES5 方式:使用构造函数和原型
在 ES5 中,方法通常通过构造函数的原型 (prototype) 来定义。
// 定义一个商品构造函数
function Product(name, price) {
this.name = name;
this.price = price;
this.stock = 0;
}
// 使用原型定义方法
Product.prototype.setStock = function(stock) {
this.stock = stock;
};
Product.prototype.getDetails = function() {
return `Product: ${this.name}, Price: ${this.price}, Stock: ${this.stock}`;
};
var product1 = new Product('Laptop', 1000);
product1.setStock(50);
console.log(product1.getDetails()); // Product: Laptop, Price: 1000, Stock: 50
2. ES6 方式:使用 class 和 getter/setter
在 ES6 中,方法可以直接在类中定义,语法简洁清晰。
class Product {
constructor(name, price) {
this.name = name;
this.price = price;
this._stock = 0; // 使用 _stock 来模拟私有属性
}
setStock(stock) {
this._stock = stock;
}
getDetails() {
return `Product: ${this.name}, Price: ${this.price}, Stock: ${this._stock}`;
}
}
const product1 = new Product('Laptop', 1000);
product1.setStock(50);
console.log(product1.getDetails()); // Product: Laptop, Price: 1000, Stock: 50
5. 总结
1、Object.defineProperty() 最早出现在 ES5,用于精确控制属性的可写性、可枚举性、可配置性。
2、ES6 新增 class、Proxy,提供更现代化的替代方案:
- class 语法可以用 get/set 定义访问器,避免 defineProperty() 的复杂性。
- Proxy 可监听整个对象的属性变化,比 defineProperty() 更强大。
3、ES6 仍支持 Object.defineProperty(),并提供 Object.getOwnPropertyDescriptors()、Reflect.defineProperty() 以增强对象操作能力。