Microsoft's Internet Explorer browser has no built-in vector graphics machinery required for "loss-free" gradient background themes.

Please upgrade to a better browser such as Firefox, Opera, Safari or others with built-in vector graphics machinery and much more. (Learn more or post questions or comments at the Slide Show (S9) project site. Thanks!)

Javascript II

as in REAL programming language

Master "Tecnologie OpenSource"

Obiettivi

  • Conoscere i principi alla base della programmazione OOP prototype-based
  • Saper utilizzare la programmazione OOP class-less con Javascript
  • Saper dichiarare metodi di un oggetto Javascript
  • Comprendere e saper definire le funzioni costruttore in Javascript
  • Conoscere e saper usare gli operatori new e delete
  • Comprendere il funzionamento della prototype chain di Javascript
  • Conoscere e saper utilizzare la delegation attraverso la prototype chain
  • Comprendere e saper utilizzare le caratteristiche di reflection di Javascript
  • Comprendere i meccanismi di incapsulamento privato realizzati mediante closure
  • Comprendere i principi e le potenzialità della metaprogrammazione Javascript
  • Saper aggiungere metodi ai tipi Javascript
  • Comprendere il funzionamento del Cascading

Paradigma OOP Prototype-based

Prototype-based programming is a style of object-oriented programming in which classes are not present, and behavior reuse (known as inheritance in class-based languages) is performed via a process of cloning existing objects that serve as prototypes. This model can also be known as class-less, prototype-oriented or instance-based programming.

The original (and most canonical) example of a prototype-based language is the programming language Self developed by David Ungar and Randall Smith. However, the classless programming style has recently grown increasingly popular, and has been adopted for the programming languages JavaScript, Cecil, NewtonScript, Io, MOO, REBOL, Lisaac and several others.

From Wikipedia

Paradigma OOP Prototype-based: Delegation

In prototype-based languages that use delegation, the language runtime is capable of dispatching the correct method or finding the right piece of data simply by following a series of delegation pointers (from object to its prototype) until a match is found. All that is required to establish this behavior-sharing between objects is the delegation pointer. Unlike the relationship between class and instance in class-based object-oriented languages, the relationship between the prototype and its offshoots does not require that the child object have a memory or structural similarity to the prototype beyond this link. As such, the child object can continue to be modified and amended over time without rearranging the structure of its associated prototype as in class-based systems.

From Wikipedia

Paradigma OOP Prototype-based: Concatenation

Under pure prototyping, which is also referred to as concatenative prototypes, and is exemplified in the Kevo language, there are no visible pointers or links to the original prototype from which an object is cloned. The prototype object is copied exactly, but given a different name (or reference). Behavior and attributes are simply duplicated as-is. Advantages to this approach include the fact that object authors can alter the copy without worrying about side-effects across other children of the parent. A further advantage is that the computational cost of method lookup during dispatch is drastically reduced when compared to delegation, where an exhaustive search must be made of the entire delegation chain before failure to find a method or slot can be admitted.

From Wikipedia

OOP…

Quali sono gli obiettivi che ci prefiggiamo di ottenere mediante l’OOP?

Class-less OOP

In Javascript è possibile combinare lo stato e il comportamento in un oggetto senza passare necessariamente dal concetto di classe:

var car = {
  color: "red",
  model: "Fiat Uno",
  state: "off",
  max_velocity: 140,
  
  turnOn: function() {
    var msg = { "on": " is already on.", "off": " is turned on." };

    if(this.state === "on") { print("Your "+this.model+msg["on"]+"!!!"); }
    else {
      this.state = "on"
      print("Now Your "+this.model+msg["off"]);
    }
  }
}

what is “this”? (1/3)

Analogamente ai linguaggi OOP noti (C++ e Java ad esempio) nei metodi di un oggetto si può far riferimento all’oggetto stesso mediante la parola chiave this . A differenza di C++ e Java pero’ un metodo può essere decontestualizzato ( unbind ) e agganciato ad un altro contesto ( bind ):

var another_car = {
  color: "green",
  model: "Fiat 500",
  state: "off"
};

var turnOn = car.turnOn;

turnOn.call(another_car);
turnOn.apply(another_car);

what is “this”? (2/3)

Per agganciare un metodo ad un oggetto in modo “definitivo” è sufficiente assegnarlo ad una proprietà dell’oggetto e richiamarlo mediante la sintassi “puntata”:

another_car.myTurnOnMethod = car.turnOn;

another_car.myTurnOnMethod();

what is “this”? (3/3)

ATTENZIONE pero’:

richiamando il metodo di un oggetto di cui si è fatto unbind senza l’uso dei metodi call o apply implica l’errato binding di this all’oggetto corrente… che disgraziatamente potrebbe essere l’oggetto globale :-(

turnOn.call(another_car);  // <--------- esegue turnOn con **this = another_car**
turnOn();                  // <--------- esegue turnOn con **this = global** 
                          //            (di solito l'oggetto window)

le funzioni Costruttore e l’operatore new (1/3)

Cosa succede se vogliamo creare diversi oggetti simili tra loro per proprietà e comportamento?

Nella programmazione OOP Class-based l’approccio naturale è quello di creare una classe da cui creare gli oggetti…

le funzioni Costruttore e l’operatore new (2/3)

In Javascript si crea una funzione (detta funzione costruttore) e gli si applica l’operatore new il quale alloca un nuovo oggetto che la funzione costruttore si occuperà di inizializzare:

function Car(model,color) {
  this.model = model || "Fiat Uno";
  this.color = color || "red";
  this.state = "off";
}

a_car = new Car();
another_car = new Car("Fiat 500","green");

le funzioni Costruttore e l’operatore new (3/3)

E’ molto IMPORTANTE non dimenticare l’operatore new davanti ad un costruttore!!!

In caso contrario this risulterebbe ancora una volta erroneamente collegato all’oggetto globale, la funzione costruttore spesso ritornerà undefined e ad essere inizializzato non sarebbe il nuovo oggetto creato da new ma l’oggetto globale.

Per questo motivo per convenzione le funzioni costruttore hanno nomi in CamelCase

your_car = Car();

your_car              // undefined

model                 // "Fiat Uno"
color                 // "red"
state                 // "off"

l’operatore delete (1/2)

Anche se, come tutti i linguaggi dinamici ed interpretati, in Javascript i meccanismi di garbage collection si occupano di liberare per noi la memoria dagli oggetti non più utilizzati ( referenziati ), mediante l’operatore delete possiamo dargli una mano, oltre a poter eliminare singole proprietà degli oggetti:

ref2a_car = a_car

delete a_car.color;          // rimuove la proprietà color dall'oggetto a_car

a_car.color;                 // La proprietà risulta rimossa da entrambi gli
ref2a_car.color;             // identificativi

delete a_car;                // elimina il riferimento all'oggetto a_car

ref2a_car;                   // <--------- l'oggetto esiste ancora

l’operatore delete (2/2)

L’operatore delete ritorna true quando è possibile eliminare un riferimento ad un oggetto, quando ciò non è possibile? Quando un identificativo è stato creato con var:

other_car = {};
var a_car = {};

delete other_car;               // true
delete a_car;                   // false

Prototipal Inheritance Style: Delegation (1/5)

Car.prototype.turnOn = function() {
  var msg = { "on": " is already on.", "off": " is turned on." };
  if(this.state === "on") { 
    print("Your "+this.model+msg["on"]+"!!!"); 
  }
  else {
    this.state = "on"
    print("Now Your "+this.model+msg["off"]);
  }
};


Car.prototype.describe = function() {
  print("This is a "+this.color+" "+this.model);
}

a_car = new Car("Fiat 500","green");

a_car.describe();

Prototipal Inheritance Style: Delegation (2/5)

Prototype chain (1 livello)

la proprietà proto è una proprietà nascosta di ogni object, in firefox e webkit è accessibile in lettura/scrittura ma non è enumerabile.

Prototipal Inheritance Style: Delegation (3/5)

function Y10() {}

Y10.prototype = new Car();

mycar = new Y10();

mycar instanceof Y10;   // true
mycar instanceof Car;   // true

mycar.turnOn         // function <--- ereditata da Car

Prototipal Inheritance Style: Delegation (4/5)

Prototype chain (2 livelli)

La prototype chain viene consultata bottom-up solo in fase di lookup, mentre le assegnazioni agiscono solo sull’object e non sui suoi prototype.

Prototipal Inheritance Style: Delegation (5/5)

function Y10(color) {
  var super_turnOn = this.turnOn;
  this.color = color;

  this.turnOn = function() {
    if(Math.random()>0.6) super_turnOn()
    else print("cCCCCcccc cccCCCCcccc");
  }
}

Y10.prototype = new Car("Y10");
Y10.model;
Y10.color;
Y10.turnOn();                    <--------------- richiama il nuovo metodo

Reflection (1/5)

Abbiamo già visto come Javascript sia dotato di capacità di reflection attraverso gli operatori typeof e instanceof .

Essendo l’Object e l’Hash Table sinomini in Javascript, ed essendo l’oggetto il costrutto base ed onnipresente di Javascript, attraverso la struttura di controllo for…in è possibile enumerare (e quindi navigare) il contenuto della stragrande maggioranza dell’ambiente.

js> for (var i in a_car) { print(i); }
model
color
state
turnOn
describe
js> 

Reflection (2/5)

L’operatore in può essere utilizzato al di fuori della struttura di controllo for…in allo scopo di effettuare un test booleano di presenza di una proprietà:

"model" in a_car          // true
"color" in a_car          // true
"state" in a_car          // true
"turnOn" in a_car         // true
"nonexistent" in a_car    // false

Reflection (3/5)

Il metodo Object.isPrototypeOf (ereditato quindi da tutti gli oggetti) effettua un test booleano per verificare se due oggetti sono collegati tra di loro da una relazione di prototipazione:

function Car();
function Y10();

Y10.prototype = new Car();

a_car = new Y10();

Y10.prototype.isPrototypeOf(a_car);           // true
Car.prototype.isPrototypeOf(a_car);           // true
Object.prototype.isPrototypeOf(a_car);        // true
Date.prototype.isPrototypeOf(a_car);          // false

Reflection (4/5)

I meccanismi di reflection visti sino ad ora (typeof, for…in, in, isPrototypeOf) sono tutti dei metodi di lookup ed in quanto tali seguono la prototype chain alla ricerca delle informazioni richieste sulle proprietà.

Per assicurarsi che una proprietà appartenga realmente ad un determinato oggetto e non alla sua prototype chain, è sufficiente far uso del metodo Object.hasOwnProperty

function Parent() {};

Parent.prototype.attribute_from_parent = "test"

obj = new Parent;
obj.attribute_from_obj = "test obj";

var msg;

for (var i in obj) {
  msg = obj.hasOwnProperty(i) ? "possiede" : "ha ereditato";
  print("L'oggetto "+msg+" la proprieta' "+i+".");
}

Reflection (5/5)

ATTENZIONE

Le proprietà degli oggetti built-in ed alcune proprietà nascoste (ad esempio Function.prototype.constructor) non sono (intenzionalmente) enumerabili.

Object Private Scope is Possible (1/3)

Come si è verificato precedentemente le proprietà di un oggetto in qualunque momento possono essere rimosse, aggiunte e modificate, possiamo ricavarci uno spazio privato/privilegiato a cui si possa accedere solo in modo controllato?

CERTO ci sono le closure :-D

Object Private Scope is Possible (2/3)

function Car(model,color) {
  var model = typeof model === "string" ? model : "Fiat Uno";
  var color = typeof color === "string" ? color : "red";
  var state = "off";

  this.get_model = function() { return model; };
  this.get_color = function() { return color; };
  this.set_color = function(value) {
    color = typeof value === "string" ? value : color;
  };
  this.turnOn = function() {
    var msg = { "on": " is already on.", "off": " is turned on." };
    if(state === "on") { print("Your "+model+msg["on"]+"!!!"); }
    else {
      state = "on"
      print("Now Your "+model+msg["off"]);
    }
  };
}

Object Private Scope is Possible (3/3)

ATTENZIONE: questo approccio interagisce con difficoltà con il meccanismo della delegation attraverso la prototype chain. Lo stato privato della closure sarà condiviso dalle due istanze in quando contenuto nel loro prototipo comune.

function Y10() {}
Y10.prototype = new Car("Y10");

a_car = new Y10();
other_car = new Y10();

a_car.get_color();
other_car.get_color();

a_car.set_color("black");
other_car.get_color();

Inoltre lo scope privato limita le nostre possibilità in termini di reflection.

Metaprogramming

Le tecniche di metaprogrammazione di Javascript sono basate soprattutto sulle closure e sulla possibilità di modificare a runtime gli oggetti e le funzioni costruttore.

La funzione eval costituisce un’altra allettante alternativa, ma la mancanza di reali meccanismi di sandboxing e il degrado di performance che la caratterizzano la rendono di fatto non utilizzabile realisticamente.

Metaprogramming – Object.beget (1/2)

Crockford dimostra come è possibile prototipare degli oggetti da altri oggetti senza passare esplicitamente dalla definizione di funzioni costruttore:

Object.beget = function (o) {          
  var F = function () {}; 
  F.prototype = o; 
  return new F(); 
};

Metaprogramming – Object.beget (2/2)

car = {
  model: "Fiat Uno",
  color: "red",
  state: "off",
  turnOn: function() {
    var msg = { "on": " is already on.", "off": " is turned on." };

    if(this.state == "on") { print("Your "+this.model+msg["on"]+"!!!"); }
    else {
      this.state = "on"
      print("Now Your "+this.model+msg["off"]);
    }
  }
}

ferrari = Object.beget(car);
ferrari.model = "Ferrari"
ferrari.turnOn();

Metaprogramming – Function.prototype.method

Altro simpatico javascript design pattern descritto da Crockford è costituito dall’aggiunta di un metodo method al prototipo dell’oggetto Function al fine di poter aggiungere metodi al prototipo relativo ad una funzione costruttore in modo meno alieno

Function.prototype.method = function (name, func) {     
  this.prototype[name] = func; 
  return this; 
};

Number.method('toInteger', function () {     
  return Math[this < 0 ? 'ceiling' : 'floor'](this); 
}); 

(10 / 3).toInteger()

Metaprogramming – Function.prototype.inherits

E per concludere, l’ultimo design pattern di Crockford consiste nell’aggiungere un metodo che renda più leggibile l’associazione tra due funzioni costruttore (che rappresentano in qualche modo dei tipi ) in una prototype chain:

Function.method('inherits', function (Parent) {     
  this.prototype = new Parent(); 
  return this; 
});

var Car = function() {}

Ferrari = function() {}.inherits(Car);

Metaprogramming – Cascading

E’ da notare come nei precedenti pattern di Crockford (method e inherits) i metodi ritornino this dopo aver modificato l’oggetto corrente ( this ). Questa semplice accortezza consente un altro pattern molto comune in molti framework javascript (e ruby) chiamato cascading

var Car = function(model, color) {
  this.model = model;
  this.color = color;
}.
method('turnOn', function() {
  print("this is the turning on method");
});

var Ferrari = function(color) {
  this.model = "Ferrari";
  this.color = color || "red";
}.
inherits(Car).
method('describe', function() {
  print("this is the describe method");
});

Metaprogramming – Fix new in Javascript (1/2)

Function.prototype.new = function new() {
  for each (var i in [Date, String, Number, Boolean, RegExp]) {
    if(this === i) return new this(arguments[0]);
  }
  var obj = new this;
  var result = this.apply(obj,arguments);

  return obj;
}

Number.new(15);
RegExp.new(/^java.*$/);

a_car = Car.new("maggiolino");

a_car instanceof Car;
Number.new(10) instanceof Number;

Date.new(2008,10,11) != new Date(2008,10,11) // <-------- dovuto all'implementazione 
                                             //           interna di Date

Metaprogramming – Fix new in Javascript (2/2)

function User(first, last){ 
  if ( !(this instanceof arguments.callee) ) 
    return new User(first, last); 
   
  this.name = first + " " + last; 
}

From http://ejohn.org/apps/learn/#36

Bibliografia

Javascript The Good Parts (O’Reilly)

Link-ografia

  • Copyright (C) 2008 - Alca Societa' Cooperativa

    http://alca.le.it - info@alca.le.it

    released under CreativeCommons 2.5 by-nc-sa

    NOTA: le immagini dei software e dei device contenuti
    nella presentazione sono proprieta' dei relativi detentori
    del copyright e sono state riprodotte a scopo esclusivamente didattico.