JavaScript uses function scope. This is pretty simple to understand - anything declared in a function has scope to that function as well as any higher scopes.
A closure can be thought of as two things being combined together: a particular scope, and a particular point in time (when the function was created).
Sure, magicIngredient is a variable accessible to make(), but what
else is?
Anything that was accessible to the scope of make at the time it was created. This includes all variables in the make function as well as any higher scopes. The scope of make is said to be closed-over the scope that existed at the point in time it was created, always giving it access to magicIngredient.
What if sandwichMaker were itself within a function?
Then make would have access to that scope (as it existed at the time make was created) as well.
And then there are the globals. What is the function looking at when it looks
for relevant values within the current scope?
The interpreter will search the currently executing scope for any referenced variables. If it can't find them, it will look in the next higher scope. It will continue looking higher and higher until it finds the variable or runs out of scopes (the global scope is the highest, aka the window object for javascript running in a browser).
A related concept is shadowing - two variables can have the same name in parent/child scopes, the child is said to "shadow" the parent because it will take precedence. Note this is a bad practice.
The easiest way to understand how closures and scoping works is to understand a simple factory pattern, such as this one. When the inner function is created and returned, it is bound to that scope at that point in time and will continue to alert the proper values even after the values no longer exist.
Hope this helped - lots of questions stuffed into one question :)
makedoes