Discussion:
Programming Corner - JavaScript Fundamentals - Scope Basics
(too old to reply)
Steve Carroll
2025-01-30 18:58:50 UTC
Permalink
I've heard scope in JS described as simply as: 'Anything not inside a function'.

To that, I'd add: 'or inside of any code block'. To simplify things, I'm going
to confine this discussion of JS scope to a web browser.

Go to address 'about:blank' in your browser, now go to dev tools' Console tab
and type in: 'window' (yes, hit return). You'll see something like this:

Window {window: Window, self: Window, document: document, name: '', etc.

If you click the little drop-down triangle you'll see tons of methods
(functions) and properites (that have values, even blank ones).

Elsewhere, I pointed out that everything in JS that isn't a primitive is an
object. That blank screen you're looking at is the window object. If you type
in 'window' followed by a '.' char, you'll get an autocompletion list of things
available to the window object. OK, so what about the window object, how does
it relate to scope?

You can think of scope as the places where a variable is accessible in your
code. Run the following in a browser where you've reloaded the 'about:blank'
page (and emptied the cache, if needed):

globalVar = "I'm global" // (always hit return)

You just created a variable called "globalVar" and assigned it a value of "I'm
global"

Now type in "globalVar", you'll see the 'value' held by it on screen.
// outputs "I'm global"

Now enter this:

let globalVar = "I'm global, too!"

And type "globalVar" in like you did previously.

What came back? Where did the other one go?!

type in: window.globalVar // outputs "I'm global"

Seems the first variable, created without using a 'keyword' (such as: 'var',
'let' or 'const') somehow got 'attached' to the windown object (JS created it
using the 'var' keyword even though you didn't use it!).

What is the one that used the 'let' keyword 'attached' to?

You may have heard that the window object is the global object in a browser,
yet, using 'let' would seemingly appear *more* global as it can be directly
accessed, as opposed to: 'window.globalVar'. What's going on?

Don't let the browser's pecularlity here confuse you. The thing we're looking
for is: What's at the top of the food chain in the JS we write or encounter?
What is 'not inside a function or block of code'? What does that even mean?

Let's define a var inside of a function. Reload your page again and enter this
function, which contiains a 'code block' (meaning: code wrapped in curly
braces... but NOT as used in JS objects!)

function scopeAdope(){
let myVar = "What's my scope?"
console.log(myVar)
}

Run it: scopeAdope() // outputs "What's my scope?"

Now type the name of the var, "myVar", to attempt to 'access' it like you did
earlier: "Uncaught ReferenceError: myVar is not defined" - Yikes!

It's 'scope' is limited to the code block in the function. Now try this:

let yourVar = "I have a broader scope!"
function scopeAdope2(){
let myVar = "What's my scope?"
console.log(myVar)
console.log(yourVar)
}

Run it: scopeAdope2() // outputs both vars

Notice that the function has 'access' to 'yourVar' even though it was defined
outside of the function. The topic of scope can get confusing as more code is
added (i.e. functions within functions) but on a basic level, this is the
general idea.

Let's take a 'scope break' and create our own object in the browser window:

let myDiv = document.createElement('div') // creates an empty, div tag (element)

Want to see all the things 'attached' to what you just created?

console.dir(myDiv)

Click the drop-down widget if needed. Looking in the list you can see a
proptery named: 'textContent'

Do this:

myDiv.textContent = "I'm the new div!"

Now type: myDiv // outputs "I'm the new div!"

But it's not on the screen! That's right, it's not been added to the DOM yet.
To do that you need a reference to the body tag, where all displayed content
should live. Do this:

let body = document.querySelector('body')

Why not use createElement() for this, like we did earlier? Look in your dev
tools Elements tab, you'll see 'body' (and other elements) already exist. All
you need is a way to reference 'body' and if you did what I just told you to
do, you now have it. All you need is to 'append' 'myDiv' to the 'body'. Back to
the console tab:

body.append(myDiv) // you'll see what this does

myDiv.style.color = 'red' // I suspect you can guess what that does...

myDiv.style.fontSize = '2em' // ...and what that does

Handy tip: things like 'font-size', as seen in CSS, become 'fontSize' in JS.

Bonus question: Is 'myDiv' attached to the window obj (or the document obj)?

--

Reference page:
<vmekhl$74sp$***@fretwizzer.eternal-september.org>

Setup:
<vmgi9e$u5m6$***@fretwizzer.eternal-september.org>

Variables:
<vmh41h$u5m6$***@fretwizzer.eternal-september.org>

Functions:
<vmh7ln$u5m6$***@fretwizzer.eternal-september.org>

Arrays/looping:
<vmmi7n$3edp3$***@fretwizzer.eternal-september.org>

Object Basics:
<vne73j$2i8m1$***@dont-email.me>

Calculator project - Part 1
<vmjd3a$2bdqi$***@fretwizzer.eternal-september.org>

Calculator project - Part 2
<vmrch2$14j7l$***@fretwizzer.eternal-september.org>

DOM - Part 1
<vmubfb$1pvlc$***@fretwizzer.eternal-september.org>

Text generator project - Part 1
<vmug55$1pvlc$***@fretwizzer.eternal-september.org>

Text generator project - Part 2
<vn66pb$36go$***@fretwizzer.eternal-september.org>

Text generator project - Part 3
<vn69if$56c0$***@fretwizzer.eternal-september.org>

Programming vs Code
<vn8vtp$192cl$***@fretwizzer.eternal-september.org>
Apd
2025-01-30 21:00:30 UTC
Permalink
Post by Steve Carroll
globalVar = "I'm global" // (always hit return)
let globalVar = "I'm global, too!"
type in: window.globalVar // outputs "I'm global"
'var', 'let' or 'const') somehow got 'attached' to the window object
(JS created it using the 'var' keyword even though you didn't use it!).
What is the one that used the 'let' keyword 'attached' to?
Good question. I don't know.
Post by Steve Carroll
let myDiv = document.createElement('div') // creates an empty, div tag (element)
Want to see all the things 'attached' to what you just created?
console.dir(myDiv)
I was unaware of "dir".
Post by Steve Carroll
myDiv.textContent = "I'm the new div!"
let body = document.querySelector('body')
body.append(myDiv) // you'll see what this does
Bonus question: Is 'myDiv' attached to the window obj (or the document obj)?
As a variable, it doesn't appear to be attached to anything (like the
second globalVar), although the object it holds is attached to the
document body and ultimately, its ownerDocument which is HTMLDocument
-> about:blank.
Steve Carroll
2025-01-30 21:35:07 UTC
Permalink
Post by Apd
Post by Steve Carroll
globalVar = "I'm global" // (always hit return)
let globalVar = "I'm global, too!"
type in: window.globalVar // outputs "I'm global"
'var', 'let' or 'const') somehow got 'attached' to the window object
(JS created it using the 'var' keyword even though you didn't use it!).
What is the one that used the 'let' keyword 'attached' to?
Good question. I don't know.
Post by Steve Carroll
let myDiv = document.createElement('div') // creates an empty, div tag (element)
Want to see all the things 'attached' to what you just created?
console.dir(myDiv)
I was unaware of "dir".
Post by Steve Carroll
myDiv.textContent = "I'm the new div!"
let body = document.querySelector('body')
body.append(myDiv) // you'll see what this does
Bonus question: Is 'myDiv' attached to the window obj (or the document obj)?
As a variable, it doesn't appear to be attached to anything (like the
second globalVar), although the object it holds is attached to the
document body and ultimately, its ownerDocument which is HTMLDocument
-> about:blank.
With the advent of ES6, in a browser, it's the JS engine that apparently
creates and manages a separate 'global' env. Where 'var' can still be used to
attach to the window, 'let and 'const' don't (and never did), they attach to
the ES6 (and above) global env. Same thing happens in node:

let globalVar = "I'm global, too!"
globalThis.globalVar // outputs undefined

Use 'var' and it attaches to node's 'globalThis'.

IOW, this particular issue isn't a 'browser peculiarity', it belongs to JS.

I remember using node before it had 'globalThis'. Your old version may not
have it.
Steve Carroll
2025-01-30 21:46:35 UTC
Permalink
Post by Steve Carroll
I remember using node before it had 'globalThis'. Your old version may not
have it.
BTW, I was thinking about doing the ever popular 'Counter' project to introduce
switch/case... but it won't be like this ;)

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Counter</title>
<style>
.count{font-size: 2em;}
#buttons{font-size: 2em;}
#inc, #dec, #reset {font-size: 1.2em;}
#mo{vertical-align: top; font-size: .3em;}
#mu{vertical-align: bottom; font-size: .3em;}
#msg{font-size: 1.4em;}

</style>
</head>
<body>

<h1>Basic Counter</h1>
<div id="wrapper">
<span class="count">Count: </span>
<span id="value">0</span><br><br>
<div id="buttons">
<button id = "dec">-</button>
<button id="mu">?</button>
<button id = "reset">Reset</button>
<button id="mo">?</button>
<button id = "inc">+</button><br><br>
</div><br>
<div id="msg"></div>
</div>

<script>
'use strict';
let count = 0.0;

const buttons = document.getElementById('buttons');
const inc = document.getElementById('inc');
const reset = document.getElementById('reset');
const dec = document.getElementById('dec');
const value = document.getElementById('value');
const msg = document.getElementById('msg');
const mu = document.getElementById('mu');
const mo = document.getElementById('mo');

function handleClick(event){
event.preventDefault();
msg.textContent = '';

switch (event.target.id) {
case 'dec':
count -= 0.5;
break;
case 'inc':
count += 0.5;
break;
case 'reset':
count = 0.0;
break;
case 'mu':
count *= count;
break;
case 'mo':
count = count % 7.0;
count = parseFloat(count.toFixed(4));
console.log(count);
break;
default:
msg.textContent = 'You need to click a button!';
}

value.textContent = count.toFixed(3);
}

buttons.addEventListener('click', handleClick)
</script>
</body>
</html>

(I probably should've started with this before the calc but it had no DOM)
Apd
2025-01-30 23:06:18 UTC
Permalink
Post by Steve Carroll
Post by Apd
Post by Steve Carroll
Bonus question: Is 'myDiv' attached to the window obj (or the document obj)?
As a variable, it doesn't appear to be attached to anything (like the
second globalVar), although the object it holds is attached to the
document body and ultimately, its ownerDocument which is HTMLDocument
-> about:blank.
With the advent of ES6, in a browser, it's the JS engine that apparently
creates and manages a separate 'global' env. Where 'var' can still be
used to attach to the window, 'let and 'const' don't (and never did),
let globalVar = "I'm global, too!"
globalThis.globalVar // outputs undefined
So I see. FF 52 doesn't have "globalThis" but Chrome has:

globalVar = "I'm global" // (same for var)
let globalVar = "I'm global, too!"

globalThis.globalVar -> "I'm global"
window.globalVar -> "I'm global"
Post by Steve Carroll
Use 'var' and it attaches to node's 'globalThis'.
Yes, and "window" must be globalThis.
Post by Steve Carroll
IOW, this particular issue isn't a 'browser peculiarity', it belongs to JS.
So is that where "let" is?
Post by Steve Carroll
I remember using node before it had 'globalThis'. Your old version may
not have it.
My node has only "global":

var test = "a test"
global.test -> 'a test'

let test = "another test"
TypeError: Identifier 'test' has already been declared
Apd
2025-01-30 23:20:57 UTC
Permalink
Post by Apd
Post by Steve Carroll
I remember using node before it had 'globalThis'. Your old version may
not have it.
var test = "a test"
global.test -> 'a test'
let test = "another test"
TypeError: Identifier 'test' has already been declared
However, without "var":

test = "a test"
let test = "another test"

global.test -> 'a test'
test -> 'another test'

I don't know what's going on!
Steve Carroll
2025-01-31 16:52:36 UTC
Permalink
Post by Apd
Post by Apd
Post by Steve Carroll
I remember using node before it had 'globalThis'. Your old version may
not have it.
var test = "a test"
global.test -> 'a test'
let test = "another test"
TypeError: Identifier 'test' has already been declared
test = "a test"
let test = "another test"
global.test -> 'a test'
test -> 'another test'
I don't know what's going on!
I'd guess it's performing a default behavior of 'apparently applying var' when
it's not there, like the window object in a browser does (neither case may
*actually* be using 'var').
Apd
2025-01-31 18:42:00 UTC
Permalink
Post by Steve Carroll
Post by Apd
Post by Apd
var test = "a test"
global.test -> 'a test'
let test = "another test"
TypeError: Identifier 'test' has already been declared
test = "a test"
let test = "another test"
global.test -> 'a test'
test -> 'another test'
I don't know what's going on!
I'd guess it's performing a default behavior of 'apparently applying var'
when it's not there, like the window object in a browser does
But not really because it allows "let" but wouldn't when "var" was
explicitly used first.
Post by Steve Carroll
(neither case may *actually* be using 'var').
Must be the case. Thanks for the detail in the other post. I suppose
it boils down to the node REPL environment, like the browser, trying
to be helpful.
Steve Carroll
2025-01-31 20:12:58 UTC
Permalink
Post by Apd
Post by Steve Carroll
Post by Apd
Post by Apd
var test = "a test"
global.test -> 'a test'
let test = "another test"
TypeError: Identifier 'test' has already been declared
test = "a test"
let test = "another test"
global.test -> 'a test'
test -> 'another test'
I don't know what's going on!
I'd guess it's performing a default behavior of 'apparently applying var'
when it's not there, like the window object in a browser does
But not really because it allows "let" but wouldn't when "var" was
explicitly used first.
"Hmmm..."
Post by Apd
Post by Steve Carroll
(neither case may *actually* be using 'var').
Must be the case. Thanks for the detail in the other post. I suppose
it boils down to the node REPL environment, like the browser, trying
to be helpful.
Only the shadow knows ;)

Steve Carroll
2025-01-31 16:46:50 UTC
Permalink
Post by Apd
Post by Steve Carroll
Post by Apd
Post by Steve Carroll
Bonus question: Is 'myDiv' attached to the window obj (or the document obj)?
As a variable, it doesn't appear to be attached to anything (like the
second globalVar), although the object it holds is attached to the
document body and ultimately, its ownerDocument which is HTMLDocument
-> about:blank.
With the advent of ES6, in a browser, it's the JS engine that apparently
creates and manages a separate 'global' env. Where 'var' can still be
used to attach to the window, 'let and 'const' don't (and never did),
let globalVar = "I'm global, too!"
globalThis.globalVar // outputs undefined
globalVar = "I'm global" // (same for var)
let globalVar = "I'm global, too!"
globalThis.globalVar -> "I'm global"
window.globalVar -> "I'm global"
Post by Steve Carroll
Use 'var' and it attaches to node's 'globalThis'.
Yes, and "window" must be globalThis.
'Kinda, sorta'... as a global lexical env, 'globalThis' does things
differently than window WRT implementation due to the need to deal with cross
env issues (i.e. Electron, Azure, AWS, etc.) in ways a browser doesn't. One
example is webworkers created in a browser, which run in a separate thread
that's not on the window object (webworkers do have their own implementation of
'self' there, just as the window does, but they're not *on* the window object).
Browser and node environments implement webworkers differently due to these
cross env issues, a thing that's often found in node (and deno) with a number
of implemenataion details of various features that differ from a browser (as
you'd expect is a server based env).

I've only briefly played with webworkers so I don't know all the differences, I
just know they exist. Aside from any technical differences, in a way, I guess
you could say they act similarly when talking about scope.
Post by Apd
Post by Steve Carroll
IOW, this particular issue isn't a 'browser peculiarity', it belongs to JS.
So is that where "let" is?
That'd be my guess. It's obviously some kind of 'global' lexical env. What else
could it be (the browser's is the 'window object' and we know it's not that)?
Post by Apd
Post by Steve Carroll
I remember using node before it had 'globalThis'. Your old version may
not have it.
var test = "a test"
global.test -> 'a test'
I couldn't recall exactly what version you were tapped out at but I knew it was
pretty early.
Post by Apd
let test = "another test"
TypeError: Identifier 'test' has already been declared
Right, when that doesn't happen in a browser, the browser is 'helping' you by
treating JS differently than it acts in a normal JS script (seems to treat
lines of redclaration as in they're in a new script). As you've seen (and
complained about when I've used it), 'const' isn't so forgiving. Once debugging
is done, it's a good idea to use const 'where/when ever' you can.
Loading...