لنتحدث عن أصل الإغلاق
الدالة a () {var i = 0 ؛ function b () {console.log (i) ؛} return b ؛} var c = a () ؛ c () ؛بشكل عام ، عندما تستخدم وظيفة مجهولة داخل دالة متغيرها الخاص ويتم إرجاع الوظيفة المجهولة ، يتم إنشاء إغلاق ، مثل الكود أعلاه.
في هذا الوقت ، حتى إذا تم تدمير نهاية المكالمة ، فسوف أكون موجودًا ولن أختفي. عندما يتم تعريف A ، سيقوم مترجم JS بتعيين سلسلة نطاق الوظيفة A على البيئة التي يتم تعريفها فيها. عند تنفيذ A ، ستدخل A بيئة التنفيذ المقابلة. فقط بعد إنشاء بيئة التنفيذ ، سيكون هناك سمة نطاق نطاق ، ثم قم بإنشاء كائن نشط ، ثم قم بتعيينه على أعلى سلسلة النطاق.
الآن سلسلة نطاق A لها كائن نشط من A و Window
ثم أضف سمة الوسائط إلى الكائن النشط
في هذا الوقت ، يتم إعطاء الإشارة إلى وظيفة الإرجاع B لـ A إلى C. تحتوي سلسلة نطاق B على مرجع الكائن النشط لـ A ، بحيث يمكن لـ C الوصول إلى الكائن النشط لـ A. في هذا الوقت ، لن يكون GC بعد العودة
ما سبق مقدمة موجزة للإغلاق. إذا قلت الكثير ، فسيكون من السهل الالتفاف. دعنا ننهيها باختصار هنا ثم ندخل المشهد الفعلي لشرحه.
المشهد الفعلي
شكوك الزملاء
طلب مني زميل لي قراءة رمز:
var user = function (opts) {var scope = this ؛ for (var k in opts) {scope ['get' + k] = function () {return opts [k] ؛الكود بسيط للغاية ، وآمل أن يولد طريقة GET/SET للكائن الوارد ، لكنه واجه مشكلة إغلاق هنا:
سبب هذه المشكلة هو أن K المستخدم داخليًا في قيمة الإرجاع هو دائمًا "العمر". يرجع هذا k إلى الكائن النشط المشترك بواسطة وظيفة getxxx ، لذلك من السهل نسبيًا تعديلها هنا.
var user = function (opts) {var scope = this ؛ for (var k in opts) {(function (k) {'get' + k] = function () {return opts [k] ؛ 11}) ؛قم بإنشاء وظيفة تنفيذ فورية داخل الحلقة ومرر K in. في هذا الوقت ، تشارك وظيفة getxxx "k" لكل وظيفة مجهولة.
توليد معرف فريد
يعد إنشاء معرف فريد أيضًا طريقة كلاسيكية لاستخدام الإغلاق.
دالة getUuid () {var id = 0 ؛ return function () {return ++ id ؛}} var uuid = getUuid () ؛هذا الرمز هو في الواقع مفيدة للغاية. نقوم باستمرار بتنفيذ UUID () في المتصفح وستحصل بالفعل على قيم مختلفة ، ولكن إذا استخدمنا getUuid () () فقط ، فستظل القيمة هي نفسها في كل مرة.
سبب هذه المشكلة هو أن نخصص النتيجة بعد تنفيذ getUuid إلى UUID. في هذا الوقت ، يحفظ UUID الإشارة إلى الوظيفة المجهولة ، وتوفر الدالة المجهولة الكائن النشط من getUuid ، لذلك لم يتم تدمير المعرف.
إذا قمت بتسميتها مباشرة ، فسيتم تجديد الكائن النشط في كل مرة ، لذلك لا يمكن حفظ المعرف.
قطعة رمز مثيرة للاهتمام
util.TryUrl = function (url) {var iframe = document.createElement ('iframe') ؛ iframe.height = 1 ؛ iframe.width = 1 ؛ iframe.frameBorder = 0 ؛ iframe.style.position = 'aboSt' '-9999px' ؛ document.body.appendChild (iframe) ؛ util.TryUrl = function (url) {iframe.src = url ؛} ؛ U.Tryurl (url) ؛} ؛هذا الرمز ممتع للغاية. عندما نسميها لأول مرة ، سنقوم بإنشاء كائن iframe ، ويوجد كائن iframe عند المرة الثانية. سنقوم بتبسيط الكود هنا.
var getUuid = function () {var i = 0 ؛ getUuid = function () {return i ++ ؛} ؛ return getUuid () ؛} ؛بعد هذا التعديل ، لا توجد في الواقع وظيفة إرجاع ، لكننا ما زلنا نشكل إغلاقًا
تفويض الحدث والإغلاق
نعلم جميعًا أن JQuery's on Use Usives Defation ، لكن لا يزال الأمر يتطلب الكثير من الجهد لفهم وفد الحدث حقًا ، لذلك دعونا نجربه هنا
الإغلاق هي حجر الزاوية في تنفيذ وفد الحدث. أخيرًا ، دعنا ننهي دراسة الإغلاق اليوم مع تفويض الأحداث.
أضف إلى صفحتنا إلى بنية DOM التالية
<input id = "input" value = "input" type = "button"/> <div id = "div"> أنا div </div> <span id = "span"> am span </span> <div id = "wrapper"> <input id = "inner" value = "i am inner" type = "button"/> </div>
إذا استخدمنا Zepto ، فإننا نستخدم الطريقة التالية لربط الأحداث
$ .on ("انقر" ، "المحدد" ، FN)ليس لدينا Zepto هنا ، فقط ننفذها ببساطة من قبل أنفسنا
مبدأ تفويض الأحداث
بادئ ذي بدء ، حجر الزاوية في تنفيذ مندوب الأحداث هو فقاعات الأحداث. كل نقرة على الصفحة ستخفق في النهاية إلى العنصر الأم ، حتى نتمكن من التقاط جميع الأحداث في المستند.
بعد معرفة هذه المشكلة ، يمكننا تنفيذ طريقة ربط حدث بسيطة مندوب أنفسنا:
وظيفة مندوب (محدد ، النوع ، fn) {document.addeventListener (type ، fn ، false) ؛} مندوب ('#input' ، 'click' ، function () {console.log ('ttt') ؛}) ؛هذا الرمز هو أبسط تطبيق. بادئ ذي بدء ، سنقوم بتنفيذ حدث النقر بغض النظر عن المكان الذي نقر فيه على الصفحة. بالطبع ، من الواضح أن هذا ليس هو الموقف الذي نريد رؤيته ، لذلك نقوم بالمعالجة لتحفيز الحدث الذي يجب أن يكون عليه النقر.
فيما يلي بعض الأسئلة الأكثر جدية:
① نظرًا لأن حدثنا مرتبط بالوثيقة ، كيف أعرف العنصر الذي أنقر عليه الآن؟
② حتى لو كان بإمكاني الحصول على عنصر النقر الحالي استنادًا إلى E.Target ، كيف أعرف أي عنصر لديه الحدث؟
③ حتى لو كان بإمكاني تحديد عنصر النقرة الحالية يحتاج إلى تنفيذ الحدث بناءً على المحدد ، فكيف يمكنني العثور على الحدث الذي هو عليه
إذا كان من الممكن حل المشكلات المذكورة أعلاه ، فستكون العملية اللاحقة بسيطة نسبيًا
تحديد ما إذا كان عنصر النقر يؤدي إلى حدوث الحدث
أولاً ، عند النقر ، يمكننا استخدام E.Target للحصول على عنصر النقر الحالي ، ثم البحث عن DOM الأصل بدوره وفقًا للمحدد. إذا تم العثور عليه ، يجب تشغيل الحدث.
لأنه لا يمكن تحديدها إلا عند تشغيلها ، نحتاج إلى إعادة كتابة وظيفة رد الاتصال FN ، لذلك بعد عملية بسيطة:
var arr = [] ؛ var slice = arr.slice ؛ var extend = function (src ، obj) {var o = {} ؛ for (var k in src) {o [k] = src [k] ؛ الوظيفة (e) {// العنصر الموجود بواسطة المحدد var selectorel = document.queryselector (محدد) ؛ // عنصر النقر الحالي var el = e.target ؛ // تحديد ما إذا كان العنصر الموجود بواسطة المحدد يحتوي على عنصر النقر الحالي. إذا تم تضمينه ، فيجب أن يتم تشغيل الحدث/*********************************: هذا مجرد تطبيق بسيط ، وسيكون هناك العديد من الأحكام في التطبيقات الفعلية ************************/if (selectorel.contains (el)) [evt] .concat (slice.call (الوسائط ، 1)) ؛ callback.apply (selectorel ، evt) ؛ var s = '' ؛حتى نتمكن من توسيع المكالمة:
تفويض ('#input' ، 'click' ، function () {console.log ('input') ؛}) ؛ مندوب ('#div' ، 'click' ، function () {console.log ('div') ؛}) ؛ depipate ('#wrapper' ، 'click' ، function () {console.log ('span') ؛}) ؛ مندوب ('#inner' ، 'click' ، function () {console.log ('inner') ؛}) ؛دعونا نحلل البرنامج بأكمله بإيجاز
① ندعو المندوب لإضافة أحداث إلى الجسم
② عند الربط ، أعدنا كتابة عمليات الاسترجاعات فيه.
③ عند النقر (عدد المرات التي سيؤدي فيها حدث الربط بالفعل إلى نقرة) ، سيتم الحصول على العنصر الحالي للتحقق مما إذا كان العنصر الذي تم تفتيشه بواسطة محدده يحتوي عليه. إذا كان يحتوي على ذلك ، فسيتم تشغيل الحدث.
④ نظرًا لأن الإغلاق يتم تشكيله في كل مرة تقوم فيها بالتسجيل هنا ، يتم الحفاظ على رد الاتصال الوارد ، بحيث يمكنك العثور على وظيفة رد الاتصال الخاصة بك في كل مرة تسميها (من المفيد جدًا فهم الإغلاق هنا)
⑤ أخيرًا ، أعد كتابة الهقر الحالي لمقبض الأحداث ، وبالتالي ينتهي تفويض الأحداث
ملاحظة: لدي مشاكل مع التنفيذ هنا ، مثل معالجة الحدث ، ولكن كعروض تجريبية ، لن أهتم به. يمكن للأصدقاء المهتمين الاطلاع على تطبيق Zepto بأنفسهم.
قضايا تفويض الأحداث
يمكن أن يحسن تفويض الأحداث الكفاءة ، ولكن هناك شيء مزعج آخر هو أنه من غير المجدي إيقاف الفقاعات.
خذ الرمز أعلاه كمثال. هناك عنصر داخلي وعنصر غلاف. يلفون علاقة بعضهم البعض.
ومع ذلك ، فإن أمر التنفيذ الخاص به ليس ترتيب فقاعات الأحداث من الداخل والخارج ، لأن جميع الأحداث مرتبطة بالوثيقة ، وبالتالي يتم تحديد أمر التنفيذ هنا حسب أمر التسجيل الخاص به
هنا سؤال: كيف "توقف الفقاعات"
التنفيذ المكتمل في Inner
estopimmediatepropagation ()
يمكن أن يحقق هذا الغرض ، لكنه لا يزال يتطلب تسجيل العنصر الداخلي من قبل
بالإضافة إلى ذلك ، هناك حدث واحد فقط يرتبط بمثل هذه العناصر المتداخلة ، ويقرر E.Target أي حدث يجب تنفيذه. لمزيد من التفاصيل ، يرجى النظر في ذلك بنفسك.
قد تتم مواجهة المشكلات المذكورة أعلاه عند استخدام العمود الفقري