A

ECMAScript Harmony

With the renewed interest in web development since 2004, conversations began taking place among browser vendors and other interested parties as to how JavaScript should evolve. Work on the fourth edition of ECMA-262 began based largely on two competing proposals: one for Netscape’s JavaScript 2.0 and the other for Microsoft’s JScript.NET. Instead of competing in the browser realm, the parties converged back into ECMA to hammer out a proposal for a new language based on JavaScript. Initially, work began on a proposal called ECMAScript 4, and for a long time, this seemed like the next evolutionary step for JavaScript. When a counterproposal called ECMAScript 3.1 was later introduced, it threw the future of JavaScript into question. After much debate, it was determined that ECMAScript 3.1 would be the next step for JavaScript and that a further effort, code-named Harmony, would seek to reconcile some features from ECMAScript 4 into ECMAScript 3.1.

ECMAScript 3.1 was ultimately renamed to ECMAScript 5 and standardized fairly quickly. The details of ECMAScript 5 have been covered throughout this book. As soon as ECMAScript 5 was finalized, work immediately began on Harmony. Harmony tries to keep to the spirit of ECMAScript 5, in making more incremental changes rather than radical language changes. While the details of Harmony, aka ECMAScript 6, are still developing as of 2011, there are several parts of the specification that have been finished. This appendix covers the parts of Harmony that will definitely make it into the final specification, though, keep in mind that the details of the final implementations may change from what’s presented here.

GENERAL CHANGES

Harmony introduces several basic changes to ECMAScript. These aren’t major changes for the language but rather the closing of some of the curiously open gaps in functionality.

Constants

One of the glaring weaknesses of JavaScript is its lack of formal constants. To rectify this, developers added constants as part of Harmony via the const keyword. Used in a manner similar to var, the const declaration lets you define a variable whose value cannot be changed once initialized. Here is the usage:

const MAX_SIZE = 25;

Constants may be defined anywhere a variable can be defined. Constant names cannot be the same as variable or function names declared in the same scope, so the following causes an error:

const FLAG = true; 
var FLAG = false;     //error!

Aside from having immutable values, constants can be used just like any other variable. Any attempt to change the value is simply ignored, as shown here:

const FLAG = true;
FLAG = false;
alert(FLAG);    //true

Constants are supported in Firefox, Safari 3+, Opera 9+, and Chrome. In Safari and Opera, const acts just like var in that values can still be changed.

Block-Level and Other Scopes

One of the constant reminders throughout this book has been that JavaScript has no concept of block-level scope. This means that variables defined inside statement blocks act as if they were defined in the containing function. Harmony introduces the concept of block-level scoping through the introduction of the let keyword.

Similarly to const and var, a let declaration can be used at any point to define a variable and initialize its value. The difference is that the variable defined with let will disappear once execution has moved outside the block in which it was defined. For example, it’s quite common to use the following construct:

for (var i=0; i < 10; i++) {
    //do something
}
                   
alert(i);    //10

When the variable i is declared in this code, it is declared as local to the function in which the code resides. This means that the variable is still accessible after the for loop has finished executing. If let were used instead of var, the variable i would not exist after the loop completed. Consider the following:

for (let i=0; i < 10; i++) {
    //do something
}
                   
alert(i);    //Error! i is undefined

If this code were to be executed, the last line would cause an error since the definition of i is removed as soon as the for loop completes. The result is an error, because you cannot perform any operations on an undeclared variable.

There are other ways to use let as well. You can create a let statement that specifically defines variables that should be used only with the next block of code, as in this example:

var num = 5;
                   
let (num=10, multiplier=2){
    alert(num * multiplier);    //20
}
                   
alert(num);   //5

In this code, the let statement defines an area within which the num variable is equal to 10 and the multiplier variable is equal to 2. This definition of num overrides the previously declared value using var, so within the let statement the result of multiplying by the multiplier is 20. Outside the let statement, the value of num remains 5. Since each let statement creates its own scope, the variable values inside it have no bearing on the values outside.

You can use a similar syntax to create a let expression where variable values are set only for a single expression. Here is an example:

var result = let(num=10, multiplier=2) num * multiplier;
alert(result);  //20

Here, a let expression is used to calculate a value using two variables. The value is then stored in the result variable. After that point, the variables num and multiplier no longer exist.

Using block-level scopes in JavaScript gives you more control over which variables exist at what point during code execution.

FUNCTIONS

Most code is written in functions, so Harmony focuses on ways to improve functions and make them easier to use. As with other parts of Harmony, these changes focus on pain points for developers and implementers.

Rest and Spread Arguments

In Harmony, the arguments object is no more; you can’t access undeclared arguments in it at all. There is, however, a way to indicate that you are expecting a variable number of arguments to be passed in through the use of rest arguments. Rest arguments are indicated by three dots followed by an identifier. This allows you to define the arguments that you know will be passed in and then collect the rest into an array. Here is an example:

function sum(num1, num2, ...nums){
    var result = num1 + num2;
    for (let i=0, len=nums.length; i < len; i++){
        result += nums[i];
    }
    return result;
}
                   
var result = sum(1, 2, 3, 4, 5, 6);

This code defines a sum() method that accepts at least two arguments. It can accept additional arguments, and all of the remaining arguments are stored in the nums array. Unlike the arguments object, rest arguments are stored in an instance of Array, so all array methods are available. The rest arguments object is always an instance of Array, even if there are no rest arguments passed into the function.

Closely related to rest arguments are spread arguments. Spread arguments allow you to pass in an array and have each item be mapped to a particular argument in the function. The notation for spread arguments is the same as rest arguments, prepending three dots to a value. The only difference is that spread arguments take place at the time a function is called, whereas rest arguments are used at the time a function is defined. For example, instead of passing in individual numbers to the sum() method, you could use spread arguments as shown here:

var result = sum(...[1, 2, 3, 4, 5, 6]);

In this code, an array of arguments is passed to sum() as spread arguments. This example is the functional equivalent of the following:

var result = sum.apply(this, [1, 2, 3, 4, 5, 6]);

Default Argument Values

All arguments in an ECMAScript function are considered optional, since no check is done against the number of arguments that have been passed in. However, instead of manually checking to see which arguments have been provided, you can specify default values for arguments. If the arguments aren’t formally passed in, then they get the given value.

To specify a default value for an argument, just add an equal sign and the default value after the argument definition, as in this example:

function sum(num1, num2=0){
    return num1 + num2;
}
                   
var result1 = sum(5);
var result2 = sum(5, 5);

The sum() function accepts two arguments, but the second one is optional and gets a default value of 0. The beauty of optional arguments is that it frees you from needing to check to see if the value was passed in and then using a special value; all of that is done for you.

Generators

A generator is an object that generates a sequence of values one at a time. With Harmony, you can create a generator by defining a function that returns a specific value using the yield operator. When a function is called that uses yield, a new Generator instance is created and returned. The next() method can then be called to retrieve the first value of the generator. When this happens, the original function is executed and stops execution when it comes to yield, returning the specified value. In this way, yield works in a similar manner to return. If next() is called again, code execution continues at the next statement following yield and then continues to run until yield is encountered again, at which point a new value is returned. Here is an example:

function myNumbers(){
    for (var i=0; i < 10; i++){
        yield i * 2;
    }        
}
                   
var generator = myNumbers();
                   
try {
    while(true){
        document.write(generator.next() + "<br />");
    }
} catch(ex){
    //intentionally blank   
} finally {
    generator.close();
}

When the function myNumbers() is called, a generator is returned. The myNumbers() function itself is very simple, containing a for loop that yields a value each time through the loop. Each call to next() causes another trip through the for loop and returns the next value. The first value is 0, the second is 2, the third is 4, and so on. When myNumbers() completes without calling yield (after the final loop iteration), calling next() throws a StopIteration error. So to output all numbers in the generator, a while loop is wrapped in a try-catch statement to prevent the error from stopping code execution.

If a generator is no longer needed, it’s best to call the close() method. Doing so ensures that the rest of the original function is executed, including any finally blocks related to try-catch statements.

Generators are useful when a sequence of values needs to be produced and each subsequent value is somehow related to the previous one.

ARRAYS AND OTHER STRUCTURES

Another area of focus for Harmony is arrays. Arrays are one of the most frequently used data structures in JavaScript, and creating more intuitive and powerful ways to work with arrays was a priority for the language.

Iterators

An iterator is an object that iterates over a sequence of values and returns them one at a time. When you use a for loop or a for-in loop, you’re typically iterating over values and processing them one at a time. Iterators provide the ability to do the same without using a loop. Harmony supports iterators for all types of objects.

To create an iterator for an object, use the Iterator constructor and pass in the object whose values should be iterated over. The next() method is used to retrieve the next value in the sequence. By default, this method returns an array whose first item is the index of the value (for arrays) or the name of the property (for objects) and whose second item is the value. When no further values are available, calling next() throws a StopIteration error. Here is an example:

var person = {
    name: "Nicholas",
    age: 29
};
var iterator = new Iterator(person);
                   
try {
    while(true){
        let value = iterator.next();
        document.write(value.join(":") + "<br>");
    }
} catch(ex){
    //intentionally blank
}

This code creates an iterator for the person object. The first time next() is called, the array ["name", "Nicholas"] is returned, and the second call returns ["age", 29]. The output from this code is as follows:

name:Nicholas
age:29

When an iterator is created for a nonarray object, the properties are returned in the same order as they would be in a for-in loop. This also means that only instance properties are returned, and the order in which the properties are returned varies upon implementation. Iterators created for arrays act in a similar manner, iterating over each position in the array, as shown here:

var colors = ["red", "green", "blue"];
var iterator = new Iterator(colors);
                   
try {
    while(true){
        let value = iterator.next();
        document.write(value.join(":") + "<br>");
    }
} catch(ex){
    
}

The output from this code is as follows:

0:red
1:green
2:blue

You can force only the property name or index to be returned from next() by passing a second argument, true, into the Iterator constructor, as shown here:

var iterator = new Iterator(colors, true);

With the second argument passed, each call to next() will return only the index of the value instead of an array containing both the index and the value.

image

It’s possible to create your own iterators for custom types by defining the special method __iterator__(), which must return an object that has a next() method. This method will be called when an instance of your custom type is passed as an argument to the Iterator constructor.

Array Comprehensions

Array comprehensions are a way to initialize an array with specific values meeting certain criteria. This feature, introduced in Harmony, is a popular language construct in Python. The basic form of array comprehensions in JavaScript is as follows:

array = [ value for each (variable in values) condition ];

The value is the actual value to be included in the final array. This value is based on all the values in the values array. The for each construct loops over each value in values and stores the value in variable. If the optional condition is met, then value is added to the resulting array. Here is an example:

//original array
var numbers = [0,1,2,3,4,5,6,7,8,9,10];
                   
//just copy all items into a new array
var duplicate = [i for each (i in numbers)];
                   
//get just the even numbers
var evens = [i for each (i in numbers) if (i % 2 == 0)];
                   
//multiply every value by 2
var doubled = [i*2 for each (i in numbers)];
                   
//multiply every odd number by 3
var tripledOdds = [i*3 for each (i in numbers) if (i % 2 > 0)];

All of the array comprehensions in this code use i as a variable to iterate over all values in numbers. Some of them use conditions to filter the results of the array. Essentially, if the condition evaluates to true, the value is added to the array. The syntax is a little different from traditional JavaScript but is more succinct than writing your own for loop to accomplish the same task. Firefox (version 2 and later) is the only browser to implement this feature, and it requires the type attribute of the <script> element to be "application/javascript;version=1.7" to enable it.

image

The values portion of an array comprehension can also be a generator or an iterator.

Destructuring Assignments

It’s quite common to have a group of values from which you want to extract one or more into individual variables. Consider the value returned from an iterator’s next() method, which is an array containing the property name and value. In order to store each in its own variable, it would require two statements, as in this example:

var nextValue = ["color", "red"];
var name = nextValue[0];
var value = nextValue[1];

A destructuring assignment allows you to assign both array items into variables using a single statement such as this:

var [name, value] = ["color", "red"];
alert(name);     //"color"
alert(value);    //"red"

In traditional JavaScript syntax, an array literal cannot be on the left side of an assignment. Destructuring assignment introduces this syntax to indicate that the variables contained in the array to the left of the equal sign should be assigned the values contained in the array to the right of the equal sign. The result is that name is filled with "color" and value is filled with "red".

If you don’t want all of the values, you can provide variables just for the ones you want, as in this example:

var [, value] = ["color", "red"];
alert(value);    //"red"

Here, only the variable value is assigned, and it receives the value "red".

You can use destructuring assignment in creative ways, such as to swap the values of two variables. In ECMAScript 3, swapping the values of two variables is typically done like this:

var value1 = 5;
var value2 = 10;
                   
var temp = value1;
value1 = value2;
value2 = temp;

You can eliminate the need for the temp variable by using a destructuring array assignment, as in this example:

var value1 = 5;
var value2 = 10;
                   
[value2, value1] = [value1, value2];

Destructuring assignment can also be accomplished with objects, like this:

var person = {
    name: "Nicholas",
    age: 29
};
                   
var { name: personName, age: personAge } = person;
                   
alert(personName);   //"Nicholas"
alert(personAge);    //29

As with array literals, when an object literal occurs to the left of an equal sign, it’s considered to be a destructuring assignment. This statement actually defines two variables, personName and personAge, that are filled with the matching information from the variable person. As with arrays, you can pick and choose which values to retrieve, as shown here:

var { age: personAge } = person;
alert(personAge);    //29

This modified code retrieves only the age property from the person object.

NEW OBJECT TYPES

Harmony introduces several new object types to the language. These object types focus on providing functionality that was previously available only to the JavaScript engine.

Proxy Objects

Harmony introduces the concept of proxies to JavaScript. A proxy is an object that presents an interface that doesn’t necessarily act on the proxy object itself. For example, setting a property on a proxy object might actually call a function on another object. Proxies are a useful abstraction mechanism for exposing only a subset of information through an API while maintaining complete control over the data source.

A proxy object is created using the Proxy.create() method and passing in a handler object and optional prototype object:

var proxy = Proxy.create(handler);
 
//create proxy that has a prototype of myObject
var proxy = Proxy.create(handler, myObject);

The handler object comprises properties that define traps. Traps are functions that handle (trap) native functionality so that it can be handled in another way. There are seven fundamental traps that are considered important to implement for all proxies to ensure that the proxy object works in a predictable way without throwing errors:

  • getOwnPropertyDescriptor — A function to call when Object.getOwnPropertyDescriptor() is called on the proxy. The function receives the property name as an argument. Should return a property descriptor or null if the property doesn’t exist.
  • getPropertyDescriptor — A function to call when Object.getPropertyDescriptor() is called on the proxy. (This is a new method in Harmony.) The function receives the property name as an argument. Should return a property descriptor or null if the property doesn’t exist.
  • getOwnPropertyNames — A function to call when Object.getOwnPropertyNames() is called on the proxy. The function receives the property name as an argument. Should return an array of strings.
  • getPropertyNames — A function to call when Object.getPropertyNames() is called on the proxy. (This is a new method in Harmony.) The function receives the property name as an argument. Should return an array of strings.
  • defineProperty — A function to call when Object.defineProperty() is called on the proxy. The function receives the property name and the property descriptor as arguments.
  • delete — Defines a function that is called when the delete operator is used on a property of the object. The property name is passed in as an argument. Return true to indicate that the deletion succeeded or false if not.
  • fix — Defines a function that is called when Object.freeze(), Object.seal(), or Object.preventExtensions() is called. Return undefined to throw an error when one of these methods is called on the proxy.

In addition to the fundamental traps, there are also six derived traps. Unlike fundamental traps, failing to define one or more derived traps will not cause errors. Each derived trap overrides a default JavaScript behavior.

  • has — Defines a function that is called when the in operator is used on the object, such as "name" in object. The property name is passed in as an argument. Return true to indicate the property is contained on the object, false if not.
  • hasOwn — Defines a function that is called when the hasOwnProperty() method is called on the proxy. The property name is passed in as an argument. Return true to indicate the property is contained on the object, false if not.
  • get — Defines a function that is called when a property is read. The function receives two arguments, the object reference being read from and the property name. The object reference may be the proxy itself or may be an object inheriting from the proxy.
  • set — Defines a function that is called when a property is written to. The function receives three arguments, the object reference being written to, the property name, and the property value. As with get, the object reference may be the proxy itself or may be an object inheriting from the proxy.
  • enumerate — Defines a function that is called when the proxy is placed in a for-in loop. The function must return an array of strings containing the appropriate property names to be used in the for-in loop.
  • keys — Defines a function that is called when Object.keys() is called on the proxy. As with enumerate, this function must return an array of strings.

Proxies are primarily used when you need to expose an API while keeping some underlying data from being manipulated directly. For example, suppose you want to implement a traditional stack data type. Even though arrays can act as stacks, you want to ensure that people use only push(), pop(), and length. In this case, you can create a proxy that works on an array but exposes only those three object members:

/*
 * Another ES6 Proxy experiment. This one creates a stack whose underlying
 * implementation is an array. The proxy is used to filter out everything
 * but "push", "pop", and "length" from the interface, making it a pure
 * stack where you can't manipulate the contents.
 */
 
var Stack = (function(){
 
    var stack = [],
        allowed = [ "push", "pop", "length" ];
    
    return Proxy.create({
        get: function(receiver, name){;
            if (allowed.indexOf(name) > -1){
                if(typeof stack[name] == "function"){
                    return stack[name].bind(stack);
                } else {
                    return stack[name];
                }
            } else {
                return undefined;
            }
        }
    
    });
 
});
 
var mystack = new Stack();
 
mystack.push("hi");
mystack.push("goodbye");
 
console.log(mystack.length);    //1
 
console.log(mystack[0]);        //undefined
console.log(mystack.pop());     //"goodbye"

This code creates a constructor called Stack. Instead of working on this, the Stack constructor returns a proxy object that works on an array. The get trap is the only one defined, and it simply checks an array of allowed properties before returning the value. All disallowed properties end up returning undefined when referenced while push(), pop(), and length work as expected. The key piece of this code is the declaration of the get trap, which filters the object member retrieval based on the allowed members. If the member is a function, then it returns a bound version of the function so that it operates on the underlying array object instead of the proxy object itself.

Proxy Functions

In addition to creating proxy objects, you can also create proxy functions in Harmony. A proxy function is the same as a proxy object except that it is executable. Proxy functions are created by using the Proxy.createFunction() method and passing in a handler object, a call trap function, and an optional constructor trap function. For example:

var proxy = Proxy.createFunction(handler, function(){}, function(){});

The handler object has the same available traps as with proxy objects. The call trap function is the code to execute when the proxy function is executed, such as proxy(). The constructor trap is the code to execute when the proxy function is called using the new operator, such as new proxy(). If the constructor trap is not defined, then the call trap is used for the constructor trap as well.

Map and Set

The Map type, also called simple map, has a singular purpose: to store a list of key-value pairs. Developers typically use generic objects for this purpose, but that comes at a cost as keys can easily be confused with native properties. Simple maps keep keys and values separate from the object’s properties to provide safer storage for this information. Some example usage:

var map = new Map();
 
map.set("name", "Nicholas");
map.set("book", "Professional JavaScript");
 
console.log(map.has("name"));   //true
console.log(map.get("name"));   //"Nicholas"
 
map.delete("name");

The basic API for simple maps is made up of get(), set(), has(), and delete(), each doing exactly what the name indicates. Keys can be primitive values for reference values.

Related to simple maps is the Set type. A set is simply a collection of items of which there are no duplicates. Unlike simple maps, sets are only keys and have no related value associated with them. The basic API has add() for adding items, has() for checking the existence of items, and delete() for removing items. Some example usage:

var set = new Set();
set.add("name");
 
console.log(set.has("name"));   //true
set.delete("name");
 
console.log(set.has("name"));   //false

The specification for both Map and Set are incomplete as of October 2011, so the details here may change before the JavaScript engines begin implementing them.

WeakMap

The WeakMap type is interesting in that it’s the first ECMAScript feature that allows you to know when an object has been completely dereferenced. A WeakMap works in a similar manner to a simple map where you store a key-value pair. The big difference for a WeakMap is that the key must be an object and when the object no longer exists, the associated key-value pair is removed from the WeakMap. For example:

var key = {},
    map = new WeakMap();
 
map.set(key, "Hello!");
 
//dereference the key so the value is also removed
key = null;

The use case for WeakMaps is as yet unclear, but this construct also appears in Java as WeakHashMap and so this brings another data structure option to JavaScript.

StructType

One of the acknowledged downsides of JavaScript is the use of a single data type to represent all numbers. WebGL introduced typed arrays to help this problem, and ECMAScript 6 introduces typed structures to further integrate more numeric data types into the language. A struct type is analogous to a struct in C, where you combine multiple properties into a single record. Struct types in JavaScript allow you to create similar data structures by specifying properties and the type of data they contain. The initial implementation defines several block types:

  • uint8 — unsigned 8-bit integer
  • int8 — signed 8-bit integer
  • uint16 — unsigned 16-bit integer
  • int16 — signed 16-bit integer
  • uint32 — unsigned 32-bit integer
  • int32 — signed 32-bit integer
  • float32 — 32-bit floating point
  • float64 — 64-bit floating point

Block types contain a single value, and there are expected to be more than these initial eight added in the future.

A struct type is created by instantiating StructType with property definitions in the form of an object literal. For example:

var Size = new StructType({ width: uint32, height: uint32 });

This code creates a new struct type called Size with two properties: width and height. Each property holds an unsigned 32-bit integer. The variable Size is actually a constructor that can be used just like an object constructor. You initialize an instance of a struct type by passing an object literal with the property values into the constructor:

var boxSize = new Size({ width: 80, height: 60 });
console.log(boxSize.width);   //80
console.log(boxSize.height);  //60

Here, a new instance of Size is created with a width of 80 and a height of 60. These properties can be written to and read from but always must contain 32-bit unsigned integers.

It’s possible to create more complex struct types by having each property defined as another struct type. For example:

var Location = new StructType({ x: int32, y: int32 });
var Box = new StructType({ size: Size, location: Location });
 
var boxInfo = new Box({ size: { width:80, height:60 }, location: { x: 0, y: 0 }});
console.log(boxInfo.size.width);   //80

This example creates a simple struct type called Location and a complex struct type called Box, whose properties are defined as struct types. The Box constructor still accepts an object literal defining the values for each property and will type-check to ensure the values are the correct data type.

ArrayType

Closely related to struct type is array type. An array type allows creation of an array whose values are limited to a specific type, very similar to WebGL typed arrays. To create a new array type, call the ArrayType constructor and pass in the type of data it should hold and how many items will be in the array. For example:

var SizeArray = new ArrayType(Size, 2);
var boxes = new BoxArray([ { width: 80, height: 60 }, { width: 50, height: 50 } ]);

This code creates a new array type called SizeArray that is initialized to hold instances of Size with an allotment of two spaces in the array. Array types are initialized by passing in an array containing the data that should be converted, allowing literals to be passed in and coerced into the correct data type (as with struct types).

CLASSES

Developers have long clamored for an easy way to define Java-like classes in JavaScript, and ECMAScript 6 finally introduces this functionality into the language. Classes are syntactic sugar that overlay the current constructor- and prototype-based approach to types. Consider the following type definition:

function Person(name, age){
    this.name = name;
    this.age = age;
}
 
Person.prototype.sayName = function(){
    alert(this.name);
};
 
Person.prototype.getOlder = function(years){
    this.age += years;
};

The equivalent using the new class syntax is:

class Person {
 
    constructor(name, age){
        public name = name;
        public age = age;
    }
 
    sayName(){
        alert(this.name);
    }
 
    getOlder(years){
        this.age += years;
    }
 
}

The new class syntax begins with the keyword class followed by the name of the type. Inside the braces is where properties and methods are created. Methods no longer require the function keyword; instead just use the name of the method followed by parentheses. If the method is named constructor, then it acts as the constructor function for the class (the same as the Person function in the prior code). All other methods and properties defined within the class braces are applied to the prototype, so in this case, sayName() and getOlder() both end up on Person.prototype.

Within the constructor function, variables preceded by the keywords public or private are created as instance properties of the object. Both name and age are defined as public properties in this example.

Private Members

The classes proposal supports private members by default, both on the instance and on the prototype. The private keyword indicates that a member is private and cannot be accessed from outside of a class’s methods. In order to access private members, a special syntax is used where the private() function is called on this and then the property may be accessed. For example, the following changes the Person class to have a private age property.

class Person {
 
    constructor(name, age){
        public name = name;
        private age = age;
    }
 
    sayName(){
        alert(this.name);
    }
 
    getOlder(years){
        private(this).age += years;
    }
 
}

The syntax for accessing private properties is still being debated and will likely change in the future.

Getters/Setters

The new class syntax allows you to define getters and setters for properties directly, avoiding the extra step of calling Object.defineProperty(). The syntax is the same as for methods but has a preceding get or set keyword. For example:

class Person {
 
    constructor(name, age){
        public name = name;
        public age = age;
        private innerTitle = "";
 
        get title(){
            return innerTitle;
        }
 
        set title(value){
            innerTitle = value;
        }
    }
 
    sayName(){
        alert(this.name);
    }
 
    getOlder(years){
        this.age += years;
    }
 
}

This version of the Person class defines a getter and a setter for the title property. Each function operates on the innerTitle variable that is defined in the constructor. Getters and setters for prototype properties are also possible by using the same syntax outside of the constructor function.

Inheritance

A key advantage of using classes over the more traditional JavaScript syntax is the ease with which inheritance is achieved. Instead of worrying about constructor stealing and prototype chaining, a simple syntax that is shared with other languages is used: the extends keyword. For example:

class Employee extends Person {
    constructor(name, age){
        super(name,age);
    }
}

This code creates a new Employee class as a subclass of Person. The prototype chaining occurs behind the scenes and constructor stealing is now formally supported by using the super() function. The preceding code is the logical equivalent of the following:

function Employee(name, age){
    Person.call(this, name, age);
}
 
Employee.prototype = new Person();

In addition to this style of inheritance, classes can also specify an object to assign directly as its prototype by using the prototype keyword in place of extends:

var basePerson = {
    sayName: function(){
        alert(this.name);
    },
 
    getOlder: function(years){
        this.age += years;
    }
};
 
class Employee prototype basePerson {
    constructor(name, age){
        public name = name;
        public age = age;
    }
}

In this example, Employee.prototype is assigned to the basePerson object directly, allowing for the same type of inheritance currently achieved through Object.create().

MODULES

Modules (or namespaces or packages) are a popular concept in organizing JavaScript applications. Each module contains specific, unique functionality that is self-contained and separable from other modules. Though several ad hoc module formats have emerged in JavaScript, ECMAScript 6 seeks to formalize how modules are created and managed.

Modules operate in their own top-level execution context and so cannot pollute the global execution context in which they’re imported. By default, all variables, functions, classes, and so on declared within a module are private to that module. You indicate that a member should be exposed to the outside world by using the export keyword in front of it. For example:

module MyModule {
    //export this stuff
    export let myobject = {};
    export function hello(){ alert("hello"); };
 
    //keep this stuff hidden
    function goodbye(){
        //...
    }
}

This module exports an object called myobject and a function called hello(). The module is used elsewhere, in a page or in another module, but imports one or both of the available members. Importation is accomplished using the import command:

//import just myobject
import myobject from MyModule;
console.log(myobject);
 
//import everything
import * from MyModule;
console.log(myobject);
console.log(hello);
 
//explicitly named imports
import {myobject, hello} from MyModule;
console.log(myobject);
console.log(hello);
 
//no import - use module directly
console.log(MyModule.myobject);
console.log(MyModule.hello);

If the execution context has access to a module, then the module may be accessed directly to get the members that it’s exporting. The process of importing simply brings individual members into the current execution context so that they may be accessed without referencing the module directly.

External Modules

Modules may be included dynamically by providing a URL from which the module is to be loaded. To do so, just add the URL after the module declaration:

module MyModule from "mymodule.js";
import myobject from MyModule;

This declaration instructs the JavaScript engine to download mymodule.js and load the module named MyModule from it. Note that this call is blocking — the JavaScript engine will not continue processing code until the URL is downloaded and evaluated.

If you want to include something that the module exports without bringing in the entire module, you can specify that using the import directive:

import myobject from "mymodule.js";

Overall, modules are just a way to group together related functionality and protect the global scope from pollution.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.218.45.80