Difference between revisions of "JavaScript"

From Organic Design wiki
(See also: Using nested (chained) ternaries)
m (Closures)
Line 50: Line 50:
 
Closures can have both a private and public aspect, they're in the form of a function rather than an object, and are brought into existence by calling them, what's returned is the public interface. The content of the scope within the closures definition is private and exists for as long as the returned public interface exists. Usually closures are executed at the same time they're created by following the function definition immediately with the function-execution operator (round-brackets).
 
Closures can have both a private and public aspect, they're in the form of a function rather than an object, and are brought into existence by calling them, what's returned is the public interface. The content of the scope within the closures definition is private and exists for as long as the returned public interface exists. Usually closures are executed at the same time they're created by following the function definition immediately with the function-execution operator (round-brackets).
  
The following example illustrates a simple closure. The variable ''foo'' is private and can only be accessed by functions in the same private scope, or by the functions returned in the public interface. An instance of the closure is created in the variable ''closure'' by being assigned the value returned by anonymously executing the declared function.
+
The following example illustrates a simple closure. The variable ''foo'' is private and can only be accessed by functions in the same private scope, or by the functions returned in the public interface. An instance of the closure is created in the variable ''closure'' by being assigned the value returned by anonymously executing the declared function. The closure's private scope remains intact as long as the variable ''closure'' exists, because it contains a second function in its structure (highlighted) which expects that scope to exist whenever it executes.
 
<source lang="js">
 
<source lang="js">
 
var closure = function() {
 
var closure = function() {
Line 56: Line 56:
 
     return {
 
     return {
 
         bar: 100,
 
         bar: 100,
         baz: function() {
+
         baz: {!function()!} {
 
             console.log( foo );
 
             console.log( foo );
 
             console.log( this.bar );
 
             console.log( this.bar );

Revision as of 12:48, 14 July 2019

JavaScript (Officially known as ECMA-262, ECMA Script, or just "ES") is most commonly used as the scripting language in the browser, but is increasingly being seen on the server-side mainly in the form of Node.js, and on the desktop such as in GNOME's desktop extension system.

Technically JavaScript is classified as a prototype-based scripting language with dynamic typing and first-class functions. This mix of features makes it a multi-paradigm language, supporting object-oriented, imperative, and functional programming styles. JavaScript has been standardised in the ECMAScript language specification.

Despite some naming, syntactic, and standard library similarities, JavaScript and Java are unrelated and have very different semantics. The syntax of JavaScript is actually derived from C, while the semantics and design are influenced by the Self and Scheme programming languages.

This article covers some of the oddities of JavaScript that are very different from the usual PHP, C++ or Java type languages. For a complete introduction to JavaScript I recommend Marijn Haverbeke's book Eloquent JavaScript which covers the basics and also goes into quite a lot of details about the more advanced topics covered in this article.

Objects

JavaScript is a prototype-based which means that there is no explicit support for classes, it's only how instances are used within a program that determines which objects are treated as classes and which as instances. Basic objects can be created simply by using braces to collect together some named properties and methods.

var foo = {
    prop: "myProp",
    func: function() { return "myMethod"; }
};

console.log( foo.prop );
console.log( foo.func() );


For more complex objects requiring initialisation and other more advanced object-oriented features, a constructor function (having its name start with a capital letter by convention) can be used to instantiate an object using the new keyword.

function MyFooObject() {
    this.prop = "myProp";
    this.func = function() { return "myMethod"; }
};
var foo = new MyFooObject();


All objects have a prototype property which is the name of the object it was based on, and that one has a prototype property too and so on. All the prototypes form an inheritance hierarchy back to Object.prototype, the root object.

A new instance can be created using a specific object as its prototype using Object.create(). This is how JavaScript implements inheritance, for example object B can be created using object A as a prototype, then add, override or extend its methods and properties, then other objects can be created based on object B using the new keyword as usual. See Chapter 6 of "Eloquent JavaScript" for a detailed introduction to JavaScript objects.

Scope

One of the main sources of confusion with scoping in JavaScript is that most programmers are used to the way scoping works in C-like languages such as PHP or Java. These language use block-based scoping, where every new block automatically forms a new scope in which new variables can be defined locally, and functions form a kind of scope-bubble where only the locally defined names are available (usually access is allowed to the global scope via the global keyword).

JavaScript on the other hand, uses lexical scoping which is unaffected by blocks such as for-loops or if-statements and is purely determined by the currently executing function. Functions can be defined inside other functions, which creates a scope hierarchy all the way up to the global scope. Names defined within any scope are available to all scopes within it, but not to scopes outside of it.

Another important thing to remember is that items defined by the var keyword or by function declarations are brought into the scope at the beginning of execution of that scope regardless of their position within that scope (the actual assignment takes place at the location of the definition for variables, or at the beginning for function declarations). For this reason it has become a common practice to include only one var statement at the beginning of each function which declares all variables that are used throughout it, this avoids confusion and ensures that the code reflects the reality of what's happening.

These are the most important points, but for more subtle details see this excellent short article about it, Scoping and hoisting by Ben Cherry.

Closures

Closures (also lexical closures or function closures) are records storing a function together with an environment. A mapping associating each free variable of the function (variables that are used locally, but defined in an enclosing scope) with the value or storage location to which the name was bound when the closure was created. A closure, unlike a plain function, allows the function to access those captured variables through the closure's reference to them, even when the function is invoked outside their scope.

Closures are achieved in JavaScript using an interesting aspect of JavaScript's scoping mechanism which is that if the return value of a function (lets call it function A) is another function (function B), then the scope that function B was created in (i.e. the private scope that was created when function A was executed) continues to exist as long as any variable references function B. This persistent private scope is the closure.

Closures can have both a private and public aspect, they're in the form of a function rather than an object, and are brought into existence by calling them, what's returned is the public interface. The content of the scope within the closures definition is private and exists for as long as the returned public interface exists. Usually closures are executed at the same time they're created by following the function definition immediately with the function-execution operator (round-brackets).

The following example illustrates a simple closure. The variable foo is private and can only be accessed by functions in the same private scope, or by the functions returned in the public interface. An instance of the closure is created in the variable closure by being assigned the value returned by anonymously executing the declared function. The closure's private scope remains intact as long as the variable closure exists, because it contains a second function in its structure (highlighted) which expects that scope to exist whenever it executes.

var closure = function() {
    var foo = 1;
    return {
        bar: 100,
        baz: function() {
            console.log( foo );
            console.log( this.bar );
        }
    }
}();

console.log( closure.bar );
closure.baz();


In the example above, the public function baz can access the private foo variable from it's local scope. The public interface also includes a variable bar which can be accessed publicly or via this from within the executing public interface functions. All of the items in both the private and the public scopes of the closure exist for the lifetime of the closure object.

In this next example, a single private variable is defined, and there are two public functions which are called one after the other. The first stores a secret message in the private variable which the second displays. This shows that the private local scope of the functions remains intact after the function exits and is available to all the functions. It is not available outside the scope of the functions though, any data which needs to be available outside the private scope of the closure needs to be prefixed with this. Note that if the private variable were not defined in the private scope, then the function that writes to it would create the variable in the global public scope because it's not using the var keyword.

var closure = function() {
    var private;
    return {
        foo: function() {
            private = 'secret message';
        },
        bar: function() {
            console.log( private );
        }
    }
}();

closure.foo();
closure.bar();

For more in-depth information about closures, see Wikipedia:Immediately-invoked function expression and Understanding closures.

The Module Pattern

The "module pattern" (also called the IIFE or Imediately Invoked Function Expression) has become the industry standard for packaging up JavaScript applications and libraries designed to be used alongside other JavaScript programs. Most people are quite familiar with the form of the module pattern these days, but how it works and what it's doing remain a mystery to most. Here's an example you've most likely seen many times before.

( function($, document, window, undefined) {

    ...

}(jQuery, document, window) );

Now that you know what a closure looks like, you can see that this is one. The main difference is that it doesn't return a public interface and so doesn't need to be assigned to a variable to be re-used later, it's just used once when it's created. Instead of returning an interface its purpose is to modify the environment directly, for example by extending other libraries or adding a widget to the page. Notice that the entire statement is surrounded in brackets too, this is because JavaScript treats a statement that starts with the function keyword as a named function declaration, so the brackets are required to make the interpreter see it as a statement.

Another difference with this format compared to the closure examples above is that it's passing arguments into the closure. These arguments are a useful way to pass globals into your closure such as jQuery as $. But passing globals with the names are unchanged, such as is the case here with window and document, is commonly seen nowadays and is done for three main reasons:

  • The interpreter doesn't need to walk the scope tree to find the value
  • The names can be compressed along with other normal variables when the code is minimised
  • The code can be sure that the names haven't been hijacked or modified by some other badly written code

For the third reason, you'll often see nowadays the undefined variable being added to the inner argument list but not the outer - this ensure that undefined has not been over-written with something else and really is undefined by not having an equivalent value assigned to it from the outer function's argument list.

For more in depth information about modules, the Module pattern in-depth and Snook.ca Why I don't love the JavaScript module pattern and most importantly What is AMD, CommonJS and UMD?.

Note: From ES6 onwards, scoped blocks are supported, so if your module has no parameters a plain brace-block is sufficient to obtain a private scope inside the block.

Module packaging

Since applications are now in the form of many different modules which all include each other, it becomes very complicated working out which order things should be loaded in and also what parts of what modules should be included or left out. This is where packaging systems such as Webpack come in. They can map out your whole dependency tree and compile them all into a single file for the browser. They can also manage different types of files such as images, JSON and CSS and handle minification for production or help with debugging for development.

New features in ES6

The spread operator (...)

The spread operator can used as an item in a list such as in an object definition or a function call. The item using the operator is itself a list which gets "spread out" into elements of the list its being used within. For example,

var foo = [1, 2, 3];
var bar = [4, 5, ...foo, 6]; // [4, 5, 1, 2, 3, 6]

Arrow functions (=>)

An arrow function expression has a shorter syntax than a function expression and does not bind its own this, arguments, super, or new.target. These function expressions are best suited for non-method functions, and they cannot be used as constructors. Basically they're just a shorter way of writing function expressions (a.k.a lamda functions or anonymous functions) that does away with the word "function" and keeps the this variable unaffected so it can still be used within the function body.

This is the simplest case, a single variable, one-line function:

// ES5
var selected = allJobs.filter( function(job) {
  return job.isSelected();
});

// ES6
var selected = allJobs.filter( job => job.isSelected() );

IMPORTANT: if your one-line expression starts with a brace, you need to enclose it in parenthesis


If the function has more than one argument, parenthesis are used as follows:

// ES5
var total = values.reduce( function(a, b) {
  return a + b;
}, 0);

// ES6
var total = values.reduce( (a, b) => a + b, 0 );


And if the function is not just a one-liner, then braces are used as follows, this example also demonstrates the differences with the this variable:

// ES5
var outerThis = this;
$("#foo").click( function(event) {
  var el = $(this);
  el.html(outerThis);
});

// ES6
$("#foo").click( event => {
  var el = $(e.currentTarget); // the this variable remains from the outer scope
  el.html(this);
});


Finally, if you have no parameters at all, you can use empty parenthesis:

// ES5
var outerThis = this;
$("#foo").click( function() {
  return $('#baz').html();
});

// ES6
$("#foo").click( () => $('#baz').html() );

Promises

Promises are a much tidier way of executing async functions one after the other. Usually this would be done by nesting the action and callback functions, but with promises the same thing can be achieved with a more intuitive promise-chain. See also this great tutorial on good promise patterns.

doSomething()
  .then(result => doSomethingElse(result))
  .then(newResult => doThirdThing(newResult))
  .then(finalResult => {
    console.log(`Got the final result: ${finalResult}`);
})
.catch(failureCallback);

Template literals

In addition to single and double quotes for strings, a back-tick character can now also be used which allows strings called template literals to be defined which can span multiple lines and include embedded expressions.

var str = `This is a multi-
  line string, with an ${ console.log('expression') } in it
  as well`;

Unicode characters can also be included:

  • Unicode escapes started by "\u", for example \u00A9
  • Unicode code point escapes indicated by "\u{}", for example \u{2F804}
  • Hexadecimal escapes started by "\x", for example \xA9
  • Octal literal escapes started by "\" and (a) digit(s), for example \251

Single-page applications

A single-page application (SPA), also known as single-page interface (SPI), is a web application that fits on a single web page. In an SPA, either all necessary code – HTML, JavaScript, and CSS – is retrieved with a single page load, or partial changes are performed loading new code on demand from web servers, usually driven by user actions.

The page does not automatically reload during user interaction with the application, nor does control transfer to another page. Necessary updates to the page display may or may not involve interaction with a server. The term single-page application was coined by Steve Yen in 2005, though the concept was discussed at least as early as 2003.

Hash fragments

A central concept making single-page applications possible is the idea of "hash fragments" which is the part of the URL appearing after the first hash character. Everything after the hash represents addressing within the document, whereas everything before the hash is outside the page and requires a server to fetch the document. This allows applications to have their current state fully reflected within the URL so it can be bookmarked - this is one of the foundation principles of the web, that any state the application is in, for example a report or query result, can be bookmarked or sent to others as a URL. So the Single Page Application model is the best of both worlds - it has the dynamic feel of a desktop application having instant responsiveness without page reloads, but also has the important URL reflection quality of the web.

The most natural way of mapping application interface elements to functionality in the system model is to make the URL portion after the hash continue on in the form of path elements separated by slashes - hence the name "hash fragments".

For example:

http://localhost/#/path/into/unified/ontology/

This could also start with a normal domain name if the peer instance is running on a server connected to the web, in that case an optional exclamation mark could also be included which indicates that the paths should be crawlable by search engines, for example:

http://foobar.com/#!/path/into/unified/ontology/

pushState

pushState is an HTML5 API that offers a different way to change the current URL, and thereby insert new back/forward history entries, without triggering a page load. This differs from hash-based navigation in that you're not limited to updating the hash fragment — you can update the entire URL. Most modern framemworks that offer Single Page Application support can determine if the browser supports pushState and use it, but fall back to hash fragment URL format otherwise.

Existing SPAs

JavaScript frameworks

Graphics libraries

Interesting libraries

Tools

JavaScript outside the browser

JavaScript on the server-side is becoming very popular and is done mainly through NodeJS which is designed to allow seamless integration between the client and server-sides of a web application. But another use for server-side JavaScript is for applications that run only on the server-side to be able to browse, navigate and scrape data from other sites using a full browser stack including fully functional JavaScript and DOM but not requiring the heavy installation and automation hackery requirements that server-side installation of a headless browser require. The PhantomJS project addresses this need perfectly.

JavaScript is now found in the desktop environments more often too, for example Gnome uses JavaScript as its default language for writing extensions, and the Electron framework allows cross-platform desktop applications to be written completely in JavaScript and uses an in-build Chromium browser to make rendering the application more familiar and easy.

See also