Skip to the code
cf.

Object.assign

MediumPolyfills

Write Object.myAssign(target, ...sources), which works like Object.assign without calling it. It copies every own enumerable property of each source onto target, one source after another, so later sources overwrite earlier ones. It returns target itself.

Copy symbol keys as well as string keys. Skip inherited and non-enumerable properties, and skip sources that are null or undefined. Throw a TypeError if target is null or undefined.

Copy values the way assignment does: read each property with a normal get, so a getter on a source runs and its value is copied, and write it with a normal set, so a setter on the target runs.

const target = { a: 1 };
const result = Object.myAssign(target, { b: 2 }, null, { a: 3 });
result === target; // true
target;            // { a: 3, b: 2 }

What the tests check

  1. copies own properties and returns the target itself
    const target = { a: 1 };
    const result = Object.myAssign(target, { b: 2 });
    expect(result).toBe(target);
    expect(target).toEqual({ a: 1, b: 2 });
    
  2. lets later sources overwrite earlier ones
    const result = Object.myAssign({ a: 0 }, { a: 1, b: 1 }, { b: 2 }, { b: 3, c: 3 });
    expect(result).toEqual({ a: 1, b: 3, c: 3 });
    
  3. skips null and undefined sources
    expect(Object.myAssign({ a: 1 }, null, undefined, { b: 2 })).toEqual({ a: 1, b: 2 });
    
  4. copies symbol keys
    const id = Symbol("id");
    const result = Object.myAssign({}, { [id]: 7, name: "Ada" });
    expect(result[id]).toBe(7);
    expect(result.name).toBe("Ada");
    
  5. skips inherited and non-enumerable properties
    const hiddenSymbol = Symbol("hidden");
    const source = Object.create({ inherited: 1 });
    source.own = 2;
    Object.defineProperty(source, "hidden", { value: 3, enumerable: false });
    Object.defineProperty(source, hiddenSymbol, { value: 4, enumerable: false });
    const result = Object.myAssign({}, source);
    expect(Reflect.ownKeys(result)).toEqual(["own"]);
    
  6. runs getters on sources and copies their values
    const source = {
      get answer() {
        return 42;
      },
    };
    const result = Object.myAssign({}, source);
    expect(Object.getOwnPropertyDescriptor(result, "answer")).toEqual({
      value: 42,
      writable: true,
      enumerable: true,
      configurable: true,
    });
    
  7. calls setters on the target
    const log = [];
    const target = {
      set name(value) {
        log.push(value);
      },
    };
    Object.myAssign(target, { name: "Ada" });
    expect(log).toEqual(["Ada"]);
    
  8. throws a TypeError when target is null or undefined
    expect(() => Object.myAssign(null)).toThrow(TypeError);
    expect(() => Object.myAssign(undefined, { a: 1 })).toThrow(TypeError);
    
  9. works without the built-in Object.assign
    const builtIn = Object.assign;
    Object.assign = function () {
      throw new Error("myAssign called the built-in Object.assign");
    };
    let result;
    try {
      result = Object.myAssign({ a: 1 }, { b: 2 });
    } finally {
      Object.assign = builtIn;
    }
    expect(result).toEqual({ a: 1, b: 2 });
    

Learn more

solution.jsSaved in this browser

Run tests runs every test on your code, here in your browser. The problem is solved when they all pass.

Ctrl + Enter runs the tests (⌘ + Enter on a Mac).