ES5 vs ES6 中 Object.defineProperty() 不同

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 限制
  1. 定义多个属性时,要调用多次 defineProperty()
  2. 无法监听对象的动态属性变化(只能通过 Object.observe(),但该方法已废弃)
  3. 不能拦截属性的读取和写入

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() 以增强对象操作能力。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值