一、先搞清两个概念
- 静态方法:定义在构造函数上,直接用
Object.method()调用,常用于创建、检查与操作对象。 - 实例方法:定义在
Object.prototype上,几乎所有对象都会继承,可用obj.method()调用。通过Object.create(null)创建的对象没有原型,不继承这些方法。
补充:属性描述符包含
value,writable,enumerable,configurableget,set(访问器属性) 默认值提醒:用defineProperty创建属性时,writable/enumerable/configurable默认为false。
二、Object 的静态方法(Object.method)
1) Object.create(proto, [propertiesObject])
- 作用:基于
proto作为原型创建对象;可同时用描述符定义属性。 - 示例:原型式继承与“纯字典”
const person = { greet() { console.log(`Hi, I'm ${this.name}`); } };
const me = Object.create(person);me.name = 'Alice';me.greet(); // "Hi, I'm Alice"
// 纯字典:无原型,适合做安全键值表const dict = Object.create(null);dict['__proto__'] = 'ok'; // 不会污染原型console.log(Object.getPrototypeOf(dict)); // null- 常用场景:原型式继承;创建“无原型字典”避免原型污染。
2) Object.assign(target, ...sources)
- 作用:将源对象的“自有可枚举属性”(含 symbol)浅拷贝到
target。 - 示例:合并默认配置与用户配置
const defaults = { color: 'blue', size: 'm', theme: { contrast: 'normal' } };const user = { color: 'red', theme: { contrast: 'high' } };
const finalOptions = Object.assign({}, defaults, user);// user 覆盖了 defaultsconsole.log(finalOptions);// { color: 'red', size: 'm', theme: { contrast: 'high' } } (浅拷贝)
// 注意:嵌套对象是浅拷贝,引用会被共享finalOptions.theme.contrast = 'low';console.log(defaults.theme.contrast); // 'low'- 小贴士:语法糖
{ ...defaults, ...user }等价。若要深拷贝/深合并,需要递归或使用库。
3) Object.defineProperty(obj, prop, descriptor)
- 作用:用描述符精细控制属性(只读、隐藏、getter/setter 等)。
- 示例:定义只读与计算属性
const user = {};Object.defineProperty(user, 'id', { value: 1001, writable: false, // 只读 enumerable: false, // 不出现在枚举中 configurable: false});
let first = 'Alice', last = 'Wang';Object.defineProperty(user, 'fullName', { get() { return `${first} ${last}`; }, set(v) { [first, last] = v.split(' '); }, enumerable: true});
console.log(user.fullName); // "Alice Wang"user.fullName = 'Bob Lee';console.log(user.fullName); // "Bob Lee"- 常见错误:同一属性不能同时设置
value/writable与get/set。
4) Object.defineProperties(obj, descriptors)
- 作用:一次性定义多个属性。
- 示例:
const product = {};Object.defineProperties(product, { id: { value: 1, enumerable: true }, name: { value: 'Book', enumerable: true }, priceWithTax: { get() { return 12.5 * 1.1; }, enumerable: true }});5) Object.getOwnPropertyDescriptor(obj, prop)
- 作用:获取某个自身(非继承的)属性 的描述符。
const o = {};Object.defineProperty(o, 'hidden', { value: 1, enumerable: false });console.log(Object.getOwnPropertyDescriptor(o, 'hidden'));// { value: 1, writable: false, enumerable: false, configurable: false }6) Object.getOwnPropertyDescriptors(obj)
- 作用:获取所有自身(非继承的)属性 的描述符映射。
- 用法:保留 getter/setter/可枚举性的“完整克隆”
function cloneWithDescriptors(src) { const clone = Object.create(Object.getPrototypeOf(src)); return Object.defineProperties(clone, Object.getOwnPropertyDescriptors(src));}7) Object.keys(obj)
- 作用:返回自有“可枚举”的字符串键数组(不包含 symbol)。
const user = { id: 1, name: 'Bob' };console.log(Object.keys(user)); // ['id', 'name']8) Object.values(obj)
- 作用:返回自有可枚举属性值数组。
console.log(Object.values({ a: 1, b: 2 })); // [1, 2]9) Object.entries(obj)
- 作用:返回自有可枚举的
[key, value]数组。 - 示例:对象映射/过滤
const user = { id: 1, name: 'Bob', password: '***' };const safe = Object.fromEntries(Object.entries(user).filter(([k]) => k !== 'password'));console.log(safe); // { id: 1, name: 'Bob' }10) Object.fromEntries(iterable)
- 作用:将键值对(如
Map或entries)转为对象。
const map = new Map([['a', 1], ['b', 2]]);console.log(Object.fromEntries(map)); // { a: 1, b: 2 }11) Object.getOwnPropertyNames(obj)
- 作用:获取自有非继承 字符串键(包含不可枚举,不包含 symbol)。
const o = Object.create(null, { a: { value: 1, enumerable: false } });console.log(Object.getOwnPropertyNames(o)); // ['a']12) Object.getOwnPropertySymbols(obj)
- 作用:获取自有 symbol 键。
const s = Symbol('x');const o = { [s]: 123 };console.log(Object.getOwnPropertySymbols(o)); // [ Symbol(x) ]- 相关:想一次拿到“字符串键 + symbol 键”,用
Reflect.ownKeys(obj)(不是 Object 方法)。
13) Object.getPrototypeOf(obj)
- 作用:获取对象原型。
const arr = [];console.log(Object.getPrototypeOf(arr) === Array.prototype); // true14) Object.setPrototypeOf(obj, proto)
- 作用:设置对象原型(性能较差,创建时用
Object.create更好)。
const a = {};const proto = { x: 1 };Object.setPrototypeOf(a, proto);console.log(a.x); // 115) Object.freeze(obj)
- 作用:冻结对象(浅冻结):不能新增/删除/改值/改描述符。
const conf = Object.freeze({ api: 'v1', nested: { a: 1 } });conf.api = 'v2'; // 无效(严格模式下报错)conf.nested.a = 2; // 有效(浅冻结)16) Object.seal(obj)
- 作用:密封对象:不可新增/删除属性,但可改值。
const o = Object.seal({ a: 1 });o.a = 2; // 可delete o.a; // 不可17) Object.preventExtensions(obj)
- 作用:阻止新增属性(已有属性能否删/改由各自描述符决定)。
const o = Object.preventExtensions({ a: 1 });o.b = 2; // 无效18) Object.is(value1, value2)
- 作用:同值相等判断(区别
===)
console.log(Object.is(NaN, NaN)); // trueconsole.log(Object.is(+0, -0)); // falseconsole.log(Object.is(1, 1)); // true19) Object.isExtensible(obj) / Object.isFrozen(obj) / Object.isSealed(obj)
- 作用:分别判断对象是否可扩展/已冻结/已密封。
const o = {};console.log(Object.isExtensible(o)); // trueObject.preventExtensions(o);console.log(Object.isExtensible(o)); // false20) Object.hasOwn(obj, prop)(推荐)
- 作用:安全判断“自有属性”,不受对象上同名字段影响(ES2022+)。
const o = Object.create({ inherited: 1 });o.own = 2;console.log(Object.hasOwn(o, 'own')); // trueconsole.log(Object.hasOwn(o, 'inherited')); // false三、Object.prototype 的实例方法(obj.method)
1) hasOwnProperty(prop)
- 作用:判断是否为“自有属性”。
- 注意:对象可能定义同名字段覆盖该方法,使用更安全的
Object.hasOwn(obj, prop)。
const o = { a: 1 };console.log(o.hasOwnProperty('a')); // true2) propertyIsEnumerable(prop)
- 作用:判断“自有属性”是否可枚举。
const o = {};Object.defineProperty(o, 'x', { value: 1, enumerable: false });console.log(o.propertyIsEnumerable('x')); // false3) isPrototypeOf(object)
- 作用:判断某对象是否在另一个对象的原型链上。
const proto = { p: 1 };const obj = Object.create(proto);console.log(proto.isPrototypeOf(obj)); // true4) toString()
- 作用:返回字符串表示;配合
call做类型判断。
console.log({}.toString()); // "[object Object]"console.log(Object.prototype.toString.call([])); // "[object Array]"console.log(Object.prototype.toString.call(null)); // "[object Null]"- 提示:可被
Symbol.toStringTag影响。
5) toLocaleString()
- 作用:本地化字符串表示;如
Date、Number已重写。
console.log((1234567.89).toLocaleString('de-DE')); // "1.234.567,89"6) valueOf()
- 作用:返回对象的“原始值”。多数内建类型已重写,一般很少自定义。
console.log(new Number(123).valueOf()); // 123- 进阶:如需自定义对象到原始值的转换,优先使用
Symbol.toPrimitive。
四、常见实战场景(含示例)
场景 1:安全遍历对象属性
- 只遍历自有可枚举属性
const user = { name: 'Alice', age: 30 };for (const [k, v] of Object.entries(user)) { console.log(k, v);}- 必须用
for...in时,配合Object.hasOwn
for (const k in user) { if (Object.hasOwn(user, k)) { console.log(k, user[k]); }}- 包含 symbol 或不可枚举属性时
for (const k of Reflect.ownKeys(user)) { const desc = Object.getOwnPropertyDescriptor(user, k); console.log(k, desc && desc.value);}场景 2:合并默认配置与用户配置
const defaults = { color: 'blue', text: 'Click', style: { fontSize: 14 } };const user = { color: 'red' };
const opts = { ...defaults, ...user }; // 或 Object.assign({}, defaults, user)- 深合并提示
function deepMerge(a, b) { const out = { ...a }; for (const [k, v] of Object.entries(b)) { if (v && typeof v === 'object' && !Array.isArray(v)) { out[k] = deepMerge(a[k] || {}, v); } else { out[k] = v; } } return out;}场景 3:创建只读配置 / 不可变对象
const config = Object.freeze({ apiKey: 'abc-123', nested: { a: 1 } });// 深冻结:对子对象递归function deepFreeze(o) { Object.getOwnPropertyNames(o).forEach((k) => { const v = o[k]; if (v && typeof v === 'object') deepFreeze(v); }); return Object.freeze(o);}场景 4:克隆对象(保留原型、getter/setter、枚举性)
function cloneWithDescriptors(src) { const clone = Object.create(Object.getPrototypeOf(src)); return Object.defineProperties(clone, Object.getOwnPropertyDescriptors(src));}场景 5:过滤/重命名对象字段
const user = { id: 1, name: 'Bob', password: '***' };const safe = Object.fromEntries(Object.entries(user).filter(([k]) => k !== 'password'));const renamed = Object.fromEntries(Object.entries(user).map(([k, v]) => [k === 'id' ? 'userId' : k, v]));场景 6:构建“纯字典”避免原型污染
const dict = Object.create(null);dict['toString'] = 'ok'; // 只是普通键,不会覆盖方法dict['__proto__'] = 'ok'; // 不会影响原型场景 7:精准类型判断与“普通对象”判定
const type = Object.prototype.toString.call([]).slice(8, -1); // "Array"
function isPlainObject(x) { if (typeof x !== 'object' || x === null) return false; const p = Object.getPrototypeOf(x); return p === Object.prototype || p === null;}场景 8:锁定对象“形状”(防意外扩展)
const opts = { a: 1 };Object.seal(opts); // 禁止新增/删除键,但可改值Object.preventExtensions(opts); // 禁止新增键(删除/修改看描述符)场景 9:对象与 Map 互转
const obj = { a: 1, b: 2 };const map = new Map(Object.entries(obj));const back = Object.fromEntries(map);五、常见坑与最佳实践
- 浅拷贝陷阱:
Object.assign与展开语法都是浅拷贝;嵌套对象会共享引用。 - Getter 会被触发:
Object.assign会读取源对象属性,可能触发 getter 和副作用。 defineProperty默认不可写/不可枚举/不可配置,记得显式设置。- 遍历顺序:
Object.keys/values/entries不包含 symbol;整数索引键会排在字符串键前。 for...in会枚举原型链,需配合Object.hasOwn(obj, key)。- 性能提示:避免频繁使用
Object.setPrototypeOf;创建时用Object.create。 - 冻结是浅的:
Object.freeze不会冻结子对象;有需要请写deepFreeze。 - 更安全的“自有属性”判断:优先
Object.hasOwn(obj, key),而不是obj.hasOwnProperty(key)。
六、附录:小工具函数合集
// 深冻结function deepFreeze(o) { Object.getOwnPropertyNames(o).forEach((k) => { const v = o[k]; if (v && typeof v === 'object') deepFreeze(v); }); return Object.freeze(o);}
// 完整克隆(保留原型与描述符)function cloneWithDescriptors(src) { const clone = Object.create(Object.getPrototypeOf(src)); return Object.defineProperties(clone, Object.getOwnPropertyDescriptors(src));}
// 选择部分字段(pick)function pick(obj, keys) { return Object.fromEntries(keys.filter(k => Object.hasOwn(obj, k)).map(k => [k, obj[k]]));}
// 排除部分字段(omit)function omit(obj, keys) { const ban = new Set(keys); return Object.fromEntries(Object.entries(obj).filter(([k]) => !ban.has(k)));}