← 返回博客

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.prototypec
传进去的父对象B.prototyped
子对象被 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 几跳21
对方本体在链上吗否是
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:

Rolea / b casec / d case
The child object Object.create producesA.prototypec
The parent object passed inB.prototyped
Was the child ever used with new?Yes (new A() → a)No
What the parent isa type representativeplain 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 wrecks constructor), you fix that by hand, and what you get in return is cross-class instanceof.
  • 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 on c, look on d".

Summary

new B()Object.create(d)
Prototype points atB.prototype (a relay object)d itself
Hops to Object.prototype21
Is the other thing itself on the chainNoYes
Constructor back-referenceYes (B.prototype.constructor === B)No
Creates a new type / usable with instanceofYesNo

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.