JavaScript: The Bad Parts (memes explained)
JavaScript nə qədər gözəl dil olsa da, bəzi absurd tərəfləri də var. Burada qarşılaşdığınız maraqlı nəticələri çətin ki, başqa haradasa görəsiniz. Bugün qarşılaşdığım bir neçə maraqlı JS memlərini sizinlə paylaşacağam və onları izah etməyə çalışacağam.
Burada izah olunacaq bir şey yoxdur əslində, amma yenə də deyim. Birinci sətirdə adi rəqəm toplaması baş verir. İkinci sətirdə toplamanın hər iki tərəfinin primitiv qiymətləri string olduğuna görə string concatenation baş verir. Dördüncü sətrin birinci hissəsində yenə concatenation baş verir və cavab "22" alınır, ikinci hissəsində isə "22"-"2" hesablanır. Bu zaman ECMAScript Qaydalarına görə çıxma əməliyyatı zamanı hər iki tərəf rəqəmə çevrilir.
Məsələnin birinci hissəsində 0="0" düzgündür. Burada Qaydalara görə hər iki tərəf value type olduğuna görə dəyərlər alınır və müqayisə olunur. İkinci hissədə 0 rəqəmi boş massiv ilə müqayisə olunur. Array value type olduğuna görə onun primitiv dəyəri alınır. Məsələn:
[‘1’, ‘May’].toString() // ‘1,May’
[].toString() // ""
Burada ikinci type coercion baş verir. Belə ki müqayisənin birinci tərəfi number type olduğuna görə ikinci tərəf çevrilməyə məruz qalır.
Number("") // 0 və
0 == 0 // true
Yaxşı, onda belə bir nəticə ilə qarşılaşırıq:
0 == "0" // true
0 == [] // true
Onda məntiqlə "0" == [] də doğru olmalıdır. Xeyr. Bu elə deyil, amma səbəbi başa düşmək çətin də deyil. Boş massivin primitive valuesu olan boş string string tipindən "0" ilə müqyisə olunur və onlar bir-birinə bərabər deyil 😊
typeof NaN == "number" // true.
Bu necə düzgün ola bilər? Baxın, burada NaN valuelar əslində number tipindədir, lakin onun number olaraq reprezentasiyası yoxdur. Bunu sadəcə belə qəbul etməyimiz lazımdır. Başqa bir prizmadan NaN primitiv tipdir və o rəqəmlərə aiddir. Onun tipinin nə olması daha düzgün olardı ki?
RED: şərhlərdə baltalaya bilərsiz.
İkinci sətirdəki məsələ JavaScript-dəki number tipi və onun həcmi ilə bağlıdır. Belə ki, Number.MAX_SAFE_INTEGER 9007199254740991-ə yəni 2**53–1-ə bərabərdir. JavaScript bu limitə qədər rəqəmlərinizi təhlükəsiz saxlamağa zəmanət verir.
“Ay Amaan! Daha yuxarı rəqəmləri necə saxlayacağıq?” deyənlər olacaq, bilirəm. Sanki nə vaxtsa o qədər rəqəmə ehtiyacları olub. Bunun üçün JavaScript-də yeni bir tip olan BigInt tipinə müraciət etməli olacaqsınız. Rəqəmin sonuna "n" əlavə etməklə və ya let num = BigInt( Number.MAX_SAFE_INTEGER ); yazmaqla yarada bilərsiniz. Bir pis xəbərim var: floating point yoxdur. Yəni bölmə zamanı: BigInt(100) / BigInt(3) = 33n kimi bir nəticəmiz olur. Nə vaxtsa böyük rəqəmlərə ehtiyacınız olsa BigInt barədə buradan girib oxuya bilərsiniz.
0.1 + 0.2 = 0.30000000000000004 😍
Ən sevdiyim! Bu əslində bu məqaləlik mövzu deyil. Bunu JavaScript-in səhvi kimi düşünməyin, bu bir çox başqa dillərdə də mövcuddur: Python, Haskel, Go, C, C++, Java, Rust və s. Bu IEEE 754: ikiqat dəqiqliklə kəsr ədədlərin saxlanması spesifikasiyasına görə belədir. IEEE 754 sadəcə sıxma alqoritmidir. Yəni, buradakı 0.00000000000000004 sürüşmə JavaScript-in rəqəmlərin necə saxlaması ilə əlaqəlidir. Bunun üçün iki video paylaşıram. Onlara baxmağınız şiddətlə tövsiyə olunur 😇
Əvvəlcə sizinlə Two's Complement ilə tanış olaq. Daha sonra isə IEEE 754 representation barədə video bizi gözləyir. Floating point numberlər binary formatda necə saxlanır onu öyrənəcəyik. Olay budur yəni. Bununla da 0.1 + 0.2 sirrini öyrənmiş olacağıq.
Ümid edirəm videoları izlədiniz. Digər məsələyə baxaq: Math.max() və Math.min() Math classının statik metodlarıdır. Max metodu ötürülən parametrlər içərisində ən böyüyünü tapır: Math.max(1, 2, 3, 4) // 4. Heç bir parametr ötürülmədikdə -Infinity cavabını qaytamağının səbəbi mənfi sonsuzluğun ilkin müqayisə olunan rəqəm olmasıdır. Çünki bütün rəqəmlər ondan böyükdür. Eynilə də Math.min().
İndi keçək əsas məsələyə: Object + Array = 0
[] + [] // ""
[] + {} // [object Object]
{} + [] // 0
İlk baxışdan elə görünə bilər amma bu belə deyil. Bu məsələni mən də yeni öyrəndim. Yaxın uşaqlara və əhli StackOverflow-ya dərin təşəkkürümü bildirirəm.
Birinci sətirdə boş arraylərin primitive valueları olan boş stringlər ("") cəmlənir: "" + "" və yenə boş string alınır. İkinci halda boş stringlə "" buradakı boş obyektin default primitive value-su olan "[object Object]" toplanır.
[].toString() // ""
{}.toString() // "[object Object]"
Üçüncü halda isə vəziyyər fərqlidir. Interpretator burada qıvrım mötərizələri {} boş obyekt yox, boş kod bloku kimi tanımlayır. Boş kod blokunun təsiri olmadığı üçün geriyə qalır +[]
Number([]) // 0
Belə! Sonuncu bir iki şey də izah edim dağılışaq. Siz də məqalədən bezdiniz yəqin ki.
( ! + [] + [] + ![] ).length // 9
Burada !+[] // true result qaytarır. Array boş olduğu üçün false value sayılır, boolean not operatoru onu əks dəyərdə booleana çevirir. Alınan dəyər sonrasında gələn boş string dəyəri ilə konkatenasiya olunur. Sonuncu hissəyə baxaq: ![]. Burada boolean not operatoru arrayi müəyyən bir value olaraq görür və bunu true qəbul edir və əksinə çevirir. Misalımız aşağıdakı kimi olur:
( "true" + false ).length ˃˃ "truefalse".length // 9
++[[]][+[]]+[+[]] // 10
Sonuncu bir əjdaham da var. Bunu addım addım yazaraq izah edəcəyəm.
Əvvəlcə hər iki tərəfdəki [+[]]-i həll edək. +[] = 0 olduğunun və bizim cavabımızın [0] olduğunu tapmaq çətin deyil.
[+[]] ˃˃ [0]
++[[]][0] + [0]
İkinci olaraq pre-increment (++) operatorunun addition (+) operatoru üzərində üstünlüyünü bilərək işimizə davam edirk. Pre-increment Arrayin 0-cı elementinə tətbiq olunacaq. Bu aşağıdakı şəkildə olacaq:
++[[]][0] ˃˃ [ Number([]) + 1 ] ˃˃ [1]
Sonda isə cavabı alırıq:
[1] + [0] ˃˃ “10”
Ümid edirəm bir az beyin fırtınası edərkən əyləndiniz də. Bilmirəm JavaScript-in bu tərəflərini də öyrənmək sizə necə təsir göstərəcək, amma mən öyrəndikcə daha çox sevirəm. Bəyəndinizsə clap edin, əks halda bəyənmədiyiniz məqamları şərhlərdə mənə bildirin. Oxuduğunuz üçün təşəkkür edirəm.
Əlavə olaraq, JavaScript ilə bağlı digər məqalələrimlə aşağıdalı linklərdən tanış ola bilərsiniz:
Event phases: capturing, target & bubbling
Javascript Events: stopPropagation()