3.1 Organizing your Code
# Namespaces
Web pages are often built with multiple scripts coming from different sources and built by different people. When a browser loads scripts through <script>
tags, they are all loaded into the same global scope so they can access each other. In situations like that, it would be very easy for variables and objects to be created with the same name. They could overwrite each other or conflict with each other.
So, one way that we avoid this problem is using let
and const
. This will cause warnings and prevent our code from running because of the name conflicts.
Instead of stopping our code from running, we can organize it in a way that helps to avoid the conflicts. We use namespaces by creating a small number of objects at the global scope level. All our other code goes inside of those objects. So, the only name conflicts that can occur are with those top level namespace names.
const APP = {
key: 'SomeAPIKey',
init: function () => {
//a function
},
};
//get the APP.init function to run when the page loads
document.addEventListener('DOMContentLoaded', APP.init);
2
3
4
5
6
7
8
9
JAVASCRIPT TIP
Always add a DOMContentLoaded
event listener to your main script. It will call your first function and start things running. This way you are guaranteed to avoid DOM issues with elements not being ready yet.
We can group our code by purpose and create a global object for each. In this way we can still use regular <script>
tags to load multiple local scripts plus scripts from multiple other domains.
<script src="js/app.js"></script>
<!-- uses const APP = {} -->
<script src="js/utils.js"></script>
<!-- uses const UTILS = {} -->
<script src="https://example.com/script.js"></script>
<!-- uses const API = {} -->
<script src="https://somedomain.com/functions.js"></script>
<!-- uses const SDOBJ = {} -->
2
3
4
5
6
7
8
Each of those files just needs to have a different top level namespace.
# Import and Export ES Modules
Then ES Modules were added as an enhanced solution to this problem. With ES Modules, each of your scripts can be loaded into its own scope and to use it you need to add type="module"
to your script tag or use an import
command inside your own script to load another script into the scope of your file.
This also allows us to have variables and functions that are private and public.
MDN reference for import (opens new window)
The first step in using ES Modules is to use a script tag for your main script with the type attribute set to module
. This will allow your webpage to use modules.
<script src="js/app.js" type="module"></script>
After we added the type attribute we can start adding import
statements to our app.js
. Alternatively, you can use the .mjs
file extension for files that you want to use as modules.
//app.js
import { someFunc } from './utils.js';
import { otherFunc, someConst } from 'https://example.com/scripts.js';
2
3
# Exports
When we want to import
code into our script, it means that the objects allowed to be exported must be defined. Be use the export
keyword to export an object. Inside that object we list the items that will be available through import
. If a function or variable is not listed inside the export
object then you can consider it to be private to
const PI = 3.14;
const name = 'Steve';
function f1() {
console.log('Hello', name);
//we can use the name variable here without exporting it
}
//we are exporting PI and f1
//name is private to this file
export { PI, f1 };
2
3
4
5
6
7
8
9
10
11
# default exports
If there is only going to be one thing exported from a file, then we can add the keyword default
to an export statement. In this way, the default export becomes an exported object instead of being wrapped in one. In the import for a default export we can also use any name we want.
//bob.js
export default function bob() {
console.log('this function can be called anything in the import');
}
2
3
4
The import statement for the function bob
would look like this:
//app.js
import frank from './bob.js';
2
Notice how you can use a different name because bob
was the default export
.
It is also possible to have a default export
as well as other exports.
export default function bob() {
//this is the default export
}
function f1() {
//allowed to be exported but is not the default
}
function f2() {
//allowed to be exported but is not the default
}
export { f1, f2 };
2
3
4
5
6
7
8
9
10
11
12
13
# Import variations
There are a number of ways we can import scripts.
import * as thing from './script.js';
//all the things exported from script.js will be wrapped in thing
import { a, b, c } from './script.js';
//importing a, b, and c from script.js
//there could be other things exported that we didn't need.
import { a as x, b as w, c } from './script.js';
//importing a, b, and c from script.js
//a is renamed as x
//b is renamed as w
//c is left with its original name
import bob from './script.js';
//this means that bob was a default export
import bob, { a, b } from './script.js';
//bob would be the default export.
//a and b are non-default exports from the same file.
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# Dynamic Imports
Another thing that you can do with imports is make the import conditional. We can wait for something to happen - like the user visiting a certain page, or spending a minimum amount of time on the site, or filling out a form, or logging in. Then once that goal is achieved then we can dynamically import another script.
//using async/await with dynamic import
async function getScript() {
const { default: myDefault, foo, bar } = await import('./js/someScript.js');
//now we know that the script is loaded...
//we use destructuring to get the items from the imported script
}
//alternatively
import('./js/someScript.js').then(({ default: defaultObj, obj1, obj2 }) => {
//now we can use defaultObj, obj1, and obj2
});
2
3
4
5
6
7
8
9
10
11
# What to do this week
TODO
Things to do before next week
- Read and Watch all the content for
3.1
,3.2
, and4.1
- Start working on Hybrid Two
- Complete Exercises 1 and 2
- Watch video about intermediate JS programming structures