极简三分钟ES6 - ES8中对象扩展

198 阅读3分钟

新增Object.values()和Object.entries()

想象我们管理一个存放水果的仓库(对象)

const fruitStock = {
  apple: 10,   // 🍎苹果:10箱 
  orange: 15,  // 🍊橙子:15箱
  banana: 8    // 🍌香蕉:8箱 
};

Object.values()  :只需清点库存数量(值)

const stockNumbers = Object.values(fruitStock); 
console.log(stockNumbers);  // [10, 15, 8] → 只关心数量 

Object.entries()  :记录完整货品清单(键+值)

const allRecords = Object.entries(fruitStock); 
console.log(allRecords);  
// [ ["apple", 10], ["orange", 15], ["banana", 8] ] → 完整台账 

一些常见的使用场景

快速提取值集合(Object.values )

// 计算水果总库存 
const total = Object.values(fruitStock).reduce((sum,  num) => sum + num, 0);
console.log(total);  // 33

对象转 Map(Object.entries )

const fruitMap = new Map(Object.entries(fruitStock)); 
console.log(fruitMap.get("orange"));  // 15 → 转为 Map 后查询 

过滤对象属性

// 找出库存 > 10 的水果
const highStock = Object.entries(fruitStock) 
  .filter(([fruit, count]) => count > 10)
  .map(([fruit]) => fruit);
 
console.log(highStock);  // ["orange"]

替代 for...in 循环

// 更安全地遍历对象(避免原型污染)
Object.entries(fruitStock).forEach(([fruit,  count]) => {
  console.log(`${fruit}:  ${count}箱`);
});

对象深拷贝技巧

const copy = Object.fromEntries(Object.entries(fruitStock)); 
console.log(copy);  // {apple:10, orange:15, banana:8} → 全新对象

特殊情况

非对象参数自动转对象

Object.values("abc");  // ["a", "b", "c"](字符串转包装对象)
Object.entries(42);    // [](数字无属性,返回空数组)

Symbol 属性会被忽略

const obj = { [Symbol("id")]: 100, name: "test" };
Object.values(obj);    // ["test"] → 跳过 Symbol 
Object.entries(obj);   // [["name", "test"]] 

原型链属性不包含

function Fruit() { this.type  = "food"; }
Fruit.prototype.color  = "red";
 
const apple = new Fruit();
Object.values(apple);  // ["food"] → 不包含原型属性 

与ES5方案对比

操作需求ES5 方案ES8 方案优势
获取所有值Object.keys(obj).map(k => obj[k])Object.values(obj)代码减少 60%
获取键值对手动 for...in 循环Object.entries(obj)避免原型链污染风险
对象转二维数组无直接方法Object.entries(obj)一行代码完成

选择哪个方法

  • 如下情况使用 Object.values() 

    • 统计值总和/平均值
    • 检查值是否满足条件
    • 仅需值集合的场景
  • 如下情况使用 Object.entries()

    • 需同时访问键和值
    • 对象转 Map 或其他数据结构
    • 重构对象属性

Object.getOwnPropertyDescriptors()

对象的"体检报告单"

想象我们要检查一台手机的完整配置

  • 传统方法Object.getOwnPropertyDescriptor() ):只能查单个部件(如摄像头参数)
  • Object.getOwnPropertyDescriptors()  :直接生成整机完整参数表(镜头+芯片+屏幕...)
const phone = {
  brand: "Pixel",
  price: 699,
  get discount() { return this.price  * 0.9 } // getter 计算属性 
};
 
// 获取手机完整配置表
const fullReport = Object.getOwnPropertyDescriptors(phone); 

解决两大痛点

完整复制对象(含 getter/setter)

const deepCopy = Object.defineProperties( 
  {}, 
  Object.getOwnPropertyDescriptors(phone)  // 保留所有特性
);
deepCopy.price  = 800;
console.log(deepCopy.discount);  // 720(getter 动态计算值)

继承原型方法时不污染目标对象

// 安全混合对象(不覆盖原型链)
const securityPhone = Object.create( 
  cameraPrototype, // 继承相机功能
  Object.getOwnPropertyDescriptors(phone)  // 添加自有属性 
);

对比其他属性查询方法

方法作用范围能否获取 getter/setter返回结构
Object.keys()自身可枚举属性属性名数组
Object.getOwnPropertyNames()所有自身属性属性名数组
Object.getOwnPropertyDescriptor()单个属性单个描述符对象
Object.getOwnPropertyDescriptors()所有自身属性描述符对象集合

一些常见的使用场景

深度克隆对象(保留特性)

function trueClone(obj) {
  return Object.defineProperties( 
    {}, 
    Object.getOwnPropertyDescriptors(obj) 
  );
}

精确控制类继承

class PremiumPhone extends Phone {
  constructor() {
    super();
    // 添加会员专属属性(带 getter)
    Object.defineProperties( 
      this, 
      Object.getOwnPropertyDescriptors({ 
        get vipDiscount() { return this.price  * 0.7 }
      })
    );
  }
}

修复 JSON 序列化缺陷

// JSON.stringify  会忽略 getter!
const phoneJSON = JSON.stringify(phone);  // {brand:"Pixel",price:699}
 
// 通过描述符重建对象 
const restoredPhone = Object.defineProperties( 
  {}, 
  Object.getOwnPropertyDescriptors(phone) 
);

创建不可变对象

// 锁定所有属性 
const sealedPhone = Object.defineProperties( 
  {}, 
  Object.entries(Object.getOwnPropertyDescriptors(phone)).map(([key,  desc]) => {
    return [key, {...desc, writable: false}] // 全部设为不可写
  })
);