m Javascript
Key Points
- run js using node as a file
- run js in web page on javascript console ( eg Firefox browser etc )
References
Key Concepts
Javascript Basics
Testing Node shell
open
run needed npm installs
> var stringer = require('node-csv').Stringifier;
> fs.writeFile('./msgsout.txt', JSON.stringify(msgs), (err) => { console.log(err) });
> fs.appendFile('./msgsout.txt', JSON.stringify(msgs), (err) => { console.log(err) });
> fs.appendFile('./msgsout.txt', JSON.stringify('more data 1 \n'), (err) => { console.log(err) });
Data Types
Data Types can be converted
https://gomakethings.com/converting-strings-to-numbers-with-vanilla-javascript/
> var a = "12";
> var b = "44";
> var c = Number(a) + Number(b)
> console.log(`var c = ${c}`);
var c = 56
methods to convert strings to numbers
parseInt
parseFloat
Number
The Event Loop model
https://developer.mozilla.org/en-US/docs/Web/JavaScript/EventLoop
- Event Loop example showing order of operations on the event stack
- JS engine sends messages in order to be processed from the stack
- variables and objects are stored in the heap
- foo defined before bar
- bar called then calls foo
function foo(b) {
const a = 10;
return a + b + 11;
}
function bar(x) {
const y = 3;
return foo(x * y);
}
const baz = bar(7); // assigns 42 to baz
Note that the arguments and local variables may continue to exist, as they are stored outside the stack — so they can be accessed by any nested functions long after their outer function has returned.
Promises
https://developers.google.com/web/fundamentals/primers/promises
At their most basic, promises are a bit like event listeners except:
- A promise can only succeed or fail once. It cannot succeed or fail twice, neither can it switch from success to failure or vice versa.
- If a promise has succeeded or failed and you later add a success/failure callback, the correct callback will be called, even though the event took place earlier.
This is extremely useful for async success/failure, because you're less interested in the exact time something became available, and more interested in reacting to the outcome.
A promise can be:
- fulfilled - The action relating to the promise succeeded
- rejected - The action relating to the promise failed
- pending - Hasn't fulfilled or rejected yet
- settled - Has fulfilled or rejected
The spec also uses the term thenable to describe an object that is promise-like, in that it has a then
method
Promise example
You can transform values simply by returning the new value:
var promise = new Promise(function(resolve, reject) {
resolve(1);
});
promise.then(function(val) {
console.log(val); // 1
return val + 2;
}).then(function(val) {
console.log(val); // 3
})
Simplifying Promise sequences
https://developers.google.com/web/fundamentals/primers/promises
The response is JSON, but we're currently receiving it as plain text. We could alter our get function to use the JSON responseType
, but we could also solve it in promises land:
get('story.json').then(function(response) {
return JSON.parse(response);
}).then(function(response) {
console.log("Yey JSON!", response);
})
Since JSON.parse()
takes a single argument and returns a transformed value, we can make a shortcut:
get('story.json').then(JSON.parse).then(function(response) {
console.log("Yey JSON!", response);
})
In fact, we could make a getJSON()
function really easily:
function getJSON(url) {
return get(url).then(JSON.parse);
}
getJSON()
still returns a promise, one that fetches a url then parses the response as JSON.
How Promises work in sequence with then
You can also chain then
s to run async actions in sequence.
When you return something from a then()
callback, it's a bit magic. If you return a value, the next then()
is called with that value. However, if you return something promise-like, the next then()
waits on it, and is only called when that promise settles (succeeds/fails). For example:
getJSON('story.json').then(function(story) {
return getJSON(story.chapterUrls[0]);
}).then(function(chapter1) {
console.log("Got chapter 1!", chapter1);
})
Here we make an async request to story.json
, which gives us a set of URLs to request, then we request the first of those. This is when promises really start to stand out from simple callback patterns.
You could even make a shortcut method to get chapters:
var storyPromise;
function getChapter(i) {
storyPromise = storyPromise || getJSON('story.json');
return storyPromise.then(function(story) {
return getJSON(story.chapterUrls[i]);
})
}
// and using it is simple:
getChapter(0).then(function(chapter) {
console.log(chapter);
return getChapter(1);
}).then(function(chapter) {
console.log(chapter);
})
We don't download story.json
until getChapter
is called, but the next time(s) getChapter
is called we reuse the story promise, so story.json
is only fetched once. Yay Promises!
Prior to promises, we registered callbacks for events
var img1 = document.querySelector('.img-1');
function loaded() {
// woo yey image loaded
}
if (img1.complete) {
loaded();
}
else {
img1.addEventListener('load', loaded);
}
img1.addEventListener('error', function() {
// argh everything's broken
});
This doesn't catch images that error'd before we got a chance to listen for them; unfortunately the DOM doesn't give us a way to do that. Also, this is loading one image, things get even more complex if we want to know when a set of images have loaded.
JS Modules - export and import system for reusable modules
https://v8.dev/features/modules
Export something to external clients
Within a module, you can use the export
keyword to export just about anything. You can export a const
, a function
, or any other variable binding or declaration. Just prefix the variable statement or declaration with export
and you’re all set:
// 📁 lib.mjs
export const repeat = (string) => `${string} ${string}`;
export function shout(string) {
return `${string.toUpperCase()}!`;
}
Import something to a client
You can then use the import
keyword to import the module from another module. Here, we’re importing the repeat
and shout
functionality from the lib
module, and using it in our main
module:
// 📁 main.mjs
import {repeat, shout} from './lib.mjs';
repeat('hello');
// → 'hello hello'
shout('Modules in action');
// → 'MODULES IN ACTION!'
Module concepts
Modules have a lexical top-level scope. This means that for example, running
var foo = 42;
within a module does not create a global variable namedfoo
, accessible throughwindow.foo
in a browser, although that would be the case in a classic script.Similarly, the
this
within modules does not refer to the globalthis
, and instead isundefined
. (UseglobalThis
if you need access to the globalthis
.)The new static
import
andexport
syntax is only available within modules — it doesn’t work in classic scripts.
Using JS modules in a browser
On the web, you can tell browsers to treat a <script>
element as a module by setting the type
attribute to module
.
<script type="module" src="main.mjs"></script>
<script nomodule src="fallback.js"></script>
Browsers that understand type="module"
ignore scripts with a nomodule
attribute. This means you can serve a module-based payload to module-supporting browsers while providing a fallback to other browsers.
Dynamic import
only load module when called. Loaded module will be cached in browser session.
<script type="module">
(async () => {
const moduleSpecifier = './lib.mjs';
const {repeat, shout} = await import(moduleSpecifier);
repeat('hello');
// → 'hello hello'
shout('Dynamic import in action');
// → 'DYNAMIC IMPORT IN ACTION!'
})();
</script>
import.meta - get metadata on a module
Another new module-related feature is import.meta
, which gives you metadata about the current module. The exact metadata you get is not specified as part of ECMAScript; it depends on the host environment. In a browser, you might get different metadata than in Node.js, for example.
Javascript Async Functions
https://developers.google.com/web/fundamentals/primers/async-functions
Async functions work like this:
async function myFirstAsyncFunction() {
try {
const fulfilledValue = await promise;
}
catch (rejectedValue) {
// …
}
}
If you use the async
keyword before a function definition, you can then use await
within the function. When you await
a promise, the function is paused in a non-blocking way until the promise settles. If the promise fulfills, you get the value back. If the promise rejects, the rejected value is thrown.
Async functions vs Promises - which reads easier?
Say we wanted to fetch a URL and log the response as text. Here's how it looks using promises:
function logFetch(url) {
return fetch(url)
.then(response => response.text())
.then(text => {
console.log(text);
}).catch(err => {
console.error('fetch failed', err);
});
}
And here's the same thing using async functions:
async function logFetch(url) {
try {
const response = await fetch(url);
console.log(await response.text());
}
catch (err) {
console.log('fetch failed', err);
}
}
Default + Rest + Spread
Callee-evaluated default parameter values. Turn an array into consecutive arguments in a function call. Bind trailing parameters to an array. Rest replaces the need for arguments
and addresses common cases more directly.
function f(x, y=12) {
// y is 12 if not passed (or passed as undefined)
return x + y;
}
f(3) == 15
function f(x, ...y) {
// y is an Array
return x * y.length;
}
f(3, "hello", true) == 6
function f(x, y, z) {
return x + y + z;
}
// Pass each elem of array as argument
f(...[1,2,3]) == 6
More MDN info: Default parameters, Rest parameters, Spread Operator
Generators
Generators simplify iterator-authoring using function*
and yield
. A function declared as function* returns a Generator instance. Generators are subtypes of iterators which include additional next
and throw
. These enable values to flow back into the generator, so yield
is an expression form which returns a value (or throws).
Note: Can also be used to enable ‘await’-like async programming, see also ES7 await
proposal.
var fibonacci = {
[Symbol.iterator]: function*() {
var pre = 0, cur = 1;
for (;;) {
var temp = pre;
pre = cur;
cur += temp;
yield cur;
}
}
}
for (var n of fibonacci) {
// truncate the sequence at 1000
if (n > 1000)
break;
console.log(n);
}
The generator interface is (using TypeScript type syntax for exposition only):
interface Generator extends Iterator {
next(value?: any): IteratorResult;
throw(exception: any);
}
More info: MDN Iteration protocols
Map + Set + WeakMap + WeakSet
Efficient data structures for common algorithms. WeakMaps provides leak-free object-key’d side tables.
// Sets
var s = new Set();
s.add("hello").add("goodbye").add("hello");
s.size === 2;
s.has("hello") === true;
// Maps
var m = new Map();
m.set("hello", 42);
m.set(s, 34);
m.get(s) == 34;
// Weak Maps
var wm = new WeakMap();
wm.set(s, { extra: 42 });
wm.size === undefined
// Weak Sets
var ws = new WeakSet();
ws.add({ data: 42 });
// Because the added object has no other references, it will not be held in the set
More MDN info: Map, Set, WeakMap, WeakSet
Proxy handlers for Object methods
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Meta_programming
const handler2 = {
get(target, name) {
console.log( `target.name = ${target} ${name}`)
return name in target ? target[name] : 44;
},
};
const p2 = new Proxy({}, handler2);
p2.a = 1;
console.log(p2.a, p2.b); // 1, 44
VM194:3 target.name = [object Object] a
VM194:3 target.name = [object Object] b
VM194:10 1 44
The Proxy.revocable()
method is used to create a revocable Proxy
object. This means that the proxy can be revoked via the function revoke
and switches the proxy off.
Reflect - reflection in js
Reflect
is a built-in object that provides methods for interceptable JavaScript operations. The methods are the same as those of the proxy handlers.
Reflect
is not a function object.
Reflect
helps with forwarding default operations from the handler to the target
.
With Reflect.has()
for example, you get the in
operator as a function:
Reflect.has(Object, 'assign') // true
Before Reflect
, you typically use the Function.prototype.apply()
method to call a function with a given this
value and arguments
provided as an array (or an array-like object).
Function.prototype.apply.call(Math.floor, undefined, [1.75])
With Reflect.apply
this becomes less verbose and easier to understand:
Reflect.apply(Math.floor, undefined, [1.75])
// 1
Reflect.apply(String.fromCharCode, undefined, [104, 101, 108, 108, 111])
// "hello"
Reflect.apply(RegExp.prototype.exec, /ab/, ['confabulation']).index
// 4
Chrome Javascript Web App Basics
PWA Web App shell for off-line work, web performance
https://developers.google.com/web/fundamentals/architecture/app-shell
An app shell using Service worker is powerful pattern for offline caching but it also offers significant performance wins in the form of instant loading for repeat visits to your PWA. You can cache your application shell so it works offline and populate its content using JavaScript.
On repeat visits, this allows you to get meaningful pixels on the screen without the network, even if your content eventually comes from there.
Potential Value Opportunities
Potential Challenges
Candidate Solutions
Flow framework - smart type checking automatically
Type Inference
Using data flow analysis, Flow infers types and tracks data as it moves through your code. You don't need to fully annotate your code before Flow can start to find bugs.
JavaScript, Your Way
Flow is designed to understand idiomatic JavaScript. It understands common JavaScript patterns and many of the weird things we JavaScript developers love to do.
Realtime Feedback
Flow gives you fast feedback while you code by incrementally rechecking your code as you make changes.
Easy Integration
Flow integrates well with many tools, making it easy to insert into your existing workflow and toolchain.
Step-by-step guide for Example
sample code block