Introduction

Introduction

Le Jardin de JavaScript est une collection croissante de documentation liée aux aspects les plus excentriques du langage de programmation JavaScript. Il donne des conseils pour éviter les erreurs communes, les bugs subtils, ainsi que les problèmes de performance et de mauvaises pratiques, que les amateurs de JavaScript peuvent rencontrer dans leurs efforts d'apprentissage en profondeur du langage.

Le Jardin de JavaScript ne cherche pas à vous enseigner JavaScript. Une connaissance préalable du langage est fortement recommandée afin de comprendre les sujets abordés dans ce guide. Veuillez vous référer à l'excellent guide du Mozilla Developer Network pour apprendre les rudiments du langage JavaScript.

Auteurs

Ce guide est l'œuvre de deux charmants utilisateurs de Stack Overflow: Ivo Wetzel (écriture) et Zhang Yi Jiang (design).

Actuellement maintenu par Tim Ruffles.

Collaborateurs

Hébergement

Le Jardin de JavaScript est hébergé sur GitHub, mais Cramer Développement nous soutient avec un mirroir à JavaScriptGarden.info.

Licence

Le Jardin de JavaScript est publié sous la licence MIT et hébergé sur GitHub. Si vous trouvez des erreurs ou fautes de frappe veuillez s'il vous plaît déposer une question ou une "pull request" sur le dépôt. Vous pouvez également nous trouver dans la Salle JavaScript sur Stack Overflow.

Objets

Utilisation des objets et propriétés

En JavaScript, tout agit comme un objet, à part deux exceptions: null et undefined.

false.toString(); // 'false'
[1, 2, 3].toString(); // '1,2,3'

function Foo(){}
Foo.bar = 1;
Foo.bar; // 1

Une méprise commune est que les littéraux numériques ne peuvent pas être utilisés comme objets, due à une imperfection de l'analyseur de JavaScript qui tente d'analyser la notation à point sur un nombre comme une virgule flottante.

2.toString(); // erreur de syntaxe SyntaxError

Des solutions de contournement existent pour forcer les littéraux numériques à agir comme des objets.

2..toString(); // le second point est correctement reconnu
2 .toString(); // notez l'espace à gauche du point
(2).toString(); // 2 est évalué en premier

Objets comme type de données

Les objets en JavaScript peuvent également être utilisés comme HashMaps; essentiellement, des propriétés nommées pointant sur des valeurs.

En utilisant un littéral d'objet - notation {} - il est possible de créer un objet vide. Ce nouvel objet hérite de Object.prototype et ne possède pas de propriétés propres définies.

var foo = {}; // un nouvel objet vide

// un nouvel objet avec une propriété 'test' à valeur 12
var bar = {test: 12};

Accéder aux propriétés

Les propriétés d'un objet sont accessibles de deux façons, soit par la notation à point, soit par la notation à crochets.

var foo = {name: 'kitten'}
foo.name; // kitten
foo['name']; // kitten

var get = 'name';
foo[get]; // kitten

foo.1234; // SyntaxError
foo['1234']; // cela marche

Les deux notations fonctionnent presque pareil, la seule différence étant que la notation à crochet permet l'écriture des propriétés et l'utilisation des noms de propriété qui autrement mèneraient à une erreur de syntaxe.

Supprimer des propriétés

La seule façon de supprimer une propriété d'un objet est d'utiliser l'opérateur delete. Mettre la propriété à null ou undefined ne supprime que la valeur associée à la propriété, et non pas la propriété elle-même.

var obj = {
    bar: 1,
    foo: 2,
    baz: 3
};
obj.bar = undefined;
obj.foo = null;
delete obj.baz;

for(var i in obj) {
    if (obj.hasOwnProperty(i)) {
        console.log(i, '' + obj[i]);
    }
}

Les résultats du programme ci-dessus sont bar undefined et foo null - seul baz a été correctement supprimé.

Notation des clefs "keys"

var test = {
    'case': 'Je suis un mot-clé, donc je dois etre écrit en tant que chaîne',
    delete: 'Je suis un mot-clé, donc moi aussi' // erreur de syntaxe SyntaxError
};

Les propriétés d'objet peuvent être écrites simplement telles quelles ou comme des chaînes "string". Une autre imperfection de l'analyseur de JavaScript, avant ECMAScript 5, provoquera une erreur de syntaxe SyntaxError dans le programme qui précède.

Cette erreur vient du fait que delete est un mot-clé; et par conséquent, il doit être écrit comme une chaîne littérale pour s'assurer qu'il sera correctement interprété par les vieux moteurs JavaScript.

Le prototype

JavaScript n'utilise pas le modèle classique d'héritage, mais un modèle prototypique.

Souvent considéré comme l'une des faiblesses de JavaScript, le modèle d'héritage prototypique est en fait plus puissant que le modèle classique. Par exemple, il est assez facile de construire un modèle classique à partir du modèle prototypique, tandis que l'inverse est une tâche beaucoup plus difficile à entreprendre.

JavaScript étant le seul langage à héritage prototypique largement utilisé, s'adapter aux différences entre les deux modèles peut prendre du temps.

La première différence majeure est que l'héritage en JavaScript utilise des chaînes de prototypes.

function Foo() {
    this.value = 42;
}
Foo.prototype = {
    method: function() {}
};

function Bar() {}

// Assigner le prototype de Bar à une nouvelle instance de Foo
Bar.prototype = new Foo();
Bar.prototype.foo = 'Hello World';

// S'assurer que Bar est le constructeur
Bar.prototype.constructor = Bar;

var test = new Bar(); // crée une nouvelle instance de bar

// La chaîne de prototypes qui en résulte
test [instance of Bar]
    Bar.prototype [instance of Foo]
        { foo: 'Hello World', value: 42 }
        Foo.prototype
            { method: ... }
            Object.prototype
                { toString: ... /* etc. */ }

Dans le code ci-dessus, l'objet test va hériter à la fois de Bar.prototype et de Foo.prototype; par conséquent, il aura accès à la fonction method qui était définie sur Foo. Il aura également accès à la propriété value de la seule instance de Foo qui est son prototype. Il est important de noter que le new Bar() ne crée pas une nouvelle instance de Foo, mais réutilise celui attribué à son prototype; ainsi, toutes les instances de Bar se partageront la même propriété value.

Recherche des propriétés

Lors de l'accès aux propriétés d'un objet, JavaScript traversera la chaîne de prototypes vers le haut jusqu'à ce qu'il trouve une propriété avec le nom demandé.

S'il atteint le sommet de la chaîne - à savoir Object.prototype - sans avoir trouvé la propriété spécifiée, la valeur undefined sera retournée.

La propriété prototype

Bien que la propriété prototype est utilisé par le langage pour construire la chaîne de prototypes, il est toujours possible de lui attribuer une valeur quelconque, mais les types primitifs seront simplement ignorés.

function Foo() {}
Foo.prototype = 1; // aucun effet

Assigner des objets, comme le montre l'exemple ci-dessus, va marcher, et permet la création dynamique de chaînes de prototypes.

Performance

Les temps de recherche pour des propriétés qui sont en haut de la chaîne de prototypes peuvent avoir un impact négatif qui être significatif pour du code où la performance est critique. Essayer d'accéder à des propriétés inexistantes causera toujours la traversée complète de la chaîne de prototypes.

De plus, itérer sur les propriétés d'un objet va causer l'énumération de toutes les propriétés qui se trouve sur la chaîne de prototype.

Extension des prototypes natifs

Une mauvaise technique souvent utilisée est d'étendre Object.prototype ou un des prototypes intégrés.

Cette technique est appelée monkey patching et casse l'encapsulation. Bien qu'utilisée par des cadriciels "frameworks" populaires tels que Prototype, il n'existe aucune bonne raison pour encombrer les types intégrés avec des fonctionnalités supplémentaires non standards.

La seule bonne raison d'étendre un prototype intégré est le rétroportage de caractéristiques des nouveaux moteurs JavaScript; par exemple, Array.forEach.

En conclusion

Il est essentiel de comprendre le modèle d'héritage prototypique avant d'écrire du code complexe qui l'utilise. Soyez conscient de la longueur des chaînes de prototypes dans votre code; découpez les si nécessaire pour éviter de possible problèmes de performance. En outre, les prototypes natifs ne devraient jamais être étendus, sauf pour des raisons de compatibilité avec de nouvelles caractéristiques du langage JavaScript.

hasOwnProperty

Pour savoir si un objet possède une propriété définie, et non pas quelque part ailleurs sur sa chaîne de prototype, il est nécessaire d'utiliser la méthode hasOwnProperty, une méthode que tous les objets héritent d'Object.prototype.

hasOwnProperty est la seule chose en JavaScript qui traite des propriétés sans traverser la chaîne de prototypes.

// Empoisonnement d'Object.prototype
Object.prototype.bar = 1;
var foo = {goo: undefined};

foo.bar; // 1
'bar' in foo; // vrai

foo.hasOwnProperty('bar'); // faux
foo.hasOwnProperty('goo'); // vrai

Seulement hasOwnProperty donnera le résultat attendu et correct. Voir la section sur les boucles for in pour plus de détails sur l'utilisation de hasOwnProperty pour traverser les propriétés d'un objet.

hasOwnProperty en tant que propriété

JavaScript ne protège pas le nom de la propriété hasOwnProperty; ainsi, la possibilité existe qu'un objet peut avoir une propriété avec ce nom, et il est donc nécessaire d'utiliser une méthode hasOwnProperty externe pour obtenir des résultats corrects.

var foo = {
    hasOwnProperty: function() {
        return false;
    },
    bar: 'Here be dragons'
};

foo.hasOwnProperty('bar'); // toujours faux

// Utiliser hasOwnProperty d'un autre object,
// et l'appeler avec foo assigné à 'this'
({}).hasOwnProperty.call(foo, 'bar'); // vrai

// Il est aussi possible d'utiliser hasOwnProperty
//du prototype d'Object
Object.prototype.hasOwnProperty.call(foo, 'bar'); // vrai

En conclusion

Utiliser hasOwnProperty est la seule méthode fiable pour vérifier l'existence d'une propriété sur un objet. Il est recommandé d'utiliser hasOwnProperty pour itérer sur les propriétés des objets comme décrit dans la section sur les boucles for in.

La boucle for in

Tout comme l'opérateur in, la boucle for in traverse la chaîne de prototypes lors de l'itération sur les propriétés d'un objet.

// Empoisonnement d'Object.prototype
Object.prototype.bar = 1;

var foo = {moo: 2};
for(var i in foo) {
    console.log(i); // imprime bar et moo
}

Puisqu'il n'est pas possible de changer le comportement de la boucle for in, il est nécessaire de filtrer les propriétés indésirables à l'intérieur du corps de la boucle. Sous ECMAScript 3 et plus, cela se fait en utilisant la méthode hasOwnProperty de Object.prototype.

Depuis ECMAScript 5, Object.defineProperty peut être utilisé avec enumerable mis à faux pour ajouter des propriétés à des objets (y compris Object) sans que ces propriétés soient énumérées. Il est raisonnable dans ce cas d'assumer que les propriétés énumérables ont été ajouté pour une raison, ce qui permet d'omettre les appels à hasOwnProperty qui réduisent la lisibilité du code. Dans du code de librairie, hasOwnProperty devrait toujours être utilisé car des propriétés énumérables pourraient résider sur la chaîne de prototypes sans qu'on le sache.

Filtrer avec hasOwnProperty

// le même foo qu'au dessus
for(var i in foo) {
    if (foo.hasOwnProperty(i)) {
        console.log(i);
    }
}

Cette version est la seule version correcte à utiliser avec les anciennes versions d'ECMAScript. L'utilisation de hasOwnProperty nous garantie que seulement moo sera imprimé. Quand hasOwnProperty n'est pas utilisé, les prototypes natifs - par exemple Object.prototype - qui ont peut-être été étendus, causeront probablement des erreurs.

Avec les versions plus récentes d'ECMAScript, des propriétés non-dénombrables peuvent être définies avec Object.defineProperty, réduisant le risque d'itération sur les propriétés quand hasOwnProperty n'est pas utilisé. Néanmoins, il faut faire attention avec l'utilisation de vieilles librairies comme Prototype qui ne bénéficient pas des nouvelles fonctions d'ECMAScript. Dans ce cadre, écrire des boucles for in sans hasOwnProperty est garanti de causer des erreurs.

En conclusion

Il est recommandé de toujours utiliser hasOwnProperty avec ECMAScript 3 ou moins, ou dans du code de librairie. Dans ces environnements, il ne faut jamais assumer que les prototypes natifs n'ont pas été étendus. Depuis ECMAScript 5, Object.defineProperty permet de définir les propriétés non-dénombrables et donc permet d'omettre les appels à hasOwnProperty dans le code de l'application.

Fonctions

Déclaration des fonctions et expressions

Les fonctions en JavaScript sont des objets de première classe. Cela signifie qu'elles peuvent être passées comme toute autre valeur. Une utilisation courante de cette caractéristique est de passer une fonction anonyme comme une fonction de rappel "callback" qui peut être asynchrone.

La déclaration function

function foo() {}

La fonction ci-dessus est hissée "hoisted" avant le démarrage du programme; ainsi, elle est donc disponible partout dans la portée "scope" d'application où la fonction a été définie, même si appelé avant sa définition dans le code source.

foo(); // Fonctionne car foo a été crée avant l'exécution de ce code
function foo() {}

L'expresssion function

var foo = function() {};

Cet exemple attribue une fonction anonyme et sans nom à la variable foo.

foo; // 'undefined'
foo(); // provoque un erreur de type TypeError
var foo = function() {};

En raison du fait que var est une déclaration qui hisse le nom de la variable foo avant que l'exécution réelle du code ne commence, foo est déjà déclarée lorsque le script est exécuté.

Mais comme les assignements ne se produisent qu'au moment de l'exécution, la valeur de foo sera par défaut mise à undefined avant l'exécution du code.

L'expression de fonction nommée

Un autre cas est l'attribution de fonctions nommées.

var foo = function bar() {
    bar(); // Works
}
bar(); // erreur de reference ReferenceError

Ici, bar n'est pas disponible dans la portée externe "outer scope", puisque la fonction est seulement assignée à foo, mais elle est disponible à l'intérieur de bar. Cela est dû à la méthode de résolution de noms de JavaScript: le nom de la fonction est toujours disponible dans la portée locale "local scope" de la fonction elle-même.

Comment marche this

Pour JavaScript, ce que le nom spécial this réfère à diffère de la plupart des autres langages de programmation. Il y a exactement cinq façons différente de lier la valeur de this dans le langage.

Le contexte global "global scope"

this;

Lorsque vous utilisez this dans le contexte global, il va simplement référer à l'objet global.

Appel de fonction

foo();

Ici, this va aussi référer à l'objet global.

Appel de méthode

test.foo(); 

Dans cet exemple, this va référer à test.

Appel de constructeur

new foo(); 

Un appel de fonction qui est précédé par le mot clé new agit comme un constructeur. Dans la fonction, this va référer à un Object nouvellement créé.

Assignement direct de this

function foo(a, b, c) {}

var bar = {};
foo.apply(bar, [1, 2, 3]); // table va s'étendre comme ci-dessous
foo.call(bar, 1, 2, 3); // mène à: a = 1, b = 2, c = 3

Lorsque vous utilisez les méthodes call (appeler) ou apply (appliquer) de Function.prototype, la valeur de this à l'intérieur de la fonction appelée est directement définie par le premier argument de l'appel correspondant.

En conséquence, dans l'exemple ci-dessus le cas d'appel de méthode ne s'applique pas, et this à l'intérieur de foo va bien référer à bar.

Pièges communs

Bien que la plupart de ces cas ont du sens, le premier cas peut être considéré comme une autre faute de design du langage, car il n'est jamais d'aucune utilité pratique.

Foo.method = function() {
    function test() {
        // this réfère à l'objet global
    }
    test();
};

Une autre erreur souvent commise est que this l'intérieur de test se réfère à foo; ce qui n'est pas du tout le cas.

Pour accéder à foo de l'intérieur de test, vous pouvez créer une variable locale à intérieur de method qui fait référence à foo.

Foo.method = function() {
    var self = this;
    function test() {
        // Utilisez self au lieu de this ici
    }
    test();
};

self est juste une variable normale, couramment utilisée pour référencer un this extérieur. Combiné avec des fermetures "closures", on peut l'utiliser pour passer les valeurs de this.

À partir d'ECMAScript 5, l'utilisation de la méthode bind avec une fonction anonyme mène au même resultat:

Foo.method = function() {
    var test = function() {
        // maintenant, this réfère à Foo
    }.bind(this);
    test();
};

Assignement de méthodes

Une autre chose qui ne marche pas en JavaScript est l'alias de fonction, ou l'assignement d'une méthode à une variable.

var test = someObject.methodTest;
test();

En raison du premier des cinq cas, test agit maintenant comme un appel de fonction normal; par conséquent, this à l'intérieur de la fonction ne va plus référer à someObject.

Bien que la liaison tardive "late binding" de this pouvait sembler comme une mauvaise idée au premier abord, c'est en fait grâce à cela que l'héritage prototypique fonctionne.

function Foo() {}
Foo.prototype.method = function() {};

function Bar() {}
Bar.prototype = Foo.prototype;

new Bar().method();

Quand method est appelée d'une instance de bar, this va référer à cette même instance.

Fermetures et réferences

Les fermetures "closures" sont une des fonctionnalités les plus puissantes de JavaScript. Avec les fermetures, les portées gardent toujours l'accès à la portée externe, dans laquelle elles ont été définies. Puisque la seule portée que JavaScript a est la portée de fonction, toutes les fonctions, par défaut, agissent comme des fermetures.

Simuler les variables privées

function Counter(start) { // compteur
    var count = start; // compte
    return {
        increment: function() {
            count++;
        },

        get: function() {
            return count;
        }
    }
}

var foo = Counter(4);
foo.increment();
foo.get(); // 5

Ici, Counter retourne deux fermetures: la fonctionincrement ainsi que la fonction get. Ces deux fonctions conservent une référence à la portée de Counter et, par conséquent, gardent toujours l'accès à la variable count qui a été définie dans cette portée.

Comment marchent les variables privées

Comme il ne est pas possible de référencer ou assigner des portées en JavaScript, il n'y a aucun moyen d'accéder à la variable count de l'extérieur. La seule façon d'interagir avec elle est par l'intermédiaire des deux fermetures.

var foo = new Counter(4);
foo.hack = function() {
    count = 1337;
};

Le code ci-dessus ne va pas changer la variable count dans la portée de Counter, car foo.hack n'a pas été défini dans cette portée. En fait, une nouvelle variable va etre crée - ou va remplacer - la variable globale count.

Fermetures dans les boucles

Une erreur souvent commise est d'utiliser les fermetures à l'intérieur de boucles comme si elles copiaient la valeur de la variable d'indice de la boucle.

for(var i = 0; i < 10; i++) {
    setTimeout(function() {
        console.log(i);  
    }, 1000);
}

La programme ci-dessus ne vas pas produire les numéros 0 à 9, il imprimera 10 dix fois.

La fonction anonyme garde une référence à i. Au moment où console.log est appelée, la boucle for est déjà achevée, et donc la valeur de i est à 10.

Afin d'obtenir le comportement souhaité, il est nécessaire de créer une copie de la valeur de i.

Eviter le problème de référence

Pour copier la valeur de la variable d'index de la boucle, il est préférable d'utiliser une enveloppe anonyme "wrapper".

for(var i = 0; i < 10; i++) {
    (function(e) {
        setTimeout(function() {
            console.log(e);  
        }, 1000);
    })(i);
}

La fonction externe anonyme est appelée immédiatement avec i en tant que premier argument, et donc le paramètre e recevra une copie de la valeur de i.

La fonction anonyme qui est passé à setTimeout a maintenant une référence à e, dont la valeur ne peut pas être changée par la boucle.

Une autre façon de faire est de retourner une fonction de l'enveloppe anonyme qui aura alors le même comportement que le code ci-dessus.

for(var i = 0; i < 10; i++) {
    setTimeout((function(e) {
        return function() {
            console.log(e);
        }
    })(i), 1000)
}

Une autre façon populaire d'achever le même comportement est d'ajouter un argument supplémentaire à la fonction setTimeout. La fonction passera ces arguments à la fonction de rappel "callback".

for(var i = 0; i < 10; i++) {
    setTimeout(function(e) {
        console.log(e);  
    }, 1000, i);
}

Sachez que certains environnements JS (Internet Explorer 9 et avant) ne supportent pas cette dernière approche.

Enfin, une dernière façon de faire et d'utiliser bind, qui peut lier le contexte this et les arguments pour la fonction.

for(var i = 0; i < 10; i++) {
    setTimeout(console.log.bind(console, i), 1000);
}

L'objet arguments

Chaque portée "scope" de fonction en JavaScript peut accéder à la variable spéciale arguments. Cette variable contient une liste de tous les arguments qui ont été passés à la fonction.

L'objet arguments n'est pas un tableau Array. Même s'il a la sémantique d'un tableau - à savoir la propriété length (longueur) - il n'hérite pas de Array.prototype mais est en fait un Object.

Pour cette raison, il n'est pas possible d'utiliser les méthodes de tableau standards comme push, pop ou slice sur arguments. Bien qu'itérer avec une boucle for fonctionne, il est nécessaire de convertir la variable arguments en un véritable Array pour pouvoir lui appliquer les fonctions de tableau Array standards.

Conversion à Array

Le code ci-dessous va retourner un nouveau tableau Array contenant tous les éléments de l'objet arguments.

Array.prototype.slice.call(arguments);

Cette conversion est lente, il n'est donc pas recommandé de l'utiliser dans des sections de code où la performance est critique.

Passage d'arguments

Voici la méthode recommandée pour passer des arguments d'une fonction à une autre.

function foo() {
    bar.apply(null, arguments);
}
function bar(a, b, c) {
    // faire qqch ici
}

Une autre astuce consiste à utiliser à la fois call et apply pour transformer des méthodes - fonctions qui utilisent la valeur de this ainsi que leurs arguments - en des fonctions normales qui n'utilisent que leurs arguments.

function Person(first, last) {
  this.first = first;
  this.last = last;
}

Person.prototype.fullname = function(joiner, options) {
  options = options || { order: "western" };
  var first = options.order === "western" ? this.first : this.last;
  var last =  options.order === "western" ? this.last  : this.first;
  return first + (joiner || " ") + last;
};

// Créer une version non liée de "fullname", utilisable sur n'importe quel
// objet avec les propriétés 'first' et 'last' passées comme premier
// argument. Cette enveloppe n'aura pas besoin de changer si fullname
// change le nombre ou l'ordre des ses arguments.
Person.fullname = function() {
  // résultat: Person.prototype.fullname.call(this, joiner, ..., argN);
  return Function.call.apply(Person.prototype.fullname, arguments);
};

var grace = new Person("Grace", "Hopper");

// 'Grace Hopper'
grace.fullname();

// 'Turing, Alan'
Person.fullname({ first: "Alan", last: "Turing" }, ", ", { order: "eastern" });

Paramètres formels et arguments indexés

L'objet arguments crée des fonctions getter et setter à la fois pour ses propriétés et les paramètres formels de la fonction.

Par conséquent, changer la valeur d'un paramètre formel va également modifier la valeur de la propriété correspondante sur l'objet arguments, et vice-versa.

function foo(a, b, c) {
    arguments[0] = 2;
    a; // 2

    b = 4;
    arguments[1]; // 4

    var d = c;
    d = 9;
    c; // 3
}
foo(1, 2, 3);

Mythes et faits sur la performance

Le seul moment où l'objet arguments n'est pas créé est quand il est déclaré comme un nom à l'intérieur d'une fonction ou l'un de ses paramètres formels. Le fait qu'il soit utilisé ou non n'est pas important.

Les deux getter et setter sont toujours créé; et donc l'utilisation d'arguments n'a aucune incidence sur la performance.

Cependant, un cas va considérablement réduire la performance des moteurs JavaScript modernes. C'est le cas de l'utilisation de arguments.callee.

function foo() {
    arguments.callee; // faire quelque chose avec cet objet de fonction
    arguments.callee.caller; // et la fonction appelante
}

function bigLoop() {
    for(var i = 0; i < 100000; i++) {
        foo(); // Seraient normalement inline...
    }
}

Dans le code ci-dessus, foo ne peut plus être inline car il a besoin de se connaitre lui-même et connaitre son appelant. Cela défait les gains possibles de performance qui découleraient d'inline, mais cela casse également l'encapsulation car la fonction peut maintenant être dépendante d'un contexte d'appel spécifique.

Utiliser arguments.callee ou l'une de ses propriétés est fortement déconseillé.

Constructeurs

Les constructeurs en JavaScript diffèrent de beaucoup d'autres langages. Tout appel de fonction précédé par le mot clé new agit comme un constructeur.

Dans le constructeur - la fonction appelée - la valeur de this se réfère à un objet nouvellement créé. Le prototype de ce nouvel objet pointe sur le prototype de l'objet de fonction qui a été invoqué comme constructeur.

Si la fonction qui a été appelée n'a pas de déclaration return explicite, elle renvoira implicitement la valeur de this - le nouvel objet.

function Person(name) {
    this.name = name;
}

Person.prototype.logName = function() {
    console.log(this.name);
};

var sean = new Person();

Le code ci-dessus appelle Person en tant que constructeur et définit le prototype du nouvel objet créé à Person.prototype.

En cas d'une déclaration return explicite, la fonction renvoie la valeur spécifiée par cette déclaration, mais seulement si cette valeur est un objet Object.

function Car() {
    return 'ford';
}
new Car(); // un nouvel objet, pas 'ford'

function Person() {
    this.someValue = 2;

    return {
        name: 'Charles'
    };
}
new Test(); // l'objet retourné ({name:'Charles'}) n'inclue pas someValue

Lorsque le mot clé new est omis, la fonction ne retournera pas un nouvel objet.

function Pirate() {
    this.hasEyePatch = true; // this est l'object global!
}
var somePirate = Pirate(); // somePirate est undefined

Bien que l'exemple ci-dessus a l'air de marcher, il utilisera l'objet global pour la valeur de this, en raison du fonctionnement particulier de this en JavaScript.

Fabriques

Pour pouvoir omettre le mot clé new, la fonction constructeur doit retourner explicitement une valeur.

function Robot() {
    var color = 'gray';
    return {
        getColor: function() {
            return color;
        }
    }
}
Robot.prototype = {
    someFunction: function() {}
};

new Robot();
Robot();

Les deux appels à Robot retournent la même chose, un objet nouvellement créé qui possède une propriété appelée getColor, qui est une fermeture "closure".

Il convient également de noter que l'appel new Robot() n'affecte pas le prototype de l'objet retourné. Bien que le prototype sera mis sur le nouvel objet créé, Robot ne retourne jamais cet objet.

Dans l'exemple ci-dessus, il n'y a pas de différence fonctionnelle entre l'utilisation et la non-utilisation du mot clé new.

Creation de nouvels objects via fabriques

Il est souvent recommandé de ne pas utiliser new car l'oublier peut conduire à des bugs.

Pour créer un nouvel objet, il faut plutôt utiliser une fabrique qui va construire un nouvel objet.

function CarFactory() {
    var car = {};
    car.owner = 'nobody';

    var milesPerGallon = 2;

    car.setOwner = function(newOwner) {
        this.owner = newOwner;
    }

    car.getMPG = function() {
        return milesPerGallon;
    }

    return car;
}

Bien que le code qui précède est robuste contre un mot clé new manquant et rend certainement l'utilisation de variables privées plus facile, il y a des inconvénients.

  1. Il utilise plus de mémoire car les objets créés ne partagent pas leurs méthodes avec un prototype.
  2. Pour hériter, la fabrique a besoin de copier toutes les méthodes de l'autre objet ou mettre l'autre objet sur le prototype du nouvel objet.
  3. Abandonner la chaîne de prototype à cause d'un mot clé new laissé de côté est contraire à l'esprit du langage.

En Conclusion

Omettre le mot clé new peut conduire à des bugs, mais ce n'est certainement pas une raison d'abandonner l'utilisation des prototypes. En fin de compte il s'agit de savoir quelle solution est la mieux adaptée pour les besoins de l'application. Il est particulièrement important de choisir un style spécifique de création d'objet et toujours l'utiliser afin de rester cohérent.

Portées "scopes" et espaces de noms "namespaces"

Bien que JavaScript utilise une syntaxe avec accolades pour les blocs, il ne crée pas de portée "scope" de bloc; par conséquent, la seule portée du langage est la portée de fonction.

function test() { // une portée "scope"
    for(var i = 0; i < 10; i++) { // pas une portée 
        // count
    }
    console.log(i); // 10
}

Il n'existe pas d'espaces de noms "namespaces" en JavaScript, ce qui signifie que tout est défini dans un espace de noms commun partagé par tous.

Chaque fois qu'une variable est référencée, JavaScript va traverser vers le haut toutes les portées jusqu'à ce qu'il la trouve. S'il atteint la portée globale sans avoir trouvé le nom demandé, il va générer une erreur de référence ReferenceError.

Le fléau des variables globales

// script A
foo = '42';

// script B
var foo = '42'

Les deux scripts ci-dessus n'ont pas le même effet. Le script A définit une variable appelée foo dans la portée globale, le script B définit foo dans la portée actuelle.

Ne pas utiliser var peut avoir des répercussions majeures.

// portée globale
var foo = 42;
function test() {
    // portée locale
    foo = 21;
}
test();
foo; // 21

En laissant de côté la déclaration var à l'intérieur de la fonction test, on remplace la valeur de foo. Même si au premier abord cela ne semble pas être une grosse affaire, des milliers de lignes de JavaScript qui n'utilisent pas var créeront des bogues horribles qui seront très difficiles à dépister.

// portée globale
var items = [/* some list */];
for(var i = 0; i < 10; i++) {
    subLoop();
}

function subLoop() {
    // portée de subLoop
    for(i = 0; i < 10; i++) { // var manquant
        // ici, des choses incroyables!
    }
}

La boucle externe se terminera après le premier appel à subLoop, car subLoop écrase la valeur globale de i. L'utilisation d'un var pour la deuxième boucle for aurait facilement évité cette erreur. La déclaration de var devrait jamais être laissé de côté, sauf si l'effet désiré est d'affecter la portée externe.

Variables locales

Seuls les paramètres de fonction et les variables déclarées avec un var peuvent créer des variables locales en JavaScript.

// portée globale
var foo = 1;
var bar = 2;
var i = 2;

function test(i) {
    // portée locale de la fonction test
    i = 5;

    var foo = 3;
    bar = 4;
}
test(10);

foo et i sont bien des variables locales à l'intérieur de la portée de la fonction test, mais l'assignment bar remplacera la variable globale portant le même nom.

Remontée "hoisting"

JavaScript hisse les déclarations. Cela signifie que les déclarations de var et function seront déplacés vers le haut de leur portée englobante.

bar();
var bar = function() {};
var someValue = 42;

test();
function test(data) {
    if (false) {
        goo = 1;

    } else {
        var goo = 2;
    }
    for(var i = 0; i < 100; i++) {
        var e = data[i];
    }
}

Le code ci-dessus est transformé avant que l'exécution ne commence. JavaScript déplace les déclarations var, ainsi que les déclarations function, vers le haut de la portée la plus proche.

// les déclarations var sont maintenant ici
var bar, someValue; // mis à 'undefined' par défaut

// les déclarations de fonction aussi
function test(data) {
    var goo, i, e; // pas de portée de bloc,
                   // donc déclarations var viennent ici
    if (false) {
        goo = 1;

    } else {
        goo = 2;
    }
    for(i = 0; i < 100; i++) {
        e = data[i];
    }
}

bar(); // échoue avec TypeError puisque bar est toujours 'undefined'
someValue = 42; // les assignements ne sont pas concernés par la remontée
bar = function() {};

test();

L'inexistence des portées de bloc va non seulement déplacer les déclarations var en dehors du corps des boucles, mais va aussi rendre les résultats de certaines constructions de if non-intuitifs.

Dans le code original, la déclaration if semblait modifier la variable globale goo, alors qu'en fait elle modifiait la variable locale - après la remontée appliquée.

Sans la connaissance du concept de remontée, on pourrait soupçonner que le code ci-dessous produirait une erreur de référence ReferenceError.

// verifie si SomeImportantThing a bien été initializé
if (!SomeImportantThing) {
    var SomeImportantThing = {};
}

Le code fonctionne pourtant bien, car la déclaration de var est déplacé vers le haut de la portée globale.

var SomeImportantThing;

// du code peut, ou pas, initializer SomeImportantThing ici

// soyons en sûr
if (!SomeImportantThing) {
    SomeImportantThing = {};
}

Ordre de la résolution de noms

Toutes les portées en JavaScript, y compris la portée globale, ont le nom spécial this défini qui se réfère à l'objet courant.

Les portées de fonction ont aussi le nom arguments défini qui contient les arguments qui ont été transmis à la fonction.

Par exemple, lorsque vous essayez d'accéder à une variable nommé foo l'intérieur de la portée d'une fonction, JavaScript va chercher le nom dans l'ordre suivant:

  1. Si il y a une déclaration var foo var dans la portée courante, l'utiliser.
  2. Si l'un des paramètres de la fonction est nommé foo, l'utiliser.
  3. Si la fonction elle-même est appelée foo, l'utiliser.
  4. Sinon, accéder à la portée externe suivante, et recommencer à #1 pour cette portée.

Remarque: Avoir un paramètre appelé arguments va empêcher la création d'objet par défaut arguments.

Espaces de noms

Le fait de n'avoir qu'un seul espace de noms global engendre un risque de conflit de noms de variables, un problème commun en JavaScript. En JavaScript, ce problème peut facilement être évité grâces aux enveloppes anonymes.

(function() {
    // un "espace de nom" autonome

    window.foo = function() {
        // une fermeture exposée
    };

})(); // exécute la fonction immédiatement

Les fonctions anonymes sont considérées comme des expressions; ainsi elles doivent d'abord être évaluées avant d'être appelées.

( // évaluer la fonction à l'intérieur des parenthèses
function() {}
) // et retourner la fonction object
() // appeler le résultat de l'évaluation

Il y a d'autres façons d'évaluer et d'appeler directement l'expression de fonction qui, bien que différentes dans la syntaxe, se comportent de la même manière.

// Autres styles d'invocation directe
!function(){}()
+function(){}()
(function(){}());
// etc.

En conclusion

Il est recommandé de toujours utiliser une enveloppe anonyme pour encapsuler du code dans son propre espace de noms. Non seulement cela protège des conflits de noms de code, cela permet également une meilleure modularisation des programmes.

En outre, l'utilisation de variables globales est considéré comme une mauvaise pratique. Leur utilisation indique un code mal écrit, sujet à des erreurs, et difficile à maintenir.

Tableaux

Tableaux: iteration et propriétés

Bien que les tableaux soient des objets en JavaScript, il n'y a pas de bonnes raisons d'utiliser la boucle for in. En fait, il y a un certain nombre de bonnes raisons contre l'utilisation de for in sur les tableaux.

Remarque: Les tableaux JavaScript ne sont pas associatifs. JavaScript n'offre que les objets pour associer des clés à des valeurs. Contrairement aux tableaux associatifs, les objets ne préservent pas l'ordre.

La boucle for in énumère toutes les propriétés qui sont sur la chaîne de prototypes, et le seul moyen d'exclure ces propriétés consiste à utiliser hasOwnProperty, par conséquent la boucle for in est vingt fois plus lente qu'une boucle for classique.

Itération

Pour itérer sur les tableaux de façon performante, il est préférable d'utiliser la boucle for classique.

var list = [1, 2, 3, 4, 5, ...... 100000000];
for(var i = 0, l = list.length; i < l; i++) {
    console.log(list[i]);
}

Notez l'optimization supplémentaire dans l'exemple ci-dessus: la longueur du tableau est mise en mémoire "cached" via l = list.length.

La propriété length est définie sur le tableau lui-même, mais la rechercher à chaque itération de la boucle à un coût. Bien que les moteurs JavaScript récents peuvent appliquer l'optimisation, il n'y a aucun moyen de savoir si le code s'exécutera sur un de ces nouveaux moteurs.

En effet, mettre la longueur du tableau en mémoire cache peut doubler la vitesse d'execution de la boucle.

La propriété length

Le getter de la propriété length (longueur) renvoie simplement le nombre d'éléments contenus dans le tableau, mais le setter peut être utilisé pour tronquer le tableau.

var arr = [1, 2, 3, 4, 5, 6];
arr.length = 3;
arr; // [1, 2, 3]

arr.length = 6;
arr.push(4);
arr; // [1, 2, 3, undefined, undefined, undefined, 4]

Attribuer une longueur inférieure tronque le tableau. Accroître la longueur crée un tableau clairsemé.

En conclusion

Pour de meilleures performances, il est recommandé de toujours utiliser la boucle for classique et de mettre en mémoire la propriété length. L'utilisation de la boucle for in sur un tableau est un signe de code mal écrit, de mauvaise performance, et sujet à des bogues.

Le constructeur Array

Le constructeur Array traite ses paramètres de façon ambigu. Il est fortement recommandé d'utiliser le littéral de tableau - notation [] - pour créer de nouveaux tableaux.

[1, 2, 3]; // Résultat: [1, 2, 3]
new Array(1, 2, 3); // Résultat: [1, 2, 3]

[3]; // Résultat: [3]
new Array(3); // Résultat: []
new Array('3') // Résultat: ['3']

Dans les cas où il n'y a qu'un seul argument passé au constructeur Array, et quand cet argument est un nombre Number, le constructeur va retourner un nouveau tableau clairsemé avec la propriété length (longueur) fixée à la valeur de l'argument. Il faut noter que de cette façon, seulement la propriété length du nouveau tableau sera mise en place, les indices réels du tableau ne seront pas initialisés.

var arr = new Array(3);
arr[1]; // undefined
1 in arr; // faux, l'indice n'existe pas

Être en mesure de régler la longueur du tableau à l'avance n'est utile que dans quelques cas, comme la répétition d'une chaîne de caractères, dans lequel on évite l'utilisation d'une boucle.

new Array(count + 1).join(chaineARepeter);

En conclusion

Les littéraux sont préférés au constructeur Array. Ils sont plus courts, ont une syntaxe plus claire, et augmente la lisibilité du code.

Types

Égalité et comparaisons

JavaScript a deux façons de comparer les valeurs des objets pour égalité.

L'opérateur d'égalité

L'opérateur d'égalité se compose de deux signes égal: ==.

JavaScript est un langage au typage faible "weak typing". Cela signifie que l'opérateur d'égalité va convertir les deux opérandes en un même type afin de les comparer.

""           ==   "0"           // faux
0            ==   ""            // vrai
0            ==   "0"           // vrai
false        ==   "false"       // faux
false        ==   "0"           // vrai
false        ==   undefined     // faux
false        ==   null          // faux
null         ==   undefined     // vrai
" \t\r\n"    ==   0             // vrai

Le tableau ci-dessus montre les résultats de la coercition de type, et c'est la raison principale pourquoi l'utilisation de == est largement considéré comme une mauvaise pratique. Les règles de conversion de types compliquées introduisent des bogues difficiles à dépister.

L'opérateur d'égalité stricte

L'opérateur d'égalité stricte se compose de trois signes égal: ===.

Il fonctionne comme l'opérateur d'égalité normale, sauf que l'égalité stricte ne converti pas le types de ses opérandes.

""           ===   "0"           // faux
0            ===   ""            // faux
0            ===   "0"           // faux
false        ===   "false"       // faux
false        ===   "0"           // faux
false        ===   undefined     // faux
false        ===   null          // faux
null         ===   undefined     // faux
" \t\r\n"    ===   0             // faux

Les résultats ci-dessus sont beaucoup plus clairs et permettent la rupture précoce de code. Cela durcit le code jusqu'à un certain degré, et améliore la performance dans le cas où les opérandes sont de types différents.

Comparaison d'objets

Bien que == et === sont appelés opérateurs d'égalité, ils se comportent différement quand au moins un des opérandes est un objet Object.

{} === {};                   // faux
new String('foo') === 'foo'; // faux
new Number(10) === 10;       // faux
var foo = {};
foo === foo;                 // vrai

En effet, les deux opérateurs comparent l'identité et non pas l'égalité. Autrement dit, ils comparent pour la même instance de l'objet, tout comme is en Python, ou la comparaison de pointeur en C.

En conclusion

Il est fortement recommandé de n'utiliser que l'opérateur d'égalité stricte. Dans les cas où les types ont à être convertis, cela devraient être fait explicitement et non pas laissé aux règles complexes de coercition de type du langage.

L'opérateur typeof

L'opérateur typeof (avec instanceof) est probablement le plus grand défaut de design de JavaScript, car il est presque complètement cassé.

Bien que instanceof (instance de) a quelques utilisations limitées, typeof (type de) n'a qu'un seul cas pratique d'utilisation, et ce cas n'est pas la vérification du type d'un objet.

table de types JavaScript

Valeur              Class      Type
-------------------------------------
"foo"               String     string
new String("foo")   String     object
1.2                 Number     number
new Number(1.2)     Number     object
true                Boolean    boolean
new Boolean(true)   Boolean    object
new Date()          Date       object
new Error()         Error      object
[1,2,3]             Array      object
new Array(1, 2, 3)  Array      object
new Function("")    Function   function
/abc/g              RegExp     object (function pour Nitro/V8)
new RegExp("meow")  RegExp     object (function pour Nitro/V8)
{}                  Object     object
new Object()        Object     object

Dans la table ci-dessus, Type se réfère à la valeur retournée pas l'opérateur typeof, et comme on peut le voir clairement, cette valeur est incohérente.

Class se réfère à la valeur de la propriété interne [[Class]] d'un objet.

Pour récupérer la valeur de [[Class]], on peut utiliser la méthode toString de Object.prototype.

La classe d'un objet

La spécification donne exactement un moyen d'accéder à la valeur [[Class]]: via Object.prototype.toString.

function is(type, obj) {
    var clas = Object.prototype.toString.call(obj).slice(8, -1);
    return obj !== undefined && obj !== null && clas === type;
}

is('String', 'test'); // vrai
is('String', new String('test')); // vrai

Dans l'exemple ci-dessus, Object.prototype.toString est appelé avec la valeur this pointant sur l'objet dont on cherche a récupérer la valeur de [[Class]].

Test pour les variables indéfinies

typeof foo !== 'undefined'

Ce qui précède vérifie si foo a été effectivement déclarée. Juste référencer la variable résulterait en une erreur de référence ReferenceError. C'est la seule chose pour laquelle typeof est réellement utile.

En conclusion

Pour vérifier le type d'un objet, il est fortement recommandé d'utiliser Object.prototype.toString parce que c'est le seul moyen fiable de le faire. Comme représenté dans la table de type ci-dessus, certaines valeurs de retour de typeof ne sont pas définies dans la spécification; et donc, elles peuvent différer entre les implémentations.

If faut éviter d'utiliser typeof, sauf pour vérifier si une variable est définie.

L'opérateur instanceof

L'opérateur instanceof (instance de) compare les constructeurs de ses deux opérandes. Il est seulement utile pour comparer des objets faits sur mesure. Utilisé sur les types intégrés, il est aussi inutile que l'opérateur typeof.

Comparer des objets personnalisés

function Foo() {}
function Bar() {}
Bar.prototype = new Foo();

new Bar() instanceof Bar; // vrai
new Bar() instanceof Foo; // vrai

// Ceci définit simplement Bar.prototype à l'objet de fonction Foo,
// mais pas à une instance réelle de Foo
Bar.prototype = Foo;
new Bar() instanceof Foo; // faux

Utiliser instanceof avec des types natifs

new String('foo') instanceof String; // vrai
new String('foo') instanceof Object; // vrai

'foo' instanceof String; // faux
'foo' instanceof Object; // faux

Une chose importante à noter ici est que instanceof ne fonctionne pas sur les objets qui proviennent de différents contextes JavaScript (par exemple, différents documents dans un navigateur web), car leurs constructeurs ne seront pas exactement le même objet.

En conclusion

L'opérateur instanceof devrait seulement être utilisé sur des objets crées sur mesure provenant du même contexte JavaScript. Tout comme l'opérateur typeof, chaque autre utilisation de celui-ci devrait être évitée.

Changements de types

JavaScript est un langage faiblement typé, il appliquera la coercition de type partout où c'est possible.

// Ceux-ci sont vrais
new Number(10) == 10; // Objet Number est converti
                      // en un nombre primitif via un appel implicite
                      // à la méthode Number.prototype.valueOf

10 == '10';           // Strings est converti en Number
10 == '+10 ';         // Encore aussi fou
10 == '010';          // Et encore
isNaN(null) == false; // null est converti en 0
                      // ce qui, bien sûr, n'est pas NaN

// Ceux-ci sont faux
10 == 010;
10 == '-10';

Pour éviter les problèmes ci-dessus, l'utilisation de l'opérateur d'égalité stricte est fortement recommandé. Bien que cela évite beaucoup de pièges communs, il y en reste encore beaucoup. Tous ces pièges découlent de la faiblesse du système de typage de JavaScript.

Constructeurs de types internes

Les constructeurs de types internes comme Number et String se comportent différemment suivant s'ils sont utilisés avec ou sans le mot clé new.

new Number(10) === 10;     // Faux, Object et Number
Number(10) === 10;         // Vrai, Number et Number
new Number(10) + 0 === 10; // Vrai, due à la reconversion implicite

L'utilisation d'un type intégré comme Number en tant que constructeur va créer une nouvel objet Number. Mais sans le mot clé new, Number se comportera comme un convertisseur.

De plus, passer des valeurs littérales ou des non-objets se traduira par encore plus de coercition de type.

La meilleure option est de forcer explicitement le type à l'un des trois types possibles .

Forcer à String

'' + 10 === '10'; // vrai

En faisant précéder une chaîne vide, une valeur peut facilement être converti en une chaîne.

Forcer à Number

+'10' === 10; // vrai

L'utilisation de l'opérateur plus unaire converti une valeur en nombre.

Forcer à Boolean

L'utilisation double de l'opérateur non converti une valeur en booléen.

!!'foo';   // vrai
!!'';      // faux
!!'0';     // vrai
!!'1';     // vrai
!!'-1'     // vrai
!!{};      // vrai
!!true;    // vrai

Cœur

Il ne faut pas utiliser eval

La fonction eval exécute une chaîne de caractères représentant du code JavaScript dans la portée locale.

var number = 1;
function test() {
    var number = 2;
    eval('number = 3');
    return number;
}
test(); // 3
number; // 1

Cependant, eval n'exécute dans la portée locale que quand il est appelé directement et quand le nom de la fonction appelée est en fait eval.

var number = 1;
function test() {
    var number = 2;
    var copyOfEval = eval;
    copyOfEval('number = 3');
    return number;
}
test(); // 2
number; // 3

L'utilisation de la fonction eval doit être évitée. 99,9% de ses "cas d'utilisation" peuvent être obtenues sans elle.

eval déguisé

Les fonctions timeout setTimeout et setInterval acceptent une chaîne comme premier argument. Cette chaîne sera toujours exécutée dans la portée globale car dans ce cas, eval n'est pas appelé directement.

Problèmes de sécurité

eval est aussi un problème de sécurité, car il exécute n'importe quel code qu'on lui donne. Il devrait jamais être utilisé avec des chaînes d'origines inconnues ou douteuses.

En conclusion

eval ne devrait jamais être utilisé. Sa presence met en doute le fonctionnement, la performance, et la sécurité du code qui l'utilise. Si quelque chose a besoin d'eval pour pouvoir fonctionner, il ne doit pas être utilisé en premier lieu. Un meilleur design qui n'utilise pas eval doit être trouvé et implementé.

undefined et null

JavaScript a deux valeurs distinctes pour "rien": null et undefined, undefined étant la plus utile.

La valeur undefined

undefined est un type avec exactement une valeur:undefined.

Le langage définit également une variable globale qui a la valeur undefined. Cette variable est aussi appelée undefined. Cependant, cette variable n'est ni une constante, ni un mot clé du langage, ce que signifie que sa valeur peut être facilement écrasée.

Voici quelques exemples de cas où la valeur undefined est retournée:

  • Accès à la variable globale (non modifié) undefined.
  • Accès à une variable déclarée, mais pas encore initialisée.
  • Retours implicites de fonctions sans déclaration return.
  • Déclarations return vides, qui ne renvoient rien.
  • Recherches de propriétés inexistantes.
  • Paramètres de fonction qui ne ont pas de valeur explicite passée.
  • Tout ce qui a été mis à la valeur de undefined.
  • Toute expression sous forme de void(expression).

Changements à la valeur de undefined

Puisque la variable globale undefined contient uniquement une copie de la valeur réelle undefined, l'attribution d'une nouvelle valeur à la variable ne modifie pas la valeur du type undefined.

Pourtant, pour pouvoir comparer quelque chose contre la valeur de undefined, il est d'abord nécessaire pour récupérer la valeur de undefined.

Afin de protéger le code contre une éventuelle variable undefined écrasée, une technique commune utilisée consiste à ajouter un paramètre supplémentaire à une enveloppe anonyme et de lui passer aucun argument.

var undefined = 123;
(function(something, foo, undefined) {
    // undefined dans la portée locale 
    // réfère bien à la valeur `undefined`

})('Hello World', 42);

Une autre façon d'obtenir le même effet est d'utiliser une déclaration à l'intérieur de l'enveloppe.

var undefined = 123;
(function(something, foo) {
    var undefined;
    ...

})('Hello World', 42);

La seule différence étant quelques caractères de plus pour écrire "var".

Utilisation de null

Alors que undefined dans le contexte du langage JavaScript est utilisé dans la plupart des cas dans le d'un null traditionnel, le null réel (un littéral et un type) est juste un autre type de données.

null est utilisé par JavaScript (comme signaler la fin de la chaîne de prototypes avec Foo.prototype = null), mais dans presque tous les cas, il peut être remplacé par undefined.

Insertion automatique du point-virgule

Bien que JavaScript a une syntaxe de style C, il n'impose pas les points-virgules dans le code source. Il est donc possible de les omettre.

JavaScript n'est pas un langage sans points-virgules. En fait, les points-virgules sont necessaires pour comprendre le code source. Par conséquent, l'analyseur JavaScript les insère automatiquement chaque fois qu'il rencontre une erreur d'analyse due à un point-virgule manquant.

var foo = function() {
} // erreur d'analyse, point-virgule attendu
test()

L'analyseur insère un point-virgule, puis tente à nouveau.

var foo = function() {
}; // plus d'error, l'analyse continue
test()

L'insertion automatique du point-virgule est considérée comme l'un des plus gros défauts de conception dans le langage parce que cela peut changer le comportement du code.

Comment cela marche

Le code ci-dessous n'a pas de points-virgules, l'analyseur va donc décider où les insérer.

(function(window, undefined) {
    function test(options) {
        log('testing!')

        (options.list || []).forEach(function(i) {

        })

        options.value.test(
            'long string to pass here',
            'and another long string to pass'
        )

        return
        {
            foo: function() {}
        }
    }
    window.test = test

})(window)

(function(window) {
    window.someLibrary = {}

})(window)

Voici le résultat du jeu de devinette de l'analyseur.

(function(window, undefined) {
    function test(options) {

        // pas inséré, les lignes ont fusionné
        log('testing!')(options.list || []).forEach(function(i) {

        }); // <- inséré

        options.value.test(
            'long string to pass here',
            'and another long string to pass'
        ); // <- inséré

        return; // <- inséré, casse la déclaration return
        { // traité comme un bloc

            // un label et une déclaration d'expression
            foo: function() {} 
        }; // <- inséré
    }
    window.test = test; // <- inséré

// les lignes ont fusionné ici encore
})(window)(function(window) {
    window.someLibrary = {}; // <- inséré

})(window); //<- inséré

L'analyseur a radicalement changé le comportement du code ci-dessus. Dans certains cas, il fait la mauvaise chose.

Parenthèse en tête

En cas de parenthèse en tête, l'analyseur ne va pas insérer de point-virgule.

log('testing!')
(options.list || []).forEach(function(i) {})

Ce code fusionne en une ligne.

log('testing!')(options.list || []).forEach(function(i) {})

Il y a de très fortes chances que log ne retourne pas de fonction; par conséquent, le programme ci-dessus va produire une erreur de type TypeError indiquant que undefined n'est pas un function undefined is not a function.

En conclusion

Il est fortement recommandé de ne jamais omettre les points-virgules. Il est également recommandé de garder les accolades sur la même ligne que leurs déclarations correspondantes et de ne jamais les omettre pour les déclaration en une ligne if / else. Ces mesures vont non seulement améliorer la cohérence du code, mais elles empêcheront également l'analyseur JavaScript de changer le comportement du code.

L'opérateur delete

Il est impossible de supprimer les variables globales, fonctions et autres choses qui ont l'attribut DontDelete en JavaScript.

Le code global et le code de fonction

Quand une variable ou une fonction est définie dans la portée globale ou une portée de fonction, c'est une propriété soit de l'objet d'activation, soit de l'objet global. Ces propriétés ont un ensemble d'attributs, dont l'un est DontDelete. Les déclarations de variables et de fonctions dans le code global et le code de fonction vont toujours créer des propriétés avec DontDelete, elle ne peuvent donc pas être supprimées.

// global variable:
var a = 1; // DontDelete est mis
delete a; // faux
a; // 1

// normal function:
function f() {} // DontDelete is mis
delete f; // faux
typeof f; // "function"

// reassigner n'aide pas:
f = 1;
delete f; // faux
f; // 1

Propriétés explicites

Les propriétés crées explicitement peuvent être supprimées normalement.

// propriété crée explicitement:
var obj = {x: 1};
obj.y = 2;
delete obj.x; // vrai
delete obj.y; // vrai
obj.x; // undefined
obj.y; // undefined

Dans l'exemple ci-dessus, les propriétés obj.x et obj.y peuvent être supprimées parce qu'elles n'ont pas l'attribut DontDelete. C'est aussi pourquoi l'exemple ci-dessous fonctionne également.

// ceci fonctionne, sauf sur IE:
var GLOBAL_OBJECT = this;
GLOBAL_OBJECT.a = 1;
a === GLOBAL_OBJECT.a; // vrai - juste une var globale
delete GLOBAL_OBJECT.a; // vrai
GLOBAL_OBJECT.a; // undefined

Ici, nous utilisons une astuce pour supprimer a. this se réfère ici à l'objet global et nous déclarons explicitement la variable a comme sa propriété, ce qui nous permet de la supprimer.

IE (au moins 6-8) a quelques bogues, le code ci-dessus n'y fonctionne pas.

Les arguments de fonction et built-ins

Les arguments normaux de fonctions, objets arguments et les propriétés intégrées "built-in" ont aussi l'attribut DontDelete.

// les arguments de fonction et les propriétés:
(function (x) {

  delete arguments; // faux
  typeof arguments; // "object"

  delete x; // faux
  x; // 1

  function f(){}
  delete f.length; // faux
  typeof f.length; // "number"

})(1);

Objets hôtes

Le comportement de l'opérateur delete peut être imprévisible pour les objets hébergés "hosted". Dû à la spécification, les objets hôte sont autorisés à mettre en œuvre tout type de comportement.

En conclusion

L'opérateur delete a souvent un comportement inattendu et ne peut être utilisé que pour supprimer les propriétés explicitement définies sur des objets normaux.

Autres

setTimeout et setInterval

Puisque JavaScript est asynchrone, il est possible de programmer l'exécution future d'une fonction en utilisant les fonctions setTimeout et setInterval.

function foo() {}
var id = setTimeout(foo, 1000); // retourne un nombre > 0

Quand setTimeout est appelé, il renvoie l'identifiant de la temporisation et fixe la date de d'exécution de foo approximativement mille millisecondes dans le future. foo sera exécuté une seule fois.

La résolution de l'horloge du moteur JavaScript exécutant le code, le fait que JavaScript est mono-thread, et la possibilité qu'autre code en cours d'exécution peut bloquer le fil "thread", font qu'il n'est pas possible de déterminer le temps exact d'attente spécifié dans l'appel setTimeout.

La fonction passée en tant que premier paramètre sera appelé par l'objet global, ce qui signifie que this à l'intérieur de la fonction appelée fait référence à l'objet global.

function Foo() {
    this.value = 42;
    this.method = function() {
        // this réfère a l'boject global
        console.log(this.value); // enregistre undefined
    };
    setTimeout(this.method, 500);
}
new Foo();

Empilement des appels avec setInterval

setTimeout exécute la fonction une seule fois. setInterval - comme son nom le suggère - exécutera la fonction toutes les 'X' millisecondes, mais son utilisation est découragée.

Lorsque que du code en cours d'exécution bloque la temporisation, setInterval continuera a émettre plusieurs appels à la fonction spécifiée. Cela peut, en particulier avec un petit intervalle, résulter à un empilement d'appels de fonction.

function foo(){
    // qq chose qui bloque pendant 1 seconde
}
setInterval(foo, 100);

Dans le code ci-dessus, foo sera appelé une fois et bloquera pendant une seconde.

Pendant ce temps, setInterval va continuer à planifier les appels à la fonction. Quand foo se termine, il y aura déjà dix autres appels qui attendent pour s'exécuter.

Traiter le code bloquant éventuel

La solution la plus simple et qui offre le plus de contrôle est d'utiliser setTimeout dans la fonction elle-même.

function foo(){
    // qq chose qui bloque pendant 1 seconde
    setTimeout(foo, 100);
}
foo();

Non seulement cela encapsule l'appel setTimeout, mais il empêche également l'empilement des appels et donne un contrôle supplémentaire. La fonction foo elle-même peut maintenant décider si elle veut s'exécuter à nouveau ou non.

Effacer un délais d'attente

L'effacement des délais d'attente et des intervalles fonctionne en transmettant l'identifiant retourné par la fonction setTimeout ou setInterval à clearTimeout ou clearInterval, respectivement.

var id = setTimeout(foo, 1000);
clearTimeout(id);

Effacer tous les délais d'attente

Comme il n'existe pas de méthode intégrée pour effacer tous les délais d'attente et/ou intervalles, il est nécessaire d'utiliser la force brute pour obtenir cette fonctionnalité.

// Effacement de "tous" les délais d'attente
for(var i = 1; i < 1000; i++) {
    clearTimeout(i);
}

Mais il pourrait encore y avoir des délais d'attente qui ne sont pas effacés par ce nombre arbitraire. Une autre façon de faire découle du fait que l'identifiant donné à un délai d'attente est incrémenté à chaque fois que vous appelez setTimeout.

// Effacement de "tous" les délais d'attente
var identifiantLePlusGrand = window.setTimeout(function(){}, 1),
i;
for(i = 1; i <= identifiantLePlusGrand; i++) {
    clearTimeout(i);
}

Même si cela fonctionne sur tous les principaux navigateurs d'aujourd'hui, il ne est pas précisé que les identifiants doivent être ordonnés de cette façon et cela peut changer. Par conséquent, il est plutôt recommandé de garder une trace de tous les identifiant crées, pour qu'ils puissent être effacées spécifiquement.

Utilisation cachée de eval

setTimeout et setInterval peuvent également prendre une chaîne de caractères comme premier paramètre. Cette fonctionnalité ne devrait jamais être utilisée car elle utilise eval en interne.

function foo() {
    // sera appelé
}

function bar() {
    function foo() {
        // ne sera jamais appelé
    }
    setTimeout('foo()', 1000);
}
bar();

Puisque eval n'est pas appelé directement, la chaîne passé à setTimeout sera exécutée dans la portée globale; ainsi, la variable foo locale à bar ne sera pas utilisée.

Il est aussi recommandé de ne pas utiliser une chaîne pour passer des arguments à la fonction qui sera appelée par l'une des fonctions de temporisation.

function foo(a, b, c) {}

// ne JAMAIS faire cela
setTimeout('foo(1, 2, 3)', 1000)

// utiliser plutôt une fonction anonyme
setTimeout(function() {
    foo(1, 2, 3);
}, 1000)

En conclusion

Une chaîne ne devrait jamais être passée comme paramètre de setTimeout ou setInterval pour passer des arguments à la fonction appelée. C'est un clair signe de mauvais code. Il faut appeler une fonction anonyme qui se charge d'appeler la fonction réelle avec les arguments nécessaires.

En outre, l'utilisation de setInterval doit être évitée car son programmateur n'est pas bloqué par le code en train de s'exécuter.