يبدو أن الاسم المستعار لوظيفة JavaScript لا يعمل
-
06-07-2019 - |
سؤال
كنت أقرأ فقط هذا السؤال وأردت تجربة طريقة الاسم المستعار بدلاً من طريقة تغليف الوظائف، لكن يبدو أنني لم أتمكن من تشغيلها في Firefox 3 أو 3.5beta4 أو Google Chrome، سواء في نوافذ تصحيح الأخطاء أو في صفحة ويب اختبارية.
فايربوج:
>>> window.myAlias = document.getElementById
function()
>>> myAlias('item1')
>>> window.myAlias('item1')
>>> document.getElementById('item1')
<div id="item1">
إذا قمت بوضعه في صفحة ويب، فإن استدعاء myAlias يعطيني هذا الخطأ:
uncaught exception: [Exception... "Illegal operation on WrappedNative prototype object" nsresult: "0x8057000c (NS_ERROR_XPC_BAD_OP_ON_WN_PROTO)" location: "JS frame :: file:///[...snip...]/test.html :: <TOP_LEVEL> :: line 7" data: no]
Chrome (مع إدراج >>> للتوضيح):
>>> window.myAlias = document.getElementById
function getElementById() { [native code] }
>>> window.myAlias('item1')
TypeError: Illegal invocation
>>> document.getElementById('item1')
<div id=?"item1">?
وفي صفحة الاختبار، أحصل على نفس "الاستدعاء غير القانوني".
أفعل شيئا خاطئا؟هل يمكن لأي شخص آخر إعادة إنتاج هذا؟
ومن الغريب أيضًا أنني حاولت للتو وهو يعمل في IE8.
المحلول
يجب عليك ربط هذه الطريقة بكائن المستند.ينظر:
>>> $ = document.getElementById
getElementById()
>>> $('bn_home')
[Exception... "Cannot modify properties of a WrappedNative" ... anonymous :: line 72 data: no]
>>> $.call(document, 'bn_home')
<body id="bn_home" onload="init();">
عندما تقوم بإنشاء اسم مستعار بسيط، يتم استدعاء الوظيفة على الكائن العام، وليس على كائن المستند.استخدم تقنية تسمى عمليات الإغلاق لإصلاح ذلك:
function makeAlias(object, name) {
var fn = object ? object[name] : null;
if (typeof fn == 'undefined') return function () {}
return function () {
return fn.apply(object, arguments)
}
}
$ = makeAlias(document, 'getElementById');
>>> $('bn_home')
<body id="bn_home" onload="init();">
بهذه الطريقة لا تفقد الإشارة إلى الكائن الأصلي.
وفي عام 2012، هناك الجديد bind
طريقة من ES5 تتيح لنا القيام بذلك بطريقة أكثر روعة:
>>> $ = document.getElementById.bind(document)
>>> $('bn_home')
<body id="bn_home" onload="init();">
نصائح أخرى
لقد بحثت بعمق لفهم هذا السلوك بالذات وأعتقد أنني وجدت تفسيرًا جيدًا.
قبل أن أتطرق إلى سبب عدم قدرتك على استخدام الاسم المستعار document.getElementById
, سأحاول شرح كيفية عمل وظائف/كائنات JavaScript.
عندما تقوم باستدعاء وظيفة JavaScript، يحدد مترجم JavaScript نطاقًا ويمرره إلى الوظيفة.
النظر في الوظيفة التالية:
function sum(a, b)
{
return a + b;
}
sum(10, 20); // returns 30;
يتم الإعلان عن هذه الوظيفة في نطاق النافذة وعند استدعائها تكون قيمتها this
داخل الدالة المبلغ ستكون العالمية Window
هدف.
بالنسبة للدالة "المجموع"، لا يهم قيمة "هذا" لأنها لا تستخدمها.
النظر في الوظيفة التالية:
function Person(birthDate)
{
this.birthDate = birthDate;
this.getAge = function() { return new Date().getFullYear() - this.birthDate.getFullYear(); };
}
var dave = new Person(new Date(1909, 1, 1));
dave.getAge(); //returns 100.
عند استدعاء وظيفة dave.getAge، يرى مترجم JavaScript أنك تستدعي وظيفة getAge على dave
كائن، لذلك يحدد this
ل dave
ويدعو getAge
وظيفة. getAge()
سوف يعود بشكل صحيح 100
.
ربما تعلم أنه في JavaScript يمكنك تحديد النطاق باستخدام ملف apply
طريقة.دعونا نحاول ذلك.
var dave = new Person(new Date(1909, 1, 1)); //Age 100 in 2009
var bob = new Person(new Date(1809, 1, 1)); //Age 200 in 2009
dave.getAge.apply(bob); //returns 200.
في السطر أعلاه، بدلاً من السماح لـ JavaScript بتحديد النطاق، فإنك تقوم بتمرير النطاق يدويًا باعتباره bob
هدف. getAge
سوف يعود الآن 200
على الرغم من أنك "اعتقدت" أنك اتصلت getAge
على ال dave
هدف.
ما الهدف من كل ما سبق؟يتم ربط الوظائف "بشكل فضفاض" بكائنات JavaScript الخاصة بك.على سبيل المثاليمكنك ان تفعل
var dave = new Person(new Date(1909, 1, 1));
var bob = new Person(new Date(1809, 1, 1));
bob.getAge = function() { return -1; };
bob.getAge(); //returns -1
dave.getAge(); //returns 100
لنأخذ الخطوة التالية.
var dave = new Person(new Date(1909, 1, 1));
var ageMethod = dave.getAge;
dave.getAge(); //returns 100;
ageMethod(); //returns ?????
ageMethod
التنفيذ يلقي خطأ!ماذا حدث؟
إذا قرأت نقاطي أعلاه بعناية، ستلاحظ ذلك dave.getAge
تم استدعاء الطريقة مع dave
مثل this
الكائن بينما لم يتمكن JavaScript من تحديد "النطاق" الخاص به ageMethod
تنفيذ.لذلك تم تمرير "النافذة" العالمية كـ "هذا".لم يكن window
لا يملك birthDate
ملكية، ageMethod
سوف يفشل التنفيذ.
كيف يمكن اصلاح هذا؟بسيط،
ageMethod.apply(dave); //returns 100.
هل كل ما سبق منطقي؟إذا كان الأمر كذلك، فستتمكن من شرح سبب عدم قدرتك على استخدام الاسم المستعار document.getElementById
:
var $ = document.getElementById;
$('someElement');
$
يسمى مع window
مثل this
و إذا getElementById
ومن المتوقع التنفيذ this
يكون document
, ، سوف تفشل.
مرة أخرى لإصلاح هذا، يمكنك القيام به
$.apply(document, ['someElement']);
فلماذا يعمل في Internet Explorer؟
لا أعرف التنفيذ الداخلي لـ getElementById
في IE، ولكن تعليق في مصدر jQuery (inArray
تنفيذ الطريقة) يقول أنه في IE، window == document
.إذا كان هذا هو الحال، ثم التعرج document.getElementById
يجب أن تعمل في IE.
ولتوضيح ذلك بشكل أكبر، قمت بإنشاء مثال مفصل.الق نظرة على Person
وظيفة أدناه.
function Person(birthDate)
{
var self = this;
this.birthDate = birthDate;
this.getAge = function()
{
//Let's make sure that getAge method was invoked
//with an object which was constructed from our Person function.
if(this.constructor == Person)
return new Date().getFullYear() - this.birthDate.getFullYear();
else
return -1;
};
//Smarter version of getAge function, it will always refer to the object
//it was created with.
this.getAgeSmarter = function()
{
return self.getAge();
};
//Smartest version of getAge function.
//It will try to use the most appropriate scope.
this.getAgeSmartest = function()
{
var scope = this.constructor == Person ? this : self;
return scope.getAge();
};
}
ل Person
وظيفة أعلاه، وهنا كيفية عمل مختلف getAge
سوف تتصرف الأساليب.
لنقم بإنشاء كائنين باستخدام Person
وظيفة.
var yogi = new Person(new Date(1909, 1,1)); //Age is 100
var anotherYogi = new Person(new Date(1809, 1, 1)); //Age is 200
console.log(yogi.getAge()); //Output: 100.
بشكل مستقيم للأمام، تحصل طريقة getAge yogi
كائن كما this
والمخرجات 100
.
var ageAlias = yogi.getAge;
console.log(ageAlias()); //Output: -1
مجموعات مترجم جافا سكريبت window
كائن كما this
و لنا getAge
سوف تعود الطريقة -1
.
console.log(ageAlias.apply(yogi)); //Output: 100
إذا قمنا بتعيين النطاق الصحيح، يمكنك استخدامه ageAlias
طريقة.
console.log(ageAlias.apply(anotherYogi)); //Output: 200
إذا مررنا كائنًا لشخص آخر، فسيظل يحسب العمر بشكل صحيح.
var ageSmarterAlias = yogi.getAgeSmarter;
console.log(ageSmarterAlias()); //Output: 100
ال ageSmarter
وظيفة القبض على الأصلي this
كائن لذا لا داعي للقلق الآن بشأن توفير النطاق الصحيح.
console.log(ageSmarterAlias.apply(anotherYogi)); //Output: 100 !!!
المشكلة مع ageSmarter
هو أنه لا يمكنك أبدًا تعيين النطاق على كائن آخر.
var ageSmartestAlias = yogi.getAgeSmartest;
console.log(ageSmartestAlias()); //Output: 100
console.log(ageSmartestAlias.apply(document)); //Output: 100
ال ageSmartest
ستستخدم الدالة النطاق الأصلي إذا تم توفير نطاق غير صالح.
console.log(ageSmartestAlias.apply(anotherYogi)); //Output: 200
سيظل بإمكانك اجتياز آخر Person
يعترض على getAgeSmartest
. :)
هذه إجابة قصيرة.
ما يلي يصنع نسخة من (إشارة إلى) الوظيفة.المشكلة هي أن الوظيفة الآن قيد التشغيل window
كائن عندما تم تصميمه للعيش على document
هدف.
window.myAlias = document.getElementById
البدائل هي
- لاستخدام غلاف (سبق أن ذكره فابيان ميناجر)
أو يمكنك استخدام اسمين مستعارين.
window.d = document // A renamed reference to the object window.d.myAlias = window.d.getElementById
إجابة قصيرة أخرى، فقط للالتفاف/التعرج console.log
وطرق تسجيل مماثلة.كلهم يتوقعون أن يكونوا في console
سياق.
هذا صالح للاستخدام عند التغليف console.log
مع بعض الإجراءات الاحتياطية، في حال واجهت أنت أو المستخدمون مشكلة ما باستخدام متصفح لا يدعمه (دائمًا)..هذا ليس حلاً كاملاً لهذه المشكلة، لأنه يحتاج إلى عمليات فحص موسعة واحتياطية - قد يختلف عدد الأميال المقطوعة.
مثال باستخدام التحذيرات
var warn = function(){ console.warn.apply(console, arguments); }
ثم استخدمه كالمعتاد
warn("I need to debug a number and an object", 9999, { "user" : "Joel" });
إذا كنت تفضل رؤية وسيطات التسجيل ملفوفة في مصفوفة (أنا أفعل ذلك في معظم الأوقات)، فاستبدلها .apply(...)
مع .call(...)
.
يجب أن تعمل مع console.log()
, console.debug()
, console.info()
, console.warn()
, console.error()
.أنظر أيضا console
على MDN.
بالإضافة إلى الإجابات الرائعة الأخرى، هناك طريقة jQuery بسيطة $.proxy.
يمكنك الاسم المستعار مثل هذا:
myAlias = $.proxy(document, 'getElementById');
أو
myAlias = $.proxy(document.getElementById, document);
أنت في الواقع لا أستطيع "الاسم المستعار الخالص" دالة على كائن محدد مسبقًا.لذلك، فإن أقرب طريقة للاستعارة التي يمكنك الحصول عليها دون التفاف هي البقاء داخل نفس الكائن:
>>> document.s = document.getElementById;
>>> document.s('myid');
<div id="myid">