Does the spread operator not copy over the prototype?

Object.create() makes a new object that is prototype linked to the object passed to it. This means that the returned object doesn't get a copy of the parent's attributes, it just has a link to the parent prototype. The object spread only copies the objects own enumerable properties, which doesn't include those up the prototype chain.

const animalProto = {
 eat() {
  // function body
 },

 sleep() {
  // function body
 },
}

let o = Object.create(animalProto)
// o doesn't have it's own eat or sleep.
console.log(Object.getOwnPropertyNames(o))

console.log({...o}) // empty

See the docs:

It copies own enumerable properties from a provided object onto a new object.

"Own enumerable" means that properties on the prototype will not be included.

If you spread an object with inherited properties (such as an object immediately created with Object.create), none of those inherited properties will be present in the result.

Object.assign is slightly different - it assigns all properties to the right of the first to the first. It would be more similar to spread if you had passed an empty object as the first argument:

return Object.assign({}, Object.create(proto), attributes)

in which case nothing in proto would be reflected in the output.

const proto = { foo: 'bar' };
const result = Object.assign({}, Object.create(proto), { another: 'prop' });
console.log(result);