قائمة بأمثلة JavaScript المضحكة والمخادعة
جافا سكريبت هي لغة عظيمة. لديها بناء جملة بسيط، ونظام بيئي كبير، والأهم من ذلك، مجتمع عظيم.
في الوقت نفسه، نعلم جميعًا أن JavaScript هي لغة مضحكة تحتوي على أجزاء صعبة. يمكن لبعضها أن يحول عملنا اليومي بسرعة إلى جحيم، وبعضها يمكن أن يجعلنا نضحك بصوت عالٍ.
الفكرة الأصلية لـ WTFJS تعود إلى Brian Leroux. هذه القائمة مستوحاة للغاية من حديثه "WTFJS" في dotJS 2012:
يمكنك تثبيت هذا الكتيب باستخدام npm
. فقط قم بتشغيل:
$ npm install -g wtfjs
يجب أن تكون قادرًا على تشغيل wtfjs
في سطر الأوامر الآن. سيؤدي هذا إلى فتح الدليل في $PAGER
الذي حددته. خلاف ذلك، يمكنك مواصلة القراءة هنا.
المصدر متاح هنا: https://github.com/denysdovhan/wtfjs
حاليًا، توجد هذه الترجمات لـ wtfjs :
مساعدة في الترجمة إلى لغتك
ملاحظة: تتم صيانة الترجمات من قبل مترجميها. وقد لا تحتوي على كل الأمثلة، وقد تكون الأمثلة الموجودة قديمة.
[]
يساوي ![]
true
لا يساوي ![]
، لكنه لا يساوي []
أيضًاNaN
ليس NaN
Object.is()
و ===
حالات غريبة[]
صحيح، ولكن ليس true
null
خطأ، لكنه ليس false
document.all
هو كائن، لكنه غير محددundefined
Number
parseInt
هو رجل سيءtrue
false
NaN
[]
و null
كائنات0.1 + 0.2
String
constructor
__proto__
`${{Object}}`
try..catch
غدرا.. قبضarguments
ووظائف السهمNumber.toFixed()
أرقامًا مختلفةMath.max()
أقل من Math.min()
null
ب 0
{}{}
غير محددarguments
ملزمةalert
من الجحيمsetTimeout
true
فقط للمتعة
- "للمتعة فقط: قصة ثوري عرضي" ، لينوس تورفالدس
الهدف الأساسي من هذه القائمة هو جمع بعض الأمثلة المجنونة وشرح آلية عملها، إن أمكن. فقط لأنه من الممتع أن نتعلم شيئًا لم نعرفه من قبل.
إذا كنت مبتدئًا، فيمكنك استخدام هذه الملاحظات للتعمق أكثر في JavaScript. آمل أن تحفزك هذه الملاحظات على قضاء المزيد من الوقت في قراءة المواصفات.
إذا كنت مطورًا محترفًا، فيمكنك اعتبار هذه الأمثلة بمثابة مرجع رائع لجميع المراوغات والحواف غير المتوقعة في JavaScript المحبوب لدينا.
على أية حال، مجرد قراءة هذا. ربما ستجد شيئًا جديدًا.
️ ملحوظة: إذا كنت تستمتع بقراءة هذه الوثيقة، من فضلك فكر في دعم مؤلف هذه المجموعة.
// ->
يستخدم لإظهار نتيجة التعبير. على سبيل المثال:
1 + 1 ; // -> 2
// >
يعني نتيجة console.log
أو مخرجات أخرى. على سبيل المثال:
console . log ( "hello, world!" ) ; // > hello, world!
//
هو مجرد تعليق يستخدم للتفسيرات. مثال:
// Assigning a function to foo constant
const foo = function ( ) { } ;
[]
يساوي ![]
المصفوفة متساوية وليست مصفوفة:
[ ] == ! [ ] ; // -> true
يقوم عامل المساواة المجردة بتحويل كلا الجانبين إلى أرقام للمقارنة بينهما، ويصبح كلا الجانبين الرقم 0
لأسباب مختلفة. المصفوفات صحيحة، لذلك على اليمين، يكون عكس قيمة الصدق false
، والذي يتم بعد ذلك إجباره على 0
. ومع ذلك، على اليسار، يتم إجبار المصفوفة الفارغة على رقم دون أن تصبح منطقية أولاً، ويتم إجبار المصفوفة الفارغة على 0
، على الرغم من كونها صحيحة.
إليك كيفية تبسيط هذا التعبير:
+ [ ] == + ! [ ] ;
0 == + false ;
0 == 0 ;
true ;
وانظر أيضًا []
صحيحًا، ولكنه غير true
.
!
)true
لا يساوي ![]
، لكنه لا يساوي []
أيضًا المصفوفة ليست متساوية true
، لكن المصفوفة ليست true
أيضًا؛ Array يساوي false
، وليس Array يساوي false
أيضًا:
true == [ ] ; // -> false
true == ! [ ] ; // -> false
false == [ ] ; // -> true
false == ! [ ] ; // -> true
true == [ ] ; // -> false
true == ! [ ] ; // -> false
// According to the specification
true == [ ] ; // -> false
toNumber ( true ) ; // -> 1
toNumber ( [ ] ) ; // -> 0
1 == 0 ; // -> false
true == ! [ ] ; // -> false
! [ ] ; // -> false
true == false ; // -> false
false == [ ] ; // -> true
false == ! [ ] ; // -> true
// According to the specification
false == [ ] ; // -> true
toNumber ( false ) ; // -> 0
toNumber ( [ ] ) ; // -> 0
0 == 0 ; // -> true
false == ! [ ] ; // -> true
! [ ] ; // -> false
false == false ; // -> true
! ! "false" == ! ! "true" ; // -> true
! ! "false" === ! ! "true" ; // -> true
خذ بعين الاعتبار هذه خطوة بخطوة:
// true is 'truthy' and represented by value 1 (number), 'true' in string form is NaN.
true == "true" ; // -> false
false == "false" ; // -> false
// 'false' is not the empty string, so it's a truthy value
! ! "false" ; // -> true
! ! "true" ; // -> true
"b" + "a" + + "a" + "a" ; // -> 'baNaNa'
هذه نكتة قديمة في جافا سكريبت، لكنها مُعاد صياغتها. وهنا الأصلي:
"foo" + + "bar" ; // -> 'fooNaN'
يتم تقييم التعبير كـ 'foo' + (+'bar')
، والذي يحول 'bar'
إلى ليس رقمًا.
+
)NaN
ليس NaN
NaN === NaN ; // -> false
تحدد المواصفات بدقة المنطق الكامن وراء هذا السلوك:
- إذا كان
Type(x)
مختلفًا عنType(y)
، فقم بإرجاع false .- إذا كان
Type(x)
هو رقم، إذن
- إذا كانت
x
هي NaN ، فسيتم إرجاع false .- إذا كانت
y
هي NaN ، قم بإرجاع false .- … … … …
— 7.2.14 مقارنة صارمة للمساواة
بعد تعريف NaN
من IEEE:
هناك أربع علاقات متنافية محتملة: أقل من، يساوي، أكبر من، وغير مرتبة. تنشأ الحالة الأخيرة عندما يكون مُعامل واحد على الأقل هو NaN. يجب على كل NaN مقارنة كل شيء غير مرتب، بما في ذلك نفسه.
- "ما هو الأساس المنطقي لجميع المقارنات التي تُرجع خطأً لقيم IEEE754 NaN؟" في StackOverflow
Object.is()
و ===
حالات غريبة يحدد Object.is()
ما إذا كانت القيمتان لهما نفس القيمة أم لا. إنه يعمل بشكل مشابه للعامل ===
ولكن هناك بعض الحالات الغريبة:
Object . is ( NaN , NaN ) ; // -> true
NaN === NaN ; // -> false
Object . is ( - 0 , 0 ) ; // -> false
- 0 === 0 ; // -> true
Object . is ( NaN , 0 / 0 ) ; // -> true
NaN === 0 / 0 ; // -> false
في لغة جافا سكريبت، NaN
و NaN
لهما نفس القيمة ولكنهما ليسا متساويين تمامًا. NaN === NaN
كاذبًا يرجع إلى أسباب تاريخية، لذا قد يكون من الأفضل قبوله كما هو.
وبالمثل، فإن -0
و 0
متساويان تمامًا، لكنهما ليسا نفس القيمة.
لمزيد من التفاصيل حول NaN === NaN
، راجع الحالة أعلاه.
لن تصدق، ولكن...
( ! [ ] + [ ] ) [ + [ ] ] +
( ! [ ] + [ ] ) [ + ! + [ ] ] +
( [ ! [ ] ] + [ ] [ [ ] ] ) [ + ! + [ ] + [ + [ ] ] ] +
( ! [ ] + [ ] ) [ ! + [ ] + ! + [ ] ] ;
// -> 'fail'
وبتقسيم تلك الكتلة من الرموز إلى أجزاء، نلاحظ أن النمط التالي يحدث في كثير من الأحيان:
! [ ] + [ ] ; // -> 'false'
! [ ] ; // -> false
لذلك نحاول إضافة []
إلى false
. ولكن بسبب عدد من استدعاءات الوظائف الداخلية ( binary + Operator
-> ToPrimitive
-> [[DefaultValue]]
) انتهى بنا الأمر إلى تحويل المعامل الصحيح إلى سلسلة:
! [ ] + [ ] . toString ( ) ; // 'false'
عند التفكير في السلسلة كمصفوفة، يمكننا الوصول إلى الحرف الأول الخاص بها عبر [0]
:
"false" [ 0 ] ; // -> 'f'
والباقي واضح، ولكن i
صعبة. يتم التقاط i
في fail
عن طريق إنشاء السلسلة 'falseundefined'
والاستيلاء على العنصر الموجود في الفهرس ['10']
.
المزيد من الأمثلة:
+ ! [ ] // -> 0
+ ! ! [ ] // -> 1
! ! [ ] // -> true
! [ ] // -> false
[ ] [ [ ] ] // -> undefined
+ ! ! [ ] / + ! [ ] // -> Infinity
[ ] + { } // -> "[object Object]"
+ { } // -> NaN
[]
صحيح، ولكن ليس true
المصفوفة هي قيمة صحيحة، لكنها لا تساوي true
.
! ! [ ] // -> true
[ ] == true // -> false
فيما يلي روابط للأقسام المقابلة في مواصفات ECMA-262:
!
)null
خطأ، لكنه ليس false
على الرغم من أن null
قيمة خاطئة، إلا أنها لا تساوي false
.
! ! null ; // -> false
null == false ; // -> false
وفي الوقت نفسه، فإن القيم الخاطئة الأخرى، مثل 0
أو ''
تساوي false
.
0 == false ; // -> true
"" == false ; // -> true
والتفسير هو نفسه كما في المثال السابق. وهذا هو الرابط المقابل:
document.all
هو كائن، لكنه غير محدد
️ يعد هذا جزءًا من واجهة برمجة تطبيقات المتصفح ولن يعمل في بيئة Node.js️
على الرغم من أن document.all
عبارة عن كائن يشبه المصفوفة ويتيح الوصول إلى عقد DOM في الصفحة، إلا أنه يستجيب للدالة typeof
على أنها undefined
.
document . all instanceof Object ; // -> true
typeof document . all ; // -> 'undefined'
وفي الوقت نفسه، document.all
لا يساوي undefined
.
document . all === undefined ; // -> false
document . all === null ; // -> false
لكن في نفس الوقت:
document . all == null ; // -> true
اعتاد
document.all
أن يكون وسيلة للوصول إلى عناصر DOM، خاصة مع الإصدارات القديمة من IE. على الرغم من أنه لم يكن معيارًا على الإطلاق، فقد تم استخدامه على نطاق واسع في كود JS القديم. عندما تقدم المعيار باستخدام واجهات برمجة التطبيقات الجديدة (مثلdocument.getElementById
) أصبح استدعاء واجهة برمجة التطبيقات هذا قديمًا وكان على لجنة المعايير أن تقرر ما يجب فعله به. نظرًا لاستخدامها على نطاق واسع، قرروا الاحتفاظ بواجهة برمجة التطبيقات (API) ولكنهم قدموا انتهاكًا متعمدًا لمواصفات JavaScript. سبب استجابتهاfalse
عند استخدام مقارنة المساواة الصارمة معundefined
بينماtrue
عند استخدام مقارنة المساواة المجردة يرجع إلى الانتهاك المتعمد للمواصفات التي تسمح بذلك صراحة.- "الميزات القديمة - document.all" في WhatWG - مواصفات HTML - "الفصل 4 - ToBoolean - القيم الزائفة" في YDKJS - الأنواع والقواعد
Number.MIN_VALUE
هو أصغر رقم، وهو أكبر من الصفر:
Number . MIN_VALUE > 0 ; // -> true
Number.MIN_VALUE
هو5e-324
، أي أصغر رقم موجب يمكن تمثيله ضمن الدقة العائمة، أي أنه أقرب ما يمكن إلى الصفر. إنه يحدد أفضل دقة يمكن أن توفرها لك العوامات.الآن أصغر قيمة إجمالية هي
Number.NEGATIVE_INFINITY
على الرغم من أنها ليست رقمية بالمعنى الدقيق للكلمة.- "لماذا يكون
0
أقل منNumber.MIN_VALUE
في JavaScript؟" في StackOverflow
️ يوجد خطأ في V8 v5.5 أو أقل (Node.js <=7)️
جميعكم تعلمون أن الـ unknown المزعجة ليست وظيفة ، لكن ماذا عن هذا؟
// Declare a class which extends null
class Foo extends null { }
// -> [Function: Foo]
new Foo ( ) instanceof null ;
// > TypeError: function is not a function
// > at … … …
هذا ليس جزءا من المواصفات. إنه مجرد خطأ تم إصلاحه الآن، لذا لن تكون هناك مشكلة معه في المستقبل.
إنها استمرار للقصة مع الأخطاء السابقة في البيئة الحديثة (تم اختبارها باستخدام Chrome 71 وNode.js v11.8.0).
class Foo extends null { }
new Foo ( ) instanceof null ;
// > TypeError: Super constructor null of Foo is not a constructor
وهذا ليس خطأ لأنه:
Object . getPrototypeOf ( Foo . prototype ) ; // -> null
إذا لم يكن للفئة مُنشئ، فسيتم الاتصال من سلسلة النموذج الأولي. ولكن في الأصل ليس لديه منشئ. فقط في حالة، سأوضح أن null
هو كائن:
typeof null === "object" ;
لذلك، يمكنك أن ترث منه (على الرغم من أنه في عالم OOP كان سيضربني بمثل هذه الشروط). لذلك لا يمكنك استدعاء المنشئ الفارغ. إذا قمت بتغيير هذا الرمز:
class Foo extends null {
constructor ( ) {
console . log ( "something" ) ;
}
}
ترى الخطأ:
ReferenceError: Must call super constructor in derived class before accessing 'this' or returning from derived constructor
وإذا أضفت super
:
class Foo extends null {
constructor ( ) {
console . log ( 111 ) ;
super ( ) ;
}
}
JS يلقي خطأ:
TypeError: Super constructor null of Foo is not a constructor
ماذا لو حاولت إضافة صفيفين؟
[ 1 , 2 , 3 ] + [ 4 , 5 , 6 ] ; // -> '1,2,34,5,6'
يحدث التسلسل. خطوة بخطوة، يبدو الأمر كما يلي:
[ 1 , 2 , 3 ] +
[ 4 , 5 , 6 ] [
// call toString()
( 1 , 2 , 3 )
] . toString ( ) +
[ 4 , 5 , 6 ] . toString ( ) ;
// concatenation
"1,2,3" + "4,5,6" ;
// ->
( "1,2,34,5,6" ) ;
لقد قمت بإنشاء مصفوفة تحتوي على 4 عناصر فارغة. على الرغم من كل شيء، ستحصل على مصفوفة مكونة من ثلاثة عناصر، بسبب الفواصل الزائدة:
let a = [ , , , ] ;
a . length ; // -> 3
a . toString ( ) ; // -> ',,'
يمكن أن تكون الفواصل اللاحقة (تسمى أحيانًا "الفواصل النهائية") مفيدة عند إضافة عناصر أو معلمات أو خصائص جديدة إلى تعليمات JavaScript البرمجية. إذا كنت تريد إضافة خاصية جديدة، يمكنك ببساطة إضافة سطر جديد دون تعديل السطر الأخير سابقًا إذا كان هذا السطر يستخدم بالفعل فاصلة زائدة. وهذا يجعل اختلافات التحكم في الإصدار أكثر وضوحًا وقد يكون تحرير التعليمات البرمجية أقل إزعاجًا.
- الفواصل الزائدة في MDN
تعد مساواة المصفوفات أمرًا صعبًا في JS، كما ترون أدناه:
[ ] == '' // -> true
[ ] == 0 // -> true
[ '' ] == '' // -> true
[ 0 ] == 0 // -> true
[ 0 ] == '' // -> false
[ '' ] == 0 // -> true
[ null ] == '' // true
[ null ] == 0 // true
[ undefined ] == '' // true
[ undefined ] == 0 // true
[ [ ] ] == 0 // true
[ [ ] ] == '' // true
[ [ [ [ [ [ ] ] ] ] ] ] == '' // true
[ [ [ [ [ [ ] ] ] ] ] ] == 0 // true
[ [ [ [ [ [ null ] ] ] ] ] ] == 0 // true
[ [ [ [ [ [ null ] ] ] ] ] ] == '' // true
[ [ [ [ [ [ undefined ] ] ] ] ] ] == 0 // true
[ [ [ [ [ [ undefined ] ] ] ] ] ] == '' // true
يجب عليك أن تراقب بعناية الأمثلة المذكورة أعلاه! تم وصف السلوك في القسم 7.2.15 مقارنة المساواة المجردة للمواصفات.
undefined
Number
إذا لم نمرر أي وسيطات إلى مُنشئ Number
، فسنحصل على 0
. يتم تعيين القيمة undefined
المحددة للوسيطات الرسمية عندما لا تكون هناك وسيطات فعلية، لذلك قد تتوقع أن Number
بدون وسيطات يأخذ قيمة undefined
كقيمة للمعلمة الخاصة به. ومع ذلك، عندما نمر undefined
، سوف نحصل على NaN
.
Number ( ) ; // -> 0
Number ( undefined ) ; // -> NaN
وفقا للمواصفات:
n
+0
.n
؟ ToNumber(value)
.undefined
، يجب أن يُرجع ToNumber(undefined)
NaN
.وهنا القسم المناسب:
argument
) parseInt
هو رجل سيء تشتهر parseInt
بمراوغاتها:
parseInt ( "f*ck" ) ; // -> NaN
parseInt ( "f*ck" , 16 ) ; // -> 15
Explanation: يحدث هذا لأن parseInt
سيستمر في التحليل حرفًا بحرف حتى يصل إلى حرف لا يعرفه. الحرف f
في 'f*ck'
هو الرقم السداسي العشري 15
.
إن تحليل Infinity
إلى عدد صحيح هو شيء ...
//
parseInt ( "Infinity" , 10 ) ; // -> NaN
// ...
parseInt ( "Infinity" , 18 ) ; // -> NaN...
parseInt ( "Infinity" , 19 ) ; // -> 18
// ...
parseInt ( "Infinity" , 23 ) ; // -> 18...
parseInt ( "Infinity" , 24 ) ; // -> 151176378
// ...
parseInt ( "Infinity" , 29 ) ; // -> 385849803
parseInt ( "Infinity" , 30 ) ; // -> 13693557269
// ...
parseInt ( "Infinity" , 34 ) ; // -> 28872273981
parseInt ( "Infinity" , 35 ) ; // -> 1201203301724
parseInt ( "Infinity" , 36 ) ; // -> 1461559270678...
parseInt ( "Infinity" , 37 ) ; // -> NaN
كن حذرًا عند التحليل null
أيضًا:
parseInt ( null , 24 ) ; // -> 23
توضيح:
إنه يحول
null
إلى السلسلة"null"
ويحاول تحويلها. بالنسبة للجذور من 0 إلى 23، لا توجد أرقام يمكن تحويلها، لذلك تُرجع NaN. عند الرقم 24، يتم إضافة الحرف"n"
، وهو الحرف الرابع عشر، إلى نظام الأرقام. عند الرقم 31، تتم إضافة الحرف"u"
وهو الحرف الحادي والعشرون ويمكن فك تشفير السلسلة بأكملها. عند 37، لم يعد هناك أي مجموعة أرقام صالحة يمكن إنشاؤها ويتم إرجاعNaN
.- "parseInt(null, 24) === 23...انتظر، ماذا؟" في StackOverflow
لا تنسى عن الثماني:
parseInt ( "06"