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.
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.
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 tekFoo 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ümfor 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 sadecemoo 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ç
hasOwnPropertyher 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 fonksiyonfoo 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şturulanObject '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, Counterikiclosure 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.hackbu 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 Arraydeğ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
prototipiniFoo.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.
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.
Oluşturulan nesneler bir prototip üzerinde metotlarını paylaşmadıkları
için daha fazla hafıza kullanılır.
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.
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ü subLoopi 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:
Geçerli kapsamda bir var foo ifadesi mevcutsa bu kullanılır.
Fonksiyonun parametrelerinden birinin adı foo ise bu kullanılır.
Fonksiyonun kendisinin adı foo ise bu kullanılır.
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.
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.
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 sadecelength ö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.
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.
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ğilaynı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.
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
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 yerlerdetip 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.
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
evalkullanı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 undefineddeğerinin kopyasını
tuttuğu için, bu değeri değiştirmek undefineddeğ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 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.
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
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.
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 herX 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 evaldirekt 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.