In a browser window, window and self are both accessor properties on the global object and both return the globalThis value of the relevant realm. I.e window==self//true
However running Object.getOwnPropertyDescriptor() on them yields different results. As you can see below, Object.getOwnPropertyDescriptor(window, 'window') returns {configurable: false, get: get(), set: undefined}, while Object.getOwnPropertyDescriptor(window, 'self') returns {configurable: true, get: get(), set: set()} where set() is a setter that overwrites self with the given value.
console.log({
window: Object.getOwnPropertyDescriptor(globalThis, 'window'),
self: Object.getOwnPropertyDescriptor(globalThis, 'self')
});
According to the spec, as properties of the main global object, window is [LegacyUnforgeable] and self is [Replaceable] In practice what that means is that unlike window, self can be deleted or overwritten. It doesn't make any sense then that self is a "read-only property" of the window object as it says in the spec.
Workers
In workers window is undefined and will throw a ReferenceError, while self is a local variable rather than a property on the global object and cannot be overwritten as it can in a browser window.
Conclusion
Both self and window return the same object in a browser window however unlike window, self can be deleted or replaced.
Only self can be safely referenced in both a browser window and a worker.
globalThis was added to ES2020 to create a uniform way of getting the global object. But as it still doesn't work in many browsers, you're better off using self in browser environments.