Skip to content

对象方法:掌握内置工具箱

一个专业的木匠工具箱里不仅有锤子、锯子这些基本工具,还有各种专用工具——量尺用于测量、水平仪用于校准、角尺用于确保直角。JavaScript 的 Object 同样提供了一套完整的内置方法,帮助我们检查对象、转换数据、控制属性,以及实现各种复杂的操作。掌握这些方法就像掌握了一套专业工具,能让你更高效地处理对象数据。

Object.keys() - 获取所有键

Object.keys() 返回一个包含对象所有可枚举属性名的数组。

javascript
let user = {
  name: "Alice",
  age: 25,
  email: "[email protected]",
  city: "New York",
};

let keys = Object.keys(user);
console.log(keys); // ["name", "age", "email", "city"]

// 遍历对象的所有键
Object.keys(user).forEach((key) => {
  console.log(`${key}: ${user[key]}`);
});
// name: Alice
// age: 25
// email: [email protected]
// city: New York

Object.keys() 只返回对象自身的属性(不包括继承的属性),并且只返回可枚举的属性。这使它成为遍历对象属性的标准方法。

实际应用:验证对象字段

javascript
function validateRequiredFields(obj, requiredFields) {
  let actualFields = Object.keys(obj);
  let missingFields = requiredFields.filter(
    (field) => !actualFields.includes(field)
  );

  if (missingFields.length > 0) {
    return {
      valid: false,
      missing: missingFields,
    };
  }

  return { valid: true };
}

let userInput = {
  username: "johndoe",
  email: "[email protected]",
  // 缺少 password
};

let result = validateRequiredFields(userInput, [
  "username",
  "email",
  "password",
]);
console.log(result); // { valid: false, missing: ["password"] }

对象属性计数

javascript
let product = {
  name: "Laptop",
  price: 999,
  brand: "TechBrand",
  inStock: true,
};

console.log(`This object has ${Object.keys(product).length} properties`);
// This object has 4 properties

// 检查对象是否为空
function isEmpty(obj) {
  return Object.keys(obj).length === 0;
}

console.log(isEmpty({})); // true
console.log(isEmpty(product)); // false

Object.values() - 获取所有值

Object.values() 返回一个包含对象所有可枚举属性值的数组。

javascript
let scores = {
  math: 95,
  english: 88,
  science: 92,
  history: 85,
};

let values = Object.values(scores);
console.log(values); // [95, 88, 92, 85]

// 计算平均分
let average = values.reduce((sum, score) => sum + score, 0) / values.length;
console.log(`Average score: ${average}`); // Average score: 90

实际应用:数据统计

javascript
let inventory = {
  laptops: 15,
  mice: 50,
  keyboards: 30,
  monitors: 20,
};

// 总库存数量
let totalItems = Object.values(inventory).reduce((sum, qty) => sum + qty, 0);
console.log(`Total items in stock: ${totalItems}`); // 115

// 最大库存商品
let maxQuantity = Math.max(...Object.values(inventory));
console.log(`Highest stock quantity: ${maxQuantity}`); // 50

// 找出库存最多的商品
let maxStockProduct = Object.keys(inventory).find(
  (key) => inventory[key] === maxQuantity
);
console.log(`Product with most stock: ${maxStockProduct}`); // mice

Object.entries() - 获取键值对

Object.entries() 返回一个包含对象所有可枚举属性的键值对数组。

javascript
let person = {
  name: "Bob",
  age: 30,
  occupation: "Developer",
};

let entries = Object.entries(person);
console.log(entries);
// [
//   ["name", "Bob"],
//   ["age", 30],
//   ["occupation", "Developer"]
// ]

// 遍历键值对
for (let [key, value] of Object.entries(person)) {
  console.log(`${key}: ${value}`);
}
// name: Bob
// age: 30
// occupation: Developer

Object.entries() 特别适合需要同时访问键和值的场景,配合解构赋值使用非常优雅。

实际应用:对象过滤

javascript
function filterObject(obj, predicate) {
  return Object.fromEntries(Object.entries(obj).filter(predicate));
}

let products = {
  laptop: 999,
  mouse: 25,
  keyboard: 75,
  monitor: 299,
  webcam: 89,
};

// 筛选价格低于 100 的产品
let affordable = filterObject(products, ([key, price]) => price < 100);
console.log(affordable);
// { mouse: 25, keyboard: 75, webcam: 89 }

// 筛选名称包含 "key" 的产品
let withKey = filterObject(products, ([name, price]) => name.includes("key"));
console.log(withKey);
// { keyboard: 75 }

对象转换

javascript
let grades = {
  Alice: 85,
  Bob: 92,
  Charlie: 78,
  David: 95,
};

// 转换为数组格式
let gradeList = Object.entries(grades).map(([name, score]) => ({
  student: name,
  grade: score,
  passed: score >= 80,
}));

console.log(gradeList);
// [
//   { student: "Alice", grade: 85, passed: true },
//   { student: "Bob", grade: 92, passed: true },
//   { student: "Charlie", grade: 78, passed: false },
//   { student: "David", grade: 95, passed: true }
// ]

Object.assign() - 合并对象

Object.assign() 将一个或多个源对象的所有可枚举属性复制到目标对象。

javascript
let target = { a: 1, b: 2 };
let source = { b: 3, c: 4 };

let result = Object.assign(target, source);
console.log(result); // { a: 1, b: 3, c: 4 }
console.log(target); // { a: 1, b: 3, c: 4 } - 目标对象被修改
console.log(target === result); // true - 返回的就是目标对象

如果有相同的属性,后面的源对象会覆盖前面的。Object.assign() 会修改目标对象,所以如果想保持原对象不变,第一个参数应该是空对象:

javascript
let defaults = {
  theme: "light",
  fontSize: 14,
  language: "en",
};

let userSettings = {
  theme: "dark",
  fontSize: 16,
};

// ❌ 这会修改 defaults
let settings1 = Object.assign(defaults, userSettings);
console.log(defaults.theme); // "dark" - 被修改了

// ✅ 使用空对象作为目标
let defaults2 = {
  theme: "light",
  fontSize: 14,
  language: "en",
};

let settings2 = Object.assign({}, defaults2, userSettings);
console.log(defaults2.theme); // "light" - 未被修改
console.log(settings2); // { theme: "dark", fontSize: 16, language: "en" }

合并多个对象

javascript
let base = { x: 1 };
let extra1 = { y: 2 };
let extra2 = { z: 3 };

let combined = Object.assign({}, base, extra1, extra2);
console.log(combined); // { x: 1, y: 2, z: 3 }

// 现代替代方案:展开运算符(推荐)
let combined2 = { ...base, ...extra1, ...extra2 };
console.log(combined2); // { x: 1, y: 2, z: 3 }

实际应用:配置合并

javascript
function createConfig(userConfig) {
  let defaultConfig = {
    apiUrl: "https://api.example.com",
    timeout: 5000,
    retries: 3,
    cache: true,
    headers: {
      "Content-Type": "application/json",
    },
  };

  return Object.assign({}, defaultConfig, userConfig);
}

let config = createConfig({
  timeout: 10000,
  retries: 5,
});

console.log(config);
// {
//   apiUrl: "https://api.example.com",
//   timeout: 10000,
//   retries: 5,
//   cache: true,
//   headers: { "Content-Type": "application/json" }
// }

Object.freeze() - 冻结对象

Object.freeze() 冻结一个对象,使其属性无法被修改、添加或删除。

javascript
let config = {
  appName: "MyApp",
  version: "1.0.0",
  mode: "production",
};

Object.freeze(config);

// 尝试修改 - 在严格模式下会报错,非严格模式下静默失败
config.mode = "development"; // 无效
config.newProperty = "test"; // 无效
delete config.version; // 无效

console.log(config);
// { appName: "MyApp", version: "1.0.0", mode: "production" }

// 检查对象是否被冻结
console.log(Object.isFrozen(config)); // true

注意 Object.freeze() 是浅冻结,只冻结对象的第一层属性:

javascript
let user = {
  name: "Alice",
  settings: {
    theme: "dark",
    notifications: true,
  },
};

Object.freeze(user);

// ❌ 无法修改第一层
user.name = "Bob"; // 无效

// ⚠️ 可以修改嵌套对象
user.settings.theme = "light"; // 有效!
console.log(user.settings.theme); // "light"

深度冻结

要完全冻结对象(包括嵌套对象),需要递归冻结:

javascript
function deepFreeze(obj) {
  // 获取所有属性名
  Object.getOwnPropertyNames(obj).forEach((prop) => {
    let value = obj[prop];

    // 如果属性值是对象且未被冻结,递归冻结
    if (value && typeof value === "object" && !Object.isFrozen(value)) {
      deepFreeze(value);
    }
  });

  return Object.freeze(obj);
}

let appConfig = {
  api: {
    baseUrl: "https://api.example.com",
    endpoints: {
      users: "/users",
      posts: "/posts",
    },
  },
  features: {
    darkMode: true,
    notifications: false,
  },
};

deepFreeze(appConfig);

// 现在所有层级都被冻结
appConfig.api.endpoints.users = "/v2/users"; // 无效
console.log(appConfig.api.endpoints.users); // "/users"

Object.seal() - 密封对象

Object.seal() 密封一个对象,阻止添加新属性和删除现有属性,但允许修改现有属性的值。

javascript
let product = {
  name: "Laptop",
  price: 999,
  inStock: true,
};

Object.seal(product);

// ✅ 可以修改现有属性
product.price = 899;
console.log(product.price); // 899

// ❌ 无法添加新属性
product.warranty = "2 years"; // 无效

// ❌ 无法删除属性
delete product.inStock; // 无效

console.log(product);
// { name: "Laptop", price: 899, inStock: true }

// 检查对象是否被密封
console.log(Object.isSealed(product)); // true

freeze vs seal 对比

javascript
let frozenObj = Object.freeze({ x: 1 });
let sealedObj = Object.seal({ x: 1 });

// freeze: 无法修改值
frozenObj.x = 2;
console.log(frozenObj.x); // 1 - 未改变

// seal: 可以修改值
sealedObj.x = 2;
console.log(sealedObj.x); // 2 - 已改变

// 两者都无法添加新属性
frozenObj.y = 3; // 无效
sealedObj.y = 3; // 无效

console.log(Object.isFrozen(frozenObj)); // true
console.log(Object.isSealed(sealedObj)); // true
console.log(Object.isSealed(frozenObj)); // true - frozen 对象也是 sealed
console.log(Object.isFrozen(sealedObj)); // false - sealed 对象不是 frozen

Object.fromEntries() - 从键值对创建对象

Object.fromEntries()Object.entries() 的逆操作,从键值对数组创建对象。

javascript
let entries = [
  ["name", "Charlie"],
  ["age", 28],
  ["city", "London"],
];

let person = Object.fromEntries(entries);
console.log(person);
// { name: "Charlie", age: 28, city: "London" }

实际应用:URL 参数解析

javascript
function parseQueryString(queryString) {
  let params = new URLSearchParams(queryString);
  return Object.fromEntries(params);
}

let url = "?name=Alice&age=25&city=New+York";
let parsed = parseQueryString(url);
console.log(parsed);
// { name: "Alice", age: "25", city: "New York" }

Map 转对象

javascript
let userMap = new Map([
  ["id", 123],
  ["username", "alice"],
  ["email", "[email protected]"],
]);

let userObj = Object.fromEntries(userMap);
console.log(userObj);
// { id: 123, username: "alice", email: "[email protected]" }

对象转换和过滤

javascript
let prices = {
  laptop: 999,
  mouse: 25,
  keyboard: 75,
  monitor: 299,
};

// 所有价格打 9 折
let discounted = Object.fromEntries(
  Object.entries(prices).map(([item, price]) => [item, price * 0.9])
);

console.log(discounted);
// { laptop: 899.1, mouse: 22.5, keyboard: 67.5, monitor: 269.1 }

// 价格向下取整
let rounded = Object.fromEntries(
  Object.entries(discounted).map(([item, price]) => [item, Math.floor(price)])
);

console.log(rounded);
// { laptop: 899, mouse: 22, keyboard: 67, monitor: 269 }

Object.create() - 创建具有指定原型的对象

Object.create() 创建一个新对象,使用指定的对象作为新对象的原型。

javascript
let animalPrototype = {
  eat() {
    return `${this.name} is eating`;
  },
  sleep() {
    return `${this.name} is sleeping`;
  },
};

let cat = Object.create(animalPrototype);
cat.name = "Whiskers";
cat.sound = "Meow";

console.log(cat.eat()); // "Whiskers is eating"
console.log(cat.sleep()); // "Whiskers is sleeping"
console.log(cat.sound); // "Meow"

let dog = Object.create(animalPrototype);
dog.name = "Buddy";
dog.sound = "Woof";

console.log(dog.eat()); // "Buddy is eating"

创建纯净对象

普通对象会继承 Object.prototype 上的方法,有时我们需要一个完全纯净的对象:

javascript
// 普通对象继承了很多方法
let normalObj = {};
console.log(normalObj.toString); // function toString() { [native code] }
console.log(normalObj.hasOwnProperty); // function hasOwnProperty() { [native code] }

// 纯净对象没有任何继承的属性
let pureObj = Object.create(null);
console.log(pureObj.toString); // undefined
console.log(pureObj.hasOwnProperty); // undefined

// 纯净对象适合作为纯粹的数据字典
pureObj.key1 = "value1";
pureObj.key2 = "value2";

其他实用方法

Object.hasOwn() - 检查自有属性

ES2022 引入的新方法,是 hasOwnProperty 的更安全替代:

javascript
let obj = {
  name: "Alice",
  age: 25,
};

// 传统方式
console.log(obj.hasOwnProperty("name")); // true

// 新方式(推荐)
console.log(Object.hasOwn(obj, "name")); // true
console.log(Object.hasOwn(obj, "toString")); // false

// 对于 Object.create(null) 创建的对象更安全
let pureObj = Object.create(null);
pureObj.key = "value";

// ❌ hasOwnProperty 不存在
// pureObj.hasOwnProperty("key"); // TypeError

// ✅ Object.hasOwn 正常工作
console.log(Object.hasOwn(pureObj, "key")); // true

Object.getOwnPropertyNames() - 获取所有属性名

返回对象所有自有属性名,包括不可枚举的:

javascript
let obj = {
  visible: 1,
};

Object.defineProperty(obj, "hidden", {
  value: 2,
  enumerable: false,
});

console.log(Object.keys(obj)); // ["visible"]
console.log(Object.getOwnPropertyNames(obj)); // ["visible", "hidden"]

实战案例:对象工具库

javascript
const ObjectUtils = {
  // 深度克隆对象
  deepClone(obj) {
    if (obj === null || typeof obj !== "object") return obj;
    if (obj instanceof Date) return new Date(obj);
    if (obj instanceof Array) return obj.map((item) => this.deepClone(item));

    const cloned = {};
    for (let key in obj) {
      if (Object.hasOwn(obj, key)) {
        cloned[key] = this.deepClone(obj[key]);
      }
    }
    return cloned;
  },

  // 深度合并对象
  deepMerge(target, ...sources) {
    if (!sources.length) return target;

    const source = sources.shift();

    if (this.isObject(target) && this.isObject(source)) {
      for (let key in source) {
        if (Object.hasOwn(source, key)) {
          if (this.isObject(source[key])) {
            if (!target[key]) target[key] = {};
            this.deepMerge(target[key], source[key]);
          } else {
            target[key] = source[key];
          }
        }
      }
    }

    return this.deepMerge(target, ...sources);
  },

  // 检查是否为对象
  isObject(item) {
    return item && typeof item === "object" && !Array.isArray(item);
  },

  // 获取嵌套属性
  getNestedValue(obj, path) {
    return path.split(".").reduce((current, key) => current?.[key], obj);
  },

  // 设置嵌套属性
  setNestedValue(obj, path, value) {
    let keys = path.split(".");
    let lastKey = keys.pop();
    let target = keys.reduce((current, key) => {
      if (!(key in current)) current[key] = {};
      return current[key];
    }, obj);
    target[lastKey] = value;
  },

  // 删除空值属性
  removeEmpty(obj) {
    return Object.fromEntries(
      Object.entries(obj).filter(([_, value]) => {
        if (value === null || value === undefined || value === "") {
          return false;
        }
        if (this.isObject(value)) {
          return Object.keys(this.removeEmpty(value)).length > 0;
        }
        return true;
      })
    );
  },

  // 比较两个对象是否相等
  isEqual(obj1, obj2) {
    if (obj1 === obj2) return true;
    if (
      !this.isObject(obj1) ||
      !this.isObject(obj2) ||
      obj1 === null ||
      obj2 === null
    ) {
      return false;
    }

    let keys1 = Object.keys(obj1);
    let keys2 = Object.keys(obj2);

    if (keys1.length !== keys2.length) return false;

    return keys1.every((key) => {
      let val1 = obj1[key];
      let val2 = obj2[key];

      if (this.isObject(val1) && this.isObject(val2)) {
        return this.isEqual(val1, val2);
      }

      return val1 === val2;
    });
  },
};

// 使用示例
let original = {
  user: {
    name: "Alice",
    settings: { theme: "dark" },
  },
};

let cloned = ObjectUtils.deepClone(original);
cloned.user.settings.theme = "light";
console.log(original.user.settings.theme); // "dark" - 未受影响

let base = { a: 1, nested: { x: 1 } };
let override = { b: 2, nested: { y: 2 } };
let merged = ObjectUtils.deepMerge({}, base, override);
console.log(merged); // { a: 1, b: 2, nested: { x: 1, y: 2 } }

let data = { user: { profile: { name: "Bob" } } };
console.log(ObjectUtils.getNestedValue(data, "user.profile.name")); // "Bob"

let dirty = { name: "Alice", email: "", age: null, city: "NYC" };
console.log(ObjectUtils.removeEmpty(dirty)); // { name: "Alice", city: "NYC" }

常见陷阱与最佳实践

1. Object.assign 是浅拷贝

javascript
let original = {
  name: "Alice",
  settings: { theme: "dark" },
};

let copy = Object.assign({}, original);
copy.settings.theme = "light";

console.log(original.settings.theme); // "light" - 被修改了!

// ✅ 需要深拷贝时使用其他方法
let deepCopy = JSON.parse(JSON.stringify(original));

2. Object.freeze 是浅冻结

javascript
let config = Object.freeze({
  api: { url: "https://api.com" },
});

// ❌ 嵌套对象仍可修改
config.api.url = "https://new-api.com";
console.log(config.api.url); // "https://new-api.com"

3. 注意方法的返回值

javascript
let obj1 = { a: 1 };
let obj2 = { b: 2 };

// Object.assign 返回目标对象(第一个参数)
let result = Object.assign(obj1, obj2);
console.log(result === obj1); // true - obj1 被修改并返回

// Object.freeze/seal 也返回传入的对象
let frozen = Object.freeze(obj1);
console.log(frozen === obj1); // true

总结

JavaScript 对象方法为我们提供了强大的工具集:

  • Object.keys/values/entries() - 提取对象的键、值或键值对
  • Object.assign() - 合并对象(浅拷贝)
  • Object.freeze() - 完全冻结对象,不可修改
  • Object.seal() - 密封对象,可修改但不可增删属性
  • Object.fromEntries() - 从键值对数组创建对象
  • Object.create() - 创建具有指定原型的对象
  • Object.hasOwn() - 安全检查自有属性

掌握这些方法能帮助你:

  • 高效遍历和转换对象数据
  • 安全地合并和克隆对象
  • 控制对象的可变性
  • 实现复杂的数据操作和验证

这些方法是现代 JavaScript 开发的基础工具,熟练运用它们将大大提升你的代码质量和开发效率。