Object.create 动作一样,挂在谁身上结果完全不同
看这段代码:
let A = function(){};
let B = function(){};
A = Object.create(B); // 这一步是坑
let a = new A(); // 直接抛错
let b = new B();
let c = {};
let d = {};
c = Object.create(d);
问:a 和 b 的关系,跟 c 和 d 的关系,差在哪?
先修坑:A 已经不是函数了
Object.create(B) 返回的是普通对象,没有 [[Construct]]:
typeof A; // "object"
new A(); // TypeError: A is not a constructor
而且 A 自己没有 prototype,会沿原型链摸到 B 的:
A.prototype === B.prototype; // true,但不是你想的那个意思
A instanceof B; // false
A instanceof B 是 false —— instanceof 查的是 B.prototype 在不在 A 的原型链上。A 的链是 A → B → Function.prototype → Object.prototype → null,B.prototype 恰恰不在里面。B 的本体在链上,但它不算数。
new 要的是 .prototype 那个中转对象,不是函数本身。
正解
A.prototype = Object.create(B.prototype);
A.prototype.constructor = A; // 补回 constructor 回指
这样 new A() 正常,a instanceof B 也是 true。
关键:Object.create 两次动作完全一样
两处都只是设置 [[Prototype]],产出的对象都是空壳:
Object.getOwnPropertyNames(A.prototype); // []
Object.getOwnPropertyNames(c); // []
所以不能拿 a 和 c 直接对 —— 得先把角色对齐:
| 角色 | a / b 场景 | c / d 场景 |
|---|---|---|
| Object.create 产出的子对象 | A.prototype | c |
| 传进去的父对象 | B.prototype | d |
子对象被 new 用过吗 | 是(new A() → a) | 否 |
| 父对象是什么 | 类型代表 | 普通数据 |
a 和 b 是实例;c / d 这一侧根本没有实例这一层。
差异一:父对象身份,决定 constructor 回指
B.prototype.hasOwnProperty("constructor"); // true —— 函数自带
d.hasOwnProperty("constructor"); // false —— 普通对象没有
于是:
A.prototype.constructor === A; // false
A.prototype.constructor === B; // true,被 Object.create 搞坏了
a.constructor === B; // true
Object.create(B.prototype) 挂的是类型代表,它 own 的 constructor: B 会被一起继承下来 —— 所以 A.prototype.constructor 变成了 B。这就是必须手动补 A.prototype.constructor = A 的原因。
再看 c / d:
c.constructor === Object; // true
d.constructor === Object; // true
两边都解析到 Object,没有不一致。因为 d 从来就不是某个 constructor 的代表,压根没有需要对齐的东西。
差异二:链深度,一个三层一个两层
a → A.prototype → B.prototype → Object.prototype → null
b → B.prototype → Object.prototype → null
c → d → Object.prototype → null
a 和 b 是两个不同类的实例,但共享祖先 B.prototype:
a instanceof A; // true a instanceof B; // true
b instanceof A; // false b instanceof B; // true
b 的原型链是 a 原型链的后缀,a 比 b 多一层。
c / d 则完全没有「是不是某个类的实例」这回事:
c instanceof d; // TypeError: Right-hand side of 'instanceof' is not callable
d 不可调用,instanceof 直接报错。
差异三:语义负载
A.prototype = Object.create(B.prototype)—— 搭类型继承链。有副作用(砸掉 constructor),要手动修,换来跨类的instanceof判断。c = Object.create(d)—— 纯原型委托(不用 class / new 的 OLOO 风格)。没有副作用,也不产生新类型,只是「c上查不到就去d查」。
小结
new B() | Object.create(d) | |
|---|---|---|
| 原型指向谁 | B.prototype(中转对象) | d 本身 |
到 Object.prototype 几跳 | 2 | 1 |
| 对方本体在链上吗 | 否 | 是 |
| constructor 回指 | 有(B.prototype.constructor === B) | 无 |
产生新类型、可 instanceof | 是 | 否 |
一句话:Object.create 每次都只做一件事 —— 设置原型。差别全在挂上去的是什么:挂 B.prototype 是接进类型系统(多层、有 constructor 回指、要手动修),挂 d 只是两个普通对象之间的一个指针。
Consider this:
let A = function(){};
let B = function(){};
A = Object.create(B); // this line is the trap
let a = new A(); // throws
let b = new B();
let c = {};
let d = {};
c = Object.create(d);
What is the difference between the a / b relationship and the c / d relationship?
First, fix the trap: A is no longer a function
Object.create(B) returns a plain object, with no [[Construct]]:
typeof A; // "object"
new A(); // TypeError: A is not a constructor
A also has no prototype of its own, so the lookup walks up and finds B's:
A.prototype === B.prototype; // true, but not in the sense you meant
A instanceof B; // false
A instanceof B is false — instanceof checks whether B.prototype is somewhere in A's prototype chain. A's chain is A → B → Function.prototype → Object.prototype → null, and B.prototype is not in it. B itself is on the chain, but that does not count.
new wants the .prototype relay object, not the function itself.
The fix
A.prototype = Object.create(B.prototype);
A.prototype.constructor = A; // restore the constructor back-reference
Now new A() works, and a instanceof B is true.
The key point: Object.create does the same thing both times
Both calls do nothing but set [[Prototype]]. Both produce an empty shell:
Object.getOwnPropertyNames(A.prototype); // []
Object.getOwnPropertyNames(c); // []
So you cannot compare a with c directly — line up the roles first:
| Role | a / b case | c / d case |
|---|---|---|
| The child object Object.create produces | A.prototype | c |
| The parent object passed in | B.prototype | d |
Was the child ever used with new? | Yes (new A() → a) | No |
| What the parent is | a type representative | plain data |
a and b are instances; the c / d side has no instances at all.
Difference 1: the parent's identity decides the constructor back-reference
B.prototype.hasOwnProperty("constructor"); // true — functions get one for free
d.hasOwnProperty("constructor"); // false — plain objects do not
And so:
A.prototype.constructor === A; // false
A.prototype.constructor === B; // true, broken by Object.create
a.constructor === B; // true
Object.create(B.prototype) attaches you to a type representative, and its own constructor: B is inherited along with everything else — which is how A.prototype.constructor ends up being B. That is exactly why you must add A.prototype.constructor = A by hand.
Now the c / d side:
c.constructor === Object; // true
d.constructor === Object; // true
Both resolve to Object. There is no inconsistency, because d was never the representative of a constructor — there is nothing to keep aligned.
Difference 2: chain depth — three levels vs two
a → A.prototype → B.prototype → Object.prototype → null
b → B.prototype → Object.prototype → null
c → d → Object.prototype → null
a and b are instances of two different classes, sharing the ancestor B.prototype:
a instanceof A; // true a instanceof B; // true
b instanceof A; // false b instanceof B; // true
b's prototype chain is a suffix of a's; a has one extra level.
The c / d pair has no "is it an instance of something" notion at all:
c instanceof d; // TypeError: Right-hand side of 'instanceof' is not callable
d is not callable, so instanceof throws.
Difference 3: what each one is semantically for
A.prototype = Object.create(B.prototype)— building a type inheritance chain. It has a side effect (it wrecksconstructor), you fix that by hand, and what you get in return is cross-classinstanceof.c = Object.create(d)— plain prototype delegation (the OLOO style, no class or new). No side effects, and it creates no new type; it just says "if it is not onc, look ond".
Summary
new B() | Object.create(d) | |
|---|---|---|
| Prototype points at | B.prototype (a relay object) | d itself |
Hops to Object.prototype | 2 | 1 |
| Is the other thing itself on the chain | No | Yes |
| Constructor back-reference | Yes (B.prototype.constructor === B) | No |
Creates a new type / usable with instanceof | Yes | No |
One line: Object.create only ever does one thing — set the prototype. Everything else depends on what you hang on it: hanging B.prototype plugs you into the type system (extra levels, a constructor back-reference you have to repair), while hanging d is just a pointer between two plain objects.