Skip to content

对象赋值

这段代码的输出是 456,原因与 JavaScript 中对象的键(属性名)的隐式转换有关。下面是详细的解释:


关键点:

  1. 对象的键只能是字符串(或 Symbol)
    当你尝试使用一个对象(如 bc)作为另一个对象的键时,JavaScript 会隐式调用该对象的 .toString() 方法,将其转换为字符串形式。

  2. 对象的默认 toString() 行为
    普通对象的 toString() 方法默认返回 "[object Object]"。因此:

    • b.toString()"[object Object]"
    • c.toString()"[object Object]"
  3. 实际的操作过程

    javascript
    a[b] = 123; 
    // 等价于:a["[object Object]"] = 123;
    
    a[c] = 456; 
    // 等价于:a["[object Object]"] = 456; (覆盖了之前的值)
  4. 最终输出

    javascript
    console.log(a[b]); 
    // 等价于:console.log(a["[object Object]"]); → 输出 456

为什么输出 456 而不是 123

  • 两次赋值操作(a[b] = 123a[c] = 456)实际上都是在操作同一个键 "[object Object]",因此第二次赋值会覆盖第一次的值。
  • 最终 a["[object Object]"] 的值为 456,所以 a[b] 也指向这个值。

验证:

你可以打印对象 a 的内容来观察:

javascript
console.log(a); // { "[object Object]": 456 }

此时 a 只有一个属性,键是字符串 "[object Object]",值为 456


如何避免这种问题?

如果需要用对象作为键,应该使用 Map 数据结构:

javascript
const map = new Map();
map.set(b, 123);
map.set(c, 456);
console.log(map.get(b)); // 123 (不会被覆盖)

总结:对象的键会被隐式转换为字符串,导致 bc 转换为相同的键 "[object Object]",从而引发覆盖。这是 JavaScript 中一个常见的隐式行为陷阱。

如果你想修改对象的 toString() 方法,使其返回不同的值(从而影响对象作为键时的行为),可以通过以下方式实现:


方法 1:直接覆盖 toString 方法

javascript
var a = {};
var b = { key: 'b' };
var c = { key: 'c' };

// 修改 b 和 c 的 toString 方法
b.toString = function() { return this.key; }; // 返回 'b'
c.toString = function() { return this.key; }; // 返回 'c'

a[b] = 123; // 相当于 a["b"] = 123
a[c] = 456; // 相当于 a["c"] = 456

console.log(a[b]); // 123
console.log(a[c]); // 456
console.log(a);    // { b: 123, c: 456 }

说明

  • 通过自定义 toString(),可以让 b 返回 "b"c 返回 "c",这样它们作为键时就不会冲突。
  • 这样 a[b]a[c] 会分别对应 a["b"]a["c"],不再覆盖。

方法 2:使用 Symbol 作为唯一标识(避免 toString 问题)

如果不想依赖 toString,可以使用 Symbol 来创建唯一的键:

javascript
var a = {};
var b = { key: 'b' };
var c = { key: 'c' };

// 使用 Symbol 作为键
const keyB = Symbol('b');
const keyC = Symbol('c');

a[keyB] = 123;
a[keyC] = 456;

console.log(a[keyB]); // 123
console.log(a[keyC]); // 456
console.log(a);       // { [Symbol(b)]: 123, [Symbol(c)]: 456 }

说明

  • Symbol 是唯一的,即使两个 Symbol 的描述相同,它们也是不同的键。
  • 这样 a[b]a[c] 不会互相覆盖。

方法 3:使用 Map(推荐)

如果希望用对象本身作为键,而不是字符串或 Symbol,可以使用 Map

javascript
const map = new Map();
var b = { key: 'b' };
var c = { key: 'c' };

map.set(b, 123);
map.set(c, 456);

console.log(map.get(b)); // 123
console.log(map.get(c)); // 456

说明

  • Map 允许对象作为键,而不会隐式调用 toString
  • 这是最符合直觉的方式,不会出现键冲突问题。

方法 4:修改 Object.prototype.toString(不推荐)

(仅用于实验,实际项目慎用!)
你可以覆盖 Object.prototype.toString,但这会影响所有对象,可能导致难以预测的行为:

javascript
Object.prototype.toString = function() {
  return this.key || "[object Object]";
};

var a = {};
var b = { key: 'b' };
var c = { key: 'c' };

a[b] = 123; // a["b"] = 123
a[c] = 456; // a["c"] = 456

console.log(a[b]); // 123
console.log(a[c]); // 456

⚠️ 风险

  • 修改全局 Object.prototype 会影响所有对象,可能导致其他代码出错。
  • 实际开发中应避免这样做。

总结

方法适用场景是否推荐
覆盖 toString需要自定义对象作为键的字符串形式✅ 可接受
使用 Symbol需要唯一键,但不依赖对象内容✅ 推荐
使用 Map需要直接用对象作为键✅ 最佳方案
修改 Object.prototype实验性代码,不用于生产❌ 不推荐

推荐做法

  • 如果只是想让对象作为键不冲突 → Map
  • 如果希望对象转字符串时有自定义行为 → 覆盖 toString
  • 如果需要完全唯一的键 → Symbol