On this page
Polyfill: Object.assign and Object.create
Last reviewed 22 Sept 2026
Problem
Implement assign(target, ...sources) and create(proto, descriptors?) to match Object.assign and Object.create:
assign({ a: 1 }, { b: 2 }, null, { a: 3 }); // { a: 3, b: 2 }
const animal = { speak() { return `${this.name} makes a sound`; } };const dog = create(animal, { name: { value: 'Rex', enumerable: true } });dog.speak(); // "Rex makes a sound"Object.getPrototypeOf(dog) === animal; // trueClarifying questions
assign: arenull/undefinedsources skipped? Yes. Anulltarget throws.- Copy symbol keys? Yes — own enumerable string and symbol keys.
- Should getters be copied as getters or invoked? Invoked:
assignreads the value and writes it with=. create: mustcreate(null)produce an object with no prototype? Yes.
Approach
assign is a loop over sources and their own enumerable keys, reading each value and assigning it to the target, which triggers setters. create needs an object whose prototype is proto: the classic trick is a throwaway constructor whose prototype is proto. The optional second argument is handed to Object.defineProperties.
Step-by-step build
Step 1 — assign, string keys
function assign(target, ...sources) { if (target == null) throw new TypeError('Cannot convert undefined or null to object'); const to = Object(target); for (const src of sources) { if (src == null) continue; for (const key of Object.keys(src)) to[key] = src[key]; } return to;}Step 2 — include enumerable symbol keys
Object.keys skips symbols. Use Reflect.ownKeys and keep only enumerable ones.
for (const key of Reflect.ownKeys(Object(src))) { if (Object.prototype.propertyIsEnumerable.call(src, key)) to[key] = src[key];}Step 3 — create with the constructor trick
function create(proto, descriptors) { if (proto !== null && typeof proto !== 'object' && typeof proto !== 'function') throw new TypeError('Object prototype may only be an Object or null'); function F() {} F.prototype = proto; const obj = new F(); if (proto === null) Object.setPrototypeOf(obj, null); // new F() would fall back to Object.prototype if (descriptors !== undefined) Object.defineProperties(obj, descriptors); return obj;}Final code
function assign(target, ...sources) { if (target == null) throw new TypeError('Cannot convert undefined or null to object'); const to = Object(target); for (const src of sources) { if (src == null) continue; const from = Object(src); for (const key of Reflect.ownKeys(from)) { if (Object.prototype.propertyIsEnumerable.call(from, key)) to[key] = from[key]; } } return to;}
function create(proto, descriptors) { if (proto !== null && typeof proto !== 'object' && typeof proto !== 'function') { throw new TypeError('Object prototype may only be an Object or null: ' + proto); } function F() {} F.prototype = proto; const obj = new F(); if (proto === null) Object.setPrototypeOf(obj, null); if (descriptors !== undefined) Object.defineProperties(obj, descriptors); return obj;}Edge cases
assign(null)throws;assign({}, null, undefined)returns{}.- String sources are spread by index:
assign({}, 'ab')→{ 0: 'a', 1: 'b' }. - Getters on sources run, and their current value is copied; setters on the target run too.
assignis shallow: nested objects are shared, not copied — see deep clone.create(null)has notoString,hasOwnProperty— useful for dictionaries.
Follow-ups
- Difference between
assignand spread{ ...a }? Spread defines properties (no setters on the target run);assignsets them. - Why not
obj.__proto__ = proto? Non-standard historically, slow, and does nothing forObject.create(null)-style objects. - Implement inheritance ES5-style using
create:Child.prototype = create(Parent.prototype); Child.prototype.constructor = Child.
Common mistakes
- Using
for…in, which also copies inherited enumerable properties. - Forgetting symbol keys.
- Treating
assignas a deep copy. create(null)returning an object that still inherits fromObject.prototype.
Related
- Concepts: prototypes and inheritance in the JavaScript track.
- Next: the new operator and instanceof.