New in JavaScript 1.7

  • Revision slug: JavaScript/New_in_JavaScript/1.7
  • Revision title: New in JavaScript 1.7
  • Revision id: 51802
  • Created:
  • Creator: Sheppy
  • Is current revision? No
  • Comment /* Introduction */

Revision Content

Introduction

Please note that this document is a work in progress. Some of the new features it covers are not implemented yet, so syntax may change.

JavaScript 1.7 support should be available in Firefox 2 beta 1 and later.

The code samples included in this article can be experimented with in the JavaScript shell. Read Introduction to the JavaScript shell to learn how to build and use the shell.

Generators and iterators

When developing code that involves an iterative algorithm (such as iterating over a list, or repeatedly performing computations on the same data set), there are often state variables whose values need to be maintained for the duration of the computation process. Traditionally, you have to use a callback function to obtain the intermediate values of an iterative algorithm.

Generators

Consider this iterative algorithm that computes Fibonacci numbers:

function do_callback(num) {
  print(num);
}

function fib() {
  var i = 0, j = 1, n = 0;
  while (n < 10) {
    do_callback(i);
    var t = i;
    i = j;
    j += t;
    n++;
  }
}

fib();

This code uses a callback routine to perform operations on each iterative step of the algorithm. In this case, each Fibonacci number is simply printed to the console.

Generators and iterators work together to provide a new, better, way to do this. Let's see how the Fibonacci number routine looks written using a generator:

function fib() {
  var i = 0, j = 1;
  while (true) {
    yield i;
    var t = i;
    i = j;
    j += t;
  }
}

var g = fib();
for (var i = 0; i < 10; i++) {
  print(g.next());
}

The function containing the yield keyword is a generator. When you call it, its formal parameters are bound to actual arguments, but its body isn't actually evaluated. Instead, a generator-iterator is returned. Each call to the generator-iterator's next() method performs another pass through the iterative algorithm. Each step's value is the value specified by the yield keyword. Think of yield as the generator-iterator version of return, indicating the boundary between each iteration of the algorithm. Each time you call next(), the generator code resumes from the statement following the yield.

You cycle a generator-iterator by repeatedly calling its next() method until you reach your desired result condition. In this example, we can obtain however many Fibonacci numbers we want by continuing to call g.next() until we have the number of results we want.

Iterators

An iterator is a special object that lets you iterate over data.

Here's a simple example:

var obj = {name:"Jack Bauer", username:"JackB", id:12345, agency:"CTU", region:"Los Angeles"};

var it = Iterator(obj);

try {
  while(true) {
    print(it.next());
  }
} catch(err) {
  if (err != StopIteration) {
    print("Unknown error: " + err.description + "\n");
  }
}
print("End of record.\n");

You can create an iterator for an object by calling Iterator(objectname). Once you have an iterator, you can easily fetch the next item in the object by calling the iterator's next() method.

If there is no data left, the StopIteration exception is thrown.

The output from this program looks like this:

name,Jack Bauer
username,JackB
id,12345
agency,CTU
region,Los Angeles
End of record.

You can, optionally, specify a second parameter when creating your iterator, which is a boolean value that indicates whether or not you only want the keys returned each time you call its next() method. Changing var it = Iterator(obj); to var it = Iterator(obj, true); in the above sample results in the following output:

name
username
id
agency
region
End of record.

Iterators are a handy way to scan through the data in objects, including objects whose content may include data you're unaware of. This can be particularly useful if you need to preserve data your application isn't expecting.

Array comprehensions

Array comprehensions are a type of generator that provide a convenient way to perform powerful initialization of arrays. For example:

var ten_squares = [i * i for i in range(0, 10)];

This pre-initializes a new array, ten_squares, to contain the squares of the values in the range 0..9.

You can use any conditional when initializing the array. If you want to initialize an array to contain the even numbers between 0 and 20, you can use this code:

var evens = [i for i in range(0, 21) if (i % 2 == 0)];

Prior to JavaScript 1.7, this would have to be coded something like this:

var evens =
  (function() {
    var a = new Array;
    var n = 0;
    for (i=0; i <= 20; i++) {
      if (i % 2 == 0) {
        a[n] = i;
        n++;
      }
    }
    return a;
  }) ();

Not only is the array comprehension much more compact, but it's actually easier to read, once you're familiar with the concept.

Scoping rules

Array comprehensions have an implicit block around them, containing everything inside the square brackets, as well as implicit let declarations.

Add details.

Block scope with let

The let statement

The let statement provides local scoping for variables, constants, and functions, and is intended to be used in place of var. It works by binding zero or more variables to a single block of code. The completion value of the let statement is the completion value of the block.

For example:

var x = 5;
var y = 0;

let (x = x+10, y = 12) {
  print(x+y);
}

print(x+y);

The output from this program will be:

27
5

The rules for the code block are the same as for any other code block in JavaScript. It may have its own local variables established using the var statement. As usual, you may not declare functions within a block.

Note: The parentheses following let are required. Failure to include them will result in a syntax error.

Scoping rules

The scope of variables defined using let is the let block itself, as well as any inner blocks contained inside it, unless those blocks define variables by the same names.

let expressions

You can use let to establish variables that are scoped only to a single expression:

var x = 5;
var y = 0;
print( let(x = x + 10, y = 12) x+y );
print(x+y);

The resulting output is:

27
5

In this case, the binding of the values of x and y to x+10 and 12 are scoped solely to the expression x+y.

Scoping rules

Given a let expression:

let (decls) expr

There is an implicit block created around expr.

let definitions

The let keyword can also be used to define variables, constants, and functions inside a block.

if (x > y)
{
   let const k = 37;
   let gamma : int = 12.7 + k;
   let i = 10;
   let function f(n) { return (n/3)+k; }
   return f(gamma) + f(i);
}

Scoping rules

Variables, functions, and constants declared by let, let function, and let const have as their scope the block in which they are defined, as well as in any sub-blocks in which they aren't redefined. In this way, let works very much like var.

In programs and classes let does not create properties on the global and class objects like var does; instead, it creates properties in an implicit block created for the evaluation of statements in those contexts. This essentially means that let won't override variables previously defined using var. For example:

var x = 'global'
let x = 42
alert(this.x)

The alert displayed by this code will display "global", not " 42".

An implicit block is one that is not bracketed by braces; it's created implicitly by the JavaScript engine.

In functions, let executed by eval() does not create properties on the variable object (activation object or innermost binding rib) like var does; instead, it creates properties in an implicit block created for the evaluation of statements in the program. This is a consequence of eval() operating on programs in tandem with the previous rule.

In other words, when you use eval() to execute code, that code is treated as an independent program, which has its own implicit block around its code.

let-scoped variables in for loops

You can use the let keyword to bind variables locally in the scope of for loops, just like you can with var.

   var i=0;
   for ( let i=i ; i < 10 ; i++ )
     print(i);

   for ( let &[name,value] in obj )
     print("Name: " + name + ", Value: " + value);

Scoping rules

for (let expr1; expr2; expr3) statement

In this example, expr2, expr3, and statement are enclosed in an implicit block that contains the block local variables declared by let expr1. This is demonstrated in the first loop above.

for (expr1 in expr2) statement

In this case, there's an implicit block containing statement. This is shown in the second loop above.

Destructuring assignment

Destructuring assignment makes it possible to extract data from arrays or objects using a syntax resembling array and object literals.

A key advantage to destructuring assignment is the ability for functions to return multiple values.

This section will be beefed up once I'm able to experiment.

This capability is similar to abilities present in languages such as Perl and Python.

Multiple-value returns

Thanks to destructuring assignment, functions can return multiple values:

function f() {
  return [1, 2];
}

var a, b;
[a, b] = f();

After running this code, a is 1 and b is 2.

You can also ignore return values that you're not interested in:

function f() {
  return [1, 2, 3];
}

var [a, , b] = f();

After running this code, a is 1 and b is 3. The value 2 is ignored. You can ignore any (or all) returned values this way. For example:

[,,,] = f();

Looping across objects

You can also use destructuring assignment to pull data out of an object:

for (let[name, value] in obj) {
  print("Name: " + name + ", Value: " + value);
}

This pulls the name and value fields from the object obj and prints them.

Looping across values in an object

You can loop across the values in an object as well:

for each (let {name: n, family: { father: f } } in obj) {
  print("Name: " + n + ", Father: " + f);
}

Add text explaining what this is doing.

Row capture and record updating

Related to destructuring assignment, row capture lets you pull individual fields out of a structure, as well as a new structure that contains the fields you didn't explicitly extract.

For example:

var personRecord = { name: "Bob", city: "Foohaven", job: "Programmer", employer: "Barco Inc." };

{ name: x, city: y, ...: z } = personRecord;

This code would extract the name and city fields into the variables x and y, respectively, and would also create a new structure, z, which is { job: "Programmer", employer: "Barco Inc." }.

One valuable use for this capability is data preservation. If your code needs to be able to work with data that may contain fields you're not aware of, you can extract unknown fields into a structure to preserve them even though you don't know their names.

You can also combine values and structures together using this syntax. For example, if Bob moves, you need to change the city field in the record above. After that, you want to reconstruct the complete record again with the new data.

You can do this easily, like this:

var newPersonRecord = { ... = z, city = "New Foohaven" };

After this, newPersonRecord contains { name: "Bob", city: "New Foohaven", job: "Programmer", employer: "Barco Inc." }.

In simple terms, "..." is shorthand for "any fields I haven't expressly mentioned."

Revision Source

<h2 name="Introduction">Introduction</h2>
<p><i>Please note that this document is a work in progress.  Some of the new features it covers are not implemented yet, so syntax may change.</i>
</p><p>JavaScript 1.7 support should be available in Firefox 2 beta 1 and later.
</p><p>The code samples included in this article can be experimented with in the JavaScript shell.  Read <a href="en/Introduction_to_the_JavaScript_shell">Introduction to the JavaScript shell</a> to learn how to build and use the shell.
</p>
<h2 name="Generators_and_iterators">Generators and iterators</h2>
<p>When developing code that involves an iterative algorithm (such as iterating over a list, or repeatedly performing computations on the same data set), there are often state variables whose values need to be maintained for the duration of the computation process. Traditionally, you have to use a callback function to obtain the intermediate values of an iterative algorithm.
</p>
<h3 name="Generators">Generators</h3>
<p>Consider this iterative algorithm that computes Fibonacci numbers:
</p>
<pre>function do_callback(num) {
  print(num);
}

function fib() {
  var i = 0, j = 1, n = 0;
  while (n &lt; 10) {
    do_callback(i);
    var t = i;
    i = j;
    j += t;
    n++;
  }
}

fib();
</pre>
<p>This code uses a callback routine to perform operations on each iterative step of the algorithm.  In this case, each Fibonacci number is simply printed to the console.
</p><p>Generators and iterators work together to provide a new, better, way to do this. Let's see how the Fibonacci number routine looks written using a generator:
</p>
<pre>function fib() {
  var i = 0, j = 1;
  while (true) {
    yield i;
    var t = i;
    i = j;
    j += t;
  }
}

var g = fib();
for (var i = 0; i &lt; 10; i++) {
  print(g.next());
}
</pre>
<p>The function containing the <code>yield</code> keyword is a generator.  When you call it, its formal parameters are bound to actual arguments, but its body isn't actually evaluated.  Instead, a generator-iterator is returned.  Each call to the generator-iterator's <code>next()</code> method performs another pass through the iterative algorithm.  Each step's value is the value specified by the <code>yield</code> keyword.  Think of <code>yield</code> as the generator-iterator version of <code>return</code>, indicating the boundary between each iteration of the algorithm.  Each time you call <code>next()</code>, the generator code resumes from the statement following the <code>yield</code>.
</p><p>You cycle a generator-iterator by repeatedly calling its <code>next()</code> method until you reach your desired result condition.  In this example, we can obtain however many Fibonacci numbers we want by continuing to call <code>g.next()</code> until we have the number of results we want.
</p>
<h3 name="Iterators">Iterators</h3>
<p>An <i>iterator</i> is a special object that lets you iterate over data.
</p><p>Here's a simple example:
</p>
<pre>var obj = {name:"Jack Bauer", username:"JackB", id:12345, agency:"CTU", region:"Los Angeles"};

var it = Iterator(obj);

try {
  while(true) {
    print(it.next());
  }
} catch(err) {
  if (err != StopIteration) {
    print("Unknown error: " + err.description + "\n");
  }
}
print("End of record.\n");
</pre>
<p>You can create an iterator for an object by calling <code>Iterator(<i>objectname</i>)</code>.  Once you have an iterator, you can easily fetch the next item in the object by calling the iterator's <code>next()</code> method.
</p><p>If there is no data left, the <code>StopIteration</code> exception is thrown.
</p><p>The output from this program looks like this:
</p>
<pre>name,Jack Bauer
username,JackB
id,12345
agency,CTU
region,Los Angeles
End of record.
</pre>
<p>You can, optionally, specify a second parameter when creating your iterator, which is a boolean value that indicates whether or not you only want the keys returned each time you call its <code>next()</code> method.  Changing <code>var it = Iterator(obj);</code> to <code>var it = Iterator(obj, true);</code> in the above sample results in the following output:
</p>
<pre>name
username
id
agency
region
End of record.
</pre>
<p>Iterators are a handy way to scan through the data in objects, including objects whose content may include data you're unaware of.  This can be particularly useful if you need to preserve data your application isn't expecting.
</p>
<h2 name="Array_comprehensions">Array comprehensions</h2>
<p>Array comprehensions are a type of generator that provide a convenient way to perform powerful initialization of arrays.  For example:
</p>
<pre class="eval">var ten_squares = [i * i for i in range(0, 10)];
</pre>
<p>This pre-initializes a new array, <var>ten_squares</var>, to contain the squares of the values in the range <code>0..9</code>.
</p><p>You can use any conditional when initializing the array.  If you want to initialize an array to contain the even numbers between 0 and 20, you can use this code:
</p>
<pre class="eval">var evens = [i for i in range(0, 21) if (i % 2 == 0)];
</pre>
<p>Prior to JavaScript 1.7, this would have to be coded something like this:
</p>
<pre>var evens =
  (function() {
    var a = new Array;
    var n = 0;
    for (i=0; i &lt;= 20; i++) {
      if (i % 2 == 0) {
        a[n] = i;
        n++;
      }
    }
    return a;
  }) ();
</pre>
<p>Not only is the array comprehension much more compact, but it's actually easier to read, once you're familiar with the concept.
</p>
<h4 name="Scoping_rules">Scoping rules</h4>
<p>Array comprehensions have an implicit block around them, containing everything inside the square brackets, as well as implicit <code>let</code> declarations.
</p><p><i>Add details.</i>
</p>
<h2 name="Block_scope_with_let">Block scope with <code>let</code></h2>
<h3 name="The_let_statement">The <code>let</code> statement</h3>
<p>The <code>let</code> statement provides local scoping for variables, constants, and functions, and is intended to be used in place of <code>var</code>.  It works by binding zero or more variables to a single block of code.  The completion value of the <code>let</code> statement is the completion value of the block.
</p><p>For example:
</p>
<pre>var x = 5;
var y = 0;

let (x = x+10, y = 12) {
  print(x+y);
}

print(x+y);
</pre>
<p>The output from this program will be:
</p>
<pre>27
5
</pre>
<p>The rules for the code block are the same as for any other code block in JavaScript.  It may have its own local variables established using the <code>var</code> statement.  As usual, you may not declare functions within a block.
</p>
<div class="note"><b>Note:</b> The parentheses following <code>let</code> are required.  Failure to include them will result in a syntax error.</div>
<h4 name="Scoping_rules_2">Scoping rules</h4>
<p>The scope of variables defined using <code>let</code> is the <code>let</code> block itself, as well as any inner blocks contained inside it, unless those blocks define variables by the same names.
</p>
<h3 name="let_expressions"><code>let</code> expressions</h3>
<p>You can use <code>let</code> to establish variables that are scoped only to a single expression:
</p>
<pre>var x = 5;
var y = 0;
print( let(x = x + 10, y = 12) x+y );
print(x+y);
</pre>
<p>The resulting output is:
</p>
<pre>27
5
</pre>
<p>In this case, the binding of the values of <var>x</var> and <var>y</var> to <code>x+10</code> and <code>12</code> are scoped solely to the expression <code>x+y</code>.
</p>
<h4 name="Scoping_rules_3">Scoping rules</h4>
<p>Given a <code>let</code> expression:
</p>
<pre class="eval">let (<var>decls</var>) <font color="blue"><var>expr</var></font>
</pre>
<p>There is an implicit block created around <font color="blue"><var>expr</var></font>.
</p>
<h3 name="let_definitions"><code>let</code> definitions</h3>
<p>The <code>let</code> keyword can also be used to define variables, constants, and functions inside a block.
</p>
<pre>if (x &gt; y)
{
   let const k = 37;
   let gamma : int = 12.7 + k;
   let i = 10;
   let function f(n) { return (n/3)+k; }
   return f(gamma) + f(i);
}
</pre>
<h4 name="Scoping_rules_4">Scoping rules</h4>
<p>Variables, functions, and constants declared by <code>let</code>, <code>let function</code>, and <code>let const</code> have as their scope the block in which they are defined, as well as in any sub-blocks in which they aren't redefined.  In this way, <code>let</code> works very much like <code>var</code>.
</p><p>In programs and classes <code>let</code> does not create properties on the global and class objects like <code>var</code> does; instead, it creates properties in an implicit block created for the evaluation of statements in those contexts.  This essentially means that <code>let</code> won't override variables previously defined using <code>var</code>.  For example:
</p>
<pre>var x = 'global'
let x = 42
alert(this.x)
</pre>
<p>The alert displayed by this code will display "global", not " 42".
</p><p>An <i>implicit block</i> is one that is not bracketed by braces; it's created implicitly by the JavaScript engine.
</p><p>In functions, <code>let</code> executed by <code>eval()</code> does not create properties on the variable object (activation object or innermost binding rib) like <code>var</code> does; instead, it creates properties in an implicit block created for the evaluation of statements in the program. This is a consequence of <code>eval()</code> operating on programs in tandem with the previous rule.
</p><p>In other words, when you use <code>eval()</code> to execute code, that code is treated as an independent program, which has its own implicit block around its code.
</p>
<h3 name="let-scoped_variables_in_for_loops"><code>let</code>-scoped variables in <code>for</code> loops</h3>
<p>You can use the <code>let</code> keyword to bind variables locally in the scope of <code>for</code> loops, just like you can with <code>var</code>.
</p>
<pre>   var i=0;
   for ( let i=i ; i &lt; 10 ; i++ )
     print(i);

   for ( let &amp;[name,value] in obj )
     print("Name: " + name + ", Value: " + value);
</pre>
<h4 name="Scoping_rules_5">Scoping rules</h4>
<pre class="eval">for (let <var>expr1</var>; <font color="blue"><var>expr2</var></font>; <font color="blue"><var>expr3</var></font>) <font color="blue"><var>statement</var></font>
</pre>
<p>In this example, <font color="blue"><var>expr2</var></font>, <font color="blue"><var>expr3</var></font>, and <font color="blue"><var>statement</var></font> are enclosed in an implicit block that contains the block local variables declared by <code>let <var>expr1</var></code>.  This is demonstrated in the first loop above.
</p>
<pre class="eval">for (<var>expr1</var> in <var>expr2</var>) <font color="blue"><var>statement</var></font>
</pre>
<p>In this case, there's an implicit block containing <font color="blue"><var>statement</var></font>.  This is shown in the second loop above.
</p>
<h2 name="Destructuring_assignment">Destructuring assignment</h2>
<p>Destructuring assignment makes it possible to extract data from arrays or objects using a syntax resembling array and object literals.
</p><p>A key advantage to destructuring assignment is the ability for functions to return multiple values.
</p><p><i>This section will be beefed up once I'm able to experiment.</i>
</p><p>This capability is similar to abilities present in languages such as Perl and Python.
</p>
<h3 name="Multiple-value_returns">Multiple-value returns</h3>
<p>Thanks to destructuring assignment, functions can return multiple values:
</p>
<pre>function f() {
  return [1, 2];
}

var a, b;
[a, b] = f();
</pre>
<p>After running this code, <var>a</var> is 1 and <var>b</var> is 2.
</p><p>You can also ignore return values that you're not interested in:
</p>
<pre>function f() {
  return [1, 2, 3];
}

var [a, , b] = f();
</pre>
<p>After running this code, <var>a</var> is 1 and <var>b</var> is 3.  The value 2 is ignored.  You can ignore any (or all) returned values this way.  For example:
</p>
<pre>[,,,] = f();
</pre>
<h3 name="Looping_across_objects">Looping across objects</h3>
<p>You can also use destructuring assignment to pull data out of an object:
</p>
<pre>for (let[name, value] in obj) {
  print("Name: " + name + ", Value: " + value);
}
</pre>
<p>This pulls the <var>name</var> and <var>value</var> fields from the object <var>obj</var> and prints them.
</p>
<h3 name="Looping_across_values_in_an_object">Looping across values in an object</h3>
<p>You can loop across the values in an object as well:
</p>
<pre>for each (let {name: n, family: { father: f } } in obj) {
  print("Name: " + n + ", Father: " + f);
}
</pre>
<p><i>Add text explaining what this is doing.</i>
</p>
<h2 name="Row_capture_and_record_updating">Row capture and record updating</h2>
<p>Related to destructuring assignment, row capture lets you pull individual fields out of a structure, as well as a new structure that contains the fields you didn't explicitly extract.
</p><p>For example:
</p>
<pre>var personRecord = { name: "Bob", city: "Foohaven", job: "Programmer", employer: "Barco Inc." };

{ name: x, city: y, ...: z } = personRecord;
</pre>
<p>This code would extract the <var>name</var> and <var>city</var> fields into the variables <var>x</var> and <var>y</var>, respectively, and would also create a new structure, <var>z</var>, which is <code>{ job: "Programmer", employer: "Barco Inc." }</code>.
</p><p>One valuable use for this capability is data preservation.  If your code needs to be able to work with data that may contain fields you're not aware of, you can extract unknown fields into a structure to preserve them even though you don't know their names.
</p><p>You can also combine values and structures together using this syntax.  For example, if Bob moves, you need to change the <var>city</var> field in the record above.  After that, you want to reconstruct the complete record again with the new data.
</p><p>You can do this easily, like this:
</p>
<pre>var newPersonRecord = { ... = z, city = "New Foohaven" };
</pre>
<p>After this, <var>newPersonRecord</var> contains <code>{ name: "Bob", city: "New Foohaven", job: "Programmer", employer: "Barco Inc." }</code>.
</p><p>In simple terms, "<code>...</code>" is shorthand for "any fields I haven't expressly mentioned."
</p>
Revert to this revision