ES3 en détail

Les types en JavaScript

Ce tutoriel fait partie de la collection ES3 dans le détail et en constitue le Chapitre 7. Il va traiter des six types existants en JavaScript dans sa version ES3.

14 commentaires Donner une note  l'article (5)

Article lu   fois.

L'auteur

Liens sociaux

Viadeo Twitter Facebook Share on Google+   

I. Introduction

Nous allons ici prendre en considération les types de données existants. Il est en premier lieu nécessaire de noter que JavaScript distingue les entités à valeurs primitives des objets. Malgré la phrase « en JavaScript, tout est objet » qu'il est possible de croiser au détour d'un article, il n'en est rien, ou plutôt, cela est partiellement correct. Les valeurs primitives sont en rapport avec les types de données. Nous allons en discuter plus en détail.

II. Types de données

Même si le JavaScript est un langage faiblement typé, il n'en reste pas moins un langage typé. C'est même un langage avec objets dynamiques et dont la conversion de type est pilotée par les opérandes utilisés : elle est automatique et implicite, on appelle cela de la coercion de type.

Le standard ES3 définit neuf types dont seulement six sont directement accessibles depuis un programme JavaScript :

  • Undefined ;
  • Null ;
  • Boolean ;
  • String ;
  • Number ;
  • Object.

Les trois autres types sont accessibles uniquement au niveau de l'implémentation (aucun objet en JavaScript ne possède ces types) et sont utilisés par la spécification pour expliquer le comportement de diverses opérations (comme le stockage de valeurs intermédiaires). Ce sont les trois types suivants :

  • Reference ;
  • List ;
  • Completion.

Pour faire court, Reference est le type utilisé pour expliquer le fonctionnement des opérateurs delete, typeof, this et bien d'autres. Il est constitué d'un objet base et d'un nom de propriété. Le type List décrit le comportement des listes d'arguments (lors de l'utilisation de l'opérateur new dans une expression ou dans les appels de fonctions). Le type Completion est quant à lui utilisé pour expliquer les comportements de break, continue, return et throw.

II-A. Les valeurs primitives

Revenons-en à nos six types utilisés dans les programmes JavaScript. Cinq d'entre eux sont des valeurs primitives : Undefined, Null, Boolean, String et Number.

En voici des exemples :

 
Sélectionnez
1.
2.
3.
4.
5.
var sephiroth = undefined;
var youfie = null;
var zack = true;
var cloud = 'test';
var xiii = 13;

Ces valeurs sont implémentées à un très bas niveau. Ce ne sont pas des objets, elles n'ont ni constructeurs, ni prototypes.

L'opérateur typeof peut être contre-intuitif s'il n'est pas proprement compris. L'un des meilleurs exemples est celui de la valeur null. Quand null est fourni à l'opérateur typeof, le résultat est "object", indépendamment du fait que la spécification indique que null est de type Null.

 
Sélectionnez
1.
console.log(typeof null); // `"object"`

La raison en est très simple. L'opérateur typeof retourne simplement une valeur lue depuis un tableau standard disant simplement : « pour la valeur null, la chaîne de caractères "object" doit être retournée ».

Cependant, la spécification ne clarifiant pas ce point, Brendan Eich, l'inventeur du JavaScript expliquera que la différence entre undefined et null s'explique justement avec le type Object, c’est-à-dire qu'il est intimement lié aux objets. Le fait est qu'une référence « vide » pour un objet peut être matérialisée par le type Null. Ce détail n'a pas été reporté dans la spécification ECMA-262-3 et actuellement le type Null est bien un type à part entière.

II-B. Le type Object

C'est au tour du type Object d'être parcouru (à ne pas confondre avec le constructeur Object dont nous ne parlerons pas dans ce tutoriel). C'est le seul type JavaScript qui représente des objets.

Un type Object est une collection non ordonnée de paires de clé-valeur.

Les clés d'un objet sont appelées des propriétés. Les propriétés sont des conteneurs pour des valeurs primitives ou d'autres objets. Si une propriété contient une fonction en tant que valeur (une fonction étant un type Object), on appelle alors cette propriété une méthode.

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
var team = { // objet `team` avec trois propriétés : `xiii`, `cloud` et `sephiroth`
    xiii: 13, // valeur primitive de type Number
    cloud: { hp: 302 }, // objet `cloud` avec la propriété `hp`
    sephiroth: function () { // méthode `sephiroth` (fonction objet)
        console.log("Je suis un glitch");
    }
};

console.log(team.xiii); // `13`
console.log(team.cloud); // `{ hp: 302 }`
console.log(team.cloud.hp); // `302`
team.sephiroth(); // `"Je suis un glitch"`

II-B-1. Nature dynamique

Les objets en JavaScript sont entièrement dynamiques. Cela signifie que nous pouvons ajouter, modifier ou supprimer les propriétés d'un objet à n'importe quel moment lors de l'exécution d'un programme.

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
var cloud = { hp: 302 };

// ajouter une nouvelle propriété
cloud.mp = 54;
console.log(cloud); // `{ hp: 302, mp: 54 }`

// changer la valeur d'une propriété en fonction
cloud.hp = function () {
    console.log("Ce sont les points de vie");
};

cloud.hp(); // `"Ce sont les points de vie"`

// supprimer une propriété
delete cloud.hp;
console.log(cloud); // `{ mp: 54 }`

Plusieurs propriétés ne peuvent pas être modifiées (read-only) ou supprimées (non-configurable). Nous en parlerons dans la section consacrée aux attributs de propriétés.

Notons que ES5 standardise les objets statiques ne pouvant pas être étendus avec de nouvelles propriétés, modifiés ou supprimés. Ils sont appelés objets gelés (« frozen ») et peuvent être gelés avec la méthode Object.freeze.

 
Sélectionnez
var cloud = { hp : 302 };

Object.defineProperty(cloud, "mp", {
    value: 54,
    writable: false, // lecture seule
    configurable: false // non configurable
});

// ne peut être modifié
cloud.mp = 0;

// ne peut être supprimé
delete cloud.mp; // `false`

// empêcher l'extension
Object.preventExtensions(cloud);
console.log(Object.isExtensible(cloud)); // `false`

// on ne peut plus ajouter de nouvelles propriétés
cloud.level = 1;

console.log(cloud); `{ hp: 302, mp: 54 }`

Il est également possible de seulement empêcher l'extension en utilisant la méthode Object.preventExtensions ou de contrôler spécifiquement les attributs avec la méthode Object.defineProperty :

 
Sélectionnez
var cloud = { hp: 302 };

// geler l'objet
Object.freeze(cloud);
console.log(Object.isFrozen(cloud)); // `true`

// il n'est ni modifiable
cloud.hp = 0;

// ni extensible
cloud.mp = 54;

// ni supprimable
delete cloud.hp;
console.log(cloud); // `{ hp: 302 }`

II-B-2. Objets préconçus, natifs et hôtes

Il est nécessaire que la spécification distingue les objets natifs des objets préconçus et des objets hôtes.

Les objets préconçus et natifs sont définis par la spécification ECMAScript. La différence exacte entre les deux est minime : les objets natifs sont ceux fournis par l'implémentation de JavaScript. Certains d'entre eux sont préconçus et d'autres sont créés pendant l'exécution du programme (comme les objets définis par l'utilisateur).

Ainsi les objets préconçus en amont sont un sous-type des objets natifs. Ils sont créés par JavaScript en début de programme (par exemple parseInt, Math, etc.).

Tous les objets hôtes sont des objets fournis par l'environnement hôte. On aura donc dans un navigateur, par exemple, window, console.log, etc.

Notez que ces objets hôtes peuvent être implémentés en utilisant intégralement la sémantique de la spécification ECMAScript. Ils sont en quelque sorte des objets « hôtes-natifs » (terme non utilisé dans la spécification).

II-B-3. Les objets Boolean, String et Number

En plus des trois primitives du même nom, la spécification définit des objets englobants spéciaux. Ce sont les trois objets suivants :

  • L'objet Boolean ;
  • L'objet String ;
  • L'objet Number.

Chaque objet peut être créé avec son constructeur interne et contient dans l'une de ses propriétés internes la valeur primitive correspondante. Ainsi la représentation des objets peut être fournie en valeurs primitives et inversement.

Exemple de valeurs d'objets correspondant aux types primitifs :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
var zack = new Boolean(true);
var cloud = new String('test');
var xiii = new Number(13);

// conversion en primitive
// utilisation de `ToPrimitive`
// il faut pour cela appliquer
// la fonction sans utiliser l'opérateur `new`
zack = Boolean(zack);
cloud = String(cloud);
xiii = Number(xiii);

// revenir à l'objet
// utilisation de `ToObject`
zack = Object(zack);
cloud = Object(cloud);
xiii = Object(xiii);

De plus, il y a aussi des objets créés par des constructeurs préconçus spéciaux comme Function (l'objet constructeur des fonctions), Array (l'objet constructeur des tableaux), RexExp (l'objet constructeur des expressions régulières), Math (le module de mathématiques), Date (l'objet constructeur des dates), etc. Ces objets sont aussi du type Object et la distinction entre les uns et les autres est gérée par des propriétés internes dont nous allons discuter plus tard.

II-B-4. Notations littérales

Pour trois valeurs d'objets : Object, Array et RegExp, il existe une notation courte qui appelle respectivement un initialiseur d'objet, un initialiseur de tableau et un initialiseur d'expression régulière :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
// équivalent de `var reactors = new Array(1, 5);`
/* ou `var reactors = new Array();
       reactors[0] = 1;
       reactors[1] = 5;` */
var reactors = [1, 5];

// équivalent de
/* `var location = new Object();
    location.north = 'Midgar';
    location.west = 'Junon';
    location.south = 'Fort Condor';` */
var location = { north: 'Midgar', west: 'Junon', south: 'Fort Condor' };

// équivalent de `var ffvii = new RegExp("^\\d+$", "g");`
var ffvii = /^\d+$/g;

Notons que dans le cas où nous réaffectons la liaison entre Object, Array ou RegExp à d'autres objets, le résultat de la notation littérale qui en découle peut varier d'une implémentation à l'autre. Par exemple, avec une implémentation, Rhino ou une vieille implémentation SpiderMonkey, les notations littérales vont créer un objet correspondant à la nouvelle valeur du nom de constructeur :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
var getClass = Object.prototype.toString;

// Avec le nom Object
Object = Number;

var busterSword = new Object;
console.log([busterSword, getClass.call(busterSword)]); // `0`, `"[object Number]"`

var ultimaWeapon = {};

// dans Rhino ou SpiderMonkey 1.7 : `0`, `"[object Number]"`
// pour les autres : toujours `"[object Object]"`, `"[object Object]"`
console.log([ultimaWeapon, getClass.call(ultimaWeapon)]);

// la même chose avec l'objet Array
Array = Number;

var limits = new Array;
console.log([limits, getClass.call(limits)]); // `0`, `"[object Number]"`

var materias = [];

// dans Rhino ou SpiderMonkey 1.7 : `0`, `"[object Number]"`
// dans les autres : toujours `""`, `"[object Object]"`
console.log([materias, getClass.call(materias)]);

// mais pour `RegExp`, la sémantique littérale
// reste inchangée dans toutes les implémentations
RegExp = Number;

ffvii = new RegExp;
console.log([ffvii, getClass.call(ffvii)]); // `0`, `"[object Number]"`

ff7 = /(?!)/g;
console.log([ff7, getClass.call(ff7)]); // `/(?!)/g`, `"[object RegExp]"`

II-B-5. L'objet RegExp et l'expression régulière littérale

Notons également qu'en ES3, les deux derniers cas avec les expressions régulières sont identiques dans ce qu'ils accomplissent, néanmoins ils diffèrent sur un point. L'expression régulière littérale existe uniquement dans une instance et est créée pendant la phase d'exécution du code, alors que le constructeur RegExp crée toujours un nouvel objet. Cela peut causer quelques problèmes, par exemple avec la propriété lastIndex des objets RegExp :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
for (var k = 0; k < 4; k++) {
    var ffvii = /FF/g;
    console.log(ffvii.lastIndex); // `0`, `2`, `0`, `2`
    console.log(ffvii.test("FF7")); // `true`, `false`, `true`, `false`
}

// par contraste avec

for (var k = 0; k < 4; k++) {
    var ffvii = new RegExp("FF", "g");
    console.log(ffvii.lastIndex); // `0`, `0`, `0`, `0`
    console.log(ffvii.test("FF7")); // `true`, `true`, `true`, `true`
}

Notons qu'en ES5 cette particularité a été résolue et ainsi toutes les expressions régulières littérales retournent toujours de nouveaux objets.

II-B-6. Tableaux associatifs ?

Souvent dans divers articles et discussions, les objets JavaScript (et plus particulièrement ceux créés via la forme littérale (ou déclarative) avec l'initialiser {}) sont appelés des tableaux de hachages (« hash-tables ») ou simplement des hashs en Ruby ou Perl, tableaux associatifs (« associative array ») en PHP, dictionnaires en Python, etc.

L'utilisation de ces terminologies est une habitude de nommage venant de ces technologies. Le concept est très similaire à celui d'un objet et respecte le même principe de paire « clé-valeur ». Ils correspondent ainsi à la description théorique des structures de données que sont les tableaux associatifs ou les tableaux de hachages. Cependant, un type de donnée « tableau de hachage » est habituellement utilisé à un niveau implémentation.

Donc, même si cette terminologie est utilisée pour décrire conceptuellement ce mécanisme, il n'est pas techniquement juste en JavaScript de l'utiliser pour parler d'un objet. Comme il a déjà été mentionné plus haut, JavaScript ne possède qu'un seul type d'objet et tous ses « sous-types » respectent l'approche de stockage de paire « clé-valeur ». Cela n'est jamais différent d'un « sous-type » à l'autre. Il n'y a donc pas de terme spécifique pour décrire un objet en termes de « hash ». N'importe quel objet, indépendamment de ses propriétés internes peut stocker des paires :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
var cloud = { hp: 302 };
cloud['mp'] = 54;
cloud.level = 1;

var xiii = new Number(12);
xiii.hp = 564;
xiii.mp = 98;
xiii['level'] = 5;

var jenova = new Function('');
jenova.hp = 400;
jenova.mp = 110;
jenova['level'] = 25;

// etc. avec n'importe quel "sous-type" d'objet.

II-B-7. De plus, les objets en JavaScript ne sont jamais uniquement une paire de clé-valeur non vide, le terme « hash » est donc mal choisi :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
Object.prototype.world = 7;

var game = {}; // créer un "hash" vide

console.log(game["world"]); // `7`, qui n'est pourtant pas vide
console.log(game.toString); // `function`

game["type"] = 'RPG'; // ajouter une nouvelle paire au "hash"
console.log(game["type"]); // `'RPG'`

Object.prototype.type = 'RPG'; // la propriété se retrouvera dans le prototype

delete game["type"]; // supprimé `type`
console.log(game["type"]); // `'RPG'`, mais la clé et la valeur sont toujours 

Notons qu'avec ES5, la possibilité de créer des objets vierges sans prototype (c'est-à-dire que leur prototype est mis à null) a été ajoutée. On peut faire cela en utilisant la méthode Object.create(null). De ce point de vue, un objet peut être interprété comme un simple hash :

 
Sélectionnez
1.
2.
var aHashTable = Object.create(null);
console.log(aHashTable.toString); // `undefined`

Un autre point à noter est que certaines propriétés peuvent avoir uniquement un accesseur ou un mutateur, ce qui rend le tout confus :

 
Sélectionnez
1.
2.
3.
var name = new String("ff7");
name['length'] = 10;
console.log(name['length']); // `3`

Et même si l'on considère qu'un hash peut avoir un « prototype » (comme en Ruby ou Python), en JavaScript cette terminologie est aussi non conforme, car il n'y a aucune distinction entre les types d'accesseur de propriété (c’est-à-dire entre le point ou les crochets droits).

Ainsi le concept de « propriété » en JavaScript n'est pas différencié du principe de « clé », d'« index de tableau », de « méthode » ou de « propriété ». Ce sont toutes des propriétés qui obéissent à la loi commune d'un algorithme de lecture/écriture en examinant la chaîne de prototype.

Dans l'exemple suivant, en Ruby, on voit bien cette distinction sémantique :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
game = {}
game.class # Hash

game.length # `0`

# nouvelle paire de "clé-valeur"
game['length'] = 7;

# mais la sémantique de la notation du point
# est différente et signifie accéder
# à la "propriété / méthode", mais pas à la "clé"

game.length # `1`

# et la notation crochet
# fournit un accès à la "clé" du hash

game['length'] # `7`

# nous pouvons augmenter dynamiquement la classe Hash
# avec de nouvelles propriétés et méthodes et cette délégation
# sera disponible dans les objets déjà créés

class Hash
    def score
        100
    end
end

# nouvelle "propriété" disponible dans `game`

game.score # `100`

# mais pas la "clé"

a['score'] # `nil`

Pour conclure, le standard ECMA-262-3 ne définit pas de concept de « hash » (ou similaire). Cependant, puisque cette structure de données colle à la théorie, il est possible de nommer les objets ainsi.

II-B-8. Conversion de type

Pour convertir un objet en une valeur primitive, la méthode valueOf peut être utilisée. Comme nous l'avons noté, l'appel du constructeur (pour certains types) en tant que fonction (c'est-à-dire sans l'opérateur new) applique la conversion d'un objet en son équivalent en valeur primitive. Pour cette conversion dite implicite, l'appel de la méthode valueOf est effectué :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
var game = new Number(7);
var primitiveGame = Number(game); // appel implicite de `valueOf`
var alsoPrimitiveGame = game.valueOf(); // appel explicite de `valueOf`

console.log([
    typeof game, // `"object"`
    typeof primitiveGame, // `"number"`
    typeof alsoPrimitiveGame // `"number"`
]);

Cela permet aux objets de participer à des opérations variées, par exemple aux additions :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
var ps1 = new Number(7);
var ps3 = new Number(13);

console.log(ps1 + ps3); // `20`

// ou même ainsi

var playstation = {
    ps1: 7,
    ps2: 10,
    valueOf: function () {
        return this.ps1 + this.ps2;
    }
};

var squaresoft = {
    ps1: 7,
    ps2: 13,
    // affectation de la même fonctionnalité
    // `valueOf` que l'objet `playstation`
    valueOf: playstation.valueOf
};

console.log(playstation + squaresoft); // `40`

La valeur de la méthode valueOf par défaut (si elle n'est pas réécrite) peut varier en fonction du type d'objet. Pour beaucoup d'objets, cela retourne la valeur de this. Par exemple :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
var soldier = {};
console.log(soldier.valueOf() === soldier); // `true`, `valueOf` de `Object` retourne la valeur `this`

// mais pas pour `Date`

var jenovasFall = new Date();
console.log(jenovasFall.valueOf()); // le temps sous forme de Number
console.log(jenovasFall.valueOf() === jenovasFall.getTime()); // `true`

Il y a aussi une autre représentation primitive d'un objet, une représentation sous forme de chaîne de caractères. Pour cela, c'est la méthode toString qui est utilisée, et qui est automatiquement appelée pour certaines opérations :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
var game = {
    valueOf: function () {
        return 13;
    },
    toString: function () {
        return 'ff';
    }
};

// dans cette opération
// la méthode `toString` est
// appelée automatiquement
console.log(game); // `"ff"`

// mais ici, c'est la méthode `valueOf`
console.log(game + 7); // `20`

// mais s'il n'existe pas de méthode
// `valueOf` elle sera
// remplacée par
// la méthode `toString`
delete game.valueOf;
console.log(game + 7); // `"ff7"`

La méthode toString définie dans Object.prototype a une signification spéciale. Elle retourne la valeur de la propriété interne [[Class]] dont nous avons discuté plus haut.

De même qu'il y a la conversion ToPrimitive, il y a la conversion ToObject qui à l'inverse transforme une primitive en un type Object.

L'une des manières explicites d'appeler ToObject est d'utiliser le constructeur de Object en tant que fonction (ou avec new dans certains cas).

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
var xiii = Object(13); // `[object Number]`
var cloud = Object('test'); // `[object String]`
var zack = Object(true); // `[object Boolean]`

// il est aussi possible pour certains types d'appeler
// `Object` avec l'opérateur `new`
var Zack = new Object(true); // `[object Boolean]`

// mais en l'utilisant sans argument,
// `new Object()` crée un simple objet
var caitSith = new Object(); // `[object Object]`

// dans le cas  l'argument pour la fonction `Object`
// est déjà une valeur objet,
// l'objet sera retourné en tant que tel
var reactors = [];
console.log(reactors === new Object(reactors)); // `true`
console.log(reactors === Object(reactors)); // `true`

L'appel d'un constructeur avec new ou sans new ne suit aucune règle générale particulière, cela dépend tout simplement du constructeur. Par exemple les constructeurs Array et Function retournent le même résultat quand leurs constructeurs sont appelés avec new ou en tant que simple fonction (sans new) :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
var aerith = Array(1, 2, 3); // `[object Array]`
var ancient = new Array(1, 2, 3); // `[object Array]`
var flowergirl = [1, 2, 3]; // `[object Array]`

var calamity = Function(''); // `[object Function]`
var jenova = new Function(''); // `[object Function]`

Il y a aussi des conversions explicites et implicites quand certains opérateurs sont utilisés :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
var xiii = 4;
var barret = 2;

// implicite
var legs = xiii + barret; // `6`, type Number
var meaningOfLife = '' + xiii + barret; // `"42"`, type String

// explicite
var game = '7'; // `"7"`, type String
var num = +game; // `7`, type Number
var numAgain = parseInt(game, 10); // `7`, type Number

// etc.

II-B-9. Attributs de propriété

Toutes les propriétés peuvent avoir un nombre d'attributs comme suit :

  • {ReadOnly} — toute tentative d'écrire une valeur dans la propriété est ignorée ; cependant, la propriété de lecture seule peut-être changée par une action de l'environnement hôte, ainsi lecture seule ne veut pas dire « valeur constante » ;
  • {DontEnum} — la propriété ne peut pas être énumérée dans une boucle for..in ;
  • {DontDelete} — l'action de l'opérateur delete appliquée est ignorée ;
  • {Internal} — la propriété est interne, elle n'a pas de nom et est utilisée uniquement au niveau de l'implémentation, aussi ces propriétés ne sont pas accessibles en JavaScript.

Notez qu'en ES5 les propriétés {ReadOnly}, {DontEnum} et {DontDelete} ont été renommées en [[Writable]], [[Enumerable]] et [[Configurable]] et celles-ci peuvent être manuellement gérées via la méthode Object.defineProperty ou d'autres méthodes similaires :

 
Sélectionnez
var cloud = {};

Object.defineProperty(cloud, "hp", {
    value: 302,
    writable: true, // aka `{ReadOnly} = false`
    enumerable: false, // aka `{DontEnum} = true`
    configurable: true // aka `{DontDelete} = false`
});

console.log(cloud.hp); // `302`

// ce lot d'attributs est appelé un descripteur
var desc = Object.getOwnPropertyDescriptor(cloud, "hp");

console.log(desc.enumerable); // `false`
console.log(desc.writable); // `true`
// etc.

II-B-10. Propriétés et méthodes internes

Les objets possèdent également un certain nombre de propriétés qui sont inaccessibles depuis un programme JavaScript directement (cependant comme nous allons le voir plus bas, certaines implémentations permettent d'avoir accès à ses propriétés). Ces propriétés sont entourées des doubles crochets droits par convention : [[ ]].

Nous allons nous intéresser à certaines d'entre elles (qui sont obligatoires pour tous les objets) : la description d'autres propriétés pouvant être trouvée dans la spécification.

Chaque objet doit implémenter les propriétés et méthodes internes suivantes :

  • [[Prototype]] — le prototype de cet objet (il sera étudié plus en détail plus bas) ;
  • [[Class]] — une représentation en chaîne de caractères du type (par exemple Object, Array, Function, etc.). Il est utilisé pour distinguer les objets ;
  • [[Get]] — une méthode pour donner la valeur de la propriété ;
  • [[Put]] — une méthode pour affecter la valeur de la propriété ;
  • [[CanPut]] — indique de quelle manière cette propriété peut être réécrite ;
  • [[HasProperty]] — indique si cette propriété appartient déjà à l'objet ;
  • [[Delete]] — retire la propriété de l'objet ;
  • [[DefaultValue]] — retourne une valeur primitive correspondant à l'objet (pour donner cette valeur, la méthode valueOf est appelée, pour certains objets, l'exception TypeError peut être levée).

Pour obtenir la propriété [[Class]] d'un programme JavaScript il est possible de la lire indirectement via la méthode Object.prototype.toString(). Cette méthode devrait retourner la chaîne de caractères suivante : "[object " + [[Class]] + "]". Par exemple :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
var getClass = Object.prototype.toString;

getClass.call({}); // `[object Object]`
getClass.call([]); // `[object Array]`
getClass.call(new Number(1)); // `[object Number]`

// etc.

Cette fonctionnalité est souvent utilisée pour vérifier le type de l'objet, cependant, il est nécessaire de préciser que la spécification autorise n'importe quelle chaîne de caractères valide et qu'il n'est pas sûr que tous les retours respectent cette convention. Par exemple, la propriété [[Class]] de la méthode document.childNodes.item(...) retourne "String" dans des vieux IE (et dans d'autres implémentations "Function" est retourné) :

 
Sélectionnez
1.
2.
// dans de vieux IE : `"String"`, dans d'autres `"Function"`
console.log(getClass.call(document.childNodes.item));

III. Conclusion

Nous venons de voir que le typage en JavaScript était loin d'être trivial. Les détails vus ici vont nous permettre de nous attaquer aux parties plus complexes du type Object, à savoir le constructeur et le prototype.

IV. Remerciements

Nous remercions Bruno Lesieur qui nous a autorisés à publier ce tutoriel.

Nous remercions également Laethy pour la mise au gabarit et f-leb pour la correction orthographique.

Vous avez aimé ce tutoriel ? Alors partagez-le en cliquant sur les boutons suivants : Viadeo Twitter Facebook Share on Google+   

  

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 © 2018 Bruno Lesieur. Aucune reproduction, même partielle, ne peut être faite de ce site ni 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.