Fork me on GitHub
www.linux-swt.de

Edwards Hobby JavaScript aus dem Obdachlosenwohnheim seit 2012 Homepage
(ohne 3D-Grafikkarte, kein Multicore, nur ein PIII/933 mit 512MB)
mit neuem gebrauchten Core 2 Duo Notebook von hp mit 2x2.1 + 2GB

JavaScript

EcmaScript 6 - Best of - Features - Demos - Implementation

Ich brauche Platz für neue Tests. Den habe ich mir eben erschaffen.

Class Declaration

Was sie hier sehen ist die neue ES6 "class" Deklaration, wie sie von einem Array erbt. Laesst man den Klassenkonstruktor weg, wird implizit constructor(...args) { super(...args); } aufgerufen.

Der String wird von syntax.js übrigens intern mit parseGoal("MethodDefinition", "constructor(...args){super(...args)}"); eingelesen und das Ergebnis ganz normal als [[Code]] Property der Funktion C, bzw. D verwendet. Sozusagen wird für class C eine Funktion erzeugt, und wenn ein Konstruktor vorhanden ist, wird der für den [[Code]] genommen, und falls nicht, der gerade genannte default String, der mit einem ...Spread des args ...Restparameter super() aufruft, was den "constructor" des [[Prototype]] der extends Angabe ruft und alle im Rest gesammelten Argumente auf den Parametern wieder ausbreitet. Spread ist sozusagen das moderne Function.prototype.apply, was diesmal auch mit constructor geht, was mit apply und call ja bisher nicht ging, wenn sie sich daran erinnern, schonmal den constructor der "Superklasse" mit variablen Argumenten rufen gewollt zu haben. Das ging bislang (auch in ES5) wirklich nur mit fixen Parametern..

class C extends Array{
    method() {
	return this.join('');
    }
}
class D extends C {
    method() {
	return "Der " + super();
    }
}
let d = new D("E","d","w","a","r","d");

Template Literal

Und hier noch das neue Template Literal. Wenn man es in Backticks an String.raw uebergibt, mit oder ohne runde Klammern, werden die Variabelnamen oder Ausdruecke zwischendrin ersetzt. Gibt man den TemplateString an eine Variable, muss man die Variablen als Argumente hinter dem Template an String.raw übergeben.

    let name = "Edward"
    let attr = "hohl";
    let string = String.raw`${name} nur etwas ${attr}!`;
    let tmpl = `${name} nur etwas ${attr}!`;
    let string2 = String.raw`${ (function() { return "Eddie"; }()) } war hier.`;

for-of

Hier noch die neue for-of Iteration. Einmal als String Iterator, damit wird jeder Charakter ausgegeben. Und einmal mit dem Array.prototype.entries() Iterator, der ein Paar [key, value] pro Index wiedergibt.

    let x = 10;
    for (let x of "Edward") {
	console.html('#console3', x);
    }
    for (let [k,v] of ["E","d","w","a","r","d"].entries()) {
	console.html('#console3', "position "+k+" enthaelt "+v);
    }

Array Comprehension

Eine gemütliche Variante um Arrays zu erzeugen ist die neue Array Comprehension. Man schliesst in die gewohnten [] einen oder mehrere for-of´s und Konditionen ein, und aggregiert die am Ende nicht gefilterten Werte in einen Array.

    let array = [for (x of "E-d-w-a-r-d") for (x of "G=e=r=h=o=l=d") if (x != "-") if (x != "=") "*"+x+"*"];

Destructuring

Besonders bequem wird´s ab ES6 mit dem Destrukturieren von Objekten und Arrays. Mit Defaultparametern, Aliasnamen und Rest- und Spreadoperationen hat man eine ganze Palette an neuen Moeglichkeiten, besseren Code zu schreiben. Allerdings möchte ich hinweisen, das hin- und herübertragen von [...rest] = [...spread] nur so zum Spass bedeutet, jeweils den Array zu durchlaufen, also eine Reihe von O(n) Operationen. Das sollte man nicht vergessen. Richtig eingesetzt ist die Destrukturierung ein unverzichtbares neues Werkzeug.

// Object Destructuring
function f({a, b}) {
    return a+b;
}
let sum1 = f({a:6, b:4});

// Array Destructuring und Rest Argument
function g([a,b], ...rest) {
    return a+b + rest.reduce((x,y)=>x+y); // arrow
}
let sum2 = g([10,10],10,10);

// Objektproperties einzelnen Variablen zuweisen.
let {a,b,c} = { a: "Eddie", b: "ist", c: "toll" };
let string1 = a + " " + b + " " + c;

// Man kann die Properties beim Destrukturieren mit : auch umbenennen.
let {a:alias1, b:alias2, c:alias3} = { a: "Edward", b: "hat´s", c: "hingekriegt" };
let string2 = alias1 + " " + alias2 + " " + alias3;

// Arrays kann man bequem in Elemente und Rest zerlegen. 
let [d,e,...rest] = [8,9,10,11,12];
let sum3 = d + e + rest.reduce((x, y) => x+y);

// Eine Funktion, die auch in Wirklichkeit alle Argumente im Rest sammelt und danach in einen neuen Array gespreaded zurueckgibt
function h(...args) {
    return [...args];
}

// Hier sieht man, dass die Elision ,, auch die Defaultargumente triggert
let [x='Def',y='ault',...array] = [,,...h(1,2,3,4,5,6)];
let sum4 = array.reduce((x,y)=>x+y);
let sum5 = x+y;

Arrow Expression

Die neuen Lightweight Pfeilfunktionen koennen mit wenig Tipparbeit geschrieben werden. Ausserdem bieten sie ein lexikalisches this. Also das this des umschliessenden Scopes, kein eigenes oder gar globales Objekt. Mit arrows lassen sich tolle Funktionen schreiben, und der Funktionskörper kann eine Expression sein oder ein { block }.

// single identifier argument braucht keine Klammern
let square = x => x*x; 

// kann aber Klammern haben
let identity = (x) => x;

// default argumente, und auch rest.
let add = (x=10,y=20) => x+y;
let sum1 = add();

let collect = ...rest => rest;
let six = collect(1,2,3).reduce(add);

// ein unreelles Beispiel, das aber das lexikalische this zeigt. es zeigt auf demo.
let demo = ({
    method() {
	let help = (x,y) => this[x] = y;
	for (let [x,y] of ["E","d","w","a","r","d"].entries())  help(x,y);
	this.length = 6;
	return this;
    }
}).method();
for (let x of Array.prototype.values.call(demo)) console.html('#console6', x);

// mit Block und notwendigem return Statement
let fx = (a,b,c) => { a*=2, b*=3; c*=4; return a+b+c; }

ArrayBuffer

Wieder mit von der Partie sind die TypedArrays. Zu ES6 sind die StructTypes nicht fertig geworden, ein Jahr später, zu ES7 wird es StructTypes, oder TypedObjects geben. Eine Kombination aus mehreren TypDeskriptoren. Sie sind mit den vorhandenen Mitteln, ArrayBuffer, DataView und *Array herstellbar, darum wird es in Zukunft auch eine StructType Implementation im Syntax.js geben.

let ab = new ArrayBuffer(1000);
let dv = new DataView(ab);
dv.setInt32(0,32756);
dv.setFloat64(10,164.33);
dv.setFloat32(18,171233.21);
dv.setUint8(23, 125);
dv.setInt8(25, 200);

Die Tests sind übrigens nicht verfälscht. Wenn man hinsieht, sieht man, dass setFloat32 und setInt8 einen anderen Wert setzen, als sie erhalten. Ich habe es mit ES5 und node (also mit V8) überprüft, es kommt das exakt gleiche Ergebnis raus.

function

Die Funktion makePerson zeigt ein neues EcmaScript 6 Feature, die Object Literal Shorthand Syntax, wo { name: name, age: age } zu { name, age } wird.

function makePerson(name, age) { return { name, age } }
let eddie = makePerson("Edward", 108);

Hier gucke ich noch einmal, ob die Regel "return [lookahead != LineTerminator] Expression" eingehalten wird. Das ein ObjektLiteral auf der naechsten Zeile mit einem LabelledStatement in einem Block verwechselt wird, und so throwt, dass ich zur Demo einen Array nehmen muss, ist bei den anderen Engines auch so. Sieht gut aus.

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

Generator Functions

Generatoren sind aehnlich den Iteratoren. Sie schreiten immer ein yield weiter, solange, bis der Code komplett abgearbeitet ist. Wenn yield auftaucht, wird die Funktion unterbrochen. (*)

function *gen() { yield 1; }
let it = gen();
let record = it.next();

(*) Generatorfunktionen werden in der aktuellen Version von syntax.js noch nicht korrekt unterstützt. Demnächst aber bestimmt.

Rest und Spread

Der neue "..." Operator hat zwei Bedeutungen. Als Angabe von Funktionsparametern oder im Destructuring Array Pattern ist es ein RestParameter, er nimmt alle Parameter ab der Position auf, wo er angegeben wird, also den Rest, der noch kommt. Gibt man ihn allerdings vor einem Array oder einem Iterable (mit dem neuen @@iterator) an, und zwar bei einem Funktionsaufruf, oder in einem Array, halt dort, wo man 0,1,2,3,4,5 Zaehlen kann, dann breitet er den Array oder das Iterable entsprechend ab der Position aus und verteilt die einzelnen Elemente auf die aktuelle und die der aktuellen Position folgenden Positionen.

function take(a,b,c,d) {
    return [a,b,...[c,d]];
}
function take2(...rest) {	// Rest
    return [...rest];		// Spread
}
let array = ["A","B","C","D"];
let copy1 = take(...array);	// Der Spread Operator verteilt den array auf a,b,c,d.
let copy2 = take2(...array);   // Der Rest Parameter von take2 nimmt die gespreadeten Elemente wieder auf.
let lowercase = [for (x of copy1) x.toLowerCase()]; // mit der Comprehension erzeuge ich einen Array kleiner Buchstaben
let join = lowercase.join('+') + copy2.join("-"); // Das ist ein String, mit Array.prototype.join erzeugt, mit einem + zwischen den kleinen und - zwischen den grossen Buchstaben.

Java und JavaScript

Die alte Seite abgespeichert.