JavaScript pour les Jedis, épisode I : au cœur des fonctions

Wassim Chegham

Wassim Chegham est un fervent défenseur des technologies Web front et suit de près les versions ECMAScript. Aujourd'hui, il nous propose un article sur les fonctions JavaScript et leurs aspects parfois complexes. Cet article parle de la boucle d'événements, du callback, des fonctions anonymes, etc.

Commentez

- Titre : JavaScript pour les Jedis, épisode I : au cœur des fonctions

- Auteur : Wassim Chegham

- Parution : 29 décembre 2014

- Mise à jour : 29 décembre 2014

- Public visé :

- Licence : Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright ® 2014 Wassim Chegham. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Sommaire

- I. Introduction

- II. JavaScript est un langage fonctionnel

- III. La boucle d'événements

- IV. Le principe de callback

- V. Les fonctions anonymes

- VI. Déclaration de fonctions

- VII. Portée et contexte(s) d'une fonction

- VIII. Invocation des fonctions

- VIII-A. Arguments et paramètres

- VIII-B. L'argument « arguments »

- VIII-C. L'argument « this »

- VIII-D. Les types d'invocations

- VIII-D-1. Invocation en tant que fonction

- VIII-D-2. Invocation en tant que méthode

- VIII-D-3. Invocation en tant que constructeur

- VIII-D-4. Invocation via apply() ou call()

- VIII-D-5. Quelle méthode utiliser ?

- VIII-E. Un mot sur bind()

- IX.  Résumé

- X. Remerciements

- XI. Références et liens

I - Introduction

Comme vous le savez tous, 2015 est l'année de JavaScript. C'est pour cette raison que j'ai décidé de me consacrer à la rédaction d'une série d'articles concernant les fondamentaux de JavaScript, un langage très populaire, mais en même temps encore méconnu. Ces articles auront pour but de vous expliquer en détail le fonctionnement de ce langage afin de faire de vous des JavaScript Jedis.

En effet, grâce à cette série d'articles, nous allons apprendre et comprendre JavaScript afin de tirer le meilleur de ce langage. À vrai dire, j'en ai un peu marre de voir beaucoup de développeurs traiter ce langage de tous les noms, car ils n'arrivent pas à faire ce qu'ils ont l'habitude de produire avec leurs langages favoris. Mais en réalité, cela vient du fait que ces mêmes personnes ne prennent même pas la peine d'apprendre le langage, probablement à cause de sa syntaxe très ressemblante à Java. Par conséquent, ces développeurs finissent donc par développer une sorte de haine contre JavaScript.

« JavaScript, tu l'apprends ou tu le quittes. »

Vous vous demandez sûrement pourquoi nous commençons cette série par les fonctions, et non pas par les objets. Eh bien ! Étant donné la nature fonctionnelle de JavaScript, pour moi, maîtriser cet aspect du langage fera de vous des Jedis. En effet, la plupart des développeurs via d'autres langages orientés objet essayent plus de reproduire des paradigmes propres à ces langages et font l'impasse sur les fonctions et les fermetures (closures). JavaScript est composé de ces trois concepts : les objets, les fonctions et les closures. Maîtriser JavaScript passe par l'apprentissage de cet aspect fonctionnel du langage ; et croyez-moi, le niveau de sophistication du code que vous écrirez dépend de cet apprentissage.

II - JavaScript est un langage fonctionnel

L'une des raisons qui font que les fonctions et la nature fonctionnelle de JavaScript sont des concepts très importants vient du fait que « la fonction » est le premier module d'exécution en JavaScript. C'est-à-dire que lorsque votre code est exécuté, il l'est au sein d'une fonction ; à l'exception des scripts qui sont interprétés lorsque le code HTML est en train d'être évalué par le navigateur. Je fais évidemment allusion à l'emblématique « document.write() » qui permettait de créer du DOM au runtime, obligeant le navigateur à bloquer l'interprétation du code HTML.

Revenons à nos moutons, une chose très importante à connaître sur les fonctions en JavaScript, ce sont des objets de premier ordre (first-class objects). Cela veut dire que les fonctions sont traitées comme des objets, elles peuvent donc être :

- créées via des littérales ;

- assignées à des variables ou propriétés d'un objet ;

- passées en paramètre ;

- retournées comme résultat.

Ces fonctions peuvent aussi posséder des objets ou méthodes.

En plus d'être traitées comme des objets, les fonctions ont également une capacité spéciale : elles peuvent être invoquées (on en reparlera plus en détail dans la suite de cet article). Cette invocation est souvent effectuée de manière asynchrone. Voilà pourquoi…

III - La boucle d'événements

Si vous avez déjà codé une interface graphique auparavant, vous avez sûrement procédé comme ceci :

- mettre en place l'interface graphique ;

- se mettre dans une boucle d'événements en attendant qu'un événement se produise ;

- invoquer les actions à exécuter pour chaque événement.

La programmation en JavaScript — dans un navigateur — suit quasiment ces étapes, sauf que la gestion des événements est totalement prise en charge par le navigateur. Nous avons juste besoin de spécifier les actions (listeners) pour les différents événements qui peuvent être déclenchés — toujours au sein du navigateur. Ces événements sont placés dans une file au fur et à mesure qu'ils se produisent, et le navigateur traite ces événements en invoquant les actions associées. À noter que ce principe reste identique pour d'autres environnements JavaScript, par exemple NodeJS. Puisque ces événements peuvent se produire à n'importe quel moment et dans n'importe quel ordre, la gestion de ces événements, et donc l'invocation de leurs actions, se fait de manière asynchrone. Pourquoi me diriez-vous ?

Une chose à savoir, la boucle d'événements du navigateur est « monothreadée », ce qui veut dire que chaque événement se trouvant dans la file d'attente est traité dans l'ordre d'arrivée (FIFO). Les événements sont donc traités un par un, à tour de rôle. Voici un schéma très simplifié de ce processus :

Un exemple typique du fonctionnement de cette boucle : lorsque vous bougez le curseur de votre souris dans votre page web, le navigateur détecte ces déplacements de souris et place cette série d'événements (mousemove) dans la FIFO. Ensuite, la boucle d'événements s'occupera du traitement de ces événements en exécutant les actions associées.

Ce principe d'actions — associer une fonction qui sera exécutée plus tard, lorsque l'événement se produira — illustre un mécanisme que l'on appelle les fonctions de callback.

IV - Le principe de callback

Les fonctions de callback sont une partie très essentielle dans la bonne compréhension de JavaScript. Explorons ce point…

Dans la partie précédente, nous avons dit que l'on pouvait associer des actions aux différents événements se produisant au sein du navigateur. Voici un exemple d'une action :

function executerAuChargement(){ /* ... */ };
window.onload = executerAuChargement;

Ici, nous avons attaché une action (listener) qui sera exécutée lorsque la page est complètement chargée par le navigateur. Parce que les fonctions sont des « first-class objects », nous pouvons affecter cette fonction à la propriété onload de l'objet window (représentant le navigateur).

Aussi, nous avons dit que les fonctions peuvent être passées en paramètre. Ce qui veut dire que l'on peut écrire ceci :

function dire(callback) {
alert(callback());
}

function bonjour() {
return "Bonjour, JavaScript!";
}

function executerAuChargement() {
return dire(bonjour)
}

window.onload = executerAuChargement;

Voilà un exemple intéressant ! Nous pouvons aussi appeler les fonctions de callback dans nos propres fonctions, pas besoin qu'elles soient associées à des événements. Dans cet exemple, nous avons attaché une fonction de callback à l'événement onload. Lorsque la boucle d'événement exécutera ce listener, à son tour il exécutera la fonction « dire » à laquelle nous avons passé la référence vers la fonction « bonjour ». Une fois exécutée, la fonction « dire » invoquera la fonction « bonjour », ce qui provoquera l'affichage du message « Bonjour, JavaScript ! ».

Mieux encore ? La nature fonctionnelle de JavaScript nous permet de créer des fonctions en tant qu'entité à part entière, comme n'importe quel autre type, et les passer directement en paramètres. Voyons un exemple :

function dire(callback) {
alert(callback());
}

function executerAuChargement() {
return dire(function(){ return "Bonjour, JavaScript!"; });
}

window.onload = executerAuChargement;

Nous avons modifié l'exemple précédent en supprimant la déclaration de la fonction « bonjour ». Nous l'avons remplacée par la déclaration d'une fonction sans nom, directement dans les paramètres de la fonction « dire ». En JavaScript, nous appelons ce genre de fonctions, les fonctions anonymes.

V - Les fonctions anonymes

Étant donné la nature fonctionnelle du langage, il est possible de créer des fonctions n'importe où dans le code — là où une expression est attendue. Cela a pour avantage de rendre le code plus clair, plus compact et en plus, cela permet d'éviter de polluer l'espace de noms global avec des noms de fonctions inutiles.

En effet, une fonction anonyme est une fonction — comme les autres — déclarée en «  inline» et qui n'a pas de nom pour la référencer. Ces fonctions anonymes sont généralement déclarées au moment où elles sont référencées.

« Pourquoi donner un nom à un chat qui ne réagit pas lorsqu'on l'appelle ? »

Généralement, en JavaScript, lorsque nous estimons qu'une fonction va être référencée dans plusieurs endroits du code, nous lui donnons un nom. Sinon, si elle est destinée à servir juste à un seul endroit, alors pas besoin qu'elle ait un nom. Ce qui nous amène aux déclarations et invocations des fonctions.

VI - Déclaration de fonctions

En JavaScript, les fonctions sont déclarées à l'aide du mot-clé « function » qui crée une valeur de type fonction. Rappelez-vous que les fonctions sont des objets de premier ordre, elles peuvent être manipulées dans le langage comme n'importe quel autre type. De ce fait, il nous est possible d'écrire ceci (en reprenant l'exemple précédent) :

var dire = function(callback) {
alert(callback()) ;
};

La valeur de la variable « dire » est une fonction ! Nous avons donc déclaré et créé une fonction anonyme référencée par la variable « dire ».

Toutes les fonctions déclarées ont une propriété « name » qui contient le nom de la fonction (de type String). Pour les fonctions anonymes, cette propriété est présente, mais elle est vide.

Lorsqu'une fonction a été déclarée avec un nom, ce dernier reste valide dans tout le contexte dans lequel cette fonction a été déclarée. Aussi, si cette fonction a été déclarée dans le contexte global, une nouvelle propriété portant le nom de la fonction est ajoutée dans le « window » ; cela est également vrai pour les variables ; ce qui nous amène donc à la suite de cet épisode concernant le contexte et la portée des fonctions.

VII - Portée et contexte(s) d'une fonction

Lorsque nous déclarons une fonction, nous devons avoir à l'esprit, non seulement le contexte dans lequel cette fonction existe, mais également quelles sont les portées que cette fonction crée. Nous devons également faire très attention aux déclarations faites au sein de ces portées.

La gestion des portées en JavaScript est très différente de la plupart des autres langages dont la syntaxe a été influencée par le langage C. Plus précisément, ceux utilisant les accolades pour délimiter la portée des variables dans les blocs. Dans ces langages, chaque bloc crée son propre contexte. Ce n'est pas le cas de JavaScript.

« En JavaScript, les portées sont créées par les fonctions, et non pas par les blocs. »

La portée d'une variable créée au sein d'un bloc continue d'exister en dehors de ce dernier. Par exemple :

if (true) {
var foo = 'bar';
}
console.log(foo); //=> 'bar'

Ce simple exemple prouve bien que la portée de la variable « foo » est globale, car elle a été déclarée dans le contexte global qui est « window ». Elle n'est pas liée au bloc « if ». Regardons un exemple un peu plus intéressant :

function fonction1() {
var a = 1;

function fonction2() {
var b = 2;
}

if (a === 1) {
var c = 3;
}
}

fonction1();

Dans cet exemple, nous avons déclaré deux fonctions, « fonction1 » et « fonction2 », et nous avons défini trois variables, a, b et c. Pour compléter cet exemple, voici un schéma plus parlant qui explique les différents contextes créés :

Ce schéma résume les règles suivantes :

- la portée d'une variable existe depuis sa déclaration, et ce jusqu'à la fin de la fonction dans laquelle elle a été déclarée, peu importe l'imbrication des blocs (variables declaration hoisting)  ;

- la portée d'une fonction (ne pas confondre avec une expression de fonction) est globale à toute la fonction dans laquelle elle a été déclarée (functions declaration hoisting) .

Cette deuxième règle nous montre que, contrairement aux variables, les fonctions peuvent être référencées avant leur déclaration ! Oui, exactement, vous pouvez invoquer « function1 » avant de la déclarer. Par contre, ce n'est pas une bonne pratique. Ce n'est pas parce que c'est autorisé par le langage qu'il faut le faire.

Maintenant que nous avons vu comment les fonctions sont déclarées, essayons de voir comment elles peuvent être invoquées. Cela risque d'être très amusant…

VIII - Invocation des fonctions

Nous avons tous invoqué des fonctions en JavaScript. Mais avez-vous déjà essayé de comprendre ce qui se passe lorsque vous invoquez une fonction ? Il existe quatre façons d'invoquer une fonction en JavaScript. Chaque type d'invocation a un effet direct sur le contexte d'exécution de cette fonction. Plus précisément, le type d'invocation agit sur le paramètre « this » passé à la fonction lors de son invocation. Écrire du code digne d'un Jedi repose sur la compréhension et la bonne maîtrise de ces mécanismes d'invocations.

Voici les différents types d'invocations en JavaScript :

1. En tant que fonction (c'est le cas le plus répandu) ;

2. En tant que méthode, ce qui permet de lier l'invocation à un objet (POO) ;

3. En tant que constructeur, ce qui permet de créer un objet (instance) ;

4. Via « apply » et « call » (voir explication plus loin).

Avant d'expliquer chaque type d'invocation, prenons quelques minutes et essayons de comprendre ce qui se passe lors d'une invocation de fonction (cela est vrai pour les cas 1, 2, et 3).

VIII-A - Arguments et paramètres

Lorsqu'une liste d'arguments est fournie lors d'une invocation de fonction, ces arguments sont affectés aux paramètres (spécifiés lors de la déclaration) de la fonction, dans le même ordre. Jusque-là rien de bien sorcier.

Maintenant, si le nombre d'arguments est différent de celui des paramètres, aucune erreur n'est déclenchée : JavaScript sait comment traiter ce cas :

- s'il y a plus d'arguments que de paramètres, alors l'excédent est ignoré ;

- s'il y a moins d'arguments que de paramètres, alors les paramètres qui n'ont pas d'argument seront mis à « undefined ».

De plus, au moment de l'invocation, deux paramètres additionnels sont passés implicitement : « this » et « arguments ». Autrement dit, ces deux paramètres sont passés à la fonction qui est invoquée, même s'ils ne sont pas spécifiés en tant que paramètres dans la déclaration. Ils sont par la suite disponibles dans le corps de la fonction et peuvent être référencés comme n'importe quels autres vrais paramètres.

VIII-B - L'argument « arguments »

Cet objet est une sorte de collection de tous les arguments passés à la fonction.

function foo() {
console.log(arguments.length); //=> 3
console.log(arguments); //=> [1,2,'bar']
console.log(arguments[2]); //=> 'bar'
console.log(arguments.filter); //=> undefined
}

foo(1,2,'bar');

Mais attention, même si cet objet possède un attribut « length », et permet d'accéder aux arguments via une notation comme pour un tableau « arguments[i] », ce n'est pas un tableau à proprement parler. Vous ne pouvez pas utiliser les méthodes spécifiques aux tableaux comme map() ou filter() par exemple ; autrement dit, il n'est pas possible d'itérer sur l'objet « arguments ». Cependant, il existe des astuces permettant de convertir ce type de collection en un tableau. Parmi ces astuces, nous pouvons citer :

// astuce #1
var args = [].slice.call(arguments, 0);

// astuce #2
[].forEach.call(arguments, function(x){ /* expression */ });

VIII-C - L'argument « this »

Cet argument est un peu particulier. Il représente l'objet qui est associé — de façon implicite — à l'invocation de la fonction. Il est aussi connu sous le terme de contexte qui est bien connu des développeurs habitués au développement orienté objet. Cependant, dans la majorité des cas, ces mêmes personnes pensent qu'en JavaScript, le mot-clé « this » représente l'instance de la classe dans laquelle la méthode est définie. Ce n'est pas vrai !

En JavaScript, il se trouve que le contexte représenté par le paramètre « this » est conditionné par la façon dont la fonction a été invoquée, et non par la façon dont elle a été définie. C'est pour cela que ce paramètre est appelé « le contexte d'invocation ».

VIII-D - Les types d'invocations

Maintenant que nous avons bien compris ce que c'est « this » et ce qu'il représente. Passons aux différents types d'invocations.

VIII-D-1 - Invocation en tant que fonction

Ce type d'invocation est sûrement le plus utilisé. En effet, si une fonction n'est pas invoquée en tant que méthode, constructeur ou via « apply » ou « call », alors nous sommes en présence d'invocation d'une fonction.

Cette invocation se produit lorsque la fonction est invoquée en utilisant les parenthèses et lorsque cette fonction n'est pas une propriété d'un objet. Voici un exemple plus parlant :

function foo() {};
foo(); //=> this === window
var bar = function() {};
bar(); //=> this === window

Lorsqu'il est invoqué de cette manière, le contexte d'invocation de ces fonctions est « window », le contexte global en JavaScript.

VIII-D-2 - Invocation en tant que méthode

Lorsqu'une fonction est une propriété d'un objet et que l'invocation se fait en appelant cette fonction, alors la fonction est invoquée en tant que méthode de cet objet. Pour illustrer cela, étudions cet exemple :

function foo() {
return this;
}
// invocation en tant que fonction
foo(); //=> this === window
var bar = {
'foo': foo
};
// invocation en tant que méthode de 'bar'
bar.foo(); //=> this === bar

Nous avons déclaré une fonction « foo » que nous invoquons de deux manières : en tant que fonction, puis en tant que méthode de l'objet « bar ». Nous constatons qu'en effet, le contexte d'invocation retourné, représenté par « this », est différent dans les deux cas. Quand invoquée en tant que fonction, « this » représente le contexte global « window ». Lorsque la fonction est invoquée en tant que méthode, « this » représente l'objet hôte de la méthode au moment de l'invocation. Cette particularité prend tout son sens en programmation orientée objet en JavaScript.

VIII-D-3 - Invocation en tant que constructeur

Un constructeur est une simple fonction, il n'a vraiment rien de spécial. La seule différence réside dans la façon dont il va être invoqué. Pour invoquer une fonction en tant que constructeur, nous le précédons par le mot-clé « new ».

Invoquer une fonction en tant que constructeur est un aspect très intéressant de JavaScript, car lorsqu'un constructeur est invoqué, plusieurs choses se produisent :

- un nouvel objet vide est créé, le fameux paramètre « this » ;

- cet objet est passé au constructeur. Il devient donc le contexte d'invocation.

En l'absence d'une instruction « return » explicite dans le constructeur, cet objet est retourné par le constructeur. Prenons cet exemple :

function Foo() {
this.bar = function() {
return this;
}
}
// exemple 1
var foo = new Foo();
console.log(foo); //=> Foo
console.log(foo.bar() instanceof Foo); //=> true

// exemple 2
var foo1 = Foo();
console.log(typeof foo1); //=> undefined
console.log(typeof window.bar); //=> function

Nous avons déclaré un constructeur « Foo » (notez la première lettre en majuscule), celui-ci déclare une propriété « bar », qui est une méthode retournant le contexte « this » (pour l'exemple).

Dans un premier temps, nous invoquons ce constructeur avec le mot-clé « new » et nous avons bien un objet de type « Foo » qui a été créé et qui possède bien une méthode « bar ». Un petit test sur le contexte « this » dans « bar », nous prouve que c'est bien une instance de « Foo ».

Dans l'exemple 2, nous avons essayé de démontrer qu'invoquer un constructeur en tant que fonction, sans le mot-clé « new », ne donne pas du tout le résultat attendu. En effet, invoquer  « Foo » en tant que fonction, exécute simplement le code de cette fonction, ce qui provoque la création de la propriété « bar » dans l'objet hôte — dans lequel « Foo » a été invoquée. Cet objet représenté par « this » dans « Foo » est bien évidemment « window », puisque la fonction « Foo » a été déclarée dans le contexte global de JavaScript. Invoquer un constructeur en omettant le mot-clé new est donc risqué, car peut potentiellement modifier d'autres objets que ceux désirés.

Jusque-là, nous avons vu que le type d'invocation d'une fonction agit directement sur le contexte d'invocation représenté par le paramètre implicite « this ». Pour les méthodes, c'est l'objet hôte ; pour les fonctions déclarées dans le contexte global, c'est le « window »; et pour les constructeurs c'est l'instance du nouvel objet créé.

Maintenant, comment pouvons-nous faire si nous voulons forcer un contexte d'invocation particulier ? C'est bien grâce à « apply » et « call ».

VIII-D-4 - Invocation via apply() ou call()

JavaScript offre une méthode simple pour invoquer une fonction et lui spécifier explicitement un contexte d'invocation. Nous pouvons réaliser cela grâce à deux méthodes proposées par toutes les fonctions : « apply » et « call ».

Pourquoi voudrions-nous faire cela ? Prenons l'exemple d'une situation que nous rencontrons régulièrement en JavaScript : les actions — ou callbacks — des événements déjà abordés précédemment. Lorsque nous déclarons une fonction de callback sur un événement, celle-ci est invoquée lorsque l'événement en question est traité par le navigateur. Cette fonction de callback est invoquée avec le contexte de l'événement, c'est un comportement par défaut du navigateur.

function foo() {
console.log(this); //=> this === window
console.log(arguments); //=> ‘a', ‘b', ‘c'
}
var element = document.querySelector('#foo');
element.addEventListener('click', function(e){
console.log(this); //=> element
console.log(arguments); //=> e === Event
foo('a', 'b', 'c');
});

Comme l'illustre le code ci-dessus, le contexte d'invocation de la fonction de callback — représentée par « this » — est celui de « window » ; la callback « foo » est une fonction déclarée dans le contexte global. Elle a été invoquée en tant que fonction.

Voyons maintenant le même code, mais cette fois-ci en utilisant la méthode « call ».

function foo() {
console.log(this); //=> this === element
console.log(arguments); //=> 'a', 'b', 'c'
}
var element = document.querySelector('#foo');
element.addEventListener('click', function(e){
console.log(this); //=> element
console.log(arguments); //=> e === Event
foo.call(element, 'a', 'b', 'c');
});

En invoquant une fonction — ou méthode — avec « call », nous passons un premier paramètre qui représente le contexte d'invocation. Dans notre exemple, ce contexte est l'élément DOM qui nous intéresse. En plus du contexte d'invocation, nous avons aussi passé une liste de paramètres à la fonction « foo ».

function foo() {
console.log(this); //=> this === element
console.log(arguments); //=> a, b, c
}

var element = document.querySelector('#foo');
element.addEventListener('click', function(e){

console.log(this); //=> element
console.log(arguments); //=> e === Event

foo.apply(element, ['a', 'b', 'c']);
});

Avec « apply », c'est quasiment la même chose : nous passons également le contexte d'invocation en premier paramètre. Mais contrairement à « call », la méthode « apply » prend en second paramètre un tableau représentant la liste des arguments.

Voici un autre exemple plus intéressant :

function forevery(list, callback) {
for (var i = 0; i < list.length; i += 1) {
callback.call(list, i);
};
}
forevery(['a', 42, false, {}], function(index) {
console.log( "index = ", index );
console.log( "this = ", this );
console.log( "this[ index ] = ", this[ index ] );
console.log( "this[ index + 1 ] = ", this[ index + 1 ] );
console.log( "this[ index - 1 ] = ", this[ index - 1 ] );

/*
*  index =  0
*  this =  ["a", 42, false, Object]
*  this[ index ] =  a
*  this[ index + 1 ] =  42
*  this[ index - 1 ] =  undefined
*
*  index =  1
*  this =  ["a", 42, false, Object]
*  this[ index ] =  42
*  this[ index + 1 ] =  false
*  this[ index - 1 ] =  a
*
*  etc.
*/
});

Dans cet exemple, nous avons déclaré une fonction « forevery » qui permet d'itérer sur une liste d'éléments. Cette fonction prend en second paramètre une fonction de callback qui sera invoquée pour chaque élément parcouru, avec le contexte de la liste (nous aurions pu spécifier un autre contexte), et nous donne en paramètre l'index de l'élément courant.

VIII-D-5 - Quelle méthode utiliser ?

Cela dépend de votre cas d'usage, et plus précisément de comment vous devez gérer vos paramètres. Si vous avez un ensemble de variables que vous devez passer en tant que paramètres, « call » serait idéale. Mais si vous avez déjà votre liste de variables sous forme de tableau (par exemple, arguments), « apply » est plus appropriée. Prenons par exemple la méthode Math.max(n1, n2, n3…) de JavaScript : cette méthode calcule le maximum d'une liste de valeurs qu'elle prend en paramètres.

Math.max(0, 1, 2, 3, 4, 5, 6, 7, 8, 9); //=> 9

Supposons que vous vouliez calculer le maximum (ou le minimum) d'une suite de valeurs dont vous ne connaissez pas la longueur :

function max() {
return (arguments.length === 0) ? 0 : Math.max.apply(Math, arguments);
}
max(0, 1, 2, 3, 4, 5, 6, 7, 8, 9); //=> 9
max(3, 8, 32, 2, 98); //=> 98
max(42, 0); //=> 42
max(); //=> 0

Dans cet exemple, je viens de déclarer une fonction nommée « max » qui calcule le maximum d'une suite de nombres passés en paramètres. S'il n'y a aucun paramètre, le résultat est zéro. Je me repose donc sur le paramètre implicite « arguments » pour réaliser cette vérification. Vous comprenez tout de suite qu'utiliser « apply » a tout son sens dans cet exemple. Veuillez noter également que j'ai passé « Math » en tant que contexte d'invocation à la méthode « Math.max() » : je n'étais pas obligé d'en spécifier un, mais c'est plus sûr comme cela !

VIII-E - Un mot sur bind()

En JavaScript, il existe une autre façon de contrôler le contexte d'invocation d'une fonction. Contrairement à « call » et « apply », ce contrôle est fait au moment de la déclaration de la fonction, et non à l'invocation.

Ce contrôle est rendu possible grâce à la méthode « bind(arg) » permettant de transformer le contexte d'invocation d'une fonction. Jetons un œil à l'exemple suivant :

var Foo = function() {
this.counter = 0;
this.inc = function() {
this.counter += 1;
console.log(this);
};
};

var foo = new Foo();
var element = document.querySelector('#foo');

// cas #1
element.addEventListener('click', foo.inc); //=> this === element

// cas #2
element.addEventListener('click', function(){ foo.inc(); }); //=> this === foot

// cas #3
element.addEventListener('click', foo.inc.bind(foo)); //=> this === foo

Nous avons déclaré un constructeur « Foo ». Ce dernier possède une méthode « inc », qui permet d'incrémenter un compteur « counter ». Ensuite, nous attachons un événement sur un élément DOM (disons, un bouton par exemple). À chaque clic, nous souhaitons incrémenter le compteur de « Foo ».

Dans un premier temps (cas #1), nous attachons la méthode « foo.inc » en tant qu'action sur le clic. Mais là, problème ! En effet, l'attribut « counter » n'est pas résolu ; tout simplement puisque le contexte d'invocation de « foo.inc » n'est pas l'instance « foo », mais « element » (rappelez-vous l'exemple cité plus haut avec l'« apply »).

Pour résoudre ce problème, nous aurions pu passer par une fonction anonyme (cas #2) dans laquelle nous aurions invoqué la méthode « foo.inc() ». Mais pourquoi déclarer une fonction juste pour invoquer une autre ? Il y a plus simple, nous utilisons la méthode « bind » en précisant « foo » comme contexte d'invocation (cas #3). Mais rappelez-vous que « bind » n'invoque pas la fonction en question, elle modifie simplement son contexte d'invocation.

IX -  Résumé

Dans ce premier épisode, nous avons étudié en détail un des aspects fascinants de JavaScript : JavaScript en tant que langage fonctionnel. J'espère qu'en comprenant comment les fonctions sont traitées en interne par JavaScript, cela vous permettra de changer votre façon d'écrire du code JavaScript, en passant d'un « banal » code qui fonctionne à un code qui fonctionne, certes, mais digne d'un vrai Jedi que vous êtes.

Voilà, nous avons atteint la fin de ce premier épisode ; rendez-vous pour le second épisode dans lequel nous allons tenter de percer le mystère des fermetures en JavaScript, connu sous le nom de « closures ».

Que la force soit avec vous…

X - Remerciements

Un grand merci à mes collègues à SII Ouest, Vincent Ogloblinsky et Aurélien Baudet pour leurs relectures et échanges techniques et à Sylvain Reyt et Nicolas Bulteau pour leurs précieux conseils et leurs encouragements.

Je tiens à remercier l'équipe de Developpez.com, et tout particulièrement M. Sylvain Pollet-Villard pour sa relecture technique qui a permis de clarifier certains points. Merci également à M. Xavier Lecomte pour son aide et son accompagnement. Un dernier merci à M. Malick SECK pour sa relecture orthographique.

XI - Références et liens

[1] :http://mzl.la/12Efmj8

[2] :http://mzl.la/1xbLo3s