Introducció al Javascript orientat a Objectes

This article needs a technical review. How you can help.

This article needs an editorial review. How you can help.

JavaScript és orientat a objectes des del nucli, amb unes capacitats potents, flexibles OOP. Aquest article comença amb la introducció de la programació orientada a objectes, després es revisa el model d'objectes de Javascript, i finalment es mostren els conceptes de la programació orientada a objectes en JavaScript.

Revisió de JavaScript

Si no us sentiu segurs amb els conceptes de Javascript com ara variables, tipus, funcions, i àmbits podeu llegir sobre aquests temes en Una reintroducció a JavaScript. També podeu consultar la Guia de JavaScript.

Programació orientada a Objectes

La programació orientada a Objectes és un paradigma de programació que usa l'abstracció per crear models basants en el món real. Fa servir diverseses tècniques de paradigmes previament establerts, inclosa la modularitat, poliformisme, i l'encapsulament. Avui, moltes llengües de programació (com Java, JavaScript, C#, C++, Python, PHP, Ruby i Objective-C) suporten la programació orientada a Objectes (POO).

La programació orientada a Objectes es pot entendre com el disseny de software fent servir una col·lecció d'objectes que cooperen, al contrari de la visió tradicional en el qual un programa es vist com una col·lecció de funcions, o simplement com una llista d'instruccions per a un ordinador. En POO, cada objecte és capaç de rebre missatges, processar data, i enviar missatges a altres objectes. Cada objecte pot ser entès com una petita màquina independent amb un rol diferent o amb responsabilitat.

La programació orientada a Objectes intenta promoure una major flexibilitat, mantenibilitat en programació, i és àmpliament popular en enginyeria de software a gran esala. En virtut de la seva forta èmfasi en modularitat, el codi orientat a objecte intenta ser més simple de desenvolupar i més facil d'entendre després, es presta a una anàlisi més directa, codificació, i comprensió de situacions complexes i procediments en comptes d'altres mètodes de programació menys modulars.1

Terminologia

Namespace
Un contenidor que permet als desenvolupadors agrupar totes les funcionalitats sota un nom únic d'aplicació específica.
Class
Defineix les característiques de l'objecte. És la definició d'una plantilla de variables i mètodes d'un objecte.
Object
Una instància d'una classe.
Property
Una característca d'un objecte, com ara un color.
Method
Una capacitat d'un objecte, com ara caminar. És una subrutina o funció associada amb una classe.
Constructor
Un mètode que es crida en el moment d'instanciació d'un objecte. Normalment té el mateix nom que el de la classe que el conté.
Inheritance
Una classe pot heretar les característiques d'una altra classe.
Encapsulation
Una manera d'agrupar les dades i mètodes que es fan servir juntes.
Abstraction
La conjunció d'una herència complexa, mètodes, les propietats d'un objecte, han de ser capaces de simular una realitat a modelar.
Polymorphism
Poli significa "molts" i morfisme significa "formes". Classes diferents poden definir el mateix mètode o propietat.

Per una descripció més extensa sobre la programació orientada a objectes vegeu {interwiki("wikipedia", "Object-oriented programming")}} a la Viquipèdia.

Programació basada en prototipus

Programació basat en prototipus és un estil de programació orientada a objectes que no fa ús de les classes. En el seu lloc, la reutilització del comportament (conegut com a herència en llenguatges basats en classes) es porta a terme a través d'un procés de decoració (o d'ampliació) on els objectes que ja existeixen serveixen com a prototipus. Aquest model també és conegut com a model sense classes, orientat a prototip, o programació basadad en instàncies.

L'exemple original (i més canònic) d'un llenguatge basat en prototipus és el llenguatge de programació Self desenvolupat per David Ungar i Randall Smith. Tanmateix, l'estil de programació sense classes s'ha anat fent més i més popular, i ha sigut adoptat per llenguatges de programació com JavaScript, Cecil, NewtonScript, Io, MOO, REBOL, Kevo, Squeak (quan s'utilitza el marc Viewer per manipular components Morphic), i altres.1

Programació orientada a Objectes de JavaScript

Namespace

Un namespace és un contenidor el qual permet als desenvolupadors agrupar totes les funcionalitats sota un únic, nom d'aplicació específic. En JavaScript un namespace és només un altre objecte que conté mètodes, propietats, i objectes.

Nota: Es important remarcar que en JavaScript, no hi ha diferència de nivell d'idioma entre els objectes regulars i els namespaces. Això dista d'altres llenguatges orientats a objectes, i pot resultat confús als programadors nous en JavaScript.

La idea darrera la creació d'un namespace en JavaScript és simple: Un objecte global és creat i totes les variables, metòdes, i funcions es converteixen en propietats d'aquest objecte. L'ús de namespaces també minimitza la possibilitat de conflictes de noms en l'aplicació, ja que cada objecte d'aplicació son propietats d'un objecte global d'aplicació definit.

Creem un objecte global anomenat MYAPP:

// global namespace
var MYAPP = MYAPP || {};

En el codi de mostra superior, primer hem comprovat si MYAPP ja està definit (ja sigui en el mateix arxiu o en un altre arxiu). En cas de ser així, s'usa l'objecte global MYAPP existent, del contrari es crea un objecte buit anomenat MYAPP el qual encapsula el mètode, funcions, variables, i objectes.

També podem crear sub-namespaces:

// sub namespace
MYAPP.event = {};

Abaix es troba la sintaxi de codi per crear un namespace i afegir variables, funcions, i un mètode:

// Create container called MYAPP.commonMethod for common method and properties
MYAPP.commonMethod = {
  regExForName: "", // define regex for name validation
  regExForPhone: "", // define regex for phone no validation
  validateName: function(name){
    // Do something with name, you can access regExForName variable
    // using "this.regExForName"
  },
 
  validatePhoneNo: function(phoneNo){
    // do something with phone number
  }
}

// Object together with the method declarations
MYAPP.event = {
    addListener: function(el, type, fn) {
    //  code stuff
    },
   removeListener: function(el, type, fn) {
    // code stuff
   },
   getEvent: function(e) {
   // code stuff
   }
  
   // Can add another method and properties
}

// Syntax for Using addListener method:
MYAPP.event.addListener("yourel", "type", callback);

Objectes estàndards integrats

JavaScript té alguns objectes inclosos en el seu nucli, per exemple, trobem objectes come Math, Object, Array, i String. L'exemple d'abaix ens mostra com fer servir l'objecte Math per aconseguir números aleatoris usant el seu mètode random().

console.log(Math.random());
Nota: Aquest i tots els exemples següents suposen que la funció anomenada console.log() es defineix globalment. De fet, la funció console.log() no és part del llenguatge de JavaScript en si, però molts navegadors ho implementen per tal d'ajudar en la depuració.

Vegeu Referència de JavaScript: Objectes estàndards integrats per una llista d'objectes cor en JavaScript.

Cada objecte en JavaScript és una instància de l'objecte Object i per tant, n'hereda totes les seves propietats i mètodes.

Objectes personalitzats

La classe

JavaScript és un llenguatge basat en prototipus que no conté cap class statement, el qual si que es pot trobar en C++ o Java. Això és confós a vegades per a programadors acostumats a llenguatges amb el class statement. En el seu lloc, JavaScript fa servir funcions com a classes. Definir duna classe és tan fàcil com definir una funció. En l'exemple d'abaix definim una nova classe anomenada Person.

var Person = function () {};

L'objecte (instància de classe)

Per crear una nova instància d'un objecte obj fem servir la declaració new obj, assignant el resultat (el qual és de tipus obj) a una variable per accedir-hi més tard. Una forma alternativa de crear una nova instància és fent servir Object.create(). Aquesta crearà una instància This will create an uninititalized instance.

En l'exemple d'adalt definim una classe anomenada Person. En l'exemple següent creem dues instàncies (person1 i person2).

var person1 = new Person();
var person2 = new Person();
Nota: Siusplau vegeu Object.create() per un mètode instantacional nou, addiccional new, additional, instantiation mètode.

El constructor

El constructor és cridat en el moment de la instantiation (el moment en que la instància de l'objecte és creat). El constructor és ún mètode de la classe. En JavaScript la funció serveix com el constructor de l'objecte, therefore there is no need to explicitly define a constructor method. Every action declared in the class gets executed at the time of instantiation.

El constructor es fa servir per establir les propietats de l'objecte o per cridar mè call methods to prepare the object for use. Per afegir mètodes de classes i les seves definicions es necessita una sintaxi diferent que s'explicarà més tard en aquest article.

En l'exemple d'abaix, el constructor de la classe Person mostra un missatge quan és crea una instànca Person.

var Person = function () {
  console.log('instance created');
};

var person1 = new Person();
var person2 = new Person();

La propietat (atribut de l'objecte)

Les propietats són variables contingudes en la classe; cada instància de l'objecte té aquestes propietats. Les propietats són asssignades en el constructor (funció) de la classe de forma que es creen en cada instància.

Per treballar amb propietats de dins de la classe s'utilitza la paraula clau this, que fa referència a l'objecte actual. Accedir (llegir o escriure) a una propietat fora d'aquesta classe es fa mitjançant la sintaxi: InstanceName.Property; Aquesta és la mateixa sintaxi que es fa servir en C++, Java, i numeroses altres llengües. (Dins la classe, la sintaxi this.Property s'utilitza per obtindre o escriure el valor de les propietats.)

En l'exemple següent definim la propietat firstName property per la classe Person i ho definim com a instanciació.

var Person = function (firstName) {
  this.firstName = firstName;
  console.log('Person instantiated');
};

var person1 = new Person('Alice');
var person2 = new Person('Bob');

// Show the firstName properties of the objects
console.log('person1 is ' + person1.firstName); // logs "person1 is Alice"
console.log('person2 is ' + person2.firstName); // logs "person2 is Bob"

Els mètodes

Els mètodes segueixen la mateixa llògica que les propietats; la diferència es que són funcions i estan definides com a funcions. Cridar un mètode és similar a accedir a una propietat, però s'afegeix () al final the nom del mètode, possiblement amb arguments. Per definir un mètode, s'assigna una funció a una propietat amb nom de la propietat de la classe prototype; el nom que s'assigna a la funció és el mateix que el nom que té el mètode en l'objecte.

En l'exemple següent definim i usem el mètode sayHello() per la classe Person.

var Person = function (firstName) {
  this.firstName = firstName;
};

Person.prototype.sayHello = function() {
  console.log("Hello, I'm " + this.firstName);
};

var person1 = new Person("Alice");
var person2 = new Person("Bob");

// call the Person sayHello method.
person1.sayHello(); // logs "Hello, I'm Alice"
person2.sayHello(); // logs "Hello, I'm Bob"

En JavaScript els mètodes són funcions normals dels objectes que són lligats a un objecte com una propietat, El que vol dir que poden ser invocats "fora del context". Vegeu el codi d'exemple següent:

var Person = function (firstName) {
  this.firstName = firstName;
};

Person.prototype.sayHello = function() {
  console.log("Hello, I'm " + this.firstName);
};

var person1 = new Person("Alice");
var person2 = new Person("Bob");
var helloFunction = person1.sayHello;

// logs "Hello, I'm Alice"
person1.sayHello();

// logs "Hello, I'm Bob"
person2.sayHello();

// logs "Hello, I'm undefined" (or fails
// with a TypeError in strict mode)
helloFunction();                                    

// logs true
console.log(helloFunction === person1.sayHello);

// logs true
console.log(helloFunction === Person.prototype.sayHello);

// logs "Hello, I'm Alice"
helloFunction.call(person1);

Com l'exemple mostra, totes les referències a la funció sayHello — les que existeixen a person1, a Person.prototype, a la variable helloFunction, etc. — fan referència a la mateixa funció. El valor de this durant una crida a la funció depen en com es crida. En el cas comú en que es crida la funció des d'una propietat de l'objecte — person1.sayHello() — this rep el valor de l'objecte d'on prové la propietat (person1), és per això que person1.sayHello() fa servir el nom "Alice" i person2.sayHello() fa servir el nom "Bob". Però si ho cridem d'altres maneres, this rebrà un valor diferent: Cridar-la des d'una variable — helloFunction() — this rep el valor de l'objecte global (window, en navegadors). Al no tenir la propietat firstName aquest objecte (probablement) , acabem amb el resultat "Hello, I'm undefined". (Això és en el mode no estricte; en el mode estricte seria diferent [un error], però per evitar confussions no entrarem aquí en detall.) O podem assignar explícitament el valor de this mitjançant Function#call (o Function#apply), com es mostra al final de l'exemple.

Nota: Vegeu més sobre this a Function#call i Function#apply

L'herència

L'herència és una manera de crear una classe com una versió especialitzada d'una o més classes (JavaScript només suporta l'herència única). La classe especialitzada és communment anomenada el fill, i l'altra classe es comunment anomenada el pare. En JavaScript això s'aconsegueix mitjançant l'assignació d'una instància de la classe pare a la classe fill, i després s'especialitza. En navegadors moderns també es pot usar Object.create per implementar herències.

Nota: JavaScript does no detecta la classe fill prototype.constructor (vegeu Object.prototype), així que ho hem de declarar manualment.

En l'exemple d'abaix, definim la classe Student com una classe fill de Person. Després redefinim el mètode sayHello() i afegim el mètode sayGoodBye().

// Define the Person constructor
var Person = function(firstName) {
  this.firstName = firstName;
};

// Add a couple of methods to Person.prototype
Person.prototype.walk = function(){
  console.log("I am walking!");
};

Person.prototype.sayHello = function(){
  console.log("Hello, I'm " + this.firstName);
};

// Define the Student constructor
function Student(firstName, subject) {
  // Call the parent constructor, making sure (using Function#call)
  // that "this" is set correctly during the call
  Person.call(this, firstName);

  // Initialize our Student-specific properties
  this.subject = subject;
};

// Create a Student.prototype object that inherits from Person.prototype.
// Note: A common error here is to use "new Person()" to create the
// Student.prototype. That's incorrect for several reasons, not least 
// that we don't have anything to give Person for the "firstName" 
// argument. The correct place to call Person is above, where we call 
// it from Student.
Student.prototype = Object.create(Person.prototype); // See note below

// Set the "constructor" property to refer to Student
Student.prototype.constructor = Student;

// Replace the "sayHello" method
Student.prototype.sayHello = function(){
  console.log("Hello, I'm " + this.firstName + ". I'm studying "
              + this.subject + ".");
};

// Add a "sayGoodBye" method
Student.prototype.sayGoodBye = function(){
  console.log("Goodbye!");
};

// Example usage:
var student1 = new Student("Janet", "Applied Physics");
student1.sayHello();   // "Hello, I'm Janet. I'm studying Applied Physics."
student1.walk();       // "I am walking!"
student1.sayGoodBye(); // "Goodbye!"

// Check that instanceof works correctly
console.log(student1 instanceof Person);  // true 
console.log(student1 instanceof Student); // true

Pel que fa a la línia Student.prototype = Object.create(Person.prototype): En els motors de Javascript més antics sense Object.create, es pot utilitzar tant un "polyfill" (també conegut com a  "falca", vegeu l'article enllaçat), o fer servir una funció que aconegueixi assolir el mateix resultat, tal com:

function createObject(proto) {
    function ctor() { }
    ctor.prototype = proto;
    return new ctor();
}

// Usage:
Student.prototype = createObject(Person.prototype);
Nota: Vegeu Object.create per més informació sobre el que fa, i una falca per a motors més vells.

L'encapsulació

En l'exemple anterior, Student no necessita saber com el mètode Person class's walk() és implementat, però tot i així pot fer-lo servir com a mètode; la classe Studentno necessita definir explícitament aquest mètode a no ser que ho volguem canviar. D'això se'n diu encapsulació, per la qual cada classe empaqueta data i mètodes en una sola unitat.

L'ocultació d'informació és una característica comuna en altres llenguatges sovint com a mètodes/propietats privats i protegits. Tot i que es podria simular alguna cosa com aquesta en JavaScript, no és un requeriment per fer programació orientada a objectes.2

L'abstracció

L'abstracció és un mecanisme que permet modelar la part que ens ocupa del problema en el qual estem treballant. Això es pot aconseguir per mitjar de l'herència (especialització), o composició. JavaScript aconsegueix l'especialització per mitjà de l'herència, i la composició per mitjà de deixar a les instàncies de classes ser valors d'atributs d'altres objectes.

La classe Function de JavaScript hereta de la classe Object (això demostra la especialització del model) i la propietat Function.prototype property és una instància d'Object (Això demostra composició).

var foo = function () {};

// logs "foo is a Function: true"
console.log('foo is a Function: ' + (foo instanceof Function));

// logs "foo.prototype is an Object: true"
console.log('foo.prototype is an Object: ' + (foo.prototype instanceof Object));

El polimorfisme

Tal i com tots els mètodes i propietats són definides dins la propietat Prototype, classes diferents poden definir mètodes amb el mateix nom; els mètodes estan en l'àmbit de la classe en la qual estan definits. Això només és cert quan les dues classes no tenen una relació pare-fill (quan un no hereta de l'altre en una cadena d'herència).

Notes

Les tècniques presentades en aquesta article per implementar programació orientada a objectes no són les úniques que es poden fer servir en JavaScript, que és molt flexible en termes de com es pot realitzar la programació orientada a objectes.

De la mateixa manera, les tècniques que s'han mostrat aquí no utilitzen cap hack del llenguatge, ni imiten cap implementació de teories d'objectes d'altres llenguatges.

Hi ha altres tècniques que proporcionen programació orientada a objectes més avançada en JavaScript, però aquests estan fora de l'abast d'aquest article introductori.

Referències

  1. Viquipèdia. "Programació orientada a Objectes"
  2. Viquipèdia. "Encapsulació (programació orientada a Objectes)"

Document Tags and Contributors

 Contributors to this page: llue
 Last updated by: llue,