Have a data structure for CPU registers.
Have a data structure for memory.
Have a data structure for i/o.
Read a series of instructions that are being emulated.
Create a series of rules as to how those instructions interact with CPU, memory, i/o, in a mutable fashion. A long if/then/elif/elif/elif/elif/else type loop, or switch case type loop, if you will.
Create bridges between particular pieces of memory and system output: video, sound, controllers.
That's most of it in the classic case at least.
Now you have an emulator.
The "big league" emulators work differently. They may be simple compilers. Instead of compiling C code or java code, they compile machine code from system A to system B. A transpiler. This affords speed beyond the simple case. Speed, however, is only necessary if the machine running the emulator is weak, or if the machine being emulated is strong.