[Çeviri] Javascript’te Mahalle Baskısı(Type Coercion)

0 1,554

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.

 

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 == null2/’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.

Tahmin edebileceğiniz gibi tüm primitif değerlerin string tipinine dönüştürülmesi doğal bir durumdur.

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.

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.(|| && !)

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.

Boolen tip dönüşümü için sadece iki tane sonuç vardır: true veyafalse, yanlış değerlerin listesini hatırlamak daha kolaydır.

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).

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.

Aşağıda primitif değerlerin number dönüşümleri mevcuttur

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 0olurken, 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 TypeErrorhatası fırlatılır. Symboller için dönüşüm kuralları burada mevcuttur.

Aklınızdan çıkmaması gereken iki temel kural var. Bunlar:

  1. null veya undefined tipine== uygulanırken numeric dönüşüm olmaz. null sadece null ve undefined eşittir, ve başka birşeye eşit değildir.

2. NaN hiçbirşeye eşit değildir. Kendisine bile:

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 StringpreferredType 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:

  1. Eğer değer(input) primitif ise herhangi bir işlem yapma, dön.
  2. input.toString() metodunu çağır(Call). Eğer sonuç primitif ise dön
  3. input.valueOf()metodunu çağır(Call). Eğer sonuç primitif ise dön
  4. 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(preferredTypebelirtilmez 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.

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:

12 / '6'

Aritmetik bölme operatörü /; string için numeric dönüşümü tetikler:

“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.

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] > null

Karşılaştırma operatörü  >;  [1] venull için numeric dönüşümü tetikler

"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ç NaNdır. İkinci adımda ise 'foo' + NaN ifadesi analiz edilir ve string dönüşümü tetiklenir.

'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

null == ''

== genellikle numeric dönüşümü tetikler. Ama bu senaryoda nullolduğ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.

!!"false" == !!"true"

!! operatörü, boş olmayan string 'true' ve 'false' ifadelerini trueboolean tipine dönüştürür. == operatörü herhangi bir coercion yapmadan saedece eşitlik durumunu kontrol eder.

['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

[] + 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ç:

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] == [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]

İş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.

!+[]+[]+![]

Bu işlem; operatör önceliğine göre adım adım şu şekilde gerçekleştirilir.

new Date(0) - 0

- operatörü; Tarih(Date) için numeric dönüşümü tetikler. DateDate.valueOf() metodu; Unix döneminden bu yana milisaniye sayısını döndürür.

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.

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.

Email adresiniz yayınlanmayacaktır.