Maison > interface Web > js tutoriel > Apprenez les bogues JScript et la gestion de la mémoire grâce aux compétences me_javascript

Apprenez les bogues JScript et la gestion de la mémoire grâce aux compétences me_javascript

WBOY
Libérer: 2016-05-16 15:31:32
original
1246 Les gens l'ont consulté

1.Bogue JScript

L'implémentation ECMAScript de JScript dans IE confond sérieusement les expressions de fonction nommées, ce qui amène de nombreuses personnes à s'opposer aux expressions de fonction nommées, et même la version qui est toujours utilisée (version 5.8 utilisée dans IE8) existe toujours. Les questions suivantes.

Jetons un coup d'œil aux erreurs commises par IE dans sa mise en œuvre. Comme le dit le proverbe, ce n'est qu'en connaissant l'ennemi que l'on peut être invincible. Jetons un coup d'œil aux exemples suivants :

Exemple 1 : identifiant de l'expression de fonction divulgué vers la portée externe

var f = function g(){};
typeof g; // "function"
Copier après la connexion

Nous avons dit plus tôt que l'identifiant d'une expression de fonction nommée n'est pas valide dans la portée externe, mais JScript viole évidemment cette spécification. L'identifiant g dans l'exemple ci-dessus est analysé dans un objet fonction, ce qui est compliqué. Oui, c'est très difficile. -to-find les bugs sont causés par cette raison.

Remarque : ce problème semble avoir été résolu dans IE9

Exemple 2 : Traiter une expression de fonction nommée à la fois comme une déclaration de fonction et une expression de fonction

typeof g; // "function"
var f = function g(){};
Copier après la connexion

Dans l'environnement des fonctionnalités, les déclarations de fonction seront analysées avant toute expression. L'exemple ci-dessus montre que JScript traite en fait l'expression de fonction nommée comme une déclaration de fonction car elle analyse g avant la déclaration réelle.

Cet exemple mène au suivant.

Exemple 3 : Les expressions de fonction nommées créent deux objets fonction complètement différents !

var f = function g(){};
f === g; // false
f.expando = 'foo';
g.expando; // undefined
Copier après la connexion

En voyant cela, tout le monde pensera que le problème est grave, car modifier n'importe quel objet ne changera pas l'autre. C'est trop mal. A travers cet exemple, on peut constater que créer deux objets différents, c'est-à-dire si l'on souhaite modifier l'attribut de f pour sauvegarder certaines informations, puis l'utiliser systématiquement en référençant le même nom d'attribut de g de le même objet, alors il y aura un gros problème parce que c'est tout simplement impossible.

Regardons un exemple un peu plus compliqué :

Exemple 4 : analyser uniquement les déclarations de fonctions de manière séquentielle et ignorer les blocs d'instructions conditionnelles

var f = function g() {
  return 1;
};
if (false) {
 f = function g(){
 return 2;
 };
}
g(); // 2
Copier après la connexion

Ce bug est beaucoup plus difficile à trouver, mais la cause du bug est très simple. Tout d'abord, g est analysé comme une déclaration de fonction. Puisque les déclarations de fonction dans JScript ne sont pas soumises à des blocs de code conditionnels, dans cette méchante branche if, g est traité comme une autre fonction function g(){ return 2 }, et il vient également d'être déclaré à nouveau. . Ensuite, toutes les expressions « régulières » sont évaluées et f reçoit une référence à un autre objet nouvellement créé. Puisque l'abominable branche if "" ne sera jamais saisie lors de l'évaluation de l'expression, f continuera à faire référence à la première fonction function g(){ return 1 } Après avoir analysé cela, le problème est très clair : si Si vous ne l'êtes pas. assez prudent et appelez g dans f, un objet fonction g non pertinent sera appelé

.

Vous vous demandez peut-être quelles sont les différences lorsque l'on compare différents objets avec arguments.callee ? Jetons un coup d'oeil :

var f = function g(){
  return [
  arguments.callee == f,
  arguments.callee == g
  ];
};
f(); // [true, false]
g(); // [false, true]
Copier après la connexion

Comme vous pouvez le voir, la référence de arguments.callee est toujours la fonction appelée. En fait, c'est aussi une bonne chose, comme cela sera expliqué plus tard.

Un autre exemple intéressant consiste à utiliser une expression de fonction nommée dans une instruction d'affectation qui ne contient pas de déclaration :

(function(){
 f = function f(){};
})();
Copier après la connexion

D'après l'analyse du code, nous voulions à l'origine créer un attribut global f (attention à ne pas le confondre avec la fonction anonyme générale, qui utilise une déclaration nommée). JScript a d'abord changé l'expression. L'expression est analysée comme une déclaration de fonction, donc f à gauche est déclarée comme une variable locale (identique à la déclaration dans une fonction anonyme générale). Ensuite, lorsque la fonction est exécutée, f est déjà définie et la fonction f(). ) à droite {} est directement affecté à la variable locale f, donc f n'est pas du tout un attribut global.

Après avoir compris à quel point JScript est anormal, nous devons éviter ces problèmes à temps. Premièrement, empêcher les identifiants de fuir dans des portées externes. Deuxièmement, les identifiants utilisés comme noms de fonction ne doivent jamais être cités comme ceux de l'exemple précédent. g? ——Si nous pouvons prétendre que g n'existe pas, combien de problèmes inutiles peuvent être évités. Par conséquent, la clé est de toujours faire référence aux fonctions via f ou arguments.callee. Si vous utilisez des expressions de fonction nommées, vous ne devez utiliser ce nom que lors du débogage. Enfin, n'oubliez pas de nettoyer les fonctions qui ont été créées de manière incorrecte lors de la déclaration d'une expression de fonction nommée.

2. Gestion de la mémoire JScript

Après avoir connu ces bugs d'analyse de code non standard, si nous l'utilisons, nous constaterons qu'il y a en fait un problème de mémoire. Regardons un exemple :

.
var f = (function(){
 if (true) {
 return function g(){};
 }
 return function g(){};
})();
Copier après la connexion

我们知道,这个匿名函数调用返回的函数(带有标识符g的函数),然后赋值给了外部的f。我们也知道,命名函数表达式会导致产生多余的函数对象,而该对象与返回的函数对象不是一回事。所以这个多余的g函数就死在了返回函数的闭包中了,因此内存问题就出现了。这是因为if语句内部的函数与g是在同一个作用域中被声明的。这种情况下 ,除非我们显式断开对g函数的引用,否则它一直占着内存不放。

var f = (function(){
 var f, g;
 if (true) {
 f = function g(){};
 }
 else {
 f = function g(){};
 }
 // 设置g为null以后它就不会再占内存了
 g = null;
 return f;
})();
Copier après la connexion

通过设置g为null,垃圾回收器就把g引用的那个隐式函数给回收掉了,为了验证我们的代码,我们来做一些测试,以确保我们的内存被回收了。

测试

测试很简单,就是命名函数表达式创建10000个函数,然后把它们保存在一个数组中。等一会儿以后再看这些函数到底占用了多少内存。然后,再断开这些引用并重复这一过程。下面是测试代码:

function createFn(){
 return (function(){
 var f;
 if (true) {
  f = function F(){
  return 'standard';
  };
 }
 else if (false) {
  f = function F(){
  return 'alternative';
  };
 }
 else {
  f = function F(){
  return 'fallback';
  };
 }
 // var F = null;
 return f;
 })();
}

var arr = [ ];
for (var i=0; i < 10000; i++) {
 arr[i] = createFn();
}

Copier après la connexion

通过运行在Windows XP SP2中的任务管理器可以看到如下结果:

IE7:

 without `null`: 7.6K -> 20.3K
 with `null`:  7.6K -> 18K

IE8:

 without `null`: 14K -> 29.7K
 with `null`:  14K -> 27K

Copier après la connexion

如我们所料,显示断开引用可以释放内存,但是释放的内存不是很多,10000个函数对象才释放大约3M的内存,这对一些小型脚本不算什么,但对于大型程序,或者长时间运行在低内存的设备里的时候,这是非常有必要的。

以上就是关于JScript的Bug与内存管理的全部介绍,希望对大家的学习有所帮助。

Étiquettes associées:
source:php.cn
Déclaration de ce site Web
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn
Tutoriels populaires
Plus>
Derniers téléchargements
Plus>
effets Web
Code source du site Web
Matériel du site Web
Modèle frontal