对象方法:掌握内置工具箱
一个专业的木匠工具箱里不仅有锤子、锯子这些基本工具,还有各种专用工具——量尺用于测量、水平仪用于校准、角尺用于确保直角。JavaScript 的 Object 同样提供了一套完整的内置方法,帮助我们检查对象、转换数据、控制属性,以及实现各种复杂的操作。掌握这些方法就像掌握了一套专业工具,能让你更高效地处理对象数据。
Object.keys() - 获取所有键
Object.keys() 返回一个包含对象所有可枚举属性名的数组。
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 YorkObject.keys() 只返回对象自身的属性(不包括继承的属性),并且只返回可枚举的属性。这使它成为遍历对象属性的标准方法。
实际应用:验证对象字段
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"] }对象属性计数
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)); // falseObject.values() - 获取所有值
Object.values() 返回一个包含对象所有可枚举属性值的数组。
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实际应用:数据统计
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}`); // miceObject.entries() - 获取键值对
Object.entries() 返回一个包含对象所有可枚举属性的键值对数组。
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: DeveloperObject.entries() 特别适合需要同时访问键和值的场景,配合解构赋值使用非常优雅。
实际应用:对象过滤
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 }对象转换
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() 将一个或多个源对象的所有可枚举属性复制到目标对象。
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() 会修改目标对象,所以如果想保持原对象不变,第一个参数应该是空对象:
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" }合并多个对象
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 }实际应用:配置合并
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() 冻结一个对象,使其属性无法被修改、添加或删除。
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() 是浅冻结,只冻结对象的第一层属性:
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"深度冻结
要完全冻结对象(包括嵌套对象),需要递归冻结:
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() 密封一个对象,阻止添加新属性和删除现有属性,但允许修改现有属性的值。
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)); // truefreeze vs seal 对比
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 对象不是 frozenObject.fromEntries() - 从键值对创建对象
Object.fromEntries() 是 Object.entries() 的逆操作,从键值对数组创建对象。
let entries = [
["name", "Charlie"],
["age", 28],
["city", "London"],
];
let person = Object.fromEntries(entries);
console.log(person);
// { name: "Charlie", age: 28, city: "London" }实际应用:URL 参数解析
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 转对象
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]" }对象转换和过滤
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() 创建一个新对象,使用指定的对象作为新对象的原型。
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 上的方法,有时我们需要一个完全纯净的对象:
// 普通对象继承了很多方法
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 的更安全替代:
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")); // trueObject.getOwnPropertyNames() - 获取所有属性名
返回对象所有自有属性名,包括不可枚举的:
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"]实战案例:对象工具库
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 是浅拷贝
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 是浅冻结
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. 注意方法的返回值
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 开发的基础工具,熟练运用它们将大大提升你的代码质量和开发效率。