buy the book to support the author.

Chapter 23. Standard Global Variables

This chapter is a reference for the global variables standardized by the ECMAScript specification. Web browsers have more global variables, which are listed on MDN. All global variables are (own or inherited) properties of the global object (window in browsers; see The Global Object).

Constructors

For details on the following constructors, see the sections indicated in parentheses:

Error Constructors

For details on these constructors, see Error Constructors:

  • Error
  • EvalError
  • RangeError
  • ReferenceError
  • SyntaxError
  • TypeError
  • URIError

Nonconstructor Functions

Several global functions are not constructors. They are listed in this section.

Encoding and Decoding Text

The following functions handle several ways of URI encoding and decoding:

  • encodeURI(uri)
  • Percent-encodes special characters in uri. Special characters are all Unicode characters except for thefollowing ones:

URI characters:

; , / ? : @ & = + $ #

Not encoded either:

a-z A-Z 0-9 - _ . ! ~ * ' ( )

For example:

  1. > encodeURI('http://example.com/Für Elise/')
  2. 'http://example.com/F%C3%BCr%20Elise/'
  • encodeURIComponent(uriComponent)
  • Percent-encodes all characters in uriComponent, except for:

Not encoded:

a-z A-Z 0-9 - _ . ! ~ * ' ( )

In contrast to encodeURI, characters that are significant in URLs and filenames are encoded, too. Youcan thus use this function to turn any text into a legal filename or URL path segment. For example:

  1. > encodeURIComponent('http://example.com/Für Elise/')
  2. 'http%3A%2F%2Fexample.com%2FF%C3%BCr%20Elise%2F'
  • decodeURI(encodedURI)
  • Decodes a percent-encoded URI that has been produced by encodeURI:
  1. > decodeURI('http://example.com/F%C3%BCr%20Elise/')
  2. 'http://example.com/Für Elise/'

encodeURI does not encode URI characters and decodeURI does not decode them, even if they have beencorrectly encoded:

  1. > decodeURI('%2F')
  2. '%2F'
  3. > decodeURIComponent('%2F')
  4. '/'
  • decodeURIComponent(encodedURIComponent)
  • Decodes a percent-encoded URI component that has been produced by encodeURIComponent. In contrast todecodeURI, all percent-encoded characters are decoded:
  1. > decodeURIComponent('http%3A%2F%2Fexample.com%2FF%C3%BCr%20Elise%2F')
  2. 'http://example.com/Für Elise/'

The following are deprecated:

  • escape(str)percent-encodes str. It is deprecated because it does not handle non-ASCII characters properly. UseencodeURIComponent() instead.
  • unescape(str)percent-decodes str. It is deprecated because it does not handle non-ASCII characters properly. UsedecodeURIComponent() instead.

Categorizing and Parsing Numbers

The following methods help with categorizing and parsing numbers:

Dynamically Evaluating JavaScript Code via eval() and new Function()

This section examines how one can dynamically evaluate code in JavaScript.

Evaluating Code Using eval()

The function call:

  1. eval(str)

evaluates the JavaScript code in str. For example:

  1. > var a = 12;
  2. > eval('a + 5')
  3. 17

Note that eval() parses in statement context (see Expressions Versus Statements):

  1. > eval('{ foo: 123 }') // code block
  2. 123
  3. > eval('({ foo: 123 })') // object literal
  4. { foo: 123 }

Use eval() in strict mode

For eval(), you really should use strict mode (see Strict Mode). In sloppy mode, evaluated code can create local variables in the surrounding scope:

  1. function sloppyFunc() {
  2. eval('var foo = 123'); // added to the scope of sloppyFunc
  3. console.log(foo); // 123
  4. }

That can’t happen in strict mode:

  1. function strictFunc() {
  2. 'use strict';
  3. eval('var foo = 123');
  4. console.log(foo); // ReferenceError: foo is not defined
  5. }

However, even in strict mode, evaluated code still has read and write access to variables in surrounding scopes. To prevent such access, you need to call eval() indirectly.

Indirect eval() evaluates in global scope

There are two ways to invoke eval():

  • Directly. Via a direct call to a function whose name is “eval.”
  • Indirectly. In some other way (via call(), as a method of window, by storing it under a different name and calling it there, etc.).

As we have already seen, direct eval() executes code in the current scope:

  1. var x = 'global';
  2.  
  3. function directEval() {
  4. 'use strict';
  5. var x = 'local';
  6.  
  7. console.log(eval('x')); // local
  8. }

Conversely, indirect eval() executes it in global scope:

  1. var x = 'global';
  2.  
  3. function indirectEval() {
  4. 'use strict';
  5. var x = 'local';
  6.  
  7. // Don’t call eval directly
  8. console.log(eval.call(null, 'x')); // global
  9. console.log(window.eval('x')); // global
  10. console.log((1, eval)('x')); // global (1)
  11.  
  12. // Change the name of eval
  13. var xeval = eval;
  14. console.log(xeval('x')); // global
  15.  
  16. // Turn eval into a method
  17. var obj = { eval: eval };
  18. console.log(obj.eval('x')); // global
  19. }

Explanation of (1): When you refer to a variable via its name, the initial result is a so-called reference, a data structure with two main fields:

  • base points to the environment, the data structure in which the variable’s value is stored.
  • referencedName is the name of the variable.

During an eval() function call, the function call operator (the parentheses) encounters a reference to eval and can determine the name of the function to be called. Therefore, such a function call triggers a direct eval(). You can, however, force an indirect eval() by not giving the call operator a reference. That is achieved by retrieving the value of the reference before applying the operator. The comma operator does that for us in line (1). This operator evaluates the first operand and returns the result of evaluating the second operand. The evaluation always produces values, which means that references are resolved and function names are lost.

Indirectly evaluated code is always sloppy. That is a consequence of the code being evaluated independently of its current surroundings:

  1. function strictFunc() {
  2. 'use strict';
  3.  
  4. var code = '(function () { return this }())';
  5. var result = eval.call(null, code);
  6. console.log(result !== undefined); // true, sloppy mode
  7. }

Evaluating Code Using new Function()

The constructor Function() has the signature:

  1. new Function(param1, ..., paramN, funcBody)

It creates a function whose zero or more parameters have the names param1, parem2, and so on, and whose body is funcBody; that is, the created function looks like this:

  1. function («param1», ..., «paramN») {
  2. «funcBody»
  3. }

Let’s use new Function() to create a function f that returns the sum of its parameters:

  1. > var f = new Function('x', 'y', 'return x+y');
  2. > f(3, 4)
  3. 7

Similar to indirect eval(), new Function() creates functions whose scope is global:[18]

  1. var x = 'global';
  2.  
  3. function strictFunc() {
  4. 'use strict';
  5. var x = 'local';
  6.  
  7. var f = new Function('return x');
  8. console.log(f()); // global
  9. }

Such functions are also sloppy by default:

  1. function strictFunc() {
  2. 'use strict';
  3.  
  4. var sl = new Function('return this');
  5. console.log(sl() !== undefined); // true, sloppy mode
  6.  
  7. var st = new Function('"use strict"; return this');
  8. console.log(st() === undefined); // true, strict mode
  9. }

eval() Versus new Function()

Normally, it is better to use new Function() than eval() in order to evaluate code: the function parameters provide a clear interface to the evaluated code and you don’t need the slightly awkward syntax of indirect eval() to ensure that the evaluated code can access only global variables (in addition to its own).

Best Practices

You should avoid eval() and new Function(). Dynamically evaluating code is slow and a potential security risk. It also prevents most tools (such as IDEs) that use static analysis from considering the code.

Often, there are better alternatives. For example, Brendan Eich recently tweeted an antipattern used by programmers who want to access a property whose name is stored in a variable propName:

  1. var value = eval('obj.'+propName);

The idea makes sense: the dot operator only supports fixed, statically provided property keys. In this case, the property key is only known at runtime, which is why eval() is needed in order to use that operator. Luckily, JavaScript also has the bracket operator, which does accept dynamic property keys. Therefore, the following is a better version of the preceding code:

  1. var value = obj[propName];

You also shouldn’t use eval() or new Function() to parse JSON data. That is unsafe. Either rely on ECMAScript 5’s built-in support for JSON (see Chapter 22) or use a library.

Legitimate use cases

There are a few legitimate, albeit advanced, use cases for eval() and new Function(): configuration data with functions (which JSON does not allow), template libraries, interpreters, command lines, and module systems.

Conclusion

This was a relatively high-level overview of dynamically evaluating code in JavaScript. If you want to dig deeper, you can take a look at the article “Global eval. What are the options?” by kangax.

The Console API

In most JavaScript engines, there is a global object, console, with methods for logging and debugging. That object is not part of the language proper, but has become a de facto standard. Since their main purpose is debugging, the console methods will most frequently be used during development and rarely in deployed code.

This section provides an overview of the console API. It documents the status quo as of Chrome 32, Firebug 1.12, Firefox 25, Internet Explorer 11, Node.js 0.10.22, and Safari 7.0.

How Standardized Is the Console API Across Engines?

The implementations of the console API vary greatly and are constantly changing. If you want authoritative documentation, you have two options. First, you can look at standard-like overviews of the API:

  • Firebug first implemented the console API, and the documentation in its wiki is the closest thing to a standard there currently is.
  • Additionally, Brian Kardell and Paul Irish are working on a specification for the API, which should lead to more consistent behavior.

Second, you can look at the documentation of various engines:

Warning

There is a bug in Internet Explorer 9. In that browser, the console object exists only if the developer tools were open at least once. That means that you get a ReferenceError if you refer to console and the tools weren’t open before. As a workaround, you can check whether console exists and create a dummy implementation if it doesn’t.

Simple Logging

The console API includes the following logging methods:

  • console.clear()
  • Clear the console.
  • console.debug(object1, object2?, …)
  • Prefer console.log(), which does the same as this method.
  • console.error(object1, object2?, …)
  • Log the parameters to the console. In browsers, the logged content may be marked by an “error” icon and/or include a stack trace or a link to the code.
  • console.exception(errorObject, object1?, …]) [Firebug-only]
  • Log object1 etc. and show an interactive stack trace.
  • console.info(object1?, object2?, …)
  • Log the parameters to the console. In browsers, the logged content may be marked by an “info” icon and/or include a stack trace or a link to the code.
  • console.log(object1?, object2?, …)
  • Log the parameters to the console. If the first parameter is a printf-style format string, use it to print the remaining parameters. For example (Node.js REPL):
  1. > console.log('%s', { foo: 'bar' })
  2. [object Object]
  3. > console.log('%j', { foo: 'bar' })
  4. {"foo":"bar"}

The only dependable cross-platform formatting directive is %s. Node.js supports %j to format data as JSON; browsers tend to support directives that log something interactive to the console.

  • console.trace()
  • Logs a stack trace (which is interactive in many browsers).
  • console.warn(object1?, object2?, …)
  • Log the parameters to the console. In browsers, the logged content may be marked by a “warning” icon and/or include a stack trace or a link to the code.

Support on various platforms is indicated in the following table:

Chrome Firebug Firefox IE Node.js Safari
clear
debug
error
exception
info
log
trace
warn

exception has been typeset in italics, because it is supported only on a single platform.

Checking and Counting

The console API includes the following checking and counting methods:

  • console.assert(expr, obj?)
  • If expr is false, log obj to the console and throw an exception. If it is true, do nothing.
  • console.count(label?)
  • Count how many times the line with this statement is executed with this label.

Support on various platforms is indicated in the following table:

Chrome Firebug Firefox IE Node.js Safari
assert
count

Formatted Logging

The console API includes the following methods for formatted logging:

  • console.dir(object)
  • Print a representation of the object to the console. In browsers, that representation can be explored interactively.
  • console.dirxml(object)
  • Print the XML source tree of an HTML or XML element.
  • console.group(object1?, object2?, …)
  • Log the objects to the console and open a nested block that contains all future logged content. Close the block by calling console.groupEnd(). The block is initially expanded, but can be collapsed.
  • console.groupCollapsed(object1?, object2?, …)
  • Works like console.group(), but the block is initially collapsed.
  • console.groupEnd()
  • Close a group that has been opened by console.group() or console.group Collapsed().
  • console.table(data, columns?)
  • Print an array as a table, one element per row. The optional parameter columns specifies which properties/array indices are shown in the columns. If that parameter is missing, all property keys are used as table columns. Missing properties and array elements show up as undefined in columns:
  1. var persons = [
  2. { firstName: 'Jane', lastName: 'Bond' },
  3. { firstName: 'Lars', lastName: 'Croft', age: 72 }
  4. ];
  5. // Equivalent:
  6. console.table(persons);
  7. console.table(persons, ['firstName', 'lastName', 'age']);

The resulting table is as follows:

(index) firstName lastName age
0 “Jane” “Bond” undefined
1 “Lars” “Croft” 72

Support on various platforms is indicated in the following table:

Chrome Firebug Firefox IE Node.js Safari
dir
dirxml
group
groupCollapsed
groupEnd
table

Profiling and Timing

The console API includes the following methods for profiling and timing:

  • console.markTimeline(label) [Safari-only]
  • The same as console.timeStamp.
  • console.profile(title?)
  • Turn on profiling. The optional title is used for the profile report.
  • console.profileEnd()
  • Stop profiling and print the profile report.
  • console.time(label)
  • Start a timer whose label is label.
  • console.timeEnd(label)
  • Stop the timer whose label is label and print the time that has elapsed since starting it.
  • console.timeStamp(label?)
  • Log a timestamp with the given label. May be logged to the console or a timeline.

Support on various platforms is indicated in the following table:

Chrome Firebug Firefox IE Node.js Safari
markTimeline
profile (devtools)
profileEnd (devtools)
time
timeEnd
timeStamp

markTimeline has been typeset in italics, because it is supported only on a single platform. The(devtools) designation means that the developer tools must be open in order for the method to work.[19]

Namespaces and Special Values

The following global variables serve as namespaces for functions. For details, see the material indicated in parentheses:

The following global variables contain special values. For more on them, review the material indicated in parentheses:

  1. > ({}.foo) === undefined
  2. true
  • NaN
  • A value expressing that something is “not a number” (NaN):
  1. > 1 / 'abc'
  2. NaN
  • Infinity
  • A value denoting numeric infinity ∞ (Infinity):
  1. > 1 / 0
  2. Infinity


[18] Mariusz Nowak (@medikoo) told me that code evaluated by Function is sloppy by default, everywhere.

[19] Thanks to Matthias Reuter (@gweax) and Philipp Kyeck (@pkyeck), who contributed to this section.