(ou juste faire une blague à vos collègues)
Cette technique fonctionne grace à l'héritage prototypal et au fait que pour une
obscure raison, Array.prototype
est un array (qui hérite de lui même, allez
savoir). Il suffit d'appeler une des méthodes mutatives d'Array.prototype
sur
lui-même :
Array.prototype.push(1, 2, 3);
Puisque dans le corps de Array.prototype.push()
, this
correspond à
Array.prototype
, c'est dans celui-ci que seront injectés les éléments.
[][0]; // 1
Et hop. À noter que vu l'implémentation de la plupart des fonctions travaillant
avec des arrays, ça devrait pas causer grand dommage puisque length
est géré
au niveau de l'array, et pas de son prototype. Ceci-dit ça peut surprendre en
faisant mumuse dans la console.
Souvent, dans une boucle for(name in object)
, on appelle
object.hasOwnProperty(name)
pour vérifier si la propriété appartient bien à
l'objet et qu'il ne s'agit pas juste d'un truc hérité.
Object.prototype.hasOwnProperty = () => true;
// peut se combiner avec un petit
for (let index = 0; index < 10; index++) {
Object.prototype[index] = undefined;
}
Même concept que pour l'exemple précédent, avec l'héritage prototypal. Le petit côté rigolo ici, c'est que c'est un pattern très courant en JavaScript, notamment dans les bibliothèques que vous utilisez probablement. Et c'est là qu'on se rend compte que de mettre la fonction qui vérifie si une propriété est héritée ou non dans l'héritage, c'est pas forcément l'idée du siècle.
let object = {};
for (let key in object) {
if (object.hasOwnProperty(object)) {
console.log(key);
}
}
// 0, 1, 2, 3, 4, 5, 6, 7, 8, 9
Celui-là est plutôt sympa quand vous ou vos bibliothèques DOM de prédilection
touchez un peu aux élements. Vu que l'appel à Math.random()
rend l'opération
aussi déterministe que l'application de styles assignés à des sélecteurs CSS
chargés de manière asynchrone, vous risquez de jolies surprises.
Element.prototype.appendChild = function(element) {
return Element.prototype.insertBefore.call(
this,
element,
this.childNodes[Math.floor(Math.random() * this.childNodes.length)],
);
};
for (let index = 0; index < 10; index++) {
document.body.appendChild(document.createTextNode(String(index)));
}
// "1895234760" (par exemple)
Bizarrement, j'ai déjà vu des sites qui faisaient ça en production (e.g. Twitter
si je me rappelle correctement). Vous rendez inopérable la console, ce qui peut
faire une très bonne blague à vos collègues en cachant ça dans un vieux commit
avec l'option amend
.
Object.keys(console).forEach(key => {
console[key] = () => {};
});
Là, on est vraiment sur le petit truc horrible, parce que vous pouvez mettre un
petit moment avant de le réaliser. Le constructeur Error
vient normalement
ajouter une propriété stack
qui vous permet de retrouver le chemin qu'a
emprunté le code avant de jeter une erreur. Eh ben fini, à vous le debug à
l'aveugle !
(() => {
function Error(message) {
this.message = message;
}
Error.prototype = window.Error.prototype;
window.Error = Error;
})();
Il est assez courrant d'avoir des setTimeout(func)
ou setTimeout(func, 0)
(les deux sont équivalents). Ça permet s'assurer qu'on décale un peu l'execution
d'une fonction, et souvent de s'assurer que si elle jette une erreur, elle
n'empêchera pas le reste de s'executer.
(() => {
let originalTimeout = window.setTimeout;
window.setTimeout = (func, duration, ...args) =>
!duration
? func(...args)
: originalTimeout.call(window, func, duration, ...args);
})();
Avec ce snippet, petites surprises bizarres assurées. Et c'est un bug présent
dans quelques bibliothèques implémentant une fonction domReady
, et qui font:
function domReady(func) {
if (document.readyState === "complete") {
func();
} else {
document.addEventListener("DOMContentLoaded", func);
}
}
Avec une implémentation comme celle qu'on voit au dessus, func
aura un
comportement différent si le DOM est chargé ou non:
domReady(() => {
throw new Error();
});
console.log(1);
// Logue 1 si le DOM est prêt, parce que l'execution de func par le handler DOMContentLoaded est asynchrone
// Logue rien du tout si func() est appelé en synchrone par la première branche de domReady
Les Promise
, c'est bien relou à débugger lorsque ça reste infiniment en
"pending": on ne sait pas forcément pourquoi, surtout si c'est derrière une API
opaque, comme fetch
. Plaisir garanti, parce qu'avec ça sur la page, c'est
probablement le dernier endroit où on va intuitivement chercher la source la
bug.
window.fetch = () => new Promise(() => {});
C'est particulièrement horrible quand un bug n'est pas reproduit à tous les coups: pourquoi ne pas attacher les évenements au hasard ?
(() => {
let originalAddEventListener = Element.prototype.addEventListener;
Element.prototype.addEventListener = function(...args) {
if (Math.random() < 0.75) {
originalAddEventListener.call(this, ...args);
}
};
})();
document.body.addEventListener("click", () => console.log(1));
document.body.addEventListener("click", () => console.log(2));
document.body.addEventListener("click", () => console.log(3));
document.body.addEventListener("click", () => console.log(4));
document.body.addEventListener("click", () => console.log(5));
(() => {
let originalParseInt = window.parseInt;
window.parseInt = n => (Math.random() > 0.9 ? originalParseInt(n) : NaN);
})();
Pour un bon petit moment à pas comprendre ce qui se passe. Je suis sûr que NaN
n'est pas toujours géré dans tous les cas dans la plupart des scripts qui
tournent aujourd'hui en prod.
parseInt("1"); // 1
parseInt("1"); // NaN
Pour ce coup, c'est bien de prévoir les deux fonctions utilisées par tout le monde pour encoder et décoder les URLs. Vu que si l'une ne marche pas, le reflexe est souvent de tester la deuxième, bon arrachage de cheveux en perspective.
window.decodeURIComponent = window.encodeURIComponent = window.escape = window.unescape = a =>
String(a);
Si dans la console de développement, vous tapez le nom d'une fonction accessible
dans le scope, le navigateur va appeler Function.prototype.toString
sur cette
fonction pour en récupérer l'allure. Couvrez vos arrières en replaçant la
méthode :
Function.prototype.toString = function() {
return `function ${this.name || ""}() {
[native code]
}`;
};
() => {};
/* function () {
[native code]
} */
Pour faire un peu de zèle, vous pouvez également déclarer ces fonctions dans un
eval
pour brouiller les pistes sur l'endroit où elles ont été déclarées.
Voilà, vous avez toutes les clés pour faire des petites blagues à vos collègues.
Et n'oubliez pas, JavaScript c'est super, mais faisez gaffe quand même, parce que dans un langage encourageant la mutabilité, qui utilise des globales et de l'héritage par dessus le marché, il suffit d'un petit bout de code innocent pour que plus rien ne marche.
Bisous.