Êtes-vous nouveau dans la programmation js, si c'est le cas, vous pourriez être frustré. Tous les langages ont leurs particularités - mais les développeurs qui passent d'un langage côté serveur basé sur un typage fort peuvent être confus. J'étais comme ça, il y a quelques années, lorsque j'ai été poussé à devenir développeur JavaScript à plein temps, et il y avait beaucoup de choses que j'aurais aimé savoir au début. Dans cet article, je vais partager certaines de mes bizarreries et j'espère pouvoir partager avec vous certaines des expériences qui m'ont donné beaucoup de maux de tête. Ceci n’est pas une liste complète – juste une liste partielle – mais j’espère qu’elle vous ouvrira les yeux sur la puissance de ce langage et sur des choses que vous avez peut-être autrefois considérées comme un obstacle.
Nous examinerons les techniques suivantes :
Égal
Point vs crochets
Contexte de la fonction
-
Déclaration de fonction vs expression de fonction
Fonction nommée vs anonyme
Exécuter immédiatement l'expression de fonction
typeof vs Object.prototype.toString
1.) Égalité Venant de C#, je connais très bien l'opérateur de comparaison ==. Les types valeur (ou chaînes) sont égaux lorsqu’ils ont la même valeur. L’égalité des types de référence nécessite d’avoir la même référence. (Nous supposons que vous ne surchargez pas l'opérateur == ou n'implémentez pas votre propre opérateur d'égalité et la méthode GetHashCode.) Je suis surpris de savoir pourquoi JavaScript a deux opérateurs d'égalité : == et ===. Au départ, la plupart de mon code utilisait ==, donc je n'avais aucune idée de ce que JavaScript faisait pour moi lorsque j'exécutais :
-
var x = 1;
if(x == "1") {
console.log("YAY ! Ils sont égaux !");
-
}
Est-ce de la magie noire ? Comment l’entier 1 est-il égal à la chaîne « 1 » ?
En JavaScript, il existe l'égalité (==) et l'égalité stricte (===). L'opérateur d'égalité effectuera une comparaison d'égalité stricte après avoir contraint les deux opérandes au même type. Ainsi, dans l'exemple ci-dessus, la chaîne "1" sera convertie en entier 1, ce processus se déroule en arrière-plan, puis comparé à la variable x.
L'égalité stricte n'effectue pas de conversion de type. Si les types d'opérandes sont différents (comme entier et chaîne), alors ils ne sont pas congruents (strictement égaux).
var x = 1;
// Egalité stricte, les types doivent être les mêmes
if(x === "1") {
console.log("Malheureusement, je n'écrirai jamais ceci sur la console");
}
if(x === 1) {
console.log("OUI ! Strict Égalité FTW.")
}
Vous pensez peut-être à toutes les horreurs causées par d'éventuels lancers - en supposant qu'un tel casting se produise dans votre référence, il peut être très difficile de trouver où se situe le problème. Cela n’est pas surprenant et c’est pourquoi les développeurs JavaScript expérimentés recommandent toujours d’utiliser une égalité stricte.
2.) Période vs parenthèses Selon l'autre langue dont vous êtes originaire, vous pouvez ou non avoir vu cela faire (c'est juste un non-sens ).
// Récupère la valeur firstName de l'objet personne
var name = person.firstName;
// Obtenez le tableau Le troisième élément de
var theOneWeWant = myArray[2] // rappelez-vous, l'index basé sur 0 n'oublie pas le premier element L'index est 0
Cependant, saviez-vous qu'il peut également référencer les membres d'un objet en utilisant des parenthèses ? Par exemple :
var name = person["firstName"];
Pourquoi est-ce utile ? Même si vous utiliserez la notation par points la plupart du temps, il existe quelques cas de parenthèses qui permettent à certaines méthodes d'échouer. Par exemple, je refactorise souvent des instructions switch volumineuses dans un planning, donc quelque chose comme ceci :
Pourquoi cela fonctionne-t-il ? Vous êtes peut-être plus familier avec l'utilisation des points auparavant, mais il existe quelques cas particuliers où seule la notation entre crochets est disponible. Par exemple, je refactorise souvent l'instruction switch dans une table de recherche (plus rapide), qui ressemble en réalité à ceci :
var doSomething = function(doWhat) {
switch(doWhat) {
cas "doThisThing":
// plus de code...
-
pause; // plus de code...
pause;
cas "doThisOtherThing":
// plus de code....
-
pause;
🎜>
comportement par défaut
pause;
}
}
可以转化为像下面这样:
var chosesWeCanDo = {
doThisThing : function() { /* behavior */ },
doThatThing : function() { /* comportement */ },
doThisOtherThing : function() { /* comportement */ },
par défaut : function() { /* comportement */ }
};
var doSomething = function(doWhat) {
var thingToDo = ThingsWeCanDo.hasOwnProperty(doWhat) ? doWhat : "par défaut"
chosesWeCanDo[thingToDo]();
}
interrupteur de commutation并且非常关注性能,switcher le commutateur.许你的属性延时求值。
3.) 函数上下文 已经有一些伟大的博客发表了文章,正确理解了JavaScript中的this上下文(在文章的结尾我会给出一些不错的链接),但它确实应该加到« 我希望我知道 »的列表。它真的困难看懂代码并且自信的知道在任何位置this的值——你仅需要学习一组规则。不幸的是,我早起读到的许多解释
第一——首先考虑全局情况(Global) 默认Il s'agit d'une fenêtre象(或在node.js中为global)。
第二——方法中的this值 当你有一个对象,其有一个函数成员,冲父对象调用这方法,this的值将指向父对象。例如:
var marty = {
prénom : "Marty",
nom de famille : "McFly",
timeTravel : function(année) {
-
console.log(this.firstName + " " + this.lastName + " est voyage dans le temps jusqu'à "+ année);
-
marty.timeTravel(1955);
-
/ / Marty McFly voyage dans le temps jusqu'en 1955
- Vous savez probablement déjà que vous pouvez référencer la méthode timeTravel d'un objet Marty et créer une nouvelle référence à un autre objet. Il s'agit en fait d'une fonctionnalité très puissante de JavaScript, nous permettant de référencer le comportement (appel de fonctions) sur différentes instances.
- var doc = {
prénom : "Emmett ",
- nom de famille : "Marron",
- >
-
doc.timeTravel = marty.timeTravel;
-
Alors, que se passera-t-il si nous appelons doc.timeTravel(1885) ?
- doc.timeTravel(1885);
/ / Emmett Brown voyage dans le temps jusqu'en 1885
-
Encore une fois, il exécute de la magie noire. Eh bien, pas vraiment. N'oubliez pas que lorsque vous appelez une méthode, ce contexte est l'objet parent du parent de la fonction appelée.
Que se passe-t-il lorsque nous enregistrons une référence à la méthode marty.TimeTravel et que nous appelons ensuite notre référence enregistrée ? Voyons :
var getBackInTime = marty.timeTravel;
getBackInTime(2014);
- // undéfini undéfini est un voyage dans le temps jusqu'en 2014
- Pourquoi est-ce « indéfini indéfini » ? ! Au lieu de « Matry McFly » ?
Posons une question clé : quel est l'objet parent/conteneur lorsque nous appelons notre fonction getBackInTime ? Lorsque la fonction getBackIntTime existe dans la fenêtre, nous l'appelons comme une fonction et non comme une méthode objet. Lorsque nous appelons une fonction comme celle-ci – sans objet conteneur – ce contexte sera l’objet global. David Shariff en a une excellente description :
Chaque fois que nous appelons une fonction, nous devons immédiatement regarder à gauche des parenthèses. S'il y a une référence sur le côté gauche du crochet, alors la valeur transmise à la fonction appelante est déterminée comme étant l'objet auquel appartient la référence, sinon il s'agit d'un objet complet.
Puisque le contexte de getBackInTime est une fenêtre - il n'y a pas de propriétés firstName et lastName - cela explique pourquoi nous voyons "undefined undefined".
Nous savons donc qu'en appelant directement une fonction - sans objet conteneur - le résultat de ce contexte est l'objet global. Cependant, j'ai aussi dit que je savais déjà que notre fonction getBackInTime existait sur la fenêtre. Comment puis-je le savoir ? D'accord, contrairement à ci-dessus où j'ai enveloppé getBackInTime dans un contexte différent (nous parlions de l'exécution immédiate des expressions de fonction), toutes les variables que j'ai déclarées ont été ajoutées à la fenêtre. Vérification depuis la console Chrome :
Il est temps de discuter du point principal de cela L’un des domaines où cela s’avère utile : le traitement des événements d’abonnement.
Troisième (juste une extension de #2) - cette valeur dans les méthodes d'appel asynchrone Alors, supposons que nous voulions appeler notre méthode marty.timeTravel Quand quelqu'un clique sur un bouton :
var flux = document.getElementById("flux-capacitor");
flux.addEventListener("click", marty.timeTravel);
Dans le code ci-dessus, lorsque l'utilisateur clique sur le bouton, nous verrons « undéfini, undéfini est un voyage dans le temps vers [objet MouseEvent] ». Quoi? OK - tout d'abord, le problème très évident est que nous n'avons pas fourni le paramètre year à notre méthode timeTravel. Au lieu de cela, nous nous abonnons directement à cette méthode en tant que gestionnaire d'événements, et le paramètre MouseEvent est transmis comme premier argument au gestionnaire d'événements. C'est facile à résoudre, mais le vrai problème est que nous voyons à nouveau « undéfini undéfini ». Ne soyez pas désespéré : vous savez déjà pourquoi cela se produit (même si vous ne vous en rendez pas encore compte). Modifions notre fonction timeTravel pour afficher ceci afin de nous aider à comprendre les faits :
marty.timeTravel = function(year) {
console.log(this.firstName + " " + this.lastName + " est un voyage dans le temps jusqu'à " + année);
console.log(this);
};
Maintenant, lorsque nous cliquons sur ce bouton, nous aurons un résultat similaire à celui-ci dans la console de votre navigateur :
Lorsque la méthode est appelée, le deuxième console.log affiche ce contexte - qui est en fait l'élément de bouton auquel nous sommes abonnés à l'événement. Êtes-vous surpris? Tout comme avant - lorsque nous avons attribué marty.timeTravel à la variable getBackInTime - la référence à marty.timeTravel est enregistrée dans le gestionnaire d'événements et appelée, mais l'objet conteneur n'est plus un objet marty. Dans ce cas, il sera appelé de manière asynchrone sur l'événement click de l'instance du bouton.
Alors, est-il possible de régler cela sur le résultat souhaité ? Absolument! Dans ce cas, la solution est très simple. Au lieu de vous abonner à marty.timeTravel directement dans le gestionnaire d'événements, utilisez une fonction anonyme comme gestionnaire d'événements et appelez marty.timeTravel dans la fonction anonyme. Cela peut également résoudre le problème des paramètres d’année manquants.
flux.addEventListener("clic", fonction(e) {
marty.timeTravel(someYearValue);
});
Cliquer sur le bouton affichera des informations similaires aux suivantes sur la console :
Succès ! Mais pourquoi est-ce que ça va ? Pensez à la façon dont nous appelons la méthode timeTravel. Dans notre premier exemple de clic sur un bouton, nous avons souscrit à une référence à la méthode elle-même dans le gestionnaire d'événements, elle n'a donc pas été appelée depuis le marty parent. Dans le deuxième exemple, il s'agit de la fonction anonyme de l'élément bouton, et lorsque nous appelons marty.timeTravel, nous l'appelons depuis son objet parent marty, donc c'est marty.
Quatrième - cette valeur dans le constructeur Lorsque vous utilisez le constructeur pour créer une instance d'objet, la valeur this à l'intérieur de la fonction est un objet nouvellement créé. Par exemple :
var TimeTraveler = function(fName, lName) {
this.firstName = fName;
-
this.lastName = lName;
// Les fonctions constructeur renvoient l'
// l'objet nouvellement créé pour nous à moins que
// nous retournons spécifiquement autre chose
};
var marty = new TimeTraveler("Marty", "McFly");
console.log(marty.firstName + " " + marty.lastName);
-
// Marty McFly
Appeler, postuler et lierCall Vous commencez peut-être à vous demander, dans l'exemple ci-dessus, qu'il n'y a aucune fonctionnalité de niveau de langue qui nous permet de La valeur this de la fonction appelante est-elle spécifiée au moment de l'exécution ? tu as raison. Les méthodes call et apply présentes sur le prototype de fonction permettent d'appeler la fonction et de transmettre cette valeur.
Le premier paramètre de la méthode d'appel est le suivant, suivi de la séquence de paramètres de la fonction appelée :
-
someFn.call(this, arg1, arg2, arg3);
Le premier paramètre de apply est également ceci, suivi par Tableau des paramètres restants :
someFn.apply(this, [arg1, arg2, arg3]);
Nos instances Doc et Marty peuvent voyager dans le temps par elles-mêmes, mais Einstein (Einstein) a besoin de leur aide pour terminer le voyage dans le temps. Ajoutons donc une méthode à notre instance doc afin que doc puisse aider Einstein dans son voyage dans le temps.
doc.timeTravelFor = function(instance, année) {
-
this.timeTravel.call(instance, year);
// Si vous utilisez apply, utilisez la syntaxe suivante
// this.timeTravel.apply(instance, [année]);
};
Maintenant, il peut téléporter Einstein :
-
var einstein = {
prénom : "Einstein",
nom de famille : "(le chien)"
};
doc.timeTravelFor(einstein, 1985);
// Einstein (le chien) voyage dans le temps 1985
Je sais que cet exemple est un peu tiré par les cheveux, mais il suffit de vous montrer le pouvoir d'appliquer des fonctions à d'autres objets .
Il existe une autre utilisation de cette méthode que nous n’avons pas encore découverte. Ajoutons une méthode goHome à notre instance Marty comme raccourci vers this.timeTravel (1985).
marty.goHome = function() {
this.timeTravel(1985);
}
Cependant, nous savons que si nous nous abonnons à marty.goHome en tant que gestionnaire d'événements de clic du bouton, la valeur de celui-ci sera le bouton - et malheureusement les boutons n'ont pas une méthode timeTravel. Nous pouvons le résoudre avec la méthode ci-dessus - utiliser une fonction anonyme comme gestionnaire d'événements et appeler la méthode ci-dessus à l'intérieur - mais nous avons une autre option - la fonction de liaison :
flux.addEventListener("click", marty.goHome.bind(marty));
La fonction de liaison renverra en fait une nouvelle fonction, et la valeur this de la nouvelle fonction est définie en fonction des paramètres que vous fournissez. Si vous devez prendre en charge les versions inférieures des navigateurs (par exemple : les versions inférieures à ie9), vous aurez peut-être besoin du shim de la fonction de liaison (ou, si vous utilisez jQuery, vous pouvez utiliser $.proxy à la place, les deux soulignent et méthode lodash provide_ .bind).
Il est important de se rappeler que si vous utilisez la méthode bind directement sur le prototype, cela créera une méthode d'instance, qui contournera les avantages de la méthode prototype. Ce n’est pas une erreur, dites-le simplement clairement dans votre esprit. J'ai écrit davantage sur ce problème ici.
4.) Expression de fonction vs déclaration de fonction La déclaration de fonction ne nécessite pas le mot-clé var. En fait, comme l'a dit Angus Croll : "Cela aide de les considérer comme des frères et sœurs de déclarations de variables". Par exemple :
fonction tempsVoyage(année) {
console.log(this.firstName + " " + this.lastName + " est le voyage dans le temps jusqu'à " + année);
} Le nom de fonction timeTravel dans l'exemple ci-dessus est non seulement visible dans la portée dans laquelle il est déclaré, mais également dans la fonction elle-même (ceci est très utile pour les appels de fonction récursifs). La déclaration de fonction est essentiellement une fonction nommée. En d'autres termes, l'attribut name de la fonction ci-dessus est timeTravel.
Une expression de fonction définit une fonction et l'affecte à une variable. Les applications typiques sont les suivantes :
var someFn = function() {
console.log("I like to express myself...");
}; 也可以对函数表达式命名——然而,不像函数声明,命名函数表达式的名字仅在它自身函数体内可访问:
var someFn = function iHazName() {
console.log("I like to express myself...");
if(needsMoreExpressing) {
iHazName(); // 函数的名字在这里可以访问
}
};
// 你可以在这里调用someFn(),但不能调用iHazName()
someFn(); Copier après la connexion
Nous ne pouvons manquer de mentionner les expressions de fonction et déclarations de fonctions "hoisting" - les déclarations de fonctions et de variables sont déplacées vers le haut de la portée par le compilateur. Nous ne pouvons pas expliquer le levage en détail ici, mais vous pouvez lire deux excellentes explications de Ben Cherry et Angus Croll.
5.) Fonction nommée ou fonction anonyme Sur la base de notre discussion de tout à l'heure, vous avez peut-être deviné qu'une fonction "anonyme" est en fait une fonction sans nom fonction. La plupart des développeurs JavaScript identifieront rapidement le premier paramètre comme une fonction anonyme :
someElement.addEventListener("click", function(e) {
// Je' Je suis anonyme !
});
Cependant, De même, notre méthode marty.timeTravvel est également une fonction anonyme :
var marty = {
prénom : "Marty",
nom de famille : "McFly",
timeTravel : fonction (année) {
-
console.log(this.firstName + " " + this.lastName + " est un voyage dans le temps jusqu'à " + année);
}
Étant donné que les déclarations de fonction doivent avoir un nom unique, seules les expressions de fonction peuvent être sans nom . 6.) Exécution immédiate des expressions de fonction
Puisque nous parlons d'expressions de fonction, il y a une chose que j'aurais aimé savoir : l'exécution immédiate de formule d'expressions de fonction (IIFE). Il existe de nombreux bons articles sur IIFE (je les énumérerai à la fin de l'article), mais pour le décrire en une phrase, les expressions de fonction ne sont pas exécutées en attribuant l'expression de fonction à un scalaire et en l'exécutant plus tard, mais en comprenant le exécution. Vous pouvez regarder ce processus dans la console du navigateur. Tout d'abord - tapons une expression de fonction - mais ne lui affectons pas de variable - et voyons ce qui se passe :
Erreur de syntaxe - ceci est considéré comme une déclaration de fonction et il manque un nom de fonction. Cependant, pour en faire une expression, nous la mettons simplement entre parenthèses :
Après l'avoir transformée en expression, la console nous renvoie une fonction anonyme (rappelez-vous, nous ne lui attribuons pas de valeur, mais l'expression renvoie une valeur). Donc, nous savons que « l’expression de fonction » fait partie de « l’expression de fonction d’appel immédiat ». Pour la fonctionnalité d'attente d'exécution, nous appelons l'expression renvoyée (tout comme nous appelons d'autres fonctions) en ajoutant d'autres parenthèses après l'expression :
"Mais attends, Jim ! (se référant à l'auteur) Je pense avoir déjà vu cet appel". En fait, vous l'avez peut-être vu : il s'agit de la syntaxe juridique (connue pour être la syntaxe préférée de Douglas Crockford)
Les deux méthodes fonctionneront, mais je vous recommande fortement de le lire ici.
OK, super – maintenant nous savons ce qu'est l'IIFE – et pourquoi devrions-nous l'utiliser ?
Cela nous aide à contrôler la portée - une partie très importante de tout didacticiel JavaScript ! La plupart des exemples que nous avons vus précédemment ont été créés à l’échelle mondiale. Cela signifie que l'objet window (en supposant que l'environnement est un navigateur) aura de nombreuses propriétés. Si nous écrivions tous notre code JavaScript de cette façon, nous accumulerions rapidement une tonne (exagération) de déclarations de variables dans la portée globale, et le code de la fenêtre serait pollué. Même dans le meilleur des cas, exposer beaucoup de détails dans une variable globale est un mauvais conseil, mais que se passe-t-il lorsque le nom de la variable est le même que celui d'une propriété de fenêtre existante ? La propriété window sera remplacée !
Par exemple, si votre site Web préféré "Amelia Earhart" déclare une variable de navigateur dans la portée globale, voici le résultat avant et après sa définition :
Oups !
Évidemment, les variables globales polluées sont mauvaises. JavaScript utilise la portée des fonctions (et non la portée des blocs, ce qui est très important si vous venez de C# ou Java !), donc la façon de garder notre code séparé de la portée globale est de créer une nouvelle portée, ce que nous pouvons faire en utilisant IIFE. implémentation car son contenu relève de sa propre portée de fonction. Dans l'exemple ci-dessous, je vais vous montrer la valeur de window.navigator dans la console, puis je créerai une IIFE (expression de fonction immédiatement exécutée) pour envelopper le comportement et les données d'Amelia Earhart. Une fois IIFE terminé, un objet est renvoyé en tant que « espace de noms de programme ». La variable de navigateur que je déclare à l'intérieur de l'IIFE ne remplacera pas la valeur de window.navigator.
En bonus supplémentaire, l'IIFE que nous avons créé ci-dessus est le modèle de module en JavaScript de l'illumination. J'inclurai des liens à la fin vers certains des modèles de modules que j'ai explorés.
7.) Opérateur 'typeof' et 'Object.prototype.toString' Finalement, vous constaterez peut-être que dans certains cas, vous devez vérifier The type d'argument passé à la fonction, ou quelque chose de similaire. Le type d’opérateur serait le choix évident, mais ce n’est pas une panacée. Par exemple, que se passe-t-il lorsque nous appelons l’opérateur typeof sur un objet, un tableau, une chaîne ou une expression régulière ?
Pas mal - au moins nous pouvons distinguer les chaînes des objets, des tableaux et des expressions régulières, n'est-ce pas ? Heureusement, nous pouvons obtenir des informations de type plus précises grâce à différentes méthodes. Nous allons utiliser la méthode Object.prototype.toString et appliquer la méthode d'appel que nous avons mentionnée précédemment :
Pourquoi utilisons-nous la méthode toString sur Object.prototype ? Parce que les bibliothèques tierces ou votre propre code peuvent remplacer la méthode toString de l'instance. Grâce à Object.prototype, nous pouvons forcer le comportement toString d'origine de l'instance.
Si vous savez quel type de retour sera alors vous n'avez pas besoin de faire de vérifications supplémentaires (par exemple, vous avez juste besoin de savoir s'il s'agit d'une chaîne ou non), auquel cas utiliser typeof est très bon. Cependant, si vous devez faire la distinction entre les tableaux et les objets, les expressions régulières et les objets, etc., utilisez Object.prototype.toString.
|