[Çeviri] Javascript’te Mahalle Baskısı(Type Coercion)
Bu yazı freeCodeCamp’te, Alexsey Samoshkin tarafından yayınlanan JavaScript type coercion explained makalesinin Türkçe çevirisidir. Makalenin orjinalini aşağıdaki linkten okuyabilirsiniz.
Type Coercion ifadesini Türkçe’ye çevirip çevirmeme konusunda kararsız kaldım. Yapacağım çeviri Chicken Translate olur endişesi yaşadım. İfadeyi sadece başlıkta , kesinlikle tam karşılığı olmayan ve eğlenceli olduğunu düşündüğüm “mahalle baskısı” şeklinde çevirdim. Yazının içerisinde type coercion olarak kullanmaya devam ettim.
Başlıyoruz…
Type Coercion; başka bir tipteki değeri, başka bir tip değerine dönüştürme işlemidir(Örneğin; String’den Number’a, Nesne’den (Object)Boolean’a vb). Herhangi bir tip için, bu primitif veya bir nesne (object) olabilir, type coercion söz konusudur. Hatırlatma, primitif tipler: number, string, boolean, null, undefined + Symbol (ES6’da eklendi.)
==
operatörünün (loose equality- zayıf eşitlik) farklı iki tipteki a ve b değişkenleri için pratikte nasıl farklı davrandığını, JavaScript Comparison Table’de gösteren matristen görebilirsiniz. Bu Matris ==
operatörünün yaptığı type coercion sebebiyle, biraz korkutucu gelebilir ve matristeki tüm kombinasyonları hatırlamak oldukça zordur. Ve bunu yapmanıza da gerek yok — coercion’un altında yatan ilkeleri öğrenmeniz yeterlidir.
Bu makale, Javascripte type coercion’ın nasıl çalıştığı konusunda derinlere inecek ve bu konu hakkında gerekli bilgilerle sizi silahlandıracak, bu sayede aşağıdaki ifadelerin hesaplanmasında kendinizi rahat ve güvende hissedeceksiniz. Makalenin sonunda aşağıdaki ifadelerin cevaplarını açıklamaları ile birlikte vereceğim.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
true + false 12 / "6" "number" + 15 + 3 15 + 3 + "number" [1] > null "foo" + + "bar" 'true' == true false == 'false' null == '' !!"false" == !!"true" [‘x’] == ‘x’ [] + null + 1 [1,2,3] == [1,2,3] {}+[]+{}+[1] !+[]+[]+![] new Date(0) - 0 new Date(0) + 0 |
Evet, bu liste bir geliştirci olarak yapabileceğiniz oldukça aptalca şeylerle dolu. Kullanım senaryolarının %90’ında implicit type coercion’dan (Kapalı/Örtük Tip Zorlama) kaçınmak daha iyidir. Yukarıdaki listenin, sizin type coercion hakkındaki bilginizi test etmek için hazırlandığını düşünün. Eğer sıkıldıysanız, wtfjs.com adresinde daha fazla örnek bulabilirsiniz.
Bu arada; Javascript iş görüşmelerinde bu tarz sorularla yüz yüze gelebilirsiniz. O yüzden okumaya devam edin 😄
Implicit (örtülü)vs. explicit(açık) coercion
Type coercion örtülü yada açık şekilde olabilir.
Bir yazılımcı türler arasında dönüşüm için bir ifade amaçladığında/yazdığında, örneğin Number(value), “explicit type coercison” yapmış olur(yada tip dönüşümü, type casting).
Bilindiği gibi, Javascript weakly-typed(değişkenlerin içeriklerinin türlerine göre kontrol edilmeme durumu) bir dil odluğundan değerler farklı tiplere otomatik olarak dönüştürülebilir ve bu duruma implicit type coercion denir. Genellikle operatörleri farklı türdeki değerlere uyguladığınızda bu işlem olur, örneğin 1 == null
, 2/’5'
, null + new Date()
, veya çevreyelen bir bağlam tetiklendiğinde olur, örneğin if (value) {…}
ifadesindeki “value” burada boolen tipine zorlanır/dönüştürülür (coerced).
==
operatörü implicit (örtülü) type coercion işlemini tetiklemez. Bu operatöre katı eşitlik (strict equality) operatörü denir. Diğer taraftan zayıf eşitlik operatörü ==
; karşılaştırma işleminde eğer gerekli ise type coercion işlemini yapar.
Implicit type coercion; büyük bir hayal kırıklığı ve hataların kaynağı olan, iki tarafı keskin bir kılıçtır. Ama aynı zamanda kod okunurluluğunu kaybetmeden daha az kod yazmayı sağlaması bakımından da çok kullanışlı bir mekanizmadır.
Üç tür için dönüşümü
Birinci kural; javascripte sadece üç tip dönüşümü olduğunu bilmektir:
- to string
- to boolean
- to number
İkincisi, primitif değerler ve nesneler (objectler) için dönüşüm mantığı farklı çalışmaktadır. Ama primitif değerler ve nesneler sadece, bu üç şekilde dönüştürülebilir.
İlk olarak primitiflerle başlayalım.
String Dönüşümü
Eğer bir değeri açık bir şekilde String’e dönüştürmek istenilirse String()fonksiyonu kullanılır. Binary(ikili) + operatörü bir string ifadeye uygulandığında örtülü coercion tetiklenir.
1 2 3 4 5 6 |
String(123) // explicit 123 + '' // implicit |
Tahmin edebileceğiniz gibi tüm primitif değerlerin string tipinine dönüştürülmesi doğal bir durumdur.
1 2 3 4 5 6 7 8 9 10 |
String(123) // '123' String(-12.3) // '-12.3' String(null) // 'null' String(undefined) // 'undefined' String(true) // 'true' String(false) // 'false' |
Symbol’de durum biraz farklıdır, çünkü dönüşüm sadece açık(explicit) bir şekilde yapılır, örtülü(implicit) bir şekilde değildir. Symbol Coercion hakkında bilgi almak için burayı tıklayınız.
1 2 3 4 5 6 |
String(Symbol('my symbol')) // 'Symbol(my symbol)' '' + Symbol('my symbol') // TypeError is thrown |
Boolean Dönüşümü
Eğer bir değeri açık bir şekilde boolean ‘a dönüştürmek istenilirse Boolean()fonksiyonu kullanılır.
Örtülü dönüşüm ise mantısal operatörlerinin kullanıldığı, mantıksal işlemlerin yapıldığı alanlarda tetiklenir.(||
&&
!
)
1 2 3 4 5 6 7 8 |
Boolean(2) // explicit if (2) { ... } // implicit due to logical context !!2 // implicit due to logical operator 2 || 'hello' // implicit due to logical operator |
Dikkat: Mantısal operatörleri, örneğin ||
ve &&
, dönüşüm işlemini dahili olarak(internally) yapar. Ama gerçekte orjinal ifadenin (operand) değerini döndürür, değer boolean tipinde olmasa bile.
1 2 3 4 5 6 |
// true değeri yerine, 123 sayısını döner, // 'hello' ve 123 ifadesi dahili(</code>internally<code class="markup--code markup--pre-code">) olarak boolean tipine zorlandı(coerced) |
1 2 3 4 5 |
let x = 'hello' && 123; // x === 123 |
Boolen tip dönüşümü için sadece iki tane sonuç vardır: true
veyafalse
, yanlış değerlerin listesini hatırlamak daha kolaydır.
1 2 3 4 5 6 7 8 9 10 11 |
Boolean('') // false Boolean(0) // false Boolean(-0) // false Boolean(NaN) // false Boolean(null) // false Boolean(undefined) // false Boolean(false) // false |
Yukarıdaki listede olmayan herhangi bir değer, true
ya dönüştürülür. Fonksiyon, Dizi(Array),Tarih (Date), kullanıcı tanımlı tip(user-defined-type) vb Symboller gerçek değerlidir(truthy value). Hatta boş nesneler (objectler) ve diziler (arrayler)gerçek değerlidir(truthy value).
1 2 3 4 5 6 7 8 9 |
Boolean({}) // true Boolean([]) // true Boolean(Symbol()) // true !!Symbol() // true Boolean(function() {}) // true |
Numeric dönüşüm
Açık dönüşüm yapılmak istenildiği zaman Number() fonksiyonu uygulayın, tıpkı Boolean() ve String() de yaptığınız gibi.
Örtülü dönüşüm biraz zordur ve kafa karıştırıcıdır, çünkü daha fazla durumda tetiklenir:
- Karşılaştırma Operatörleri (
>
,<
,<=
,>=
) - Bitsel Operatörler(bitwise operators) (
|
&
^
~
) - Aritmetik Operatörler(
-
+
*
/
%
). Akınızın bir köşesine not edin, Binary(İkili)+
operatörü bir string ifadeye uygulandığında; numeric dönüşümü tektiklemez. - Unary(tekli)
+
operatör - Zayıf eşitlik operatörü
==
(!
= de dahil) . Yine akınızın bir köşesine not edin,==
operatörünün her iki tarafında bulunan ifade string ise numeric dönüşüm tetiklenmez.
1 2 3 4 5 6 7 8 9 10 |
Number('123') // explicit +'123' // implicit 123 != '456' // implicit 4 > '5' // implicit 5/null // implicit true | 0 // implicit |
Aşağıda primitif değerlerin number dönüşümleri mevcuttur
1 2 3 4 5 6 7 8 9 10 11 12 13 |
Number(null) // 0 Number(undefined) // NaN Number(true) // 1 Number(false) // 0 Number(" 12 ") // 12 Number("-12.34") // -12.34 Number("\n") // 0 Number(" 12s ") // NaN Number(123) // 123 |
String bir tipi number tipine dönüştürürken; Js engine, ilk olarak başta ve sonda bulunan boşlukları ve\n
, \t
karakterlerini kaldırır(trim). Eğer trim edilen değer sayısal bir değer değilse NaN
değerini döner. Eğer string boş ise 0 değerini döner.
null
ve undefined
ise daha farklı bir şekilde işlem görür. null
0
olurken, undefined
ise NaN
olur.
Symboller’e açıkça ve örtülü olarak number tip dönüşümü uygulanamaz. Ayrıca symbol üzerine numeric dönüşümü uygulanırsa NaN
yerine TypeError
hatası fırlatılır. Symboller için dönüşüm kuralları burada mevcuttur.
1 2 3 4 5 6 |
Number(Symbol('my symbol')) // TypeError is thrown +Symbol('123') // TypeError is thrown |
Aklınızdan çıkmaması gereken iki temel kural var. Bunlar:
null
veyaundefined
tipine==
uygulanırken numeric dönüşüm olmaz.null
sadecenull
veundefined
eşittir, ve başka birşeye eşit değildir.
1 2 3 4 5 6 7 8 |
null == 0 // false, null is not converted to 0 null == null // true undefined == undefined // true null == undefined // true |
2. NaN hiçbirşeye eşit değildir. Kendisine bile:
1 2 3 4 5 |
if (value !== value) { console.log("we're dealing with NaN here") } |
Nesneler (Objects) için type coercion
Şimdiye kadar primitif değerler için type coercion hakkında bilgi sahibi olduk. Bu çok heyecan verici değil.
Nesneler ve motor karşılaşmaları(engine encounters) ifadesi söz konusu olduğunda, örneğin [1] + [2,3]
, ilk önce nesnenin primitif bir değere dönüştürülmesi gerekiyor. Bu da daha sonra son değere dönüştürülür. Ve halen ve yalnız üç tür tip dönüşümü söz konusudur: numeric, string ve boolean
Boolean dönüşümü en kolayıdır. primitif olmayan herhangi bir değer her zaman true olarak zorlanır(coerced). Nesnenin(object) veya dizinin(array) dolu yada boş olması önemli değildir.
Nesneler; hem numeric hem de string dönüşümden sorumlu olan [[ToPrimitive]]
metodu ile dahili(internal) olarak primitife dönüştürülür.
[[ToPrimitive]]
metodunun sözde uygulaması şu şekildedir:
[[ToPrimitive]]
metoduna bir değer(input) ve isteğe bağlı olan dönüşüm için tercih edilen tip geçirilir: Number
veya String
. preferredType
isteğe bağlıdır.
Hem numeric hem de string dönüşüm için, giriş nesnesinin(input object) valueOf
ve toString
metodlarından faydalanılır. Bu iki metod Object.prototype
da tanımlanmıştır. Bu sayede türetilmiş tüm tiplerde kullanılır. Örneğin Tarih (Date), Dizi (Array) vb
Genel olarak algoritma aşağıdaki gibidir:
- Eğer değer(input) primitif ise herhangi bir işlem yapma, dön.
input.toString()
metodunu çağır(Call). Eğer sonuç primitif ise döninput.valueOf()
metodunu çağır(Call). Eğer sonuç primitif ise dön- Ne
input.toString()
ne deinput.valueOf()
primitif sonuç vermiyorsa;TypeError
fırlat
Numeric dönüşüm ilk olarak valueOf
methodunu çalıştırır(3) toString
geri dönüşü ile (2). String dönüşümü bu işlemin tam tersini yapar. toString
(2) daha sonra valueOf
(3).
Bir çok dahili tipler (built-in types) valueOf
veya this
nesnesinin kendisini dönen valueOf
metoduna sahip değildir. Ve bu durum göz ardı edilir, çünkü bu tip primitif değildir. İşte bu yüzdendir ki numeric ve string dönüşümleri benzer mantıkta çalışır — her kisi kapanışta toString()
methodunu çağırır.
Farklı operatörler, preferredType
parametresi ile numeric veya string dönüşümünü tetikleyebilir. iki istisna vardır. ==
and binary(ikili) +
operatörleri varsayılan dönüşüm modlarını tetikler(preferredType
belirtilmez veya varsayılana eşitlenmez). Bu durumda bir çok dahili tipler var sayılan olarak numeric dönüşümü yapar, Tarih(Date) varsayılan olarak string dönüşümü yapar.
İşte Tarih (Date) dönüşüm davranışının bir örneği:(Yazının orjinalinde de bu örnek açıklanmamış. olduğu gibi bırakılmıştır.)
Object-to-primitive dönüşüm mantığına bağlanmak için varsayılantoString()
and valueOf()
metodlarını override edebilirsiniz.
obj + ‘’
işleminin ‘101’
şeklinde string olarak nasıl döndüğünü not ediniz. +
operatörü varsayılan dönüşüm modunu tetikler ve daha önce belirttiğimiz gibi Nesneler için numeric dönüşümünü -ilk önce valueOf()
metodu daha sonra toString()
metodları çağrılarak- varsayılan olarak yapar.
ES6 Symbol.toPrimitive metodu
ES5’te ;object-to-primitive dönüşüm mantığına bağlanmak için toString
ve valueOf
metodları override edibilirisiniz.
ES6’da ise bir adım daha atarak bir nesne üzerindeki [Symbol.toPrimtive]
metodunu kullanarak, dahili [[ToPrimitive]]
yordamını değiştirebilirsiniz.
Örnekler
Teori kısmını öğrendik. Şimdi makalenin başında verdiğimiz örneklere geri dönelim.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
true + false // 1 12 / "6" // 2 "number" + 15 + 3 // 'number153' 15 + 3 + "number" // '18number' [1] > null // true "foo" + + "bar" // 'fooNaN' 'true' == true // false false == 'false' // false null == '' // false !!"false" == !!"true" // true ['x'] == 'x' // true [] + null + 1 // 'null1' [1,2,3] == [1,2,3] // false {}+[]+{}+[1] // '0[object Object]1' !+[]+[]+![] // 'truefalse' new Date(0) - 0 // 0 new Date(0) + 0 // 'Thu Jan 01 1970 02:00:00(EET)0' |
Yukarıdaki ifadelerin tek tek açıklamaları aşağıda verilmiştir
true + false
Binary +
operatörü; true ve false için numeric dönüşümü tetikler:
1 2 3 4 5 6 7 |
true + false ==> 1 + 0 ==> 1 |
12 / '6'
Aritmetik bölme operatörü /
; string için numeric dönüşümü tetikler:
1 2 3 4 5 6 7 |
12 / '6' == > 12 / 6 == > 2 |
“number” + 15 + 3
+
operatörü soldan sağda doğru ilişkilendirme özelliğine sahip olduğu için (left-to-right associativity); ilk olarak "number" + 15
bölümü çalışır. İlk işlenen değer string olduğundan; +
operatörü, 15
sayısal değeri için string dönüşümünü uygular. ikinci adım benzer işlem "number15" + 3
için yapılır.
1 2 3 4 5 6 7 |
“number” + 15 + 3 ==> "number15" + 3 ==> "number153" |
15 + 3 + "number"
İlk önce 15 + 3
ifadesi analiz edilir. Bu esnada her iki değerde sayısal olduğu için coercion uygulanmaz. İkinci adımda 18 + ‘number’ ifadesi analiz edilir. işlenen ifadelerden biri string olduğu için string dönüşümü uygulanır.
1 2 3 4 5 6 7 |
15 + 3 + "number" ==> 18 + "number" ==> "18number" |
[1] > null
Karşılaştırma operatörü >; [1] venull
için numeric dönüşümü tetikler
1 2 3 4 5 6 7 8 |
[1] > null ==> '1' > 0 ==> 1 > 0 ==> true |
"foo" + + "bar"
Tekil +
operatörü, binary +
operatörü üzerinde daha önceliklidir. Bu yüzden ilk olarak +'bar'
ifadesi analiz edilir. String için numeric dönüşüm tetiklenir. String ifade numeric olamayacağı için sonuç NaN
dır. İkinci adımda ise 'foo' + NaN
ifadesi analiz edilir ve string dönüşümü tetiklenir.
1 2 3 4 5 6 7 8 |
"foo" + + "bar" ==> "foo" + (+"bar") ==> "foo" + NaN ==> "fooNaN" |
'true' == true ve false == 'false'
==
operatörü numeric dönüşümünü tetikler. 'true'
string olduğundan NaN olarak dönüştürülür, boolean true
ise 1 değerine dönüştürülür
1 2 3 4 5 6 7 8 9 10 11 |
'true' == true ==> NaN == 1 ==> false false == 'false' ==> 0 == NaN ==> false |
null == ''
==
genellikle numeric dönüşümü tetikler. Ama bu senaryoda null
olduğundan, farklı bir durum söz konusudur. null
yalnızca null’a veya undefined eşit veya hiç birşeye eşit olmadığından herhangi bir dönüşüm yapılmaz.
1 2 3 4 5 6 |
null == '' ==> false |
!!"false" == !!"true"
!!
operatörü, boş olmayan string 'true'
ve 'false'
ifadelerini true
boolean tipine dönüştürür. ==
operatörü herhangi bir coercion yapmadan saedece eşitlik durumunu kontrol eder.
1 2 3 4 5 6 7 |
!!"false" == !!"true" ==> true == true ==> true |
['x'] == 'x'
==
operatörü dizi(array) için numeric dönüşümü tetikler. Dizilerde bulunan valueOf()
metodu; array primitif olmadığından göz ardı edilerek çalıştırılmaz ve dizinin kendisini döner. Dizilerde bulunan toString()
methodu ise ['x']
dizisini string olan 'x'
değerine çevirir
1 2 3 4 5 6 7 |
['x'] == 'x' ==> 'x' == 'x' ==> true |
[] + null + 1
+
operatörü; []
için numeric dönüşümü tetikler. []
primitif olmadığından valueOf()
metodu göz ardı edilir ve dizinin(array) kendisi döndürülür. toString
metodu ise boş string ifadesi döner.
İkinci adımda '' + null + 1
analiz edilir. ve sonuç:
1 2 3 4 5 6 7 8 |
[] + null + 1 ==> '' + null + 1 ==> 'null' + 1 ==> 'null1' |
0 || "0" && {}
||
ve &&
mantıksal operatörleri ifadelere(operand) boolean coerce uygular. Ama sonuç olarak ifadenin kendisini döner (boolean değil). 0 yanlış oysa '0'
boş bir string olmadığından doğrudur. {}
şeklindeki boş nesnede doğrudur.
1 2 3 4 5 6 7 8 9 10 |
0 || "0" && {} ==> (0 || "0") && {} ==> (false || true) && true // internally(bu işlem arka planda oluyor) ==> "0" && {} ==> true && true // internally(bu işlem arka planda oluyor) ==> {} |
[1,2,3] == [1,2,3]
Her iki ifade(operand) aynı tip olduğu için; herhangi bir coercion işlemi yapılmaz. ==
operatörü nesne kimliğini kontrol eder(nesne eşitliğini değil). Her iki dizide iki farklı instance olduğu için sonuç false
dir
1 2 3 4 5 6 |
[1,2,3] == [1,2,3] ==> false |
{}+[]+{}+[1]
İşlem gören tüm ifadeler (operand) primitif olmadığından; +
operatörü en soldaki nesneye numeric dönüşümü tetikler. Dizi ve nesnede bulunan valueOf
metodu göz ardı edilerek dizinin ve nesnenin kendisini döner. Daha sonra toString()
metodu uygulanır ve hatalı döner. Buradaki küçük nüans ise {}
ifadesi bir nesne değişmez değeri olarak değil, bir blok bildirim ifadesi olarak kabul edilir ve göz ardı edilir. Bu yüzden işlem +[]
ifadesi ile başlar. Bu ifade toString()
metodu ile boş bir string değerine ardın da 0 değerine dönüştürülür.
1 2 3 4 5 6 7 8 9 10 11 |
{}+[]+{}+[1] ==> +[]+{}+[1] ==> 0 + {} + [1] ==> 0 + '[object Object]' + [1] ==> '0[object Object]' + [1] ==> '0[object Object]' + '1' ==> '0[object Object]1' |
!+[]+[]+![]
Bu işlem; operatör önceliğine göre adım adım şu şekilde gerçekleştirilir.
1 2 3 4 5 6 7 8 9 10 |
!+[]+[]+![] ==> (!+[]) + [] + (![]) ==> !0 + [] + false ==> true + [] + false ==> true + '' + false ==> 'truefalse' |
new Date(0) - 0
-
operatörü; Tarih(Date) için numeric dönüşümü tetikler. Date
. Date.valueOf()
metodu; Unix döneminden bu yana milisaniye sayısını döndürür.
1 2 3 4 5 6 7 |
new Date(0) - 0 ==> 0 - 0 ==> 0 |
new Date(0) + 0
+
operatörü varsayılan dönümüşü tetikler. Yukarıda belirtildiği gibi Tarih(Date) için string dönüşümünü var sayılan olarak uygulanır. toString()
metodu valueOf)
yerine kullanılır.
1 2 3 4 5 6 7 |
new Date(0) + 0 ==> 'Thu Jan 01 1970 02:00:00 GMT+0200 (EET)' + 0 ==> 'Thu Jan 01 1970 02:00:00 GMT+0200 (EET)0' |
Kaynaklar
Nicholas C. Zakas tarafından yazılan “Understanding ES6” kitabını öneririm. Çok üst düzey değil ve derinlere dalmadan ,ES6 öğrenimi için harika bir kaynak.
Ve sadece ES5 için harika bir kitap. Axel Rauschmayer tarafından yazılan SpeakingJS.
(Rusça) Современный учебник Javascript — https://learn.javascript.ru/. Özellikle type coercion için şu iki sayfa.
JavaScript Comparison Table — https://dorey.github.io/JavaScript-Equality-Table/
wtfjs —nefret etmemize rağmen sevdiğimiz dil hakkında küçük bir kod blogu— https://wtfjs.com/
Makalede gözüne çarpan “hatalı çevirileri” bildirirseniz düzeltirim. Teşekkürler.