コードの本質は、特に JavaScript におけるシーケンスと順序の概念を強調しています。結局のところ、JavaScript はシングルスレッド エンジンです。
JavaScript には関数型プログラミングの特性があり、JavaScript のシングルスレッド エンジンのため、関数は常に順序正しく実行される必要があります。優れたコードは、関数を独自のモジュールに分割し、特定の条件下で実行することがよくあります。これらの関数は順序立てて実行されるため、これらの関数の管理を支援する統合管理オブジェクト、つまりコールバック (コールバック関数) を作成してみてはいかがでしょうか。 )が誕生しました。
コールバックとは
JavaScript には関数型プログラミングが豊富に含まれています。たとえば、最も単純な window.onload は関数を受け取りますが、必要な関数が複数ある場合、window.onload は 1 つの関数しか受け取ることができません。 onload で実行します。次に、次のコードを記述する必要があります:
コールバック関数の本来の目的は、これらの関数を分散させるのではなく、これらの関数を統合した方法で構築することです。ご覧のとおり、window.onload の要素に対して 2 つのことを行います。まず HTML 構造を変更し、次に HTML のスタイルを変更します。 2 つの関数は Element に対しても動作し、これら 2 つの関数の最終的な実行は順番に実行されます。そこで、これらの関数を管理するために次のようなオブジェクトを作成してみませんか。もちろん、これはコールバック関数の最も基本的な意味にすぎません。このような単純なコールバック関数オブジェクトだけではなく、より強力なコールバック関数が必要です。これは単なる単純な使用例なので、このコールバック関数が関数を 1 つずつ実行する以外に何ができるかを説明します。
コールバックの本質は、関数の規則的な実行を制御することです。JavaScript はシングルスレッド エンジンです。つまり、同時に実行されるのは JavaScript 内の 1 つのコードだけです (Ajax と setTimeout も同様)。 これら 2 つの関数は非同期であるように見えますが、そうではありません。ブラウザが JavaScript コードを実行すると、これらのコードは順番にキューにプッシュされます。Ajax を実行すると、ブラウザはコード キューにプッシュされます。ブラウザは、このシングルスレッド エンジンに対応した JavaScript コード (コールバック) を処理するときに、コード キューからコードを 1 つずつ取得します。
もちろん、私たちが必要とするのは、そのような単純なツール オブジェクトだけではありません。jQuery ソース コードでは、コールバックは一連の関数の基本的な管理を提供し、Deferred (非同期キュー) の基盤を提供し、Queue も提供します。 (同期キュー)。 Deferred は、ピラミッド プログラミング (リクエストの戻りコードに基づいて実行する必要がある Ajax のコードなど、多数の入れ子になったコールバック関数) をスムーズ/平坦化するために使用され、Queue は jQuery.animate (アニメーション エンジン) を駆動します。
それではコールバックを書きましょう。
コールバックモデル
配列:
コールバックは一連の関数を受け入れる必要があるため、コンテナーが必要です。配列を使用し、各関数を実行する必要がある場合は、配列の項目をループして実行できます。
作業モデル:
このコールバックは、関数をプッシュして実行するだけではなく、非常に強力である必要があります。優れた実行モデルが必要です。
Once: 現在の Callbacks オブジェクト内のすべての関数は 1 回だけ実行され、実行後に解放されます。Callbacks オブジェクトを使用するユーザーに、関数が 1 回だけ実行され、実行されないことを保証する安定した効果的なソリューションを提供できます。が再度実行され、これらの関数のスレッドが安定します。
auto: 自動実行モデル これは興味深いモデルです。たとえば、関数 b の実行は関数 a に依存します。次に、このコールバックの最初の実行後に、次のような自動実行モデルを提供します。関数がコールバックに到達すると、過去に追加された関数が自動的に実行され、過去の関数に指定された最後のパラメータ データが渡されます。これにより、コールバックからこれらの依存関数間でトリガーを繰り返す必要がなくなります。興味深いモデルです。
Once&auto: これをより強力にして、once モデルと auto モデルを同時に操作できます。つまり、関数がコールバックに追加されるたびに、過去の関数が実行され、その後、これらの過去の関数が解放されます。次回も引き続き機能が追加されます その際、過去の機能は一度リリースされた機種なので実行できなくなります。
API:
add(function) - Callbacks オブジェクトに 1 つ (または複数) の関数を追加します。もちろん、関数を追加せず、単に Callbacks を見てみたいだけの場合は、引き続きお楽しみいただけます。 - 例外はスローしません。それは私たちが得意ではないためです。
Remove(function) - コールバック内の関数を削除します。関数を追加したので、それを後悔するための計画も提供する必要があります。私たちはとても親しみやすく、他の人が過去に行ったことすべてを容認します。
has(function) - コールバックに関数が含まれているかどうかを判断します。この機能を含めるかどうかはわかりませんが、最初から入れておきました。なぜそんなに不注意なのですか?しかし、あなたが私に尋ねたので、コールバックにこの機能が含まれているかどうかを教えてください。あなたはとても忙しく、すべてを覚えて判断することはできないと思います。
empty() - 空のコールバック: これらの関数はあなたにとって意味を失っていませんか?何?実行された後はもう要りませんか?それでクリアできたらいいのに?まあ、記憶のために、私はまだあなたの要求を容認します。
disable() - コールバックを無効にする: 他の人のコードとの安定した存在を維持するために、私は自己犠牲を選択しました - はい、このメソッドはコールバックを無効にし、まるで以前には存在していないかのように完全に無効にすることができます。
disabled() - コールバックが無効になっているかどうかを判断します。コールバックが本当に自己犠牲的であるかどうかまだ信じられない場合は、このメソッドを使用すると安心できます。
lock(boolean) - この Callbacks オブジェクトをロックします。不安定であることは心配ですが、オブジェクトをロックする必要があるかどうかを示すブール値パラメーターを受け取ります。もちろん、パラメータはありません。コールバックがロックされているかどうかを決定できます。
fire(data) - このコールバックで関数を実行します。現時点では、私たちが行うすべてのことは実行の運命のためではありませんか?パラメータは、実行する必要があるこれらの関数のパラメータになります。
fireWith(context,data) - コールバックで関数を実行し、コンテキストを指定します。 fire() では、すべての関数の Context が Callbacks オブジェクトであり、fireWidth() を使用すると、実行されるこれらの関数のコンテキストを再定義できます。Callbacks では、すべてを考慮してプログラミングが自由です。
fired() - この Callbacks が過去に実行されたかどうかを判断します。ほとんどの場合、ユーザーが過去に何をしたかわからないと考えられますが、この Callbacks オブジェクトを実行した場合は、ユーザーが行ったすべての操作が記録されます。過去にこのコールバックを実行したかどうかはわかっているため、それを否定することはできません。
基本モジュールの実装
簡単な実装:
まず単純にコールバックを実装しましょう:
ブラウザコンソールを開くと、実行結果が正常であることがわかります。
1 回と自動 (メモリ) 実装
1 回:
Once は、このコールバック内の関数を 1 回実行し、その後は実行しないようにします。原理は非常に簡単です。上記のコードでは、関数リストを引き継ぐ変数リストがあることがわかります。そのため、過去に実行されたコードをクリアするだけで済みます。グローバル変数を使用して現在の実行モデルを保存します。それが 1 回限りのモデルの場合は、fireWith() のリストを無効にするだけです:
自動:
自動 (メモリ) モデルは、jQuery のメモリにちなんで名付けられました。最初はこの名前に混乱しましたが、使い方を注意深く検討した後、機能を「最初の fire() の後に」に変更することにしました。後続の「add() 関数が自動的に実行される」は、次の状況で使用できます。一連の関数をコールバックに追加した後、一時的に関数を追加する必要があり、その後、新しく追加された関数をすぐに実行する必要があります。使いやすさを考えると、このパターンは少しわかりにくくなります。 add()時に自動モデルかどうかを判定する実装です。自動モデルの場合はこの関数を実行します。 ただし、最初の fire() の後に自動的に実行する必要があります。さらに、各自動実行後に、最後に使用されたパラメータをこの自動実行関数に渡す必要があります。
おそらく次のコードを思い浮かべるでしょう:
しかし、jQuery ではさらに素晴らしい使用法が採用されています。jQuery の作者はこの使用法を誇りに思っており、このモデルをメモリと名付けました。つまり、上記の変数 auto は現在の自動実行モードを表すだけでなく、パラメータの最後のコンテナとして、自動とメモリの両方を表します。 (次のコードは jQuery ではなく、ソース コードではなく jQuery コードのアイデアに基づいて記述されています):
add() 中に、jQuery は変数 auto(memory) に値を代入しませんでしたが、coreFire() で auto(memory) に値を代入することを選択したため、最初の実行までは変数がオンにならないことが保証されます。 fire() が自動的に実行されます。
上で述べたように、coreFire() によって受け取られるパラメータは実際には配列であり、最初のパラメータはコンテキストであり、2 番目のパラメータは外部から渡されるパラメータです。同時に、この配列を auto (メモリ) に割り当てます。これにより、変数 auto の定義 (モードを自動的に実行するかどうか) がメモリ (最後に渡されたパラメータのメモリ) になります。
once&auto に関しては、これら 2 つのコードを組み合わせるだけです。auto モードの場合はリストを新しい配列にリセットし、それ以外の場合は直接未定義に設定することを coreFire() で決定するだけです。
このコードはjQueryに対応して私が手書きしたもので、jQueryのパブリック関数がいくつか書かれています。コードフラグメントではないので直接参照して実行することができます。
//ツール関数
var isIndexOf = Array.prototype.indexOf, //Es6
toString = Object.prototype.toString, //toString メソッドをキャッシュします
TosLice = Array.prototype.slice, // Slice メソッドをキャッシュします
return "object" === typeof document.getElementById ?
isFunction = 関数 (fn) {
// ie
での DOM と BOM の認識に問題があります。
{
をお試しください
Return /^s*bfunctionb/.test("" fn);
} catch (x) {
false を返します
}
} :
isFunction = function (fn) { return toString.call(fn) === '[オブジェクト関数]' };
})()、
//最初のパラメータはループされる配列を表し、2 番目のパラメータはループを通じて毎回実行される関数です
If (arguments.length
//スライスが無効なのはなぜですか? ?
var list = toSlice.call(arguments[0]),
fn = 引数[1],
アイテム;
while ((item = list.shift())) {//長さを直接決定しないため、高速化されます
// なぜここで call を使用し、Apply は問題ないのでしょうか?
// 完了 - apply の 2 番目のパラメータは配列オブジェクトでなければなりません (配列のようなことが可能かどうかの検証はなく、呼び出しにはこの要件がありません)
//apply は次のように記述されます。 argArray (2 番目のパラメーター) が有効な配列でない場合、または引数オブジェクトではない場合、TypeError が発生します。
fn.call(window, item);
}
}、
inArray = function () { //配列に項目が含まれているかどうかを検出し、項目のインデックスを返します
//プレコンパイル
return isIndexOf function (array, elem, i) {
If (配列)
return isIndexOf.call(array, elem, i);
return -1;
} : 関数 (elem, array, i) {
var len;
if (配列) {
len = 配列.長さ;
私=私?私は< 0 ? Math.max(0, len i) : i : 0;
for (; i
if (配列内の i && array[i] === elem) {
私を返します;
}
}
}
-1 を返す;
}
}();
var Callbacks = function (オプション) {
オプション = toString.call(オプション) === '[オブジェクト オブジェクト]' ? オプション: {};
//新しいコールバックにはそれぞれ独自の状態があるため、クロージャを使用します
var list = [], var list = [], var list =
_list = [], // このコールバック オブジェクトがロックされている場合は、リストをクリアして、元のリストを _list
に入れます
ヘイブンは実行されました
firingStart, //現在のコールバック関数リストによって実行される関数インデックス (開始点)
fireLength, // コールバック関数の配列長
。
// この変数の使い方は非常に奇妙かつ鋭いもので、実行を指定するかどうかのフラグが含まれるだけでなく、データも記録されます
//この自動は、once で使用すると単純にクレイジーです: [初めて] fire の実行後に自動的に実行されます: 一度実行すると、後でコードが追加されたり実行されたりすることはなく、安定性が確保されます。およびコールバック データのセットの安定性。
stack = !option.once && [], //コールバック スタック。コールバック配列が現在実行中で、実行中に新しいコールバック関数が追加されると、新しいコールバック関数はコールバック配列にプッシュされます。 >
fire = false, //コールバックが動作/実行しているかどうか
//コールバック関数をトリガー
fire = 関数 (データ) {
//このデータは配列であることに注意してください。auto モードが設定されている場合、auto は array
になるため、auto は false になりません。
auto = option.auto && data; // ここで、構成で最後のパラメーターを記憶する必要がある場合は、このパラメーターを覚えておいてください (非常に鋭い使用法で、データを直接取得します)
解雇 = true;
発射インデックス = 発射開始 || 0;
firingStart = 0;// firingStart をクリア (クリアしないと次回の実行で問題が発生します)
Firinglength = list.length; // 外部からアクセスできるキャッシュ リストの長さ
fire = true // コールバック関数を実行します
for (; firingIndex < firingLength; firingIndex ) {
If (list[firingIndex].apply(data[0], data[1]) === false) {
// なお、option.auto (自動実行) が設定されており、スタック (スタック) に関数がある場合、自動判定のためにこのメソッドを直接実行するコードのセクションが add() コード内にあります。 🎜>
//そのコードをブロックしたいので、auto を false に設定します
auto = false;
休憩;
}//関数が false を返した場合、後続のキューの実行を終了します
}
// コールバック関数が実行されたことを示すフラグ [スタック (スタック) 内の関数はまだ実行されていません]
//このスタックが一度も設定されていない場合は [] でなければならないため、
が必要です
//ここでの主な機能は、once が設定されていない場合、次のコードがインターセプトされることです。once が設定されている場合、コード
の実行後にデータがクリアされます。
if (スタック) {
If (stack.length) // まず、以下のリスト状態のコードをインターセプトし、次にスタックがあるかどうかを判断します
Fire (stack.shift ()) // スタックの先頭から取り出し、FIRE () メソッドを再帰します
}
Else if (auto) // ここにコードが来て、Option.once (一度だけ実行) が設定されていることを証明するため、リストはクリアです
list = [];
Else // 設定 AUTO は存在しないが、ONCE が設定されていることを証明するため、犠牲は究極の大法であり、コールバック オブジェクトは直接廃止されます
self.disable();
};
var self = {
add: function () {//コールバック関数を追加します
if (リスト) {
var start = list.length;
(関数 addCallback(args) {
each(args, function (item) {
If (isFunction(item)) {//関数の場合、コールバック リストをプッシュします
//typeof と Object.prototype.toString は異なることに注意してください
} else if (toString.call(item) === '[object Array]') {//配列の場合、再帰的にコールバック リストにプッシュします。この判断により配列のようなものは放棄されます
addcallback(item);
}
});
})(引数);
}
If (起動)//コールバック関数が現在実行中の場合、現在のコールバック関数リストの長さを更新する必要があります。更新しない場合は、新しくプッシュされたコールバック関数が渡されます。
fireLength = list.length;
else if (auto) {//コールバック関数が現在実行されておらず、自動実行が必要な場合
//上記のFire Startに値が割り当てられていることに注意してください。
firingStart = 開始;
//新しく追加されたパートナーを実行します
火(自動);
}
これを返します;
},
fire: function () {//トリガーコールバック関数
self.fireWith(this, argument);
これを返します;
},
fireWith: function (context, args) {//コールバック関数をトリガーし、コンテキストを指定します
//once が設定されている場合、スタックは未定義になり、once は 1 回だけ実行されることが保証される必要があるため、一度実行されると、ここのコードは再度実行されません
If (リスト && (!fired || スタック)) {
//補正パラメータ
//ここで、コンテキストインデックスは 0
//パラメータリストのインデックスは 2
// 配列アクセスへの変換は、オブジェクト表現がより多くのリソースを消費するためです。トップレベルの fire() コードには auto [メモリ パラメータ、自動実行] 関数があり、オブジェクトが使用される場合、より多くのメモリが消費されます。
args = [コンテキスト,
args.slice && args.slice()
];
fire(args);
}
これを返します;
},
remove: function () {//コールバック関数を削除します
if (リスト) {
each(引数, 関数 (項目) {
var インデックス;
// 複数の項目が存在する可能性があり、インデックスはループ内の検索範囲を表すことができ、以前に検索された項目を再度検索する必要はありません
While ((index = inArray(item, list,index)) > -1) {
list.splice(index, 1);
If (発火) {
//上記の fire で実行されている関数リストが正しく実行できることを確認します。これらのグローバル変数は、非同期で削除できるように fire に設定されます。
If (index
発射長さ--;
If (index
発射インデックス--;
}
}
});
}
これを返します;
},
Has: function (fn) {//コールバック関数が含まれているかどうか
inArray ? (fn, list) > -1 : リスト && list.length;
},
empty: function () {//このコールバック オブジェクトを空にします
リスト = [];
発火長 = 0;
これを返します;
},
disable: function () {//このコールバック オブジェクトを破棄すると、後続のコールバック関数リストは実行されなくなります
リスト = スタック = 自動 = 未定義;
これを返します;
},
無効: function () {//無効になっているかどうか