[Çeviri] Nedir bu Javascripteki “Value ve Reference Types” dedikleri şey?
Bu makale Arnav Aggarwal tarafından yazılan Explaining Value vs. Reference in Javascript makalesinin Türkçe çevirisidir. Makaleyi orijinal dilinde okumak için aşağıdaki linki tıklayabilirsiniz.
Başlıyoruz…
Bilgisayar belleğine basit bir bakış neler olduğunu açıklıyor.
Bu makale Step Up Your JS: A Comprehensive Guide to Intermediate JavaScript online kursumdan bir bölümdür. Makalede bulunan kodları deneyebileceğiniz ve içinde küçük bir sınavın bulunduğu içeriğe buradan bakabilirsiniz.
Javascript’te passed by value tipinde, beş tane veri tipi mevcuttur. Bunlar: Boolean
, null
, undefined
, String
ve Number
. Bu veri tiplerine primitif tipdiyoruz.
Ayrıca Javascript’te passed by reference tipinde, 3 üç tane veri tipi vardır. Bunlar: Array
, Function
, ve Object
. Bunlar temelde Nesnedir(Object), bu yüzden hepsine Nesne(Object) diyeceğiz.
Primitifler
Eğer primitif bir veri tipi, bir değişkene atanırsa, bu değişkeni primitif değeri içeren olarak düşünebiliriz.
1 2 3 4 5 6 7 |
var x = 10; var y = 'abc'; var z = null; |
x
; 10
’u içerir.y
; 'abc'
’yi içerir. Bu durumu daha iyi anlamak için, bu değişkenlerin ve bunların ilgili değerlerinin bellekte nasıl göründüğünün bir görüntüsünü koyacağız.
Bu değişkenleri =
kullanarak başka bir değişkenlere atadığımız zaman; yeni değişkenlere değerleri kopyalıyoruz. Bu değişkenler copeid by value’dir.
1 2 3 4 5 6 |
var x = 10; var y = 'abc'; |
1 2 3 4 5 6 |
var a = x; var b = y; |
1 2 3 4 5 |
console.log(x, y, a, b); // -> 10, 'abc', 10, 'abc' |
a
and x
değişkenleri 10
değerini içeriyor. b
and y
değişlenleri de 'abc'
değerini içeriyor. Değerler kopyalandığı için, hepsi birbirinden ayrıdırlar.
Bir değişkenin değerini değiştirmek, diğerinin değerini değiştirmeyecektir. Değişkenlerin birbirleri ile herhangi bir ilişkileri yok gibi düşünebiliriz.
1 2 3 4 5 6 |
var x = 10; var y = 'abc'; |
1 2 3 4 5 6 |
var a = x; var b = y; |
1 2 3 4 5 6 |
<strong class="markup--strong markup--pre-strong">a = 5; b = 'def';</strong> |
1 2 3 4 5 |
console.log(x, y, a, b); // -> 10, 'abc', 5, 'def' |
Nesneler(Objects)
Bu bölüm biraz kafa karıştırıcı gibi gelebilir ama bana katlanıp, okumaya devam ederseniz, ne kadar kolay olduğunu göreceksiniz.
Değişkenler; primitif olmayan bir değişkene atandığı zaman, bu değere bir referans verilir. Nesnenin hafızadaki konumuna işaret eder. Bu değişken aslında değişkeni gerçekte içermez.
Nesneler, bilgisayarınızın belleğinde bir noktada yaratılır. arr = []
yazdığımız zaman, bellekte bir dizi(array) yaratmış oluyoruz. arr
değişkenin aldığı adres, dizinin bellekteki konumudur.
address
değişkeninin, passed by value(number
veya string
)tipinde olan yeni bir veri türü olduğunu varsayalım. address
; referans tarafından geçirilen( passed by reference) bir değerin bellekteki konumuna işaret eder. Tıpkı stringlerin tırnak işaretleri (''
veya ""
) ile gösterildiği gibi, address
‘in değeri<>
şeklinde gösterilecek.
Aşağıdaki gibi, bir reference-type tipinde bir değişken tanımlayıp ve atama işlemi yaptığımızda:
1 2 3 4 5 6 |
1) var arr = []; 2) arr.push(1); |
1 ve 2 numaralı satırların bellekteki durumu şu şekilde olacaktır:
1.
2.
Değişkenin değerini ve adres bilgisini içeren, arr
’in static olduğuna dikkat ediniz. Bellekte bulunan dizide işlem yapıldığında neler değişir? arr
dizisinde bir şeyler yaptığımızda, örneğin diziye yeni bir değer eklediğimizde(push), JavaScript motoru, arr
’in bellekteki yerine gider ve burada tutulan bilgiye işlem yapar.
Assigning by Reference
Reference type tipindeki bir nesne; =
kullanılarak bir değişkene kopyalandığı zaman, değerin kendisi değil nesnenin bellekteki adresi kopyalanır.
1 2 3 4 5 6 |
ar reference = [1]; var refCopy = reference; |
Yukarıdaki satırların bellekteki durumu şu şekildedir.
Her iki değişken de aynı dizinin referansını içerir. Bu demektir ki; eğer reference
değişkenini değiştirirsek aynı değişiklikleri refCopy
değişkeninde de göreceğiz.
1 2 3 4 5 6 |
reference.push(2); console.log(reference, refCopy); // -> [1, 2], [1, 2] |
2
değerini bellekteki diziye ekledik(push). reference
ve refCopy
’yi kullandığımızda bellekteki aynı diziye işaret ediyoruz.
Reassigning a Reference
Bir referans değişkenine yeni atama yapıldığında, yapılan işlem eski referans değerinin yerini alır.
1 2 3 4 5 |
var obj = { first: 'reference' }; |
Bellekte:
Aşağıdaki satırı eklediğimizde:
1 2 3 4 5 6 |
var obj = { first: 'reference' }; obj = { second: 'ref2' } |
obj
nesnesinin bellekteki adresi değişir. Birinci ve işlem yapılan yeni nesne halen bellektedir:
Yukarıdaki #234
adresine referans eden bir nesne olmadığından; Javascript motoru garbage collection’ı çalıştırır. Bunun anlamı; yazılımcı bu nesneye ait bütün referansları kaybeder ve nesneyi artık kullanamaz. Bu yüzden motor hafızadan ilgili bilgiyi güvenle siler. Bu senaryoda; { first: 'reference' }
nesnesi artık erişilebilir ve mevcut olmadığından, garbage collection devreye girer.
== ve ===
Referans type nesneler için ==
ve ===
operatörleri kullanıldığı zaman, bu operatörler sadece referansı kontrol eder. Eğer değişkenler aynı referans bilgisini içeriyorsa karşılaştırma true
olur.
1 2 3 4 5 6 |
var arrRef = [’Hi!’]; var arrRef2 = arrRef; |
1 2 3 4 5 |
console.log(arrRef === arrRef2); // -> true |
Aynı özellikleri içeren nesneler ayrık/farklı ise, karşılaştırma false
olur.
1 2 3 4 5 6 |
var arr1 = ['Hi!']; var arr2 = ['Hi!']; |
1 2 3 4 5 |
console.log(arr1 === arr2); // -> false |
Eğer aynı özelliklere sahip iki farklı nesnemiz varsa; Karşılaştırma yapmak için en kolay yöntem bu değişkenleri string’e çevirmektir. Eşitlik operatörleri primitif tipleri karşılaştırırken, sadece değerler aynı mı diye kontrol eder.
1 2 3 4 5 6 |
var arr1str = JSON.stringify(arr1); var arr2str = JSON.stringify(arr2); |
1 2 3 4 5 |
console.log(arr1str === arr2str); // true |
Diğer seçenek ise, iç içe yinelemeli döngüler ile nesnenin tüm elemanlarının aynı olup olmadığı kontrol edilmesidir.
Parametrelerin fonksiyonlardan geçirilmesi
Primitif bir değer bir fonksiyona parametre olarak geçtiğimizde, fonksiyon parametre değerini kopyalar. =
ile yapılan işlemle aynıdır.
1 2 3 4 5 6 |
var hundred = 100; var two = 2; |
1 2 3 4 5 6 7 8 |
function multiply(x, y) { // PAUSE return x * y; } |
1 2 3 4 5 |
var twoHundred = multiply(hundred, two); |
Yukarıdaki örnekte; hundred
değişkenine 100
değerini veriyoruz. Daha sonra hundred
değişkenini multiply
fonksiyona parametre olarak verdiğimizde, x
değişkeni 100
değerini alır. Sanki =
ile yapılan atama işlemi yapılıyormuş gibi değer kopyalandı. hundred
değişkeni bu durumdan etkilenmez. multiply
fonksiyonundaki PAUSE yorum satırında; değişkenlerin bellekteki durumu ile ilgili anlık görüntü şu şekildedir.
Pure Functions
Dış kapsamdaki(the outside scope) hiçbir şeye etki etmeyen fonksiyonlara Pure Fonksiyon denir. Bir fonksiyon, parametre olarak yalnız primitif değer alıyor ve iç kapsamda bunu kullanmıyor ayrıca dış kapsamdaki hiç bir şeyi etkilemiyorsa, otomatik olarak bu fonksiyon pure fonksiyondur. Fonksiyon değer döner dönmez; fonksiyonun içinde yaratılan tüm değişkenler garbage-collected olarak işlem görür.
Bir fonksiyon parametre olarak nesne de alabilir. Bununla birlikte nesnenin durumu fonksiyon kapsamında(scope) değişebilir. Eğer bir fonksiyon parametre olarak diziyi referans ediyorsa ve fonksiyonun içerisinde bu dizinin işaret ettiği noktadaki değeri değiştiriyorsa(diziye bir eleman ekleyebilir), referans edilen dizi de aynı değişiklileri görecektir. Fonksiyon değer döndükten sonra da değişiklikler dış kapsamda devam edecektir. Bu durum takip edilmesi güç ve istenilmeyen yan etkilere(side effects) sebep olabilir.
Bir çok Dizi(Array) fonksiyonu, örneğin Array.map
ve Array.filter
, pure fonksiyon olarak yazılmıştır. Parametre olarak dizi referansı alırlar ve içeride dizinin bir kopyasını oluşturup, orijinal dizinin yerine ,bu kopya üzerinden işlemlerini yapar. Bu fonksiyonlar(map, filter vb); referans edilen diziye hiç dokunmadan, dış kapsam etkilemeden, yeni bir dizi döner.
Pure fonksiyonla anlamak için, aşağıdaki pure olmayan fonksiyon örneğine bakalım.
1 2 3 4 5 6 7 8 |
function changeAgeImpure(person) { person.age = 25; return person; } |
1 2 3 4 5 6 7 8 |
var alex = { name: 'Alex', age: 30 }; |
1 2 3 4 5 |
var changedAlex = changeAgeImpure(alex); |
1 2 3 4 5 6 |
console.log(alex); // -> { name: 'Alex', age: 25 } console.log(changedAlex); // -> { name: 'Alex', age: 25 } |
Bu pure olmayan fonksiyon bir nesne alıyor ve nesnedeki age
özelliğini 25 olarak değiştiriyor. Bu işlemden dolayı referans olarak verilen nesne de bu değişiklikten etkileniyor. Dikkat ederseniz fonksiyon kendisine referans olarak verilen person
nesnesini işlemin sonunda geri dönüyor. alex
ve alexChanged
değişkenleri aynı referansı içermektedir. Bu yüzdendir ki fonksiyondan dönen person
değişkenini yeni bir değişkende tutmak gereksizdir.
Şimdi de pure fonksiyon örneğine bakalım
1 2 3 4 5 6 7 8 9 |
function changeAgePure(person) { var newPersonObj = JSON.parse(JSON.stringify(person)); newPersonObj.age = 25; return newPersonObj; } |
1 2 3 4 5 6 7 8 |
var alex = { name: 'Alex', age: 30 }; |
1 2 3 4 5 |
var alexChanged = changeAgePure(alex); |
1 2 3 4 5 6 |
console.log(alex); // -> { name: 'Alex', age: 30 } console.log(alexChanged); // -> { name: 'Alex', age: 25 } |
Bu fonksiyonda, parametre olarak geçtiğimiz nesneyi string’e dönüştürmek için JSON.stringify
’yi ve daha sonra nesneye dönüştürmek için sonra JSON.parse
’yi kullanıyoruz. Bu dönüşüm ile sonucu yeni bir değişkende tutarak yeni bir nesne yaratıyoruz. Aynı işlem farklı yöntemlerle de yapılabilir, mesela nesnenin tüm özellikleri döngü ile yeni bir özelliğe aktarılabilir, ama bu yöntem en kolayıdır. Yeni nesne aynı özelliklere sahip olmasına rağmen bu nesneler bellekte birbirinden ayrıdır.
Yeni nesnedeki age
özelliğini değiştirdiğimizde, orijinal nesne bu durumdan etkilenmez. Bu fonksiyon şimdi pure fonksiyon oldu. Parametre olarak bir nesne geçsek bile, fonksiyon kendi kapsamı dışındaki hiç bir nesneyi etkilemiyor. Bu fonksiyondan dönen değeri tutmak için yeni bir nesneye ihtiyacımız var. Aksi durumda garbage collection, fonksiyon işlemi tamamladıktan sonra nesneyi silecektir. Ve nesne kapsamda olmayacaktır.
Kendiniz Test Edin
Value ve Refence; yazılım iş görüşmelerinde sıklıkla sorulan bir konudur. Aşağıdaki örnekte neyi logladığımızı anlamak için kendiniz deneyin.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
function changeAgeAndReference(person) { person.age = 25; person = { name: 'John', age: 50 }; return person; } |
1 2 3 4 5 6 7 8 |
var personObj1 = { name: 'Alex', age: 30 }; |
1 2 3 4 5 |
var personObj2 = changeAgeAndReference(personObj1); |
1 2 3 4 5 6 |
console.log(personObj1); // -> ? console.log(personObj2); // -> ? |
Fonksiyon, parametre olarak geçilen orijinal nesnenin age
özelliğini değiştiriyor. Daha sonra bu nesneye yeni bir nesne ataması yapıyor ve bu nesneyi dönüyor. İşte logladığımız iki nesne şu şekildedir.
1 2 3 4 5 6 |
console.log(personObj1); // -> { name: 'Alex', age: 25 } console.log(personObj2); // -> { name: 'John', age: 50 } |
Bir fonksiyona parametre geçmek; =
değer ataması ile işlem yapmakla aynı olduğunu hatırlayın. Fonksiyondaki person
değişkeni, personObj1
nesnesinin bir referansını içerdiğinden, yapılan tüm işlemler öncelikle nesneyi doğrudan etkiler. person
değişkenini yeni bir nesneye atadığımız için orijinal nesnenin yapılan işlemlerden etkilenmesi durdu ama bu atama personObj1
nesnesini değiştirmedi.
Yukarıdaki kod parçasının eşleniği şu şekilde olabilir:
1 2 3 4 5 6 7 8 |
var personObj1 = { name: 'Alex', age: 30 }; |
1 2 3 4 5 6 |
var person = personObj1; person.age = 25; |
1 2 3 4 5 6 7 8 |
person = { name: 'john', age: 50 }; |
1 2 3 4 5 |
var personObj2 = person; |
1 2 3 4 5 6 |
console.log(personObj1); // -> { name: 'Alex', age: 25 } console.log(personObj2); // -> { name: 'John', age: '50' } |
Eğer bu makale sizin için faydalı olmuş ise, lütfen beğeni butonuna basınız ve diğer çalışmalarıma bakabilirsiniz
Çalışmalarım
Online kurs
educative.io de orta seviye javascript konuları olan scope, closures, OOP, this
, new
, apply
/call
/bind
, asynchronous code, array and object manipulation, and ES2015+ içi bir kurs hazırladım.
Son Makaleler
Çeviri notları:
Makalede gözünüze çarpan çeviri hatalarını iletirseniz gerekli düzenlemeleri yaparım. Başka bir çeviride görüşmek üzere.