this is set by the call
Inside a normal function, this is a hidden value the function receives every time it is called. It is not fixed where the function is written. How you call the function decides what this is.
One function, two objects
"use strict" turns on strict mode for the whole file. One rule it changes: when a call gives this no value, this stays undefined instead of becoming the global object. Classes and modules are always strict.
Line 2 makes one function, hi. Its body reads this.name, but this has no value yet: the body does not run until hi is called. The function is stored once in memory.
Two objects, cat and dog, each get a name and a property hi. Writing hi alone is short for hi: hi, so both properties point to the same single function. Nothing is copied.
Four ways to call it
cat.hi() is a method call: the object before the dot becomes this. So this is cat, this.name is "Tom", and hi returns "I am Tom". console.log prints it.
The very same function, called through dog. Now the object before the dot is dog, so this is dog and hi returns "I am Rex". The code of hi did not change; only the call did.
const f = cat.hi only reads the property: it stores the function in f, without calling it. f does not remember cat. f is simply hi, the same function under another name.
Every function has a call method. f.call(dog) runs f with this set to its first argument, dog. So it returns "I am Rex", even though f was taken from cat.
f.bind(cat) does not run f. It returns a new function, g, with this locked to cat. Calling g() with no dot still gives this = cat, so it returns "I am Tom".
f() has no object before the dot, and no call or bind. In strict mode, this is undefined. Reading this.name then means reading undefined.name, which throws a TypeError and stops the script.
The rules
Object before the dot: that object. call or bind: the value you pass. new: a brand-new object. Plain call: undefined in strict mode. Arrow functions have no this of their own; they use the this around them.
What happens
// step 1/11 · rule
this a hidden value inside a function
set each time the function is CALLED
not set where the function is written
so one function can see a different
this on every call
Without "use strict", a plain call like f() sets this to the global object (globalThis, which is window in a browser), so f() would not throw: it would read the global object's name instead. Code inside a class or a module (<script type="module">) is always strict. Arrow functions, such as () => this.name, have no this of their own. They see the this of the code around them, and call, apply and bind cannot change it. That is why arrows are handy for callbacks inside methods.