Implement a polyfill for Object.create
Object.create(proto) makes a new object whose [[Prototype]] is proto. Classic polyfill: a temp constructor function F whose prototype is set to proto, then return new F(). Handle null proto and the optional propertiesDescriptor second argument via Object.defineProperties.
Object.create(proto, [propertiesObject]) creates a new object with its prototype set to proto. The polyfill is a clever use of the only prototype-setting tool that existed before Object.create: new.
The classic polyfill
Object.create = Object.create || function (proto, propertiesObject) {
// 1. validate proto
if (typeof proto !== "object" && typeof proto !== "function" && proto !== null) {
throw new TypeError("Object prototype may only be an Object or null");
}
// 2. the trick: a throwaway constructor whose .prototype is the proto arg
function F() {}
F.prototype = proto;
const obj = new F(); // obj.[[Prototype]] === proto
// 3. null proto: F.prototype can't truly be null, so patch after
if (proto === null) {
Object.setPrototypeOf
? Object.setPrototypeOf(obj, null)
: (obj.__proto__ = null);
}
// 4. optional second arg — property descriptors
if (propertiesObject !== undefined) {
Object.defineProperties(obj, propertiesObject);
}
return obj;
};Why the temp-constructor trick works
Before Object.create, the only way to control an object's prototype was new: new F() produces an object whose internal [[Prototype]] is F.prototype. So you set F.prototype = proto, do new F(), and you've created an object with the prototype you wanted — F itself is just a disposable vehicle (F() {} is empty, so no construction side effects).
The semantics to get right
- The prototype link —
Object.create(p)→ the new object delegates top. Property lookups not found on the object walk up top. proto === null—Object.create(null)makes a "bare" object with no prototype — notoString,hasOwnProperty, etc. Useful as a clean dictionary/map. The temp-constructor trick can't directly produce a null prototype (F.prototype = nullfalls back toObject.prototype), so you patch it after — which is why the polyfill is imperfect for the null case in truly old environments.- Second argument —
propertiesObjectis a property descriptors map (like the 2nd arg ofObject.defineProperties), not plain key/values:{ x: { value: 1, writable: true, enumerable: true } }. - Validation — throw
TypeErrorifprotoisn't an object, function, or null.
The framing
"Object.create makes a new object with a given prototype. The polyfill leans on the only pre-existing prototype-setting mechanism — new: define an empty throwaway constructor F, set F.prototype = proto, and new F() gives you an object whose [[Prototype]] is proto. Then handle the edges: validate proto, special-case proto === null (the trick can't natively produce a null prototype, so patch it), and apply the optional second argument via Object.defineProperties since it takes descriptors, not values."
Follow-up questions
- •Why does the empty constructor function trick work?
- •What's special about Object.create(null)?
- •What form does the second argument take?
- •Why was Object.create added when `new` already existed?
Common mistakes
- •Treating the second argument as plain key/values instead of property descriptors.
- •Not handling proto === null.
- •Forgetting to validate the proto argument type.
- •Adding construction logic to F (it must be empty).
Performance considerations
- •Object creation is cheap. Object.create(null) dictionaries can be slightly faster for pure key/value use (no prototype chain to walk) and avoid prototype-key collisions.
Edge cases
- •proto === null — bare object, no inherited methods.
- •proto is a function (valid — functions are objects).
- •proto is a primitive — should throw TypeError.
- •Second argument with getters/setters in descriptors.
Real-world examples
- •Object.create(null) for safe hash maps free of prototype pollution.
- •Setting up prototype chains for inheritance before ES6 classes.