Maison > Article > interface Web > Explication détaillée des rappels dans le code source jQuery Analysis_jquery
L'essence du code met en évidence le concept de séquence et d'ordre, en particulier en JavaScript - après tout, JavaScript est un moteur à thread unique.
Javascript a les caractéristiques de la programmation fonctionnelle, et en raison du moteur JavaScript monothread, nos fonctions doivent toujours être exécutées de manière ordonnée. Un excellent code découpe souvent les fonctions dans leurs propres modules, puis les exécute sous certaines conditions. Puisque ces fonctions sont exécutées de manière ordonnée, pourquoi ne pas écrire un objet de gestion unifié pour nous aider à gérer ces fonctions - donc, les rappels (fonctions de rappel). ) sont nés.
Que sont les rappels
Javascript regorge de programmation fonctionnelle. Par exemple, le window.onload le plus simple accepte une fonction. Ce qui est triste, c'est que window.onload ne peut recevoir qu'une seule fonction si elle est affectée directement. exécuter dans onload, Ensuite, nous devons écrire le code suivant :
L'intention initiale de la fonction de rappel est de s'appuyer sur une telle chose. Elle ne nous permet plus de disperser ces fonctions, mais d'organiser ces fonctions de manière unifiée. Comme vous pouvez le voir, nous voulons faire deux choses pour un élément dans window.onload : d'abord changer la structure HTML, puis changer le style du HTML. Les deux fonctions opèrent également sur un Elément, et l'exécution finale de ces deux fonctions s'effectue dans l'ordre. Alors pourquoi ne pas écrire un objet comme celui-ci pour gérer ces fonctions. Bien sûr, ce n’est que la signification la plus élémentaire de la fonction de rappel. Nous avons besoin de plus qu’un simple objet de fonction de rappel, nous avons besoin d’une fonction de rappel plus puissante. Eh bien, ce n'est qu'un simple cas d'utilisation, je peux donc vous dire ce que cette fonction de rappel peut faire en plus d'exécuter les fonctions une par une.
L'essence des rappels est de contrôler l'exécution ordonnée des fonctions. Javascript est un moteur monothread, ce qui signifie qu'un seul code JavaScript s'exécute en même temps - même Ajax et setTimeout. Ces deux fonctions semblent asynchrones, mais ce n'est pas le cas. Lorsque le navigateur exécute du code javascript, ces codes seront placés dans une file d'attente de manière ordonnée. Lorsque vous exécutez Ajax, le navigateur les poussera dans la file d'attente de codes. Le navigateur prend le code un par un dans la file d'attente de code lors du traitement du code JavaScript - Rappels, destinés à ce moteur monothread.
Bien sûr, ce que nous voulons, c'est plus qu'un simple objet outil - dans le code source de jQuery, les rappels fournissent la gestion de base d'un ensemble de fonctions, constituent la base de Deferred (file d'attente asynchrone) et servent également de file d'attente. (file d'attente de synchronisation). Deferred est utilisé pour lisser/aplatir la programmation pyramidale (un grand nombre de fonctions de rappel imbriquées, telles que le code en Ajax qui doit être exécuté en fonction du code de retour de la requête et les lecteurs de file d'attente jQuery.animate (moteur d'animation).
Alors écrivons un rappel.
Modèle de rappels
Tableau :
Puisque nos Callbacks veulent accepter une série de fonctions, nous devons avoir un conteneur. Nous pouvons utiliser un tableau et pousser chaque fonction dans le tableau Lorsqu'elle doit être exécutée, bouclez les éléments du tableau à exécuter.
Modèle de travail :
Ces rappels doivent être très puissants. Ce n'est pas aussi simple que de pousser une fonction puis de l'exécuter.
once : toutes les fonctions de l'objet Callbacks actuel ne seront exécutées qu'une seule fois et seront publiées après l'exécution. Nous pouvons fournir une solution stable et efficace aux utilisateurs qui utilisent l'objet Callbacks pour garantir que la fonction ne sera exécutée qu'une seule fois et ne le sera pas. être exécuté à nouveau, ce qui stabilise les threads de ces fonctions.
auto : modèle d'exécution automatique. C'est un modèle intéressant. Certaines fonctions dépendent de la fonction de la couche précédente. Par exemple, l'exécution de la fonction b dépend de la fonction a. Ensuite, nous fournissons un modèle d'exécution automatique : après la première exécution de ce Callbacks, chaque fois qu'elle est ajoutée. Lorsque la fonction atteint les rappels, elle exécute automatiquement les fonctions ajoutées dans le passé et transmet les dernières données de paramètre données aux fonctions passées. Cela élimine le besoin de déclenchements répétés entre ces fonctions dépendantes à partir des rappels. un modèle intéressant.
once&auto : nous pouvons le rendre plus puissant et travailler avec les modèles once et auto en même temps, c'est-à-dire : chaque fois qu'une fonction est ajoutée aux rappels, les fonctions passées seront exécutées, puis ces fonctions passées seront libérées, et les fonctions continueront à être ajoutées la prochaine fois. À ce moment-là, ces fonctions du passé ne seront plus exécutées car le modèle une fois les a publiées.
API :
add(function) - Ajoutez une (ou plusieurs) fonctions à l'objet Callbacks : Bien sûr, si vous n'ajoutez pas de fonctions et que vous êtes simplement curieux de jeter un œil aux Callbacks, nous vous laissons continuer à vous amuser - nous ne lancerons pas d'exception car ce n'est pas quelque chose pour lequel nous sommes bons.
remove(function) - supprime une fonction dans un rappel : maintenant que nous l'avons ajoutée, nous devrions également fournir un plan pour le regretter. Nous sommes très accessibles et tolérons tout ce que d'autres ont fait dans le passé.
has(function) - Détermine si les rappels contiennent une fonction : Oh ? Vous ne savez pas si vous devez inclure cette fonction, mais vous l'avez ajoutée en premier lieu ! Pourquoi es-tu si négligent ? Mais puisque vous me l'avez demandé, je vais quand même vous dire si les rappels contiennent cette fonction. Je sais que vous êtes très occupé et que vous ne pouvez pas tout mémoriser et déterminer.
empty() - Rappels vides : ces fonctions ont-elles perdu leur sens pour vous ? Quoi? Vous n’en voulez plus après son exécution ? Alors tu aimerais pouvoir l'effacer ? Eh bien, pour des raisons de mémoire, je tolère toujours votre demande.
Disable() - Désactiver un rappel : afin de maintenir une existence stable avec le code des autres, j'ai choisi le sacrifice de soi - oui, cette méthode peut désactiver les rappels, les désactiver complètement, comme s'ils n'existaient pas auparavant.
désactivé () - Détermine si les rappels ont été désactivés : si vous ne croyez toujours pas si les rappels sont vraiment sacrifiés, cette méthode peut vous apporter une tranquillité d'esprit.
lock(boolean) - Verrouillez cet objet Callbacks : vous craignez qu'il soit instable, mais vous ne souhaitez pas l'abandonner. Lock est une bonne méthode. Il reçoit un paramètre booléen pour indiquer si l'objet doit être verrouillé. Bien sûr, il n'a aucun paramètre. Permet de déterminer si les rappels sont verrouillés.
fire(data) - Exécuter la fonction dans ce rappel : tout ce que nous faisons n'est-il pas destiné au sort de l'exécution en ce moment ? Les paramètres deviendront les paramètres de ces fonctions qui doivent être exécutées.
fireWith(context,data) - Exécutez la fonction dans Callbacks et spécifiez le contexte. Dans fire(), le Context de toutes les fonctions sont des objets Callbacks, et fireWidth() vous permet de redéfinir le contexte de ces fonctions à exécuter. Quelle que soit la gratuité de la programmation, Callbacks considère tout pour vous.
Fired() - Déterminez si cet objet Callbacks a été exécuté dans le passé : nous pensons que la plupart du temps, vous ne savez pas ce que vous avez fait dans le passé, mais nous enregistrons tout ce que vous faites si vous avez exécuté cet objet Callbacks. le passé, alors vous ne pouvez jamais le nier, car nous savons si vous avez exécuté ces rappels dans le passé.
Mise en œuvre du module de base
Mise en œuvre simple :
Commençons par simplement implémenter un rappel :
Ouvrez la console du navigateur et nous pouvons voir que les résultats d'exécution sont normaux.
implémentation unique et automatique (en mémoire)
une fois :
Once permet à la fonction de ce rappel de s'exécuter une fois, puis de ne plus s'exécuter. Le principe est très simple. Dans le code ci-dessus, nous pouvons voir qu'il existe une liste de variables qui prend le relais de la liste des fonctions, il suffit donc d'effacer les codes qui ont été exécutés dans le passé. Nous utilisons une variable globale pour enregistrer le modèle d'exécution actuel, s'il s'agit d'un modèle unique, invalidez simplement la liste dans fireWith() :
auto :
Le modèle auto (mémoire) porte le nom de la mémoire dans jQuery. Au début, j'ai été dérouté par ce nom. Après avoir soigneusement examiné son utilisation, j'ai décidé de le changer en auto - sa fonction est "après le premier incendie()". , la "fonction add() suivante s'exécute automatiquement" peut être utilisée dans les situations suivantes : après avoir ajouté un ensemble de fonctions aux rappels, et vous devez temporairement ajouter une fonction, puis exécutez immédiatement la fonction nouvellement ajoutée - il faut dire que pour le commodité d'utilisation, ce modèle devient un peu difficile à comprendre. L'implémentation consiste à déterminer s'il s'agit d'un modèle automatique lors de add(). S'il s'agit d'un modèle automatique, exécutez cette fonction. Cependant, nous devons l'exécuter automatiquement après le premier fire(). Les rappels qui n'ont pas été déclenchés() ne doivent pas être automatiquement exécutés. De plus, après chaque exécution automatique, les derniers paramètres utilisés doivent être transmis à cette fonction exécutée automatiquement.
Peut-être penserez-vous au code suivant :
Mais une utilisation plus merveilleuse est adoptée dans jQuery. L'auteur de jQuery est également fier de cette utilisation, c'est pourquoi il a nommé ce modèle mémoire - c'est-à-dire que la variable ci-dessus auto représente non seulement le mode d'exécution automatique actuel, mais sert également. comme dernier conteneur de paramètres, qui représente à la fois l'auto et la mémoire. (Le code suivant n'est pas jQuery et est écrit sur la base d'idées de code jQuery, et non de code source) :
Pendant add(), jQuery n'a pas attribué de valeur à la variable auto(memory), mais a choisi d'attribuer une valeur à auto(memory) dans coreFire(), garantissant ainsi qu'elle ne sera pas activée avant le premier fire(). Exécuté automatiquement.
Comme mentionné ci-dessus, les paramètres reçus par coreFire() sont en fait un tableau. Le premier paramètre est le contexte et le deuxième paramètre est le paramètre transmis de l'extérieur. En même temps, affectez ce tableau à auto (mémoire), pour que la définition de la variable auto (si exécuter le mode automatiquement) devienne mémoire (mémoire du dernier paramètre passé).
Quant à once&auto, il combine simplement ces deux codes. Il vous suffit de déterminer dans coreFire() que s'il est en mode automatique, puis de réinitialiser la liste sur un nouveau tableau, sinon de la définir directement sur non défini.
Ce code est écrit à la main par moi et correspond à jQuery. Certaines fonctions publiques de jQuery sont écrites. Ce n'est pas un fragment de code, il peut donc être directement référencé et exécuté.
//Fonction outil
var isIndexOf = Array.prototype.indexOf, //Es6
toString = Object.prototype.toString, //Méthode Cache toString
TosLice = Array.prototype.slice, // Méthode Caches Slice
return "object" === typeof document.getElementById ?
isFunction = function (fn) {
//Il y a un problème avec la reconnaissance du DOM et du BOM sous ie
essayez {
Retour /^s*bfunctionb/.test("" fn);
} attraper (x) {
Renvoyer faux
}
} :
isFunction = function (fn) { return toString.call(fn) === '[object Function]' };
;
})(),
//Le premier paramètre représente le tableau à boucler, et le deuxième paramètre est la fonction exécutée à chaque fois dans la boucle
Si (arguments.length < 2 || !isFunction(arguments[1])) return;
//Pourquoi la tranche n'est-elle pas valide ? ?
var list = toSlice.call(arguments[0]),
fn = arguments[1],
article ;
while ((item = list.shift())) {//Pas de détermination directe de la longueur, accélère
// Pourquoi utiliser call ici, et Apply ne convient pas ?
// Terminé - le deuxième paramètre de apply doit être un objet tableau (il n'y a aucune vérification si l'apparence d'un tableau est possible, et l'appel n'a pas cette exigence)
//apply est décrit comme suit : Si argArray (le deuxième paramètre) n'est pas un tableau valide ou n'est pas un objet d'arguments, une TypeError sera provoquée.
fn.call(window, item);
}
},
inArray = function () { //Détecte si le tableau contient un élément et renvoie l'index de l'élément
// Pré-compilation
return isIndexOf ? (array, elem, i) {
Si (tableau)
return isIndexOf.call(array, elem, i);
return -1 ;
} : fonction (elem, array, i) {
var len;
if (tableau) {
len = tableau.longueur;
je = je ? je &Lt ; 0 ? Math.max(0, len i) : i : 0;
pour (; je < len; je ) {
if (i in array && array[i] === elem) {
reviens-moi ;
>
>
>
retourner -1 ;
>
}();
var Rappels = fonction (option) {
option = toString.call(option) === '[objet Objet]' option : {};
//Utilisez les fermetures car chaque nouveau rappel a son propre état
var list = [], var list =
_list = [], // Si cet objet de rappel est verrouillé, effacez la liste et placez la liste d'origine dans _list
ont été exécutés
shootingStart, //Index de fonction (point de départ) exécuté par la liste de fonctions de rappel actuelle
ignitionLength, // Longueur du tableau de la fonction de rappel
.
// L'utilisation de cette variable est très étrange et pointue, elle contient non seulement l'indicateur indiquant s'il faut spécifier l'exécution, mais enregistre également les données
//Cette auto est tout simplement folle lorsqu'elle est utilisée avec une fois : [Pour la première fois] elle sera automatiquement exécutée après l'exécution de Fire Avec une fois, cela peut être fait : une fois exécuté, aucun code ne sera ajouté ou exécuté plus tard, assurant la stabilité. et la stabilité d'un ensemble de données de rappel. Safe
.
stack = !option.once && [], //Une pile de rappels est en cours d'exécution et qu'une nouvelle fonction de rappel est ajoutée pendant l'exécution, alors la nouvelle fonction de rappel sera poussée dans le tableau de rappel<🎜. >
shooting = false, //Si les rappels fonctionnent/s'exécutent
//Fonction de rappel de déclenchement
feu = fonction (données) {
// Notez que ces données sont un tableau. Si le mode auto est configuré, auto ne sera jamais faux car auto sera un tableau
.
auto = option.auto && data //Ici, si la configuration nécessite de mémoriser le dernier paramètre, alors mémorisez ce paramètre (utilisation très pointue, récupérer directement les données)
viré = vrai ;
shootingIndex = shootingStart || 0;
FireStart = 0;//Effacer FireStart (si vous ne l'effacez pas, il y aura des problèmes lors de la prochaine exécution)
Firinglength = list.length; // longueur de la liste de cache, le monde extérieur peut accéder à
shooting = true ; // Exécution de la fonction de rappel
pour (; FireIndex < FireLength; FireIndex ) {
Si (list[firingIndex].apply(data[0], data[1]) === false) {
// Notez que si option.auto (exécution automatique) est configuré et qu'il y a une fonction dans la pile (stack), alors il y a une section de code dans le code add() qui exécutera directement cette méthode pour le jugement automatique
//Nous voulons bloquer ce code, alors définissez auto sur false
auto = false;
pause ;
}//Lorsque la fonction renvoie false, terminez l'exécution de la file d'attente suivante
}
shooting = false ; // L'état du drapeau a été exécuté, fonction de rappel [la fonction dans la pile (stack) n'a pas encore été exécutée]
//Si cette pile n'est pas configurée une seule fois, elle doit être [], donc il doit y avoir
//La fonction principale ici est que si une fois n'est pas configuré, le code suivant sera intercepté. Si une fois est configuré, les données seront effacées après l'exécution du code
.
if (pile) {
If (stack.length) // Interceptez d'abord le code de l'état de liste ci-dessous, puis déterminez s'il existe une pile
Fire (stack.shift ()); // Sortez-le de la tête de la pile et répétez la méthode FIRE ()
}
Sinon, si (auto) // le code est venu ici, prouve qu'il a été configuré Option.once (une seule fois exécuté), donc la liste est claire
list = [];
Sinon // prouve qu'il n'y a pas de configuration AUTO, mais ONCE est configuré, donc le sacrifice est le Dafa ultime, et l'objet de rappel est directement aboli
self.disable();
};
var soi = {
add : function () {//Ajouter une fonction de rappel
if (liste) {
var start = list.length;
(fonction addCallback(args) {
chacun (arguments, fonction (élément) {
If (isFunction(item)) {//S'il s'agit d'une fonction, poussez la liste de rappel
//Notez que typeof et Object.prototype.toString sont différents
} else if (toString.call(item) === '[object Array]') {//S'il s'agit d'un tableau, poussez de manière récursive dans la liste de rappel, ce jugement abandonne le type de tableau
addCallback(item);
}
});
})(arguments);
}
Si (tiring)//Si une fonction de rappel est en cours d'exécution, alors la longueur de la liste actuelle des fonctions de rappel doit être mise à jour, sinon la fonction de rappel nouvellement poussée sera ignorée.
shootingLength = list.length;
else if (auto) {//Si la fonction de rappel n'est pas actuellement exécutée et qu'une exécution automatique est requise
// Notez que la valeur est attribuée à FireStart. Cela n'affectera pas la ligne d'exécution du code ci-dessus
shootingStart = démarrer;
//Exécuter nos partenaires nouvellement ajoutés
feu(auto);
}
renvoyez ceci ;
},
fire: function () {//Déclencher la fonction de rappel
self.fireWith(this, arguments);
renvoyez ceci ;
},
fireWith: function (context, args) {//Déclenchez la fonction de rappel et spécifiez le contexte
//Si once est configuré, la pile ne sera pas définie et once doit être garanti pour être exécuté une seule fois, donc une fois exécuté une fois, le code ici ne sera plus exécuté
If (list && (!fired || stack)) {
//Paramètres de correction
//Ici, l'index de contexte est 0
//L'index de la liste des paramètres est 2
// La conversion en accès au tableau est due au fait que la représentation d'objet consomme plus de ressources. Il existe une fonction auto [paramètres de mémoire, exécution automatique] dans le code fire() de niveau supérieur. Si des objets sont utilisés, plus de mémoire sera consommée
.
args = [contexte,
args.slice && args.slice()
];
feu(arguments);
}
renvoyez ceci ;
},
delete: function () {//Supprimer une fonction de rappel
if (liste) {
chacun (arguments, fonction (élément) {
var index ;
// Il peut y avoir plusieurs éléments, l'index peut représenter la plage de recherche dans la boucle et les éléments précédemment recherchés n'ont pas besoin d'être recherchés à nouveau
Tandis que ((index = inArray(item, list, index)) > -1) {
list.splice(index, 1);
Si (tir) {
//Assurez-vous que la liste de fonctions exécutée dans Fire ci-dessus peut s'exécuter correctement. Ces variables globales sont définies dans Fire afin qu'elles puissent être supprimées de manière asynchrone
.
Si (index <= longueur de tir)//longueur de correction
longueur de tir--;
Si (index <=fireLength)//Indice de correction
shootingIndex--;
}
}
});
}
renvoyez ceci ;
},
A : function (fn) {//Qu'il contienne une fonction de rappel
? (fn, liste) > -1 : liste && list.length;
},
vide : function () {//Vider cet objet de rappel
liste = [];
longueur de tir = 0;
renvoyez ceci ;
},
désactiver : function () {//Détruisez cet objet de rappel et la liste de fonctions de rappel suivante ne sera plus exécutée
list = stack = auto = undefined;
renvoyez ceci ;
},
désactivé : fonction () {//Si elle a été désactivée
& nbsp