ES6
Selamlar. Bu yazıda javascript'in gelecek nesil versiyonundan bahsedeceğim: EcmaScript 6 veya EcmaScript 2015. Kullanacağım teknoloji babel js olacak.
Neden babel JS
Javascript bildiğiniz gibi hem client tarafında hem de server tarafında çalışan yorumlamalı bir dildir. Client tarafı değişkenlik gösterdiği için her özelliğin çalışması beklenemez. Mesela eski browserlarda javascript'in yeni özelliklerin çoğu yoktur.
Bu sorunu aşmak için babel js preprocessor kullanacağım. basitçe ES6'da yazdığınız javascript kodunu ES5'e çevirir.
Babel'in yaptığı da şu anki eski browser'larda javascript dilindeki en son yenilikleri kullanabilmenizi sağlamak. Bir yönüyle Babel bir zaman makinesi olabilir :)
Örnek çeviri: http://babeljs.io/repl
Konular
Template Strings
Multiline Strings
1"ben burada line break\nkullanabiliyorum"
2//ben burada line break
3//kullanabiliyorum
4
5`ben burada line break
6kullanabiliyorum`
7//ben burada line break
8//kullanabiliyorum
Expression interpolation
1var a = 5;
2var b = 10;
3console.log("onbeş = " + (a + b) + " dir\n" + (2 * a + b) + " değildir.");
4//onbeş 15 dir
5//20 değildir.
6
7console.log(`onbeş ${a + b} dir
8${2 * a + b} değildir.`);
9//onbeş 15 dir
10//20 değildir.
Çok hoş değil mi? Artık native olarak bu özellikleri kullanabiliyoruz. Yalnız bu biraz da güvenlik zaafiyeti de doğurmuyor değil.
1var badString = `${document.cookie}`;
2console.log(`${badString}`);
3//cookie yi aldım canım geçmiş olsun :)
Tagged Template Strings
Templateleri kendiniz de işleyebilirsiniz. Size içindeki değerleri array olarak döndürüyor ve dönüş değerini override edebilirsiniz. Bunun için fonksiyon yazıyoruz.
1var who = "seni";
2var when = "geceleri";
3
4function capitalizeFirstLetter(string) {
5 string = string.trim();
6 return string.charAt(0).toUpperCase() + string.slice(1);
7}
8
9function ConvertPoem(strings, ...values) {
10 return `${capitalizeFirstLetter(strings[2])} ${values[0]}
11${values[1]} ${strings[0].toLowerCase()}`;
12}
13
14console.log(ConvertPoem`Gözlerim ${when}
15${who} bekler`);
16//Bekler geceleri
17//seni gözlerim
Alın size düzyazıyı manzumeye çeviren bir programcık :)
Yalnız ilk kısım değişken olursa strings[0] boş string dönecektir.
Raw
Stringin başına @ koyarak tıpkı C#'ta yaptığımız gibi özel karakterleri escape etmeden kullanabiliriz. Bunun için tagged template strings için kullanılan fonksiyonun ilk parametresindeki özel raw property'sini kullanabiliriz.
1function tag(strings, ...values) {
2 console.log(strings.raw[0]);
3 // "line break için \\n kullanılır"
4}
5
6tag`line break için \n kullanılır`;
ya da
1String.raw`line break için \n kullanılır ${2+4}`;
2//line break için \\n kullanılır 6
Destructuring
Fonksiyonel programlamanın pattern matching konusuna giren bu konu da tıpkı regex kullanır gibi değişken atama yapmamızı sağlıyor.
!!! TODO
Generators
1setTimeout(function(){
2 console.log("Hello World");
3},1);
4
5function foo() {
6 // çok uzun bir döngü
7 for (var i=0; i<=1E10; i++) {
8 console.log(i);
9 }
10}
11
12foo();
13// 0..1E10
14// "Hello World"
Javascript, ister browserda ister serverda (node.js gibi) çalışsın; tek process, tek thread (tek millet)'tir. Yukarıda da görüldüğü gibi 1 ms sonra başlaması gereken kod ne kadar uzun sürerse sürsün foo fonksiyonunun çalışmasının bitmesini beklemek zorunda.
Generators kullanarak fonksiyonları çalıştır-durdur-devam modelinde kullanabiliyoruz. Burada bir fonksiyon kendi çalışmasının interrupt edilmesine izin verir. Bunu da yield keyword'üyle sağlarız. Ancak kendisini durduran fonksiyonun devam etmesi için dışarıdan müdahaleye ihtiyaç duyarız. Bunu da devam etmeyle sağlarız.
1function *foo() {
2 // ..
3}
Fonksiyonun generator tipi olduğunu * işaretiyle gösterebiliriz. Bir generator fonksiyonunda iki yönlü iletişim söz konusudur. yield ile fonksiyon dışına veri gönderirken, next(...) ile fonksiyona parametre geçerek içeri veri gönderiyoruz. Fonksiyon kaldığı yerden çalışmaya devam eder.
1// not: `yaz(..)` generator değildir
2function yaz(x) {
3 console.log("x: " + x);
4}
5
6function *gen() {
7 yield "ilk yield"; // bekle
8 yaz( yield "ikinci yield" ); // bekle, sonra gelen veriyi yaz'a geçir
9}
10
11// generator'ı yarat
12var g = gen();
13
14var result = g.next(1);
15console.log("sonuç:", result.value, ", done:", result.done);
16result = g.next(2);
17console.log("sonuç:", result.value, ", done:", result.done);
18result = g.next(3);
19console.log("sonuç:", result.value, ", done:", result.done);
Sonuç:
1sonuç: ilk yield , done: false
2sonuç: ikinci yield , done: false
3x: 3
4sonuç: undefined , done: true
Şimdi daha karışık bir örnek ile devam edelim:
1function *foo(x) {
2 var y = 2 * (yield (x + 1));
3 var z = yield (y / 3);
4 return (x + y + z);
5}
6
7var it = foo( 5 );
8
9// not: constructor'a parametre geçildiğinden
10// ilk next parametresi kullanılmaz
11console.log( it.next() ); // { value:6, done:false }
12console.log( it.next( 12 ) ); // { value:8, done:false }
13console.log( it.next( 13 ) ); // { value:42, done:true }
Arkadaş ben bu fonksiyonu bozarım!
1function foo1(x) {
2 return (x + 1);
3}
4
5console.log(foo1(5));
6
7function foo2(x) {
8 var y = (2 * x) // x:12, y -> 24
9 return y / 3;
10}
11
12console.log(foo2(12));
13
14function foo3(x, y, z) {
15 return x + (2 * y) + z //z:13
16}
17
18console.log(foo3(5, 12, 13));
19
20//6
21//8
22//42
Karışık değil mi? Değil:
- burada yield gördüğünüz yeri yield ifadesi olarak düşünün.
- hesaplamaya yield ifadesinden başlayıp, yield sonucunu döndürün.
- Dikkat!: Bir sonraki çağrımda önceki yield ifadesi yerine next(...) ifadesini koyun
- sonraki yield'a kadar devam edin.
- tüm yield'lar bitip fonksiyondan çıktığımızda done: true oluyor.
for..of
Javascriptteki for in'e ek olarak generator'ları iterator pattern olarak kullanabilmemiz için for of yapısı eklenmiş.
1function *factorial(n){
2 var total = 1;
3 var from = 1;
4
5 while (from <= n) {
6 total *= from++;
7 yield total;
8 }
9}
10
11for (var n of factorial(5)) {
12 console.log(n)
13}
14// 1, 2, 6, 24, 120
Not: Örnekleri recursive yapmayı tercih ettim. İsterseniz döngü kullanarak da aynı sonuçları elde edebilirsiniz.
yield delegation
yield *foo() yardımıyla çağrımı başka generatora yönlendirebiliriz. Mesela, iç içe array içeren bir değeri düzlemek istersek:
1function *flat (arr) {
2 if (!arr.length) {
3 yield arr;
4 } else if (arr.length === 1) {
5 yield arr[0];
6 } else if (arr.length > 1) {
7 yield *flat(arr.shift());
8 yield *flat(arr);
9 }
10}
11
12var A = [1, [2, [3, 4], 5], 6];
13for (var f of flat(A)) {
14 console.log( f );
15}
16// 1 2 3 4 5 6
yine güzel bir recursive örneği oldu :)
try catch
Generator içinde try catch ifadeleri kullanılabilir. Dizi içindeki değerleri büyük harfe çeviren kod:
1function *upper (items) {
2 var elem = items.shift();
3
4 if(elem){
5 try {
6 yield elem.toUpperCase();
7 } catch (e) {
8 yield null;
9 }
10
11 yield *upper(items);
12 }
13}
14
15var bad_items = ['a', 'B', 1, 'c'];
16
17for (var item of upper(bad_items)) {
18 console.log(item);
19}
20// A, B, null, C
Not: Nümerik 1 değeri toUpperCase()'de hataya yol açar, ancak biz try catch ile bunu telafi edip yolumuza devam ettik.
Not2: generator'a özel throw() metodunu kullanarak en yakın try catch blokunda yakalanmasını sağlayabiliriz. Eğer bulunmuyorsa bir sonraki blokta başarılı bir şekilde yakalanacaktır. Bu özelliği bildiğimiz callback async yöntemlerde kullanamıyorduk.
Generator'lar ile Asynchronicity
Senkron çalışan kodun okunabilirliğini kaybetmeden asenkron yapıları generatorlar vasıtasıyla yapabiliriz. Şu örnek normal bir içiçe asenkron çağrımdır:
1function makeAjaxCall(url,cb) {
2 // do some ajax fun
3 // call `cb(result)` when complete
4}
5
6makeAjaxCall( "http://some.url.1", function(result1){
7 var data = JSON.parse( result1 );
8
9 makeAjaxCall( "http://some.url.2/?id=" + data.id, function(result2){
10 var resp = JSON.parse( result2 );
11 console.log( "The value you asked for: " + resp.value );
12 });
13});
Konunun başında şunu yazmışım:
Generators kullanarak fonksiyonları çalıştır-durdur-devam modelinde kullanabiliyoruz. Burada bir fonksiyon kendi çalışmasının interrupt edilmesine izin verir. Bunu da yield keyword'üyle sağlarız. Ancak kendisini durduran fonksiyonun devam etmesi için dışarıdan müdahaleye ihtiyaç duyarız. Bunu da devam etmeyle sağlarız.
Aslında fonksiyon içinden de kendisinin devamını sağlayabiliyoruz. Şimdi üstteki örneği değiştirdiğimiz şu örneği inceleyelim:
1function request(url) {
2 makeAjaxCall( url, function(response){
3 it.next( response );
4 } );
5}
6
7function *main() {
8 var result1 = yield request( "http://some.url.1" );
9 var data = JSON.parse( result1 );
10
11 var result2 = yield request( "http://some.url.2?id=" + data.id );
12 var resp = JSON.parse( result2 );
13 console.log( "The value you asked for: " + resp.value );
14}
15
16var it = main();
17it.next(); // get it all started
main metodu içinde yield dışarıdan veri aldığımız yapılardı ve fonksiyonun çalışmasını durduruyordu. Biz makeAjaxCall içinden callback'te generator'un next(...) metodunu ajaxtan dönen değeri göndermek için kullandık. Daha sonra aynı yöntemi de 2. çağrımda gerçekleştirdik. Görüldüğü gibi asenkron metotları (ajax) senkron çalışıyormuş gibi yazabildik.
Şimdi de node.js'te bir örnek inceleyelim:
helpers.js:
1exports.AsyncFunc = function (swear, callback){
2 setTimeout(function() {
3 if(swear){
4 callback(null, "fuck you!");
5 } else {
6 callback(null, "love you!");
7 }
8 }, 2000);
9}
10
11exports.Run = function (generator) {
12 var g = generator(done);
13
14 function done(err, result){
15 g.next(result);
16 };
17
18 g.next();
19}
program.js:
1var run= require('./helpers.js').Run;
2var asyncFunc = require('./helpers.js').AsyncFunc;
3
4run(function* (done) {
5 try {
6 var message = yield asyncFunc(false, done);
7 console.log(message);
8
9 message = yield asyncFunc(true, done);
10 console.log(message);
11 } catch (e) {
12 console.log("null");
13 }
14});
sonuç:
1node program.js
2love you!
3fuck you!
2'şer saniye arayla love you! ve fuck you! yazdı, terbiyesiz...
Syntax çok karışık gelmiş olabilir diye programı ikiye ayırdım. helpers.js'e programı kullanabilmek için gerekli iki fonksiyonu koydum. Böylece başka yerlerde de kullanabiliriz.
AsyncFunc setTimeout ile asenkronize ettiğim fonksiyonum. işi bitince kendisine verilen callback fonksiyonunu çağırıyor.
Runner asıl sihrin gerçekleştiği yer:
- parametre olarak aldığı generator fonksiyonunu alıyor
- aldığı generator fonksiyonunu yaratıyor
- fonksiyonun parametre olarak aldığı callback tanımlanıyor
- callback içinde generator'ın devam etmesi ve veriyi dışarı gönderebilmesi için next(...) tanımlanıyor
- kendi kendini başlatan next() tanımlanıyor
program.js de ise çalıştırmak sadece çalıştırılacak generator fonksiyonunun tanımı mevcut. Runner'a parametre olarak bu fonksiyon gönderiliyor. İçinde:
- dışarıdan parametreyle gelen callback handle'ı asyncFunc'a geçilir.
- asyncFunc işini bitince devam edebilmesi için yield ile çağrılır.
- bu işlem farklı bir parametreyle tekrarlanır.
Promises
Bu konuyu en iyi özetleyen budur herhalde:
https://www.youtube.com/watch?v=hUFPooqKllA asdfasdfasdffds
Şaka bir yana generators konusununu iyiyce anladıktan sonra promise o kadar da karışık değil. Promiseler de tıpkı generator'lar gibi asenkron yapıları kurmamıza yardım ederler. Ama bunu generator'ların karmaşıklığından uzak yapabiliriz. Hemen örnekle konuya dalıyorum.
1function asyncFunc() {
2 return new Promise( function(resolve,reject){
3 // işimiz bittiğinde resolve(...) veya reject(...)
4 // callbacklerini çağırmamız lazım
5 });
6}
7
8var p = asyncFunc();
9
10p.then(
11 function(){
12 // sonuç başarılı :)
13 },
14 function(){
15 // hata var :(
16 }
17);
Promise'in genel özellikleri:
- sadece resolve veya reject tetiklenir
- birden fazla kez tetiklenme ihtimali yoktur
- promise resolve olursa success mesajı gönderir ve biz bunu success callback'ten alabiliriz
- ya reject yoluyla ya da beklenmeyen javascript hatası oluştuğunda bunu error callbackten alabiliriz
helpers.js:
1exports.AsyncAdd2 = function(initialValue) {
2 return new Promise(function(resolve,reject){
3 console.log('asyncAdd2 enter');
4 setTimeout(function () {
5 resolve({message: 'asyncAdd2 exit', value: initialValue + 2});
6 }, 1000);
7 });
8}
9
10exports.AsyncMul2 = function(initialValue) {
11 return new Promise(function(resolve, reject) {
12 console.log('asyncMul2 enter');
13 setTimeout(function () {
14 resolve({message: 'asyncMul2 exit', value: initialValue * 2});
15 }, 1000);
16 });
17}
18
19exports.ErrorFunc = function(initialValue) {
20 return new Promise(function (resolve, reject) {
21 console.log('errorFunc enter'); // (1)
22 throw 'error happenned'; // (2)
23 // reject(Error('error happenned in errorFunc'));
24 // resolve(JSON.parse("This ain't JSON"));
25 setTimeout(function () {
26 throw 'error happenned'; // (3)
27 resolve('errorFunc exit');
28 }, 1000);
29 });
30}
program.js:
1var asyncAdd2 = require('./helpers.js').AsyncAdd2;
2var asyncMul2 = require('./helpers.js').AsyncMul2;
3var errorFunc = require('./helpers.js').ErrorFunc;
4
5var p = asyncAdd2(1);
6
7p.then(function (ctx) {
8 console.log(ctx.message);
9 console.log(ctx.value);
10 return ctx.value;
11}).then(function (result) {
12 return asyncMul2(result);
13}).then(function (ctx) {
14 console.log(ctx.message);
15 console.log(ctx.value);
16 // throw 'thats a new error'; (4)
17}).then(
18 errorFunc
19).catch(function(error){
20 console.error(error);
21}).then(function () {
22 console.log('end of execution...');
23});
Çıktısı:
1asyncAdd2 enter
2asyncAdd2 exit
33
4asyncMul2 enter
5asyncMul2 exit
66
7errorFunc enter
8error happenned
9end of execution...
Neler oluyor?
- helpers.js'te Promise döndüren function'larımız var
- bunları then kullanarak çağırıyoruz
- then'ler birbirine bağlanabiliyor ve bağlananlar da birer promise
- resolve ile gönderdiğimiz veriyi then içinde parametre olarak alabiliyoruz. sonucu return ile sonraki promise'e parametre olarak geçebiliyoruz.
- catch ile hataları yakalıyoruz
- son then ile tüm işlemler sonunda yapılacakları belirtiyoruz.
Hata yakalama kısmındaki açıklamalar:
- numaralı satır henüz hata olmadığı için çalışır
- numaralı satır hata fırlatıyor. catch tarafından yakalacak. bu satır yerine aşağıdaki reject veya diğer satır da kullanılabilirdi.
- numaralı satır setTimeout içinde olduğundan catch tarafından yakalanamıyor. bu duruma tekrar döneceğiz.
- numaralı satır açılırsa da catch içinde başarıyla yakalanır
Promise.all
İçine verilen tüm promise'lerin işinin bitmesini bekleyebiliriz.
1var asyncAdd2 = require('./helpers.js').AsyncAdd2;
2var asyncMul2 = require('./helpers.js').AsyncMul2;
3
4Promise.all([
5 asyncAdd2(),
6 asyncMul2()
7]).then(
8 function (results) {
9 console.log(results[0]);
10 console.log(results[1]);
11 console.log('success');
12 },
13 function (error) {
14 console.error(error);
15 }
16);
Çıktı:
1asyncAdd2 enter
2asyncMul2 enter
3{ message: 'asyncAdd2 exit', value: 2 }
4{ message: 'asyncMul2 exit', value: 0 }
5success
Promise.race
Promise.all gibi ancak içlerinden hangisi daha önce biterse onun sonucu döner.
1var asyncAdd2 = require('./helpers.js').AsyncAdd2;
2var asyncMul2 = require('./helpers.js').AsyncMul2;
3
4Promise.race([
5 asyncAdd2(),
6 asyncMul2()
7]).then(
8 function (result) {
9 console.log('and the winner is: ');
10 console.log(result.message);
11 },
12 function (error) {
13 console.error(error);
14 }
15).catch(function (error) {
16 console.error(error);
17});
Çıktı:
1asyncAdd2 enter
2asyncMul2 enter
3and the winner is:
4asyncMul2 exit
Helper metotlarının setTimeout değerlerini değiştirerek deneyin
Generators & Promises
Şimdi size Promise'leri Generator'lar ile birlikte kullanıldığında nasıl kolaylık sağladığını göstereceğim.
helpers.js
1var Promise = require('bluebird');
2
3module.exports.spawn = function spawn(generatorFunc) {
4 function continuer(verb, arg) {
5 var result;
6 try {
7 result = generator[verb](arg);
8 } catch (err) {
9 return Promise.reject(err);
10 }
11 if (result.done) {
12 return result.value;
13 } else {
14 return Promise.resolve(result.value).then(onFulfilled, onRejected);
15 }
16 }
17 var generator = generatorFunc();
18 var onFulfilled = continuer.bind(null, "next");
19 var onRejected = continuer.bind(null, "throw");
20 return onFulfilled();
21};
22
23function MyCustomError(message) {
24 this.message = message;
25 this.name = "MyCustomError";
26 Error.captureStackTrace(this, MyCustomError);
27}
28
29MyCustomError.prototype = Object.create(Error.prototype);
30MyCustomError.prototype.constructor = MyCustomError;
31
32module.exports.MyCustomError = MyCustomError;
33
34module.exports.delay = function(ms, func, errorType, reject) {
35 var args = Array.prototype.slice.call(arguments, 4);
36 return Promise.delay(ms).then(function() {
37 func.apply(null, args);
38 }).catch(errorType, function(err) {
39 reject(err);
40 }).catch(function(err){
41 // tanımlıysa diğer tipteki Error'ları yut
42 if(!errorType){
43 reject(err);
44 } else {
45 console.error(err);
46 }
47 });
48};
app.js:
1var spawn = require('./helpers').spawn;
2var MyCustomError = require('./helpers').MyCustomError;
3var delay = require('./helpers').delay;
4
5var promiseFunction = function(name, ms, throwError){
6 return new Promise(function(resolve, reject){
7 delay(ms, function(throwError) {
8 if(throwError)
9 throw new MyCustomError(name);
10 resolve("Resolved: " + name);
11 }, MyCustomError, reject, throwError);
12 });
13};
14
15spawn(function *() {
16 try {
17 var a1 = yield promiseFunction('async 1', 1000);
18 console.log(a1);
19 var a2 = yield promiseFunction('async 2', 1000);
20 console.log(a2);
21 var a3 = yield promiseFunction('async 3', 1000, true);
22 console.log(a3);
23 }
24 catch (err) {
25 // try/catch just works, rejected promises are thrown here
26 console.error("Error handled: (" + err + ')');
27 }
28});
Çıktısı:
1Resolved: async 1
2Resolved: async 2
3Error handled: (MyCustomError: async 3)
Şimdi n'aptık n'oldu WTF!?
- generator pattern'i kullanarak Promise'lerimizi then then diye bağlamak zorunda kalmadık.
- Sync kod ile async kodu bir güzel birleştirebildik.
Peki nasıl yaptık?
- bluebird adında yeni bir Promise kütüphanesi kullandık
- spawn adında bir helper fonksiyonu yazdık ki bu fonksiyon generator'ı yaratıp next'i çağırıyor ve dönen promise'in bir sonraki promise'e bağlanmasını sağlıyor
- Promise.delay fonksiyonunu bluebird'den aldık. setTimeout oluşan hataları yutuyordu ve bunları yakalamak için ayrı try catch yapıları kullanmamız gerekiyordu. Şimdi hataları promise'lerdeki gibi yakalayıp reject etmemiz gerekiyor.
- javascript'te try catch yapılarında hataların tip kontrolünü C#, Java gibi strong-typed dillerdeki gibi yapamıyorduk, burada bluebird bize tip kontrolü imkanı verdi.
- kendimize delay adında helper bir function yazdık. Error handling için kod yazdık. Yakalamak istediğimiz Error tipini ve reject fonksiyonunu gönderdik.
NOT: bluebird bize spawn fonksiyonunu Promise.coroutine vasıtasıyla zaten veriyor, ama kütüphane kullanmak istemezsek diye veya nasıl çalıştığı hakkında bir bilgimiz olsun diye buraya koymak istedim.
NOT: burada bluebird ile kullandığım örnekleri üç aşağı beş yukarı diğer pek çok promise kütüphanesi de sunuyor. Örnek olması açısından bunu da göstermek istedim.
let ve const
Let
let aynı var gibi değişken tanımlamamızı sağlar. ama bu sefer bu tanım sadece içinde bulunduğu kavram (scope) içinde geçerli olacaktır.
varın scope'e tanımlı en yakın fonksiyonken (fonksiyon içinde değilse globaldir), let tanımlı en yakın scope/block'a aittir. bu if, for'larda gördüğümüz { .. } parantezleri de olabileceği gibi düz parantez de bir scope'tur.
1for (let i=1; i<=5; i++) { // --> scope
2 { // --> scope
3 let a=3;
4 ...
5 }
6
7 console.log(i);
8 console.log(a); // ReferenceError: `a` is not defined
9}
Ee? Nolmuş yani bu gerçek hayatımızda ne işe yarayacak?
1for (var i=1; i<=5; i++) {
2 setTimeout(function(){
3 console.log("i:",i);
4 },i*1000);
5}
6
7// 6 6 6 6 6
8
9for (let i=1; i<=5; i++) {
10 setTimeout(function(){
11 console.log("i:",i);
12 },i*1000);
13}
// 1 2 3 4 5
for içinde let kullandığımızda aslında şuna dönüşmüş oluyor:
1{
2 let k;
3 for (k=1; k<=5; k++) {
4 let i = k; // --> her döngü için yeni bir `i`
5 setTimeout(function(){
6 console.log("i:",i);
7 },i*1000);
8 }
9}
Ayrıca bu kullanımın memory açısından faydası olacağı kesindir. Bilahare kod içinde karışıklık ve potansiyel bugların önüne geçmek adına, her zaman global/genel değişkenlerden uzak durmalıyız. Gelecekte kodun bakımı sırasında yol gösterecek, bozulmayı önleyecek şekilde kod yazmalıyız.
Const
const javascript'te immutable ya da readonly değişkenler tanımlar yapmamızı sağlar. Mesela şu koda bir bakın:
1const a = 4;
2a = 5;
3console.log(a);
Console'da şu çıktıyı verir.
1Line 2: unknown: Line 2: "a" is read-only
Çünkü artık a
const ile bir kez tanımlandıktan sonra bir daha değiştirilemez.
Peki buna neden ihtiyacımız var? Bunun en basit cevabı güvenlik olacaktır. Bir kere tanımlama yaptıktan sonra kod içindeki başka yerlerden değiştirilmesini önlediğimiz için
a
'nın kullanıldığı her yer bize ilk değerini verecektir. Bu da bize kod içinde determinizmi ve güvenliği verir. Karmaşıklığı azaltan bir etkendir. Bu tarz yapılar functional programing'te çok sık kullanılır ve bu da başka bir yazının konusu olabilir :)
Görüldüğü gibi tekrar atama yaptığımızda hata alıyoruz ve eski değerimiz böylece korunuyor. Ama daha kompleks veri tiplerine geçtiğimizde işler düşündüğümüz gibi olmuyor.
1const myObject = { nick: 'dhalsim', age:'32' };
2
3myObject = { nick: 'dhalsim', age:'22' }; // Hata fırlatır
4
5myObject.age = 22; // Hata falan fırlatmaz
6
7console.log(myObject) // { nick: 'dhalsim', age:'22' }
Yeni atama yapamasak da verinin değişmesine engel olamıyoruz. Eğer öyle bir niyetiniz varsa https://facebook.github.io/immutable-js/ veya http://swannodette.github.io/mori/ gibi gerçek immutable veri yapıları kütüphanelerine bakmalısınız.