Giriş

Giriş

JavaScript Garden JavaScript programlama dilinin acayiplikleri üzerine derlenmiş bir döküman koleksiyonudur. Henüz ustalaşmamış JavaScript programcılarının sıkça yaptığı yanlışlar, dile has incelikler ve performans sorunlarına karşı tavsiyeler içerir.

JavaScript Garden'ın amacı size JavaScript öğretmek değildir. Bu rehberde anlatılan konuları anlamak için JavaScript dilini önceden biliyor olmanız gerekir. Eğer JavaScript dilinin temellerini öğrenmek istiyorsanız, lütfen Mozilla Programcı Ağı'nda bulunan mükemmel rehbere başvurun.

Yazarlar

Bu rehber, sevimli birer Stack Overflow kullanıcısı olan Ivo Wetzel (Yazım) ve Zhang Yi Jiang (Tasarım) tarafından hazırlanmıştır.

Katkıda Bulunanlar

Sunum

JavaScript Garden GitHub üzerinden, ve ayrıca Cramer Development tarafından desteklenen JavaScriptGarden.info adresinden sunulmaktadır.

Lisans

JavaScript Garden MIT lisansı altında yayınlanmıştır ve GitHub üzerinde bulunmaktadır. Eğer rehberde yanlışlıklar veya yazım hatalarına rastlarsanız lütfen sorunu bize bildirin veya bir pull request gönderin. Bizi ayrıca Stack Overflow'da JavaScript sohbet odasında da bulabilirsiniz.

Nesneler

Nesne Kullanımı ve Özellikleri

JavaScript'te iki istisna dışında her şey bir nesne olarak davranır; bu istisnalar da null ve undefined 'dır.

false.toString(); // 'false'
[1, 2, 3].toString(); // '1,2,3'

function Foo(){}
Foo.bar = 1;
Foo.bar; // 1

Sık düşülen bir yanılgı sayı sabitlerinin nesne olarak kullanılamayacağıdır. Bu yanılgının sebebi de JavaScript çözümleyicisinin nokta notasyonu ile girilen sayıları bir reel sayı olarak algılama hatasıdır.

2.toString(); // SyntaxError hatası verir

Bu hatayı aşıp sayı sabitlerinin de nesne olarak davranmasını sağlamak için uygulanabilecek bazı çözümler vardır.

2..toString(); // ikinci nokta doğru şekilde algılanır
2 .toString(); // noktanın solundaki boşluğa dikkat edin
(2).toString(); // ilk önce 2 değerlendirilir

Bir veri türü olarak nesneler

JavaScript nesneleri aynı zamanda bir Hashmap olarak da kullanılabilir, nesneler temelde isimli özellikler ve bunlara karşılık gelen değerlerden ibarettir.

Nesne sabiti ({} notasyonu) ile düz bir nesne yaratmak mümkündür. Bu yeni nesne kalıtım ile Object.prototype 'dan türüyecektir ve hiçbir baz özelliğe sahip olmayacaktır.

var foo = {}; // yeni bir boş nesne

// adı 'test' ve değeri 12 olan bir özelliği sahip yeni bir nesne
var bar = {test: 12}; 

Özelliklere erişmek

Bir nesnenin özelliklerine iki yolla erişilebilir, ya nokta notasyonu ile veya köşeli parantez notasyonu ile.

var foo = {name: 'kitten'}
foo.name; // kitten
foo['name']; // kitten

var get = 'name';
foo[get]; // kitten

foo.1234; // SyntaxError
foo['1234']; // çalışır

Her iki notasyon da aynı şekilde çalışır, tek fark köşeli parantez notasyonunun özelliklerin dinamik olarak oluşturulmasına ve normalde bir yazım hatasına yol açabilecek özellik isimlerinin kullanılmasına izin vermesidir.

Özellikleri silmek

Bir nesnenin özelliklerinden birini silmenin tek yolu delete operatörünü kullanmaktır; özelliğe undefined veya null değerlerini atamak sadece özelliğin değerini kaldırır, anahtarı değil.

var obj = {
    bar: 1,
    foo: 2,
    baz: 3
};
obj.bar = undefined;
obj.foo = null;
delete obj.baz;

for(var i in obj) {
    if (obj.hasOwnProperty(i)) {
        console.log(i, '' + obj[i]);
    }
}

Yukarıdaki örnek sonuç olarak hem bar undefined hem de foo null yazacaktır. Sadece baz özelliği kaldırılmış olacak ve çıktıda görünmeyecektir.

Anahtar notasyonu

var test = {
    'case': 'anahtar kelime olduğu için katar olarak girildi',
    delete: 'yine bir anahtar kelime' // SyntaxError hatası
};

Nesne özellikleri düz karakterler olarak da katar notasyonu ile de tanımlanabilir. Fakat JavaScript çözümleyicisinin bir başka tasarım hatası yüzünden, yukarıdaki örnek ECMAScript 5 öncesinde bir SyntaxError hatası verecektir.

Bu hata delete 'in bir anahtar kelime olmasından kaynaklanır, bu nedenle eski JavaScript motorlarının bu örneği doğru algılaması için karakter katarı notasyonu ile girilmelidir.

Prototip

JavaScript klasik bir kalıtım modeli değil prototip modeli kullanır.

Çoğu zaman bu modelin JavaScript'in zayıf yönlerinden biri olduğu söylense de, aslında prototip model klasik modelden daha güçlüdür. Mesela prototip model temel alınarak klasik kalıtım modeli oluşturulabilir, fakat bunun tersini yapmak çok daha zordur.

Prototip kalıtım modeli kullanan tek popüler dil JavaScript olduğu için iki model arasındaki farklılıklara alışmak biraz zaman alır.

İlk büyük farklılık JavaScript'te kalıtımın prototip zincirleri ile yapılmasıdır.

function Foo() {
    this.value = 42;
}
Foo.prototype = {
    method: function() {}
};

function Bar() {}

// Bar nesnesinin prototipi olarak yeni bir Foo nesnesini ata
Bar.prototype = new Foo();
Bar.prototype.foo = 'Hello World';

// Nesne oluşturucusunun Bar olmasını sağla
Bar.prototype.constructor = Bar;

var test = new Bar() // yeni bir Bar oluştur

// Sonuçta ortaya çıkan prototip zinciri
test [bir Bar sınıfı nesnesi]
    Bar.prototype [bir Foo sınıfı nesnesi] 
        { foo: 'Hello World', value: 42 }
        Foo.prototype
            { method: ... }
            Object.prototype
                { toString: ... /* vs. */ }

Yukarıda, test nesnesi hem Bar.prototype hem de Foo.prototype 'dan türeyecektir; bu nedenle Foo 'da tanımlanmış olan method fonksiyonuna da erişebilir. Ayrıca, prototipi olan tek Foo nesnesinin value özelliğine de erişebilir. Dikkat edilmesi gereken bir nokta, new Bar() ifadesinin yeni bir Foo nesnesi yaratmayıp, prototipine atanmış olan nesneyi kullanmasıdır; bu nedenle, tüm Bar nesneleri aynı value özelliğine sahip olacaktır.

Özelliklere bulmak

Bir nesnenin özelliklerine erişildiğinde, JavaScript, istenen isimdeki özelliği bulana kadar prototip zincirinde yukarı doğru dolaşır.

Zincirin en üstüne ulaştığında (yani Object.prototype) ve hala istenen özelliği bulamamışsa sonuç olarak undefined verecektir.

prototype özelliği

prototype özelliği dil tarafından prototip zincirleri oluşturmak için kullanılsa da, bu özelliğe herhangi bir değer atamak mümkündür. Fakat prototip olarak atanan ilkel nesne türleri göz ardı edilecektir.

function Foo() {}
Foo.prototype = 1; // hiç bir etkisi olmaz

Bir önceki örnekte gösterildiği gibi, prototip olarak nesneler atanabilir, bu da prototip zincirlerinin dinamik olarak oluşturulabilmesini sağlar.

Performans

Prototip zincirinin yukarısındaki özellikleri aramanın performansı kritik olan programlarda olumsuz etkileri olabilir. Ek olarak, mevcut olmayan özelliklere erişmeye çalışmak da tüm prototip zincirinin baştan sona taranmasına neden olacaktır.

Ayrıca, bir nesnenin özellikleri üzerinde iterasyon yapıldığında da prototip zinciri üzerindeki tüm özelliklere bakılacaktır.

Temel prototiplerin genişletilmesi

Sıklıkla yapılan bir hata Object.prototype 'ı veya diğer baz prototipleri genişletmektir.

Bu tekniğe monkey patching denir ve kapsüllemeyi bozar. Bu teknik Prototype gibi bazı popüler sistemlerde kullanılsa bile, temel nesne türlerine standart olmayan özellikler eklenmesinin geçerli iyi bir nedeni yoktur.

Temel prototipleri genişletmenin tek bir geçerli nedeni vardır, o da daha yeni JavaScript motorlarında bulunan özelliklerin eski motorlara getirilmesidir; mesela Array.forEach.

Sonuç

Prototip kalıtım modeli kullanan karmaşık programlar yazmadan önce bu modelin tamamen anlaşılması şarttır. Ayrıca, prototip zincirinin uzunluğuna dikkat edilmeli ve çok uzaması durumunda performans sorunları yaşamamak için parçalara bölünmelidir. Bundan başka, temel prototipler yeni JavaScript motorları ile uyumluluk sağlamak dışında bir nedenle asla genişletilmemelidir.

hasOwnProperty

Bir özelliğin nesnenin prototip zinciri üzerinde bir yerde değil, kendisi üzerinde tanımlandığını belirlemek için, Object.prototype kalıtımı ile tüm nesnelerin sahip olduğu hasOwnProperty metodunun kullanılması gerekir.

hasOwnProperty JavaScript'te nesne özellikleri üzerinde çalışıp prototip zincirinin tümünü dolaşmayan tek şeydir.

// Object.prototype'a bar özelliğini ekle
Object.prototype.bar = 1; 
var foo = {goo: undefined};

foo.bar; // 1
'bar' in foo; // true

foo.hasOwnProperty('bar'); // false
foo.hasOwnProperty('goo'); // true

Sadece hasOwnProperty beklenen doğru sonucu verecektir, nesne özellikleri üzerinde iterasyon yaparken bu çok önemlidir. Bir nesnenin kendisi üzerinde değil de protip zinciri üzerinde bir yerde tanımlanmış olan özelliklerini çıkarmanın başka hiçbir yolu yoktur.

hasOwnProperty özelliği

JavaScript hasOwnProperty adının bir özellik olarak kullanılmasını engellemez; bu nedenle bir nesnenin bu isimde bir özelliğe sahip olması ihtimali varsa, doğru sonuç alabilmek için hasOwnPropertyharicen kullanılmalıdır.

var foo = {
    hasOwnProperty: function() {
        return false;
    },
    bar: 'Here be dragons'
};

foo.hasOwnProperty('bar'); // her zaman false verir

// hasOwnProperty başka bir nesne üzerinde
// kullanıldığında 'this' foo olur
({}).hasOwnProperty.call(foo, 'bar'); // true

Sonuç

Bir nesnenin bir özelliği sahip olup olmadığını kontrol etmek için kullanılabilecek tek yöntem hasOwnProperty 'dir. Aynı zamanda, nesne prototiplerinin genişletilmesinden kaynaklanabilecek hataların önüne geçmek için, tüm for in döngüleri ile hasOwnProperty kullanılması tavsiye olunur.

for in Döngüsü

Tıpkı in operatörü gibi for in döngüsü de bir nesnenin özellikleri üzerinde iterasyon yaparken prototip zincirini dolaşır.

// Object.prototype'a bar özelliğini ekle
Object.prototype.bar = 1;

var foo = {moo: 2};
for(var i in foo) {
    console.log(i); // hem bar hem de moo yazar
}

for in döngüsünün davranışını değiştirmek mümkün olmadığı için, istenmeyen özelliklerin döngünün içinde filtrelenmesi gerekir, bu da Object.prototype nesnesinin hasOwnProperty metodu ile yapılır.

hasOwnProperty kullanarak filtrelemek

// yukarıdaki örnekteki foo nesnesi
for(var i in foo) {
    if (foo.hasOwnProperty(i)) {
        console.log(i);
    }
}

Doğru kullanım bu yeni versiyonda gösterildiği gibidir. hasOwnProperty kontrol edildiği için sadece moo yazacaktır. hasOwnProperty kullanılmaz ise ve Object.prototype 'ın baz özellikleri değiştirilmişse, program bazı hatalara yatkın olabilir.

Bunu yapan ve yaygın olarak kullanılan bir JavaScript sistemi Prototype 'dır. Bu sistemde hasOwnProperty kullanmayan for in döngüleri kesinlikle hatalı sonuç verecektir.

Sonuç

hasOwnProperty her zaman kontrol edilmelidir. Programın içinde çalıştığı ortam için, nesnelerin baz özelliklerinin değiştirilip değiştirilmediğine dair hiçbir kabul yapılmamalıdır.

Fonksiyonlar

Fonksiyon Tanımlaması ve Fonksiyon İfadesi

Fonksiyonlar JavaScript'te birinci sınıf nesnelerdir, yani sıradan bir değer gibi kullanılabilirler. Bu özellik sıklıkla bir isimsiz fonksiyonu başka bir fonksiyona - ki bu muhtemelen asenkron bir fonksiyondur - callback olarak geçirmekte kullanılır.

function tanımlaması

function foo() {}

Yukarıdaki fonksiyon tanımlaması program çalışmadan önce yukarı taşınır ve böylece tanımlandığı kapsam içinde her yerde (hatta tanımlanmadan önce bile) kullanılabilir.

foo(); // foo bu satır çalışmadan önce oluşturuldu
function foo() {}

function ifadesi

var foo = function() {};

Bu örnekte isimsiz fonksiyon foo değişkenine atanır.

foo; // 'undefined'
foo(); // Bu satır bir TypeError hatasına neden olur
var foo = function() {};

Yukarıdaki var anahtar kelimesi bir bildirim olduğu için foo değişkeni program çalışmadan önce yukarı alınır, program çalıştığında foo tanımlanmştır.

Fakat değer atamaları sadece program çalışırken gerçekleşeceği için, ilgili satır çalıştığında, foo değişkeninin değeri varsayılan olarak undefined olacaktır.

İsimli fonksiyon ifadesi

Bir başka özel durum isimli fonksiyon ifadesidir.

var foo = function bar() {
    bar(); // Çalışır
}
bar(); // ReferenceError hatası verir

Burada bar fonksiyonuna dış kapsamdan ulaşılamaz, çünkü sadece foo değişkenine atanmıştır; fakat iç kapsamda bar fonksiyonuna erişilebilir. Bunun nedeni JavaScript'te isim çözümlemenin çalışma şeklidir, fonksiyonun adına fonksiyonun içinden her zaman erişilebilir.

this Nasıl Çalışır

JavaScript'te this özel kelimesinin anlamı diğer programlama dillerinden farklıdır. this kelimesinin birbirinden farklı anlamlar yüklendiği tam beş durum vardır.

Genel kapsam

this;

this kelimesi genel kapsamda kullanıldığında global nesneye işaret eder.

Bir fonksiyon çağırma

foo();

Burada this yine global nesneye işaret eder.

Bir metod çağırma

test.foo(); 

Bu örnekte this kelimesi test 'e işaret edecektir.

Bir nesne oluşturucu çağırma

new foo(); 

Bir fonksiyon başında new anahtar kelimesi ile birlikte çağrılırsa bir nesne oluşturucu olarak davranır. Bu fonksiyonun içinde this kelimesi yeni oluşturulan Object 'e işaret eder.

this kelimesinin atanması

function foo(a, b, c) {}

var bar = {};
foo.apply(bar, [1, 2, 3]); // dizi aşağıdaki gibi açılır
foo.call(bar, 1, 2, 3); // sonuç: a = 1, b = 2, c = 3

Function.prototype 'ın call veya apply metodları kullanıldığında, çağrılan fonksiyonun içinde this 'in değeri ilk argümanın değeri olarak atanır.

Sonuç olarak, yukarıdaki örnekte metod çağırma durumu geçerli olmayacak, bunun yerine foo fonksiyonu içinde this 'in değeri bar olacaktır.

Sık düşülen yanılgılar

Yukarıdaki durumların çoğu mantıklı görünse bile, ilk durum dilin tasarım hatalarından biri olarak değerlendirilmelidir çünkü hiçbir pratik kullanılımı yoktur.

Foo.method = function() {
    function test() {
        // this genel nesneye işaret eder
    }
    test();
};

Bir başka yanılgı test fonksiyonunun içinde this 'in Foo 'ya işaret edeceğinin sanılmasıdır, ama bu doğru değildir.

test fonksiyonu içinden Foo 'ya erişmenin yolu method içinde bir lokal değişken oluşturmaktır.

Foo.method = function() {
    var that = this;
    function test() {
        // Burada this yerine that kullanın
    }
    test();
};

that kelimesinin dilde özel bir anlamı yoktur, ama sıklıkla dış kapsamdaki this 'e işaret etmek için kullanılır. Bu yöntem closure kavramı ile birlikte kullanıldığında this değerini program içinde taşımaya da yarar.

Metodları değişkenlere atamak

JavaScript'te mevcut olmayan bir başka özellik de fonksiyon isimlendirmedir, başka bir deyişle bir metodu bir değişkene atamak.

var test = someObject.methodTest;
test();

İlk durum nedeniyle test artık sıradan bir fonksiyon olarak davranacaktır; bu nedenle test fonksiyonu içinde this artık someObject 'e işaret etmeyecektir.

this kelimesinin geç bağlanması ilk bakışta yanlış görünse de, aslında prototipsel kalıtımı mümkün kılan şey budur.

function Foo() {}
Foo.prototype.method = function() {};

function Bar() {}
Bar.prototype = Foo.prototype;

new Bar().method();

Yukarıda Bar sınıfına ait bir nesnenin method 'u çağrıldığında this bu nesneye işaret edecektir.

Closure ve Referanslar

JavaScript'in en güçlü özelliklerinden biri de closure 'lara sahip olmasıdır. Bunun anlamı her hangi bir kapsamın her zaman kendisini içeren kapsama erişebilmesidir. JavaScript'te tek kapsam fonksiyon kapsamı olduğu için temelde tüm fonksiyonlar closure 'durlar.

Private değişkenler

function Counter(start) {
    var count = start;
    return {
        increment: function() {
            count++;
        },

        get: function() {
            return count;
        }
    }
}

var foo = Counter(4);
foo.increment();
foo.get(); // 5

Burada, Counter iki closure verir: increment fonksiyonu ve get fonksiyonu. Bu iki fonksiyon da Counter fonksiyonun kapsamına ait bir referans 'a sahiptir, ve bu nedenle söz konusu kapsamda tanımlanmış olan count değişkenine erişebilirler.

Private değişkenler nasıl işler

JavaScript'te kapsam referanslarına erişmek yada atama yapmak mümkün olmadığı için, dış kapsamdan count değişkenine ulaşmak mümkün değildir. Bu değişkene ulaşmanın tek yolu yukarıdaki iki closure 'dur.

var foo = new Counter(4);
foo.hack = function() {
    count = 1337;
};

Bu program parçası Counter fonksiyonun kapsamındaki count değişkeninin değerini değiştirmez, çünkü foo.hack bu kapsamda tanımlanmamıştır. Bunun yerine global kapsamda yeni bir değişen oluşturur (yada mevcut bir değişkeni değiştirir).

Döngü içinde closure

Sık yapılan bir hata, döngü içinde closure kullanıp döngünün indeks değişkeninin değerinin kopyalanacağını varsaymaktır.

for(var i = 0; i < 10; i++) {
    setTimeout(function() {
        console.log(i);  
    }, 1000);
}

Yukarıdaki örnek çıktı olarak 0 - 9 arası sayıları vermek yerine, 10 sayısını on kez yazacaktır.

İçteki isimsiz fonksiyon i değişkeninin değerine değil referansına sahiptir ve console.log çağrıldığında, for döngüsü çoktan tamamlanmış ve i değişkeninin değeri 10 olmuştur.

İstenen davranışı elde etmek için i değişkeninin değerinin kopyalanması gerekir.

Referans probleminin çözümü

Döngünün indeks değişkeninin değerini kopyalamanın en iyi yolu bir isimsiz fonksiyon kullanmaktır.

for(var i = 0; i < 10; i++) {
    (function(e) {
        setTimeout(function() {
            console.log(e);  
        }, 1000);
    })(i);
}

Dıştaki isimsiz fonksiyon her adımda çağrılacak ve e parametresi olarak i 'nin değerinin bir kopyası verilecektir.

setTimeOut fonksiyonuna verilen isimsiz fonksiyon artık e 'ye ait bir referansa sahip olacaktır, ve referansın değeri döngü tarafından değiştirilmeyecektir.

Bu davranışı başka bir yolla da elde etmek mümkündür; isimsiz fonksiyondan başka bir fonksiyon döndürmek. Bu durumda yukarıdaki ile aynı davranış elde edilecektir.

for(var i = 0; i < 10; i++) {
    setTimeout((function(e) {
        return function() {
            console.log(e);
        }
    })(i), 1000)
}

arguments Nesnesi

JavaScript'te her fonksiyon kapsamında arguments adlı özel bir nesne tanımlıdır. Bu nesne fonksiyon çağrılırken verilen argümanların listesini içerir.

arguments nesnesi bir Array değildir. Bir dizinin özelliklerinin bir kısmına sahip olsa da (length özelliği) Array.prototype sınıfından türetilmemiştir, aslında bir Object bile değildir.

Bu nedenle, arguments nesnesi üzerinde push, pop ve slice gibi standart dizi metotlarını kullanmak mümkün değildir. Klasik for döngüsü arguments nesnesi ile kullanılabilir, ancak standart dizi metotlarını kullanmak için gerçek bir diziye dönüştürmek gerekir.

Diziye dönüştürmek

Aşağıdaki program parçası arguments nesnesinin tüm elemanlarına sahip yeni bir dizi verecektir.

Array.prototype.slice.call(arguments);

Bu dönüşüm yavaştır, ve performansın belirleyici olduğu durumlarda kullanılması tavsiye olunmaz.

Argümanların geçirilmesi

Aşağıdaki örnekte, argümanların bir fonksiyondan diğerine geçirilmesi için önerilen yöntem gösterilmiştir.

function foo() {
    bar.apply(null, arguments);
}
function bar(a, b, c) {
    // do stuff here
}

Bir başka püf noktası da call ve apply 'ı birlikte kullanarak hızlı, ilişkisiz fonksiyonlar yaratmaktır.

function Foo() {}

Foo.prototype.method = function(a, b, c) {
    console.log(this, a, b, c);
};

// "method" 'un ilişkisiz bir versiyonunu yarat
// Aldığı parametreler: this, arg1, arg2...argN
Foo.method = function() {

    // Sonuç: Foo.prototype.method.call(this, arg1, arg2... argN)
    Function.call.apply(Foo.prototype.method, arguments);
};

Tanımlı parametreler ve argüman indisleri

arguments nesnesi her iki özelliği ve fonksiyonun tanımlı parametreleri için getter ve setter fonksiyonlar oluşturur.

Sonuç olarak, bir tanımlı parametrenin değerini değiştirmek arguments nesnesindeki karşılık gelen özelliğin değerini de değiştirecektir.

function foo(a, b, c) {
    arguments[0] = 2;
    a; // 2                                                           

    b = 4;
    arguments[1]; // 4

    var d = c;
    d = 9;
    c; // 3
}
foo(1, 2, 3);

Performans mitleri ve gerçekler

arguments nesnesi fonksiyon kapsamında bir değişken veya tanımlı parametre olarak kullanılmış olması durumları dışında her zaman oluşturulur. Kullanılıp kullanılmaması fark etmez.

getter ve setter fonksiyonlar her zaman oluşturulur; dolayısıyla arguments nesnesini kullanmanın performans üzerinde olumsuz bir etkisi yoktur, özellikle de sadece arguments nesnesinin özelliklerine erişmekten ibaret olmayan gerçek programlarda.

Fakat, modern JavaScript motorlarının performansını ciddi bir şekilde etkileyen bir durum vardır. Bu durum arguments.callee nesnesinin kullanılmasıdır.

function foo() {
    arguments.callee; // içinde olduğumuz fonksiyon nesnesi
    arguments.callee.caller; // ve çağıran fonksiyon nesnesi
}

function bigLoop() {
    for(var i = 0; i < 100000; i++) {
        foo(); // Normalde inline edilirdi...
    }
}

Yukarıdaki program parçasında, foo fonksiyonuna inlining uygulanması mümkün değildir çünkü fonksiyonun hem kendisini ve kendisini çağıran fonksiyonu bilmesi gerekmektedir. Bu yüzden hem inlining yapılamadığı için bir performans artışı sağlanamamış hem de kapsüllenme bozulmuş olmaktadır, çünkü fonksiyon artık kendisini çağıran kapsama bağımlı hale gelmiş olabilir.

arguments.callee ve özelliklerinin asla kullanılmaması şiddetle tavsiye olunur.

Nesne Oluşturucular

JavaScript'te oluşturucular diğer dillerden farklıdır. Başında new bulunan her fonksiyon çağrısı bir oluşturucudur.

Oluşturucunun (çağrılan fonksiyonun) içinde this 'in değeri yeni yaratılan Object 'dir. Bu yeni nesnenin prototipi oluşturucu olarak çağrılan fonksiyon nesnesinin prototipidir.

Çağrılan fonksiyonda bir return ifadesi yoksa, this (yani yeni nesneyi) döndürür.

function Foo() {
    this.bla = 1;
}

Foo.prototype.test = function() {
    console.log(this.bla);
};

var test = new Foo();

Yukarıdaki program Foo oluşturucusunu çağırır ve yeni yaratılan nesnenin prototipini Foo.prototype olarak belirler.

Oluşturucunun içinde bir return ifadesi bulunması durumunda, ve sadece bu değer bir Object ise oluşturucu fonksiyon verilen değeri döndürür.

function Bar() {
    return 2;
}
new Bar(); // yeni bir Bar nesnesi

function Test() {
    this.value = 2;

    return {
        foo: 1
    };
}
new Test(); // döndürülen nesne

new anahtar kelimesi ihmal edilirse, fonksiyon yeni bir nesne döndürmez.

function Foo() {
    this.bla = 1; // global nesnenin özelliğini değiştirir
}
Foo(); // undefined

Yukarıdaki örnek bazı durumlarda doğru çalışıyor gibi görünebilir, ama JavaScript'te this 'in çalışma şeklinden dolayı this 'in değeri global nesne olacaktır.

Nesne fabrikaları

new anahtar kelimesini ihmal edebilmek için oluşturucu fonksiyonun bir değer döndürmesi gerekir.

function Bar() {
    var value = 1;
    return {
        method: function() {
            return value;
        }
    }
}
Bar.prototype = {
    foo: function() {}
};

new Bar();
Bar();

Yukarıda Bar fonksiyonunu çağıran her iki ifade de aynı şeyi döndürecektir: method adında bir closure özelliği olan yeni yaratılmış bir nesne.

Başka bir nokta da new Bar() fonksiyonunun döndürülen nesnenin prototipini etkilememesidir. Yeni nesnenin prototipi oluşturulacaktır ancak Bar bu nesneyi döndürmez.

Yukarıdaki örnekte new anahtar kelimesini kullanmakla kullanmamak arasında hiçbir bir fark yoktur.

Fabrikalar ile yeni nesneler oluşturmak

new anahtar kelimesinin kullanılmaması tavsiye edilir, çünkü unutulması durumu hatalara sebep olabilir.

Bunun yerine yeni bir nesne oluşturmak için bir fabrika kullanılmalıdır.

function Foo() {
    var obj = {};
    obj.value = 'blub';

    var private = 2;
    obj.someMethod = function(value) {
        this.value = value;
    }

    obj.getPrivate = function() {
        return private;
    }
    return obj;
}

Yukarıdaki örnek hem new anahtar kelimesinin unutulmasından etkilenmez hem de private değikenlerin kullanılmasını kolaylaştırır, ama bazı dezavantajları da vardır.

  1. Oluşturulan nesneler bir prototip üzerinde metotlarını paylaşmadıkları için daha fazla hafıza kullanılır.
  2. Başka bir sınıf türetmek için fabrikanın tüm metotları başka bir nesneden kopyalaması veya bu nesneyi yeni nesnenin prototipine yerleştirmesi gerekir.
  3. Sadece new anahtar kelimesinin ihmal edilmesinden kaynaklanacak sorunları gidermek için prototip zincirinden vazgeçmek dilin ruhuna aykırıdır.

Sonuç

new anahtar kelimesini ihmal etmek hatalara neden olabilir, fakat bu kesinlikle prototip zincirinden vazgeçmek için bir neden olamaz. Hangi çözümün belirli bir programa uygun olduğu kararını verirken, en önemli nokta nesne oluşturmak için belirli bir yöntemi seçip bu çözüme bağlı kalmaktır.

Kapsamlar ve İsim Uzayları

JavaScript'te birbiri ile eşleşen ayraçlar kullanılmasına karşın blok kapsamı bulunmaz; bu nedenle, dilde sadece fonksiyon kapsamı mevcuttur.

function test() { // fonksiyon kapsamı
    for(var i = 0; i < 10; i++) { // kapsam değil
        // sayaç
    }
    console.log(i); // 10
}

JavaScript'te isim uzayları kavramı da bulunmaz, tanımlanan herşey genel olarak paylaşılmış tek bir isim uzayının içindedir.

Bir değişkene erişildiğinde, JavaScript değişkenin tanımını bulana dek yukarıya doğru tüm kapsamlara bakar. Genel kapsama ulaşıldığı halde hala değişkenin tanımı bulanamamışsa bir ReferenceError hatası oluşur.

Genel değişkenler felaketi

// A programı
foo = '42';

// B programı
var foo = '42'

Yukarıdaki iki program birbirinden farklıdır. A programında genel kapsamda bir foo değişkeni tanımlanmıştır, B programındaki foo değişkeni ise mevcut kapsamda tanımlanmıştır.

Bu iki tanımlamanın birbirinden farklı etkileri olacaktır, var anahtar kelimesini kullanmamanın önemli sonuçları olabilir.

// genel kapsam
var foo = 42;
function test() {
    // lokal kapsam
    foo = 21;
}
test();
foo; // 21

test fonksiyonun içinde var anahtar kelimesinin atlanması genel kapsamdaki foo değişkeninin değerini değiştirecektir. İlk bakışta bu önemsiz gibi görünse de, binlerce satırlık bir programda var kullanılmaması korkunç ve takibi güç hatalara neden olacaktır.

// genel kapsam
var items = [/* bir dizi */];
for(var i = 0; i < 10; i++) {
    subLoop();
}

function subLoop() {
    // subLoop fonksiyonun kapsamı
    for(i = 0; i < 10; i++) { // var kullanılmamış
        // do amazing stuff!
    }
}

Dışarıdaki döngüden subLoop fonksiyonu bir kez çağrıldıktan sonra çıkılacaktır, çünkü subLoop i değişkeninin dış kapsamdaki değerini değiştirir. İkinci for döngüsünde de var kullanılması bu hatayı kolayca engelleyecektir. Bilinçli olarak dış kapsama erişilmek istenmiyorsa var ifadesi asla atlanmamalıdır.

Lokal değişkenler

JavaScript'te lokal değişkenler sadece fonksiyon parametreleri ve var ifadesi ile tanımlanan değişkenlerdir.

// genel kapsam
var foo = 1;
var bar = 2;
var i = 2;

function test(i) {
    // test fonksiyonunun lokal kapsamı
    i = 5;

    var foo = 3;
    bar = 4;
}
test(10);

test fonksiyonun içinde foo ve i lokal değişkenlerdir, bar değişkenine değer atanması ise genel kapsamdaki aynı isimdeki değişkenin değerini değiştirecektir.

Yukarı taşıma

JavaScript'te tanımlamalar yukarı taşınır. Yani hem var ifadesi hem de function bildirimleri içindeki bulundukları kapsamın en üstüne taşınırlar.

bar();
var bar = function() {};
var someValue = 42;

test();
function test(data) {
    if (false) {
        goo = 1;

    } else {
        var goo = 2;
    }
    for(var i = 0; i < 100; i++) {
        var e = data[i];
    }
}

Program çalışmadan önce yukarıdaki kod dönüştürülür. JavaScript, var ifadelerini ve function bildirimlerini içinde bulundukları kapsamın en üstüne taşır.

// var ifadeleri buraya taşınır
var bar, someValue; // varsayılan değerleri 'undefined' olur

// function bildirimi de yukarı taşınır
function test(data) {
    var goo, i, e; // blok kapsamı olmadığı için buraya taşınır
    if (false) {
        goo = 1;

    } else {
        goo = 2;
    }
    for(i = 0; i < 100; i++) {
        e = data[i];
    }
}

bar(); // bir TypeError hatası oluşur çünkü bar hala 'undefined'
someValue = 42; // değer atamaları etkilenmez
bar = function() {};

test();

Blok kapsamının bulunmaması nedeniyle hem var ifadeleri döngülerin dışına taşınır hem de bazı if ifadeleri anlaşılmaz sonuçlar verebilir.

Orijinal programda if ifadesi goo isimli genel değişkeni değiştiriyor gibi görünüyordu, fakat yukarı taşımadan sonra anlaşıldığı gini aslında lokal değişkeni değiştiriyor.

Yukarı taşıma dikkate alınmadığında aşağıdaki programın bir ReferenceError oluşturacağı sanılabilir.

// SomeImportantThing değişkenine değer atanmış mı, kontrol et
if (!SomeImportantThing) {
    var SomeImportantThing = {};
}

Fakat var değişkeni genel kapsamın en üstüne taşınacağı için bu program çalışacaktır.

var SomeImportantThing;

// SomeImportantThing arada bir yerde atanmış olabilir

// Değer atandığından emin ol
if (!SomeImportantThing) {
    SomeImportantThing = {};
}

İsim çözümleme

JavaScript'te genel kapsam da dahil tüm kapsamlarda this adında bir özel değişken tanımlanmıştır, bu değişken geçerli nesneyi gösterir.

Fonksiyon kapsamlarında aynı zamanda arguments adında bir değişken tanımlanmıştır ve fonksiyonun argümanlarını içerir.

Örnek olarak bir fonksiyon kapsamında foo değişkenine erişildiğinde JavaScript isim çözümlemeyi aşağıdaki sıra ile yapacaktır:

  1. Geçerli kapsamda bir var foo ifadesi mevcutsa bu kullanılır.
  2. Fonksiyonun parametrelerinden birinin adı foo ise bu kullanılır.
  3. Fonksiyonun kendisinin adı foo ise bu kullanılır.
  4. Bir dıştaki kapsama geçilir ve yeniden 1 adımına dönülür.

İsim uzayları

Tek bir genel isim uzayının bulunmasının yol açtığı yaygın sonuç isim çakışmasıdır. JavaScript'te bu sorun isimsiz fonksiyonlar ile kolayca önlenebilir.

(function() {
    // bir "isim uzayı"

    window.foo = function() {
        // korunmasız bir closure
    };

})(); // fonksiyonu hemen çalıştır

İsimsiz fonksiyonlar ifade olarak değerlendirilir; bu nedenle çağrılabilmeleri için önce değerlendirilmeleri gerekir.

( // parantezin içindeki fonksiyonu değerlendir
function() {}
) // ve fonksiyon nesnesini döndür
() // değerlendirmenin sonucu fonksiyon nesnesini çağır

Bir fonksiyon ifadesini değerlendirip çağırmanın başka yolları da vardır ve yukarıdaki ile aynı sonucu verirler.

// İki farklı yöntem
+function(){}();
(function(){}());

Sonuç

Programı kendi isim uzayı ile kapsamak için her zaman isimsiz fonksiyonların kullanılması tavsiye edilir. Böylece hem isim çakışmalarından korunulmuş olunur, hem de programlar daha modüler halde yazılmış olur.

Ayrıca, genel değişkenlerin kullanılması kötü bir uygulamadır. Genel değişkenlerin herhangi bir şekilde kullanılmış olması programın kötü yazılmış olduğuna, hatalara eğilimli olduğuna ve sürdürülmesinin zor olacağına işaret eder.

Diziler

Dizi İterasyonu ve Özellikleri

Diziler JavaScript nesneleri olmalarına rağmen, iterasyon yapmak için for in döngüsü kullanmak için bir neden yoktur. Aslında dizilerde for in kullanılmasına karşı bazı iyi nedenler vardır.

for in döngüsü prototip zincirindeki tüm özellikleri dolaştığı için ve bunu engellemenin tek yolu hasOwnProperty kullanmak olduğu için for in döngüsü sıradan bir for döngüsünden yirmi kata kadar daha yavaştır.

İterasyon

Dizilerde iterasyon yaparken en iyi performansı elde etmenin en iyi yolu klasik for döngüsünü kullanmaktır.

var list = [1, 2, 3, 4, 5, ...... 100000000];
for(var i = 0, l = list.length; i < l; i++) {
    console.log(list[i]);
}

Yukarıdaki örnekte bir optimizasyon var, o da dizinin uzunluğun iterasyonun başında l = list.length ile saklanmış olması.

length özelliği dizinin kendisinde tariflenmiş olmasına rağmen, her adımda bu özelliği okumanın yine de bir maliyeti vardır. Modern JavaScript motorları bu tür durumlar için muhtemelen optimizasyon yapıyor olsa bile, programın her zaman modern bir motorda çalışacağından emin olmak mümkün değildir.

Aslında, yukarıdaki örnekteki optimizasyonu uygulamamak döngünün iki kat daha yavaş çalışmasına neden olabilir.

length özelliği

length özelliğine değer atanarak diziyi kısaltmak için kullanılabilir.

var foo = [1, 2, 3, 4, 5, 6];
foo.length = 3;
foo; // [1, 2, 3]

foo.length = 6;
foo; // [1, 2, 3]

Daha küçük bir uzunluk atanması diziyi kısaltır, fakat daha büyük bir uzunluk atanmasının dizi üzerinde bir etkisi yoktur.

Sonuç

En iyi performans için her zaman sıradan for döngüsü kullanılmalı ve length özelliği saklanmalıdır. Dizilerde for in döngüsünün kullanılmış olması hatalara meyilli kötü yazılmış bir programa işaret eder.

Array Oluşturucusu

Array oluşturucusunun parametrelerini nasıl değerlendirdiği belirsiz olduğu için, yeni diziler oluşturulurken her zaman dizi sabitlerinin ([] notasyonu) kullanılması tavsiye olunur.

[1, 2, 3]; // Sonuç: [1, 2, 3]
new Array(1, 2, 3); // Sonuç: [1, 2, 3]

[3]; // Sonuç: [3]
new Array(3); // Sonuç: []
new Array('3') // Sonuç: ['3']

Array oluşturucusuna tek bir argüman verildiğinde, ve bu argümanın türü Number ise, oluşacak boş dizinin length özelliği argümanın değerine eşit olacaktır. Bu şekilde oluşturulan bir dizinin sadece length özelliği belirlenmiş olup dizi indisleri tanımsız olacaktır.

var arr = new Array(3);
arr[1]; // undefined
1 in arr; // false, indisler atanmadı

Dizinin uzunluğunu bu şekilde önceden belirlemek sadece bir iki durumda kullanışlıdır. Bunlardan birisi bir döngüye gerek olmadan bir karakter katarını tekrarlamaktır.

new Array(count + 1).join(stringToRepeat);

Sonuç

Array oluşturucusunun kullanılmasından mümkün olduğu kadar kaçınılmalıdır. Bunun yerine her zaman dizi sabitleri tercih edilmelidir. Hem daha kısadırlar hem de daha anlaşılır bir sentaksa sahiptirler; bu nedenle programın okunabilirliğini de artırırlar.

Nesne Tipleri

Eşitlik ve Karşılaştırmalar

JavaScript'de nesnelerin değerlerinin eşitliğini kontrol etmenin iki farklı yolu vardır.

Eşitlik operatörü

Eşitlik operatörü iki adet eşittir işaretinden oluşur: ==

JavaScript weakly typed bir dildir. Bu nedenle, eşitlik operatörü ile değişkenleri karşılaştırırken tip dönüşümü yapar.

""           ==   "0"           // false
0            ==   ""            // true
0            ==   "0"           // true
false        ==   "false"       // false
false        ==   "0"           // true
false        ==   undefined     // false
false        ==   null          // false
null         ==   undefined     // true
" \t\r\n"    ==   0             // true

Yukarıdaki tablo tip dönüşümünün sonuçlarını verir, ve == kullanımının kötü bir uygulama olarak değerlendirilmesinin başlıca sebebidir. Bu karmaşık dönüşüm kuralları tespit edilmesi zor hatalara neden olur.

Ayrıca tip dönüşümü işin içine girdiğinde performans üzerinde de olumsuz etkisi olur; mesela, bir katarın bir sayı ile karşılaştırılabilmesi için önce bir sayıya dönüştürülmesi gerekir.

Kesin eşitlik operatörü

Kesin eşitlik operatörü üç adet eşittir işaretinden oluşur: ===

Eşitlik operatörünün aksine, keşin eşitlik operatörü karşılaştırdığı değerler arasında tip dönüşümü yapmaz.

""           ===   "0"           // false
0            ===   ""            // false
0            ===   "0"           // false
false        ===   "false"       // false
false        ===   "0"           // false
false        ===   undefined     // false
false        ===   null          // false
null         ===   undefined     // false
" \t\r\n"    ===   0             // false

Yukarıdaki sonuçlar hem daha anlaşılırdır, hem de progamdaki hataların erkenden ortaya çıkmasını sağlar. Bu programı bir miktar sağlamlaştırır ve ayrıca karşılaştırılan değerlerin farklı tiplerden olması durumunda performansı da artırır.

Nesneleri karşılaştırmak

Hem == hem de === operatörlerinin eşitlik operatörü olarak adlandırılmasına rağmen, değerlerden en azından birinin bir Object olması durumunda farklı davranış gösterirler.

{} === {};                   // false
new String('foo') === 'foo'; // false
new Number(10) === 10;       // false
var foo = {};
foo === foo;                 // true

Bu durumda her iki operatör de eşitlik değil aynılık karşılaştırması yapar; yani, terimlerin aynı nesnenin örnekleri olup olmadığını kontrol ederler, tıpkı Python dilindeki is ve C dilindeki gösterici karşılaştırması gibi.

Sonuç

Sadece kesin eşitlik operatörünün kullanılması şiddetle tavsiye edilir. Tip dönüşümü yapılmasının gerekli olduğu durumlarda, bu açıkça yapılmalıdır ve dilin karmaşık dönüşüm kurallarına bırakılmamalıdır.

typeof Operatörü

typeof operatörü (instanceof ile birlikte) herhalde JavaScript'in en büyük tasarım hatalarından biridir, çünkü neredeyse tamamen arızalıdır.

instanceof operatörünün sınırlı kullanımı olsa da, typeof operatörünün gerçekte tek bir pratik kullanımı vardır, ve bunun da bir nesnenin tipini kontrol etmekle ilgili yoktur.

JavaScript tip tablosu

Değer               Sınıf      Tip
-------------------------------------
"foo"               String     string
new String("foo")   String     object
1.2                 Number     number
new Number(1.2)     Number     object
true                Boolean    boolean
new Boolean(true)   Boolean    object
new Date()          Date       object
new Error()         Error      object
[1,2,3]             Array      object
new Array(1, 2, 3)  Array      object
new Function("")    Function   function
/abc/g              RegExp     object (function in Nitro/V8)
new RegExp("meow")  RegExp     object (function in Nitro/V8)
{}                  Object     object
new Object()        Object     object

Yukarıdaki tabloda Tip sütunu typeof operatörünün verdiği sonucu gösterir. Açıkça görülebileceği gibi, bu sonuç tutarlı olmaktan çok uzaktır.

Sınıf sütunu bir nesnenin dahili [[Class]] özelliğini gösterir.

[[Class]] özelliğinin değerini almak için Object.prototype 'ın toString metodu kullanılmalıdır.

Bir nesnenin sınıfı

Spesifikasyona göre [[Class]] değerine erişmenin tek yolu Object.prototype.toString kullanmaktır.

function is(type, obj) {
    var clas = Object.prototype.toString.call(obj).slice(8, -1);
    return obj !== undefined && obj !== null && clas === type;
}

is('String', 'test'); // true
is('String', new String('test')); // true

Yukarıdaki örnekte, Object.prototype.toString çağrıldığında this 'in değeri [[Class]] değeri aranan nesne olarak atanmış olmaktadır.

Bir değişkenin tanımlandığını kontrol etmek

typeof foo !== 'undefined'

Yukarıdaki satır foo değişkeninin tanımlanıp tanımlanmadığını belirler; tanımlanmamış bir değişkene erişmek bir ReferenceError hatası oluştur. typeof operatörünün tek kullanışlı olduğu şey işte budur.

Sonuç

Bir nesnenin tipini kontrol etmek için Object.prototype.toString 'in kullanılması şiddetle tavsiye edilir; çünkü bunu yapmanın tek güvenilir yoludur. Yukarıdaki tip tablosunda gösterildiği gibi, typeof operatörünün bazı sonuçları spesifikasyonda tanımlanmamıştır; bu nedenle, çeşitli platformlarda farklılık gösterebilirler.

Bir değişkenin tanımlandığını kontrol etmek dışında, typeof operatörün kullanımından her ne pahasına olursa olsun kaçınılmalıdır.

instanceof Operatörü

instanceof operatörü verilen iki terimin nesne oluşturucularını karşılaştırır. Kullanışlı olduğu tek durum özel nesnelerin karşılaştırılmasıdır. Temel nesneler üzerinde kullanıldığında neredeyse typeof operatörü kadar yararsızdır.

Özel nesneleri karşılaştırmak

function Foo() {}
function Bar() {}
Bar.prototype = new Foo();

new Bar() instanceof Bar; // true
new Bar() instanceof Foo; // true

// Bu satır sadece Bar.prototype'a Foo fonksiyon nesnesinin atar
// Bir Foo sınıfı nesnesine değil
Bar.prototype = Foo;
new Bar() instanceof Foo; // false

Temel nesnelerle instanceof kullanımı

new String('foo') instanceof String; // true
new String('foo') instanceof Object; // true

'foo' instanceof String; // false
'foo' instanceof Object; // false

Dikkat edilmesi gereken ilginç bir nokta, instanceof operatörünün farklı JavaScript kaynaklarından gelen nesneler üzerinde çalışmamasıdır (mesela bir internet tarayıcısının farklı dökümanları), çünkü bu durumda nesne oluşturucuları aynı olmayacaktır.

Sonuç

instanceof operatörü sadece aynı JavaScript kaynağından gelen özel nesneler ile kullanılmalıdır. Tıpkı typeof operatöründe olduğu gibi, bunun dışındaki tüm kullanımlarından kaçınılmalıdır.

Tip Dönüşümleri

JavaScript weakly typed bir dildir, bu yüzden mümkün olan yerlerde tip dönüşümü uygular.

// Bunlar true verir
new Number(10) == 10; // Number.toString() tekrar sayıya
                      // dönüştürülür

10 == '10';           // Katarlar sayıya dönüştürülür
10 == '+10 ';         // Bir başka katar çılgınlığı
10 == '010';          // Ve bir tane daha 
isNaN(null) == false; // null 0'a dönüştürülür
                      // tabii 0 NaN değildir

// Bunlar false verir
10 == 010;
10 == '-10';

Yukarıdakilerden kaçınmak için, kesin eşitlik operatörünün kullanılması şiddetle tavsiye edilir. Böylece yaygın hataların çoğundan kaçınılabilir, yine de JavaScript'in weak typing sisteminden kaynaklanan başka sorunlar da vadır.

Temel tiplerin nesne oluşturucuları

Number ve String gibi temel tiplerin nesne oluşturucuları new anahtar kelimesi ile kullanılıp kullanılmamalarına göre farklı davranış gösterir.

new Number(10) === 10;     // False, Object ve Number
Number(10) === 10;         // True, Number ve Number
new Number(10) + 0 === 10; // True, tip dönüşümü nedeniyle

Number gibi bir temel tipin nesne oluşturucusunu kullanmak yeni bir Number nesnesi yaratacaktır, fakat new kelimesi kullanılmazsa Number fonksiyonu bir dönüştürücü olarak davranacaktır.

Ayrıca, sabitler ve nesne olmayan değerler kullanılması durumunda başka tür dönüşümler de söz konusu olacaktır.

En iyi seçenek üç olası tipten birine açıkça dönüşüm yapılmasıdır.

Karakter katarına dönüştürmek

'' + 10 === '10'; // true

Bir değerin başına boş bir katar eklenerek kolayca katara dönüştürülebilir.

Sayıya dönüştürmek

+'10' === 10; // true

Tek terimli toplama operatörü kullanılarak bir değer sayıya dönüştürülebilir.

Mantıksal değişken tipine dönüştürmek

Değil operatörü iki kez üst üste kullanılarak bir değer mantıksal değişken tipine dönüştürülebilir.

!!'foo';   // true
!!'';      // false
!!'0';     // true
!!'1';     // true
!!'-1'     // true
!!{};      // true
!!true;    // true

Temel

Neden eval Kullanılmamalı

eval fonksiyonu bir JavaScript kodunu lokal kapsamda yürütür.

var foo = 1;
function test() {
    var foo = 2;
    eval('foo = 3');
    return foo;
}
test(); // 3
foo; // 1

Fakat eval sadece direkt olarak çağrıldığında ve çağrılan fonksiyonun adı eval ise lokal kapsamda çalışır.

var foo = 1;
function test() {
    var foo = 2;
    var bar = eval;
    bar('foo = 3');
    return foo;
}
test(); // 2
foo; // 3

eval fonksiyonu asla kullanılmamalıdır. Kullanıldığı durumların %99.9'unda eval kullanılmadan da istenen sonuç elde edilebilir.

Gizli eval

Zamanlama fonksiyonları setTimeout ve setInterval'ın her ikisinin de ilk argümanları bir karakter katarıdır. Bu durumda eval dolaylı olarak çağrıldığı için bu argüman her zaman genel kapsamda yürütülecektir.

Güvenlik sorunları

eval kendisine verilen her kodu işlettiği için aynı zamanda bir güvenlik sorunudur ve asla kaynağı bilinmeyen yada güvenilir olmayan karakter katarları ile kullanılmamalıdır.

Sonuç

eval asla kullanılmamalıdır, kullanan programlar ise doğruluk, performans ve güvenlik açılarından sorgulanmalıdır. eval kullanımı gerekli görülmüşse, programın tasarımı sorgulanmalı ve kullanılmamalı, bunun yerine eval gerektirmeyen daha iyi bir tasarım kullanılmalıdır.

undefined ve null

JavaScript'te tanımsız anlamına gelen iki değer vardır, ve bunlardan undefined daha kullanışlıdır.

undefined değeri

undefined bir değişken türüdür ve tek bir değere sahip olabilir: undefined.

JavaScript'te ayrıca değeri undefined olan bir de genel kapsam değişkeni tanımlanmıştır ve bu değişkenin adı da undefined'dır. Fakat bu değişken bir sabit yada dilin anahtar kelimelerinden biri değildir. Yani bu değişkenin değeri kolayca değiştirilebilir.

undefined değerinin verildiği durumlara bazı örnekler:

  • Genel kapsamlı undefined değişkeninin (değiştirilmedi ise) değeri
  • return ifadesi içermeyen fonksiyonların verdiği değer
  • Bir değer döndürmeyen return ifadeleri
  • Mevcut olmayan nesne özellikleri
  • Değer atanmamış fonksiyon parametreleri
  • Değeri undefined olarak atanmış değişkenler

undefined değerinin değiştirilmesi durumu

Genel kapsamdaki undefined değişkeni asıl undefined değerinin kopyasını tuttuğu için, bu değeri değiştirmek undefined değişken türünün değerini değiştirmez.

Fakat, bir şeyi undefined ile karşılaştırmak için önce undefined'ın değerini geri almak gerekir.

Programı undefined değişkeninin değiştirilmesi olasılığına karşı korumak için uygulanan yaygın bir yöntem isimsiz bir fonksiyona kullanılmayan bir parametre eklemektir.

var undefined = 123;
(function(something, foo, undefined) {
    // lokal kapsamda undefined değişkeni
    // yine undefined değerine sahip

})('Hello World', 42);

Benzer bir yöntem yine isimsiz fonksiyonun içinde değer atanmamış bir değişken deklare etmektir.

var undefined = 123;
(function(something, foo) {
    var undefined;
    ...

})('Hello World', 42);

Buradaki tek fark program sıkıştırılırsa ortaya çıkacaktır, eğer fonksiyonun başka bir yerinde var ifadesi kullanılmıyorsa fazladan 4 bayt kullanılmış olacaktır.

null kullanımı

JavaScript dilinde undefined geleneksel null yerine kullanılmaktadır, asıl null (hem null değişmezi hem de değişken türü) ise kabaca başka bir veri türüdür.

null JavaScript içinde kapalı olarak kullanılır (mesela prototip zincirinin sonuna gelindiği Foo.prototype = null ile belirtilir), fakat hemen her durumda bunun yerine undefined kullanılabilir.

Otomatik Noktalı Virgül İlavesi

JavaScript sentaksı C'ye benzese de, noktalı virgül kullanılması zorunlu değildir.

Fakat JavaScript noktalı virgül kullanmayan bir dil değildir, hatta programı anlayabilmek için noktalı virgüllere ihtiyaç duyar. Bu yüzden JavaScript gramer çözümleyicisi eksik bir noktalı virgül yüzünden bir hata ile karşılaştığında otomatik olarak eksik noktalı virgülleri ekler.

var foo = function() {
} // hata, noktalı virgül gerekiyor
test()

Eklemeden sonra çözümleme tekrarlanır.

var foo = function() {
}; // hata ortadan kalktı, çözümleme devam edebilir
test()

Noktalı virgüllerin bu şekilde otomatik olarak eklenmesi JavaScript'in en büyük tasarım hatalarından biri olarak kabul edilir, çünkü programın davranışını değiştirmesi mümkündür.

Ekleme nasıl olur

Aşağıdaki örnekte hiç noktalı virgül yok, bu yüzden nereye noktalı virgül eklenmesi gerektiğini gramer çözümleyicinin karar vermesi gerekiyor.

(function(window, undefined) {
    function test(options) {
        log('testing!')

        (options.list || []).forEach(function(i) {

        })

        options.value.test(
            'long string to pass here',
            'and another long string to pass'
        )

        return
        {
            foo: function() {}
        }
    }
    window.test = test

})(window)

(function(window) {
    window.someLibrary = {}

})(window)

Çözümleyicinin "tahmin" oyununun sonucu aşağıdadır.

(function(window, undefined) {
    function test(options) {

        // Eklenmedi, satırlar birleştirildi
        log('testing!')(options.list || []).forEach(function(i) {

        }); // <- eklendi

        options.value.test(
            'long string to pass here',
            'and another long string to pass'
        ); // <- eklendi

        return; // <- eklendi, return ifadesi bozuldu
        { // bir blok olarak değerlendirildi

            // bir yer etiketi ve bir ifade
            foo: function() {} 
        }; // <- eklendi
    }
    window.test = test; // <- eklendi

// Burada da satırlar birleştirildi
})(window)(function(window) {
    window.someLibrary = {}; // <- eklendi

})(window); //<- eklendi

Çözümleyici yukarıdaki program parçasının davranışını büyük ölçüde değiştirdi, belirli durumlarda da grameri değerlendirirken yanlış kararlar verdi.

Satır başındaki parantezler

Bir satırın parantez ile başlaması durumunda, çözümleyici noktalı virgül eklemez.

log('testing!')
(options.list || []).forEach(function(i) {})

Yukarıdaki program parçası aşağıdaki tek satıra dönüşür.

log('testing!')(options.list || []).forEach(function(i) {})

Büyük ihtimalle yukarıdaki log bir fonksiyon döndürmüyordur; bu nedenle, yukarıdaki satır undefined is not a function hata mesajı ile bir TypeError oluştumasına neden olacaktır.

Sonuç

Noktalı virgüllerin hiç bir zaman ihmal edilmemesi tavsiye edilir, ayrıca ayraçların kendilerinden önceki ifade ile aynı satırda tutulması ve tek satırlık if ve else ifadelerinde bile ayraçların ihmal edilmemesi önerilir. Her iki önlem de hem programın tutarlılığını artıracak, hem de JavaScript çözümleyicisinin programın davranışını değiştirmesini engelleyecektir.

delete Operatörü

Kısacası, genel kapsamda tanımlanmış değişkenleri, fonksiyonları ve DontDelete niteliğine sahip bazı başka şeyleri silmek imkansızdır.

Genel kapsam ve fonksiyon kapsamı

Bir değişken veya fonksiyon genel kapsamda veya fonksiyon kapsamında tanımlandığında aktivasyon nesnesinin veya global nesnenin bir özelliği olacaktır. Bu tür özelliklerin bir takım nitelikleri vardır ve bunlardan biri DontDelete niteliğidir. Genel kapsamda ve fonksiyon kapsamında tanımlanan değişkenler ve fonksiyonlar yaratıldıklarında her zaman DontDelete niteliğine sahip olacaktır, ve bu nedenle silinemezler.

// genel kapsam değişkeni:
var a = 1; // DontDelete niteliğine sahip
delete a; // false
a; // 1

// normal bir fonksiyon:
function f() {} // DontDelete niteliğine sahip
delete f; // false
typeof f; // "function"

// başka bir değişkene atamak işe yaramaz:
f = 1;
delete f; // false
f; // 1

Açıkça tanımlanan özellikler

Açıkça tanımlanan özellikleri silmek mümkündür.

// tanımlanan özellik:
var obj = {x: 1};
obj.y = 2;
delete obj.x; // true
delete obj.y; // true
obj.x; // undefined
obj.y; // undefined

Yukarıdaki örnekte obj.x ve obj.y silinebilir çünkü DontDelete niteliğine sahip değillerdir. Aynı nedenle aşağıdakini yapmak da mümkündür:

// IE hariç çalışır:
var GLOBAL_OBJECT = this;
GLOBAL_OBJECT.a = 1;
a === GLOBAL_OBJECT.a; // true - genel değişken
delete GLOBAL_OBJECT.a; // true
GLOBAL_OBJECT.a; // undefined

Burada a'yı silmek için bir hile kullanıyoruz. this burada genel nesneye işaret ediyor ve a değişkenini onun özelliği olarak atıyoruz, ve böylece onu silebiliyoruz.

IE (en azından 6-8) bazı hatalar içerdiğinden yukarıdaki örnek çalışmayacaktır.

Fonksiyon argümanları ve önceden tanımlı özellikler

Fonksiyonlara verilen argümanlar, arguments nesnesi ve önceden tanımlı özellikler de DontDelete niteliğine sahiptir.

// fonksiyon argümanları ve özellikler:
(function (x) {
  delete arguments; // false
  typeof arguments; // "object"

  delete x; // false
  x; // 1

  function f(){}
  delete f.length; // false
  typeof f.length; // "number"
})(1);

Host nesneler

Host nesneler üzerinde kullanıldığında delete operatörünün davranışı belirsiz olabilir. Standarda göre host nesneler istedikleri davranışı uygulayabilirler.

Sonuç

delete operatörünün davranışı genellikle belirsizdir ve güvenle kullanılabileceği tek yer sıradanan nesneler üzerinde açıkça tanımlanan özelliklerdir.

Diğer

setTimeout ve setInterval

JavaScript asenkron olduğu için setTimeout ve setInterval kullanarak bir fonksiyonun ileri bir zamanda çalışmasını sağlamak mümkündür.

function foo() {}
var id = setTimeout(foo, 1000); // 0'dan büyük bir sayı verir

Yukarıdaki örnekte setTimeout fonksiyonu çağrıldığında, oluşturulan zamanlayıcı tanımlayan bir ID sayısı verir ve foo fonksiyonu yaklaşık bin milisaniye sonra çalıştırılmak üzere programlanır. foo fonksiyonu tam olarak bir kez çağrılacaktır.

Kullanılan JavaScript motorunun zamanlayıcı hassasiyetine bağlı olarak, ve ayrıca JavaScript tek thread ile çalıştığı ve çalışan başka program parçaları bu tek thread 'i bloke edeceği için, setTimeout ile belirlenen erteleme süresinin tam olarak gerçekleşeceği hiçbir şekilde garanti edilemez.

İlk argüman olarak verilen fonksiyon global nesne tarafından çağrılacaktır, yani çağrılan fonksiyonun içinde this bu nesneye işaret edecektir.

function Foo() {
    this.value = 42;
    this.method = function() {
        // this global nesneye işaret eder
        console.log(this.value); // undefined yazar
    };
    setTimeout(this.method, 500);
}
new Foo();

setInterval ile fonksiyon çağrılarının yığılması

setTimeout verilen fonksiyonu bir kez çağırırken, setInterval (adından da anlaşılacağı gibi) verilen fonksiyonu her X milisaniyede bir çağırır. Fakat kullanılması önerilmez.

Mevcut program parçası çalışırken zamanlama bloke olduğu halde, setInterval verilen fonksiyonu çağırmaya devam edecektir. Bu da, özellikle küçük aralıklarla kullanıldığında, fonksiyon çağrılarının istiflenmesine neden olur.

function foo(){
    // 1 saniye süren bir işlem
}
setInterval(foo, 100);

Yukarıdaki örnekte foo fonksiyonu bir kez çağrılıp bir saniye boyunca bloke edecektir.

foo programı bloke etmişken, setInterval fonksiyon çağrılarını zamanlamaya devam edecektir. foo tamamlandığında, çalıştırılmayı bekleyen on çağrı daha olacaktır.

Bloke eden programlarla başa çıkmak

En kolay ve kontrol edilebilir çözüm, setTimeout 'u fonksiyonun içinde kullanmaktır.

function foo(){
    // 1 saniye süren bir işlem
    setTimeout(foo, 100);
}
foo();

Bu örnekte hem setTimeout çağrısı fonksiyonun kendisi içinde kapsanmış olmakta, hem de fonksiyon çağrılarının istiflenmesinin önüne geçilerek daha fazla kontrol sağlanmaktadır. Artık foo fonksiyonunun kendisi tekrar çalışmak isteyip istemediğine karar verebilir.

Zamanlayıcıları iptal etmek

Zamanlayıcıları iptal etmek için ilgili ID sayıları ile kullanılan zamanlayıcı fonksiyonuna karşılık gelen clearTimeout ve clearInterval fonksiyonlarından biri kullanılır.

var id = setTimeout(foo, 1000);
clearTimeout(id);

Tüm zamanlayıcıları iptal etmek

Tüm zamanlayıcıları iptal etmenin dahili bir yolu olmadığı için, bu amaca ancak kaba kuvvetle ulaşılabilir.

// "tüm" zamanlayıcıları iptal et
for(var i = 1; i < 1000; i++) {
    clearTimeout(i);
}

Bu rastgele seçilmiş sayıdan etkilenmeyen zamanlayıcılar kalabilir; bu yüzden tüm zamanlayıcı ID'lerinin saklanarak, teker teker iptal edilmeleri tavsiye edilir.

eval fonksiyonun gizli kullanımı

setTimeout ve setInterval fonksiyonları ilk parametreleri olarak bir katar da kabul eder. Bu özellik asla kullanılmamalıdır, çünkü bu durumda dahili olarak eval kullanılır.

function foo() {
    // setTimeOut ile bu fonksiyon çağrılacaktır
}

function bar() {
    function foo() {
        // bu fonksiyon çağrılmayacaktır
    }
    setTimeout('foo()', 1000);
}
bar();

Bu durumda eval direkt olarak çağrılmadığı için, setTimeout fonksiyonuna verilen katar genel kapsamda çalıştırılacaktır; bu nedenle, bar fonksiyonu kapsamındaki lokal foo değişkenini kullanmayacaktır.

Zamanlama fonksiyonlarına verilen fonksiyona argüman sağlamak için de bir katar kullanılması tavsiye edilmez.

function foo(a, b, c) {}

// ASLA bu şekilde kullanılmamalı
setTimeout('foo(1, 2, 3)', 1000)

// Bunu yerine isimsiz bir fonksiyon kullanın
setTimeout(function() {
    foo(1, 2, 3);
}, 1000)

Sonuç

setTimeout veya setInterval fonksiyonlarına asla bir katar parametre verilmemelidir. Bu kullanım çok kötü bir programa işaret eder. Çağrılan fonksiyona argümanlar verilmesinin gerektiği durumlarda gerçek çağrıyı içinde bulunduran bir isimsiz fonksiyon kullanılmalıdır.

Ayrıca, setInterval fonksiyonu çalışan JavaScript programı tarafından bloke olmadığı için tercih edilmemelidir.