javascript - ECMAScript 6'da ok fonksiyonlarını ne zaman kullanmalıyım?
() => {} ve function() {} ile ES6'da fonksiyon yazmanın iki çok benzer yolunu elde ediyoruz. Diğer dillerde lambda fonksiyonları genellikle anonim olmalarıyla ayırt edilir, ancak ECMAScript'te herhangi bir fonksiyon anonim olabilir. Bu iki türün her birinin kendine özgü kullanım alanları vardır (özellikle this ya açıkça bağlanması gerektiğinde ya da açıkça bağlanmaması gerektiğinde). Bu alanların arasında, her iki gösterim şeklini de kullanabileceğiniz çok sayıda durum vardır.
ES6'da ok fonksiyonlarının en az iki sınırlaması vardır:
newile çalışmaz veprototypeoluştururken kullanılamaz- Başlangıçta kapsamına sabitlenmiş
this
Bu iki sınırlamanın dışında, ok fonksiyonları teorik olarak normal fonksiyonların neredeyse her yerde yerine geçebilir. Pratikte onları kullanmanın doğru yaklaşımı nedir? Örneğin, ok fonksiyonları şu durumlarda mı kullanılmalıdır:
- “çalıştıkları her yerde”, yani bir fonksiyonun
thisdeğişkeni hakkında agnostik olmasına gerek olmayan her yerde ve bir nesne oluşturmadığımız yerlerde. - sadece "gerekli oldukları her yerde", yani belirli bir kapsamla bağlanması gereken olay dinleyicileri, zaman aşımı
- 'short' fonksiyonlarla, ama 'long' fonksiyonlarla değil
- başka bir ok fonksiyonu içermeyen fonksiyonlarla sınırlı
Gelecekteki ECMAScript sürümünde uygun fonksiyon gösterimini seçmek için bir kılavuz arıyorum. Kılavuzun, bir ekibe geliştiricilere öğretilebilecek kadar açık olması ve bir fonksiyon gösteriminden diğerine sürekli yeniden düzenlemeye gerek kalmadan tutarlı olması gerekecek.
Soru, yaklaşan ECMAScript 6 (Harmony) bağlamında kod stilini düşünen ve zaten bu dil ile çalışmış olan kişilere yöneliktir.
Cevap #1
Bir süre önce ekibimiz tüm kodunu (orta ölçekli bir AngularJS uygulaması) Traceur Babel kullanarak derlenen JavaScript'e taşıdı. Şu anda ES6 ve sonrasında fonksiyonlar için şu temel kuralı kullanıyorum:
function'ı küresel alanda veObject.prototypeözellikleri için kullanın.- Nesne oluşturucular için
classkullanın. - Her yerde
=>kullanın.
Neredeyse her yerde ok fonksiyonları neden kullanılır?
- Kapsam güvenliği: Ok fonksiyonları tutarlı şekilde kullanıldığında, her şeyin kök olarak aynı
thisObject'i kullanacağı garanti edilir. Bir avuç ok fonksiyonunun içine tek bir standart fonksiyon geri çağrısı karıştırılırsa, kapsamın karışma ihtimali vardır. - Kompaktlık: Ok fonksiyonları okumak ve yazmak daha kolaydır. (Bu görüş gibi görünebilir, bu yüzden birkaç örnek vereceğim.)
- Açıklık: Neredeyse her şey bir ok fonksiyonu olduğunda, herhangi bir normal
functionkapsamı tanımlamak için hemen göze çarpar. Bir geliştirici her zaman bir sonraki üstfunctionifadesine bakarakthisObject'in ne olduğunu görebilir.
Neden her zaman global kapsamda veya modül kapsamında normal fonksiyonlar kullanıyorsunuz?
thisObject'e erişmemesi gereken bir fonksiyonu belirtmek için.windownesnesine (küresel alan) en iyi şekilde açıkça başvurulur.- Birçok
Object.prototypetanımı global kapsamda yaşar (örn.String.prototype.truncatevb.) ve bunların genellikle zatenfunctiontüründe olması gerekir. Global kapsamda tutarlı bir şekildefunctionkullanmak hatalardan kaçınmaya yardımcı olur. - Küresel kapsamda birçok fonksiyon, eski tarz sınıf tanımları için nesne oluşturuculardır.
- Fonksiyonlara isim verilebilir1. Bunun iki faydası vardır: (1)
function foo(){}yazmak,const foo =() => {}yazmaktan daha az garip gelir — özellikle diğer fonksiyon çağrılarının dışında. (2) Fonksiyon ismi, yığın izinde (stack trace) görünür. Her iç geri çağırmaya isim vermek zahmetli olsa da, tüm genel fonksiyonlara isim vermek muhtemelen iyi bir fikirdir. - Fonksiyon bildirimleri yukarı taşınır (yani bildirilmelerinden önce erişilebilirler), bu da statik bir yardımcı fonksiyonda kullanışlı bir özelliktir.
Nesne oluşturucular
Bir ok fonksiyonunu başlatmaya çalışmak bir istisna fırlatır:
var x = () => {};
new x(); // TypeError: x is not a constructor
Fonksiyonların ok fonksiyonlarına göre en önemli avantajlarından biri, fonksiyonların aynı zamanda nesne yapıcıları olarak da kullanılabilmesidir:
function Person(name) {
this.name = name;
}
Bununla birlikte, işlevsel olarak özdeş2 ECMAScript Harmony taslak sınıf tanımı neredeyse aynı derecede kompaktır:
class Person {
constructor(name) {
this.name = name;
}
}
Eski gösterim biçiminin sonunda tavsiye edilmeyeceğini bekliyorum. Nesne oluşturucu gösterimi, nesneler programatik olarak oluşturulduğunda basit anonim nesne fabrikaları için bazı kişiler tarafından hâlâ kullanılabilir, ancak başka pek çok şey için kullanılmaz.
Bir nesne yapıcısına ihtiyaç duyulduğunda, yukarıda gösterildiği gibi fonksiyonu bir class'a dönüştürmeyi düşünmelisiniz. Sözdizimi anonim fonksiyonlar/sınıflarla da çalışır.
Ok fonksiyonlarının okunabilirliği
Düz işlevlere bağlı kalmanın muhtemelen en iyi argümanı - kapsam güvenliği bir yana - okuma kolaylığı açısından ok işlevlerinin düz işlevlerden daha az okunabilir olmasıdır. Kodunuz baştan fonksiyonel değilse, ok işlevleri gerekli görünmeyebilir ve ok işlevleri tutarlı bir şekilde kullanılmadığında çirkin görünürler.
ECMAScript, ECMAScript 5.1 bize fonksiyonel Array.forEach, Array.map ve daha önce for döngülerinin kullanılacağı yerlerde fonksiyon kullanmamızı sağlayan tüm bu fonksiyonel programlama özelliklerini verdiğinden bu yana oldukça değişti. Asenkron JavaScript oldukça popüler hale geldi. ES6 ayrıca bir Promise nesnesi sunacak, bu da daha fazla anonim fonksiyon anlamına geliyor. Fonksiyonel programlamada geri dönüş yok. Fonksiyonel JavaScript'te, ok fonksiyonları normal fonksiyonlara kıyasla tercih edilir.
Örneğin, bu (özellikle kafa karıştırıcı) kod parçasını ele alalım3:
function CommentController(articles) {
this.comments = [];
articles.getList()
.then(articles => Promise.all(articles.map(article => article.comments.getList())))
.then(commentLists => commentLists.reduce((a, b) => a.concat(b)));
.then(comments => {
this.comments = comments;
})
}
Aynı kod parçası, normal fonksiyonlarla:
function CommentController(articles) {
this.comments = [];
articles.getList()
.then(function (articles) {
return Promise.all(articles.map(function (article) {
return article.comments.getList();
}));
})
.then(function (commentLists) {
return commentLists.reduce(function (a, b) {
return a.concat(b);
});
})
.then(function (comments) {
this.comments = comments;
}.bind(this));
}
Herhangi bir ok fonksiyonu standart bir fonksiyonla değiştirilebilirken, bunu yapmaktan kazanılacak çok az şey olur. Hangi sürüm daha okunabilir? Ben ilkini söylerdim.
Bence ok fonksiyonları mı yoksa normal fonksiyonları mı kullanma meselesi zamanla daha az önemli hale gelecek. Çoğu fonksiyon ya function anahtar kelimesine gerek kalmadan sınıf yöntemleri olacak ya da sınıflar haline gelecek. Fonksiyonlar, sınıfları Object.prototype üzerinden yamalamak için kullanımda kalmaya devam edecek. Bu arada function anahtar kelimesini gerçekten bir sınıf yöntemi veya sınıf olması gereken her şey için ayırmanızı öneririm.
Notlar
- Adlandırılmış ok fonksiyonları ES6 spesifikasyonunda ertelenmiştir. Gelecek bir sürüme eklenebilirler.
- Taslak spesifikasyona göre, "Sınıf bildirimleri/ifadelemeleri, bir sınıf
extendanahtar sözcüğünü kullanmadığı sürece, fonksiyon bildirimleri için olduğu gibi bir yapıcı fonksiyon/prototip çifti oluşturur". Küçük bir fark olarak, sınıf bildirimleri sabittir, oysa fonksiyon bildirimleri değildir. - Tek ifadeli ok fonksiyonlarındaki bloklarla ilgili not: Sadece yan etki için çağrılan bir ok fonksiyonu söz konusu olduğunda (ör. atama), blok kullanmayı severim. Bu şekilde, dönüş değerinin göz ardı edilebileceği anlaşılır.
Cevap #2
Öneriye göre oklar, "geleneksel fonksiyon ifadelerinin birkaç yaygın sorununu ele almak ve çözmek" amacıyla tasarlandı. Bu konuları, this değerini leksikal olarak bağlayarak ve özlü bir sözdizimi sunarak iyileştirmeyi amaçladılar.
Ancak,
- Bir kişi
this'i tutarlı bir şekilde sözlük anlamıyla bağlayamaz - Ok fonksiyonu sözdizimi hassas ve belirsizdir
Bu nedenle, ok fonksiyonları kafa karışıklığı ve hatalar için fırsatlar yaratır ve bir JavaScript programcısının sözlüğünden çıkarılmalı, yerine yalnızca function kullanılmalıdır.
Leksikal this hakkında
this sorunlu:
function Book(settings) {
this.settings = settings;
this.pages = this.createPages();
}
Book.prototype.render = function () {
this.pages.forEach(function (page) {
page.draw(this.settings);
}, this);
};
Arrow fonksiyonları, bir geri çağırma (callback) içinde this özelliğine erişmemiz gereken problemi çözmeyi amaçlar. Bunu yapmanın zaten birkaç yolu vardır: this değerini bir değişkene atayabilir, bind kullanabilir veya Array toplama yöntemlerinde mevcut olan üçüncü argümanı kullanabilirsiniz. Yine de oklar en basit çözüm gibi görünmektedir, bu yüzden yöntem şu şekilde yeniden düzenlenebilir:
this.pages.forEach(page => page.draw(this.settings));
Ancak, kodun jQuery gibi yöntemlerin this'i özel olarak bağladığı bir kütüphane kullandığını düşünün. Şimdi, ilgilenilecek iki this değeri vardır:
Book.prototype.render = function () {
var book = this;
this.$pages.each(function (index) {
var $page = $(this);
book.draw(book.currentPage + index, $page);
});
};
each öğesinin this bağlamasını dinamik olarak yapabilmesi için function kullanmalıyız. Burada ok fonksiyonunu kullanamayız.
Birden fazla this değeriyle uğraşmak da kafa karıştırıcı olabilir, çünkü bir yazarın hangi thisten bahsettiğini bilmek zordur:
function Reader() {
this.book.on('change', function () {
this.reformat();
});
}
Yazar gerçekten Book.prototype.reformat'ı çağırmak mı istedi? Yoksa this'i bağlamayı unuttu ve Reader.prototype.reformat'ı çağırmak mı istedi? Eğer handler'ı bir ok fonksiyonuna değiştirirsek, benzer şekilde yazarın dinamik this'i mi istediğini, yoksa bir satıra sığdığı için ok fonksiyonu mu seçtiğini merak edeceğiz:
function Reader() {
this.book.on('change', () => this.reformat());
}
Birisi şunu sorabilir: "Okların bazen kullanılması yanlış bir işlev olması olağanüstü mü? Belki sadece nadiren dinamik this değerlerine ihtiyacımız oluyorsa, o zaman çoğu zaman okları kullanmak yine de sorun olmaz."
Ama kendinize şu soruyu sorun: "Kodu hata ayıklamak ve bir hatanın sonucunun bir 'uç durum'dan kaynaklandığını görmek 'değerli' olur muydu?" Ben sorunlardan kaçınmayı sadece çoğu zaman değil, %100 zamanında tercih ederim.
Daha iyi bir yol var: Her zaman function kullanın (böylece this her zaman dinamik olarak bağlanabilir) ve this'e her zaman bir değişken aracılığıyla başvurun. Değişkenler leksiktir ve birçok isim alabilir. this'i bir değişkene atamak niyetinizi netleştirecektir:
function Reader() {
var reader = this;
reader.book.on('change', function () {
var book = this;
book.reformat();
reader.reformat();
});
}
Ayrıca, bir değişkene this değerini her zaman atamak (tek bir this olsa bile veya başka fonksiyonlar olmasa bile), kod değiştirildikten sonra niyetlerin açık kalmasını sağlar.
Ayrıca, dinamik this neredeyse istisnai değildir. jQuery, 50 milyonun üzerinde web sitesinde kullanılmaktadır (bu yazının yazıldığı Şubat 2016 itibarıyla). İşte this'i dinamik olarak bağlayan diğer API'ler:
- Mocha (~dün yaklaşık 120 bin indirme) testleri için metodları
thisüzerinden sunar. - Grunt (~dün 63 bin indirme) yapı görevleri için yöntemleri
thisüzerinden sunar. - Backbone (~dün 22 bin indirme)
this'e erişen yöntemleri tanımlar. - Etkinlik API'leri (DOM'unki gibi),
thisile birEventTarget'e başvurur. - Yamalı veya genişletilmiş prototip API'leri,
thisile örneklere atıfta bulunur.
(İstatistikler http://trends.builtwith.com/javascript/jQuery ve https://www.npmjs.com üzerinden alınmıştır.)
Muhtemelen dinamik this bağlamlarına zaten ihtiyaç duyacaksınız.
Bir leksikal this bazen beklenir, bazen beklenmez; tıpkı dinamik this'in bazen beklenmesi, bazen beklenmemesi gibi. Neyse ki, her zaman beklenen bağıntıyı üreten ve ileten daha iyi bir yol vardır.
Kısa sözdizimi ile ilgili
Ok fonksiyonları, fonksiyonlar için "daha kısa sözdizimsel bir form" sağlamada başarılı oldu. Peki bu daha kısa fonksiyonlar sizi daha başarılı yapacak mı?
x => x * x, function (x) { return x * x; } kodundan "daha okunaklı" mı? Belki öyledir, çünkü tek ve kısa bir kod satırı üretme olasılığı daha yüksektir. Dyson'un The influence of reading speed and line length on the effectiveness of reading from screen adlı çalışmasına göre,
Orta uzunluktaki bir satır (satır başına 55 karakter) normal ve hızlı hızlarda etkili okumayı destekliyor gibi görünmektedir. Bu, en yüksek anlama düzeyini sağladı . . .
Benzer gerekçeler, koşullu (üçlü) operatör ve tek satırlık if ifadeleri için de yapılır.
Ancak, öneride ilan edilen basit matematiksel fonksiyonları gerçekten yazıyor musunuz? Benim alanlarım matematiksel değil, bu yüzden alt rutinlerim nadiren bu kadar zarif olur. Daha ziyade, ok fonksiyonlarının bir sütun sınırını aştığını ve editör veya stil kılavuzu nedeniyle başka bir satıra geçtiğini sıkça görüyorum; bu da Dyson’un tanımıyla “anlaşılırlığı” geçersiz kılar.
Biri şöyle sorabilir: "Mümkün olduğunda kısa fonksiyonlar için sadece kısa versiyonu kullanmak nasıl olur?". Ama şimdi bir stil kuralı bir dil kısıtlamasıyla çelişiyor: "Bazen yalnızca en uzun gösterim this'i beklendiği gibi bağlayacağından, mümkün olan en kısa fonksiyon gösterimini kullanmaya çalışın." Bu tür karışıklık okları özellikle yanlış kullanıma eğilimli hale getiriyor.
Ok fonksiyonu sözdizimi ile ilgili birçok sorun vardır:
const a = x =>
doSomething(x);
const b = x =>
doSomething(x);
doSomethingElse(x);
Bu iki fonksiyon da sözdizimsel olarak geçerlidir. Ancak doSomethingElse(x);, b'nin gövdesinde değildir. Bu sadece kötü girintilenmiş, üst düzey bir ifade olarak durmaktadır.
Blok formuna genişletildiğinde, artık geri yüklemeyi unutulabilecek örtük bir return yoktur. Ancak ifade sadece bir yan etki üretmek için tasarlanmış olabilir, bu yüzden ileriye dönük olarak açık bir return gerekli olup olmayacağını kim bilir?
const create = () => User.create();
const create = () => {
let user;
User.create().then(result => {
user = result;
return sendEmail();
}).then(() => user);
};
const create = () => {
let user;
return User.create().then(result => {
user = result;
return sendEmail();
}).then(() => user);
};
Dinlenme parametresi olarak amaçlanabilecek şey, yayılma operatörü olarak ayrıştırılabilir:
processData(data, ...results => {}) // Spread
processData(data, (...results) => {}) // Rest
Atama, varsayılan argümanlarla karıştırılabilir:
const a = 1;
let x;
const b = x => {}; // No default
const b = x = a => {}; // "Adding a default" instead creates a double assignment
const b = (x = a) => {}; // Remember to add parentheses
Bloklar nesneler gibi görünür:
(id) => id // Returns `id`
(id) => {name: id} // Returns `undefined` (it's a labeled statement)
(id) => ({name: id}) // Returns an object
Bu ne anlama geliyor?
() => {}
Yazar bir işe yaramayan bir fonksiyon mu yoksa boş bir nesne döndüren bir fonksiyon mu yaratmayı amaçladı? (Bunu göz önünde bulundurarak, hiç => sonrası { koymalı mıyız? Kendimizi sadece ifade sözdizimiyle mi sınırlamalıyız? Bu, ok fonksiyonlarının sıklığını daha da azaltır.)
=>, <= ve >= gibi görünüyor:
x => 1 ? 2 : 3
x <= 1 ? 2 : 3
if (x => 1) {}
if (x >= 1) {}
Bir ok fonksiyon ifadesini hemen çağırmak için ()'yi dışarıya yerleştirmek gerekir, ancak ()'yi içeriye yerleştirmek de geçerlidir ve kasıtlı olabilir.
(() => doSomething()()) // Creates function calling value of `doSomething()`
(() => doSomething())() // Calls the arrow function
Ancak, eğer biri (() => doSomething()()); yazarsa ve amacı hemen çağrılan bir fonksiyon ifadesi yazmaksa, hiçbir şey olmayacaktır.
Yukarıdaki tüm durumlar göz önünde bulundurulduğunda ok fonksiyonlarının "daha anlaşılır" olduğunu tartışmak zor. Bu sözdizimini kullanmak için gereken tüm özel kuralları bir kişi öğrenebilir. Acaba gerçekten buna değer mi?
function sözdizimi olağanüstü derecede genelleştirilmiştir. Yalnızca function kullanmak, dilin kendisinin kafa karıştırıcı kod yazmayı önlemesi anlamına gelir. Her durumda sözdizimsel olarak anlaşılabilir prosedürler yazmak için functionı seçiyorum.
Bir yönerge ile ilgili
Açık ve tutarlı olması gereken bir yönerge talep ediyorsunuz. Ok fonksiyonlarını kullanmak, iki fonksiyon biçiminin hem anlamlı hem de rastgele bir şekilde iç içe geçmesiyle sözdizimsel olarak geçerli, mantıksal olarak geçersiz kodla sonuçlanacaktır. Bu nedenle, aşağıdakileri sunuyorum:
ES6'da Fonksiyon Gösterimi için Yönerge:
- Her zaman
functionile prosedürler oluşturun. - Her zaman
thisöğesini bir değişkene ata.() => {}kullanma.
Cevap #3
Ok fonksiyonları, fonksiyon scope'unu basitleştirmek ve this anahtar kelimesini daha basit hale getirerek çözmek için oluşturuldu. => sembolünü kullanırlar, bu sembol bir oka benzer.
Not: Mevcut fonksiyonların yerini almaz. Eğer tüm fonksiyon sözdizimlerini ok fonksiyonları ile değiştirirseniz, tüm durumlarda çalışmayacaktır.
Mevcut ES5 sözdizimine bir göz atalım. Eğer this anahtar kelimesi bir nesnenin metodunun (bir nesneye ait bir fonksiyon) içinde olsaydı, neyi ifade ederdi?
var Actor = {
name: 'RajiniKanth',
getName: function() {
console.log(this.name);
}
};
Actor.getName();
Yukarıdaki kod parçası bir object öğesine başvuracak ve "RajiniKanth" adını yazdıracaktır. Aşağıdaki kod parçasını inceleyelim ve buradaki işaret edeceği şeye bakalım.
var Actor = {
name: 'RajiniKanth',
movies: ['Kabali', 'Sivaji', 'Baba'],
showMovies: function() {
this.movies.forEach(function(movie) {
alert(this.name + " has acted in " + movie);
});
}
};
Actor.showMovies();
Peki ya this anahtar kelimesi metodun fonksiyonu içinde olsaydı ne olurdu?
Burada this, scope dışına çıktığı için inner function yerine window object'e atıfta bulunacaktır. Çünkü this, her zaman bulunduğu fonksiyonun sahibini referans alır, bu durumda — artık scope dışında olduğu için — window/global nesnesini belirtir.
object’in bir metodunun içinde olduğunda — function’ın sahibi objedir. Bu nedenle this anahtar kelimesi objeye bağlanır. Ancak, bir fonksiyonun içinde, ister bağımsız ister başka bir metodun içinde olsun, her zaman window/global objesini referans alır.
var fn = function(){
alert(this);
}
fn(); // [object Window]
Bu sorunu ES5'te kendimiz çözmenin yolları var. Bunu, ES6 ok fonksiyonlarına geçmeden önce nasıl çözeceğimize bakalım.
Genellikle, metodun iç fonksiyonunun dışında bir değişken oluşturursunuz. Şimdi ‘forEach’ metodu this öğesine erişebilir ve böylece nesnenin özelliklerine ve değerlerine ulaşabilir.
var Actor = {
name: 'RajiniKanth',
movies: ['Kabali', 'Sivaji', 'Baba'],
showMovies: function() {
var _this = this;
this.movies.forEach(function(movie) {
alert(_this.name + " has acted in " + movie);
});
}
};
Actor.showMovies();
bind kullanarak, metoda başvuran this anahtar kelimesini metodun iç fonksiyonuna bağlamak.
var Actor = {
name: 'RajiniKanth',
movies: ['Kabali', 'Sivaji', 'Baba'],
showMovies: function() {
this.movies.forEach(function(movie) {
alert(this.name + " has acted in " + movie);
}.bind(this));
}
};
Actor.showMovies();
Artık ES6 ok fonksiyonu ile lexical scoping sorunuyla daha basit bir şekilde başa çıkabiliriz.
var Actor = {
name: 'RajiniKanth',
movies: ['Kabali', 'Sivaji', 'Baba'],
showMovies: function() {
this.movies.forEach((movie) => {
alert(this.name + " has acted in " + movie);
});
}
};
Actor.showMovies();
Arrow fonksiyonlar, fonksiyon ifadelerine daha çok benzer, ancak this değerini ebeveyn kapsamına bağlar. Eğer arrow fonksiyon en üst kapsamda ise, this argümanı window/global kapsamına referans gösterir, oysa normal bir fonksiyon içindeki bir arrow fonksiyonun this argümanı, dış fonksiyonuyla aynı olur.
Arrow fonksiyonlarda this, oluşturulma zamanında kapsayan scope'a bağlanır ve değiştirilemez. New operatörü, bind, call ve apply bunun üzerinde hiçbir etkisi yoktur.
var asyncFunction = (param, callback) => {
window.setTimeout(() => {
callback(param);
}, 1);
};
// With a traditional function if we don't control
// the context then can we lose control of `this`.
var o = {
doSomething: function () {
// Here we pass `o` into the async function,
// expecting it back as `param`
asyncFunction(o, function (param) {
// We made a mistake of thinking `this` is
// the instance of `o`.
console.log('param === this?', param === this);
});
}
};
o.doSomething(); // param === this? false
Yukarıdaki örnekte, this'in kontrolünü kaybettik. Yukarıdaki örneği, this'in bir değişken referansını kullanarak veya bind'i kullanarak çözebiliriz. ES6 ile, this'i yönetmek daha kolay hale geliyor çünkü leksikal kapsam ile bağlanıyor.
var asyncFunction = (param, callback) => {
window.setTimeout(() => {
callback(param);
}, 1);
};
var o = {
doSomething: function () {
// Here we pass `o` into the async function,
// expecting it back as `param`.
//
// Because this arrow function is created within
// the scope of `doSomething` it is bound to this
// lexical scope.
asyncFunction(o, (param) => {
console.log('param === this?', param === this);
});
}
};
o.doSomething(); // param === this? true
Ok fonksiyonlarını ne zaman kullanmamalı
Bir nesne literalinin içinde.
var Actor = {
name: 'RajiniKanth',
movies: ['Kabali', 'Sivaji', 'Baba'],
getName: () => {
alert(this.name);
}
};
Actor.getName();
Actor.getName, bir ok fonksiyonu ile tanımlanmıştır, ancak çağrıldığında this.name undefined olduğu için uyarı verir çünkü bağlam window olarak kalır.
Bu, ok fonksiyonunun bağlamı window nesnesi ile sözlüksel olarak bağlamasından kaynaklanır... yani dış kapsam. this.name çalıştırmak, window.name ile eşdeğerdir ve bu tanımsızdır.
Nesne prototipi
Aynı kural, bir prototype object üzerinde yöntemler tanımlarken de geçerlidir. Yanlış bir context window getiren sayCatName yöntemini tanımlamak için ok fonksiyonu kullanmak yerine:
function Actor(name) {
this.name = name;
}
Actor.prototype.getName = () => {
console.log(this === window); // => true
return this.name;
};
var act = new Actor('RajiniKanth');
act.getName(); // => undefined
Yapıcıları çağırma
this bir yapıcı çağrısında, yeni oluşturulmuş nesnedir. new Fn() çalıştırıldığında, constructor Fn bağlamı yeni bir nesnedir: this instanceof Fn === true.
this, dış bağlamdan, yani yeni oluşturulan nesneye atanmamasını sağlayan dış kapsamdan alınmıştır.
var Message = (text) => {
this.text = text;
};
// Throws "TypeError: Message is not a constructor"
var helloMessage = new Message('Hello World!');
Dinamik bağlam ile geri arama
Ok fonksiyonu, context'i beyan sırasında statik olarak bağlar ve bunu dinamik yapmak mümkün değildir. DOM elemanlarına olay dinleyicileri eklemek, istemci tarafı programlamada yaygın bir görevdir. Bir olay, işleyici fonksiyonu bu öğe hedef olarak tetikleyerek çağırır.
var button = document.getElementById('myButton');
button.addEventListener('click', () => {
console.log(this === window); // => true
this.innerHTML = 'Clicked button';
});
this, global bağlamda tanımlanmış bir ok fonksiyonunda window’dur. Bir tıklama olayı gerçekleştiğinde, tarayıcı işleyici fonksiyonu button bağlamıyla çağırmaya çalışır, ancak ok fonksiyonu önceden tanımlanmış bağlamını değiştirmez. this.innerHTML, window.innerHTML ile eşdeğerdir ve anlamı yoktur.
Hedef elemana bağlı olarak bunu değiştirmeyi sağlayan bir fonksiyon ifadesi uygulamanız gerekiyor:
var button = document.getElementById('myButton');
button.addEventListener('click', function() {
console.log(this === button); // => true
this.innerHTML = 'Clicked button';
});
Kullanıcı butona tıkladığında, işleyici fonksiyondaki this butondur. Böylece this.innerHTML='Clicked button' buton metnini tıklanmış durumu yansıtacak şekilde doğru şekilde değiştirir.
Referanslar
- Ok Fonksiyonlarını Ne Zaman Kullanılmamalı
Cevap #4
Ok fonksiyonları - şimdiye kadar en çok kullanılan ES6 özelliği ...
Kullanım: Aşağıdaki durumlar haricinde tüm ES5 fonksiyonları ES6 ok fonksiyonları ile değiştirilmelidir:
Ok fonksiyonları kullanılmamalıdır:
Fonksiyon yükseltmesini istediğimizde
- ok fonksiyonları anonim olduğu için.
Bir fonksiyonda
this/argumentskullanmak istediğimizde- Ok fonksiyonlarının kendi
this/argumentsdeğerleri olmadığından, dış bağlamlarına bağlıdırlar.
- Ok fonksiyonlarının kendi
Adlandırılmış fonksiyonu kullanmak istediğimizde
- ok fonksiyonları anonim olduğu için.
Bir fonksiyonu
constructorolarak kullanmak istediğimizde- ok fonksiyonlarının kendi
thisdeğerleri olmadığı için.
- ok fonksiyonlarının kendi
Nesne literaline bir fonksiyonu özellik olarak eklemek ve nesnede kullanmak istediğimizde
- çünkü
this'e (kendisi olması gereken nesne) erişemiyoruz.
- çünkü
Daha iyi anlamak için ok fonksiyonlarının bazı varyantlarını inceleyelim:
Varyant 1: Bir fonksiyona birden fazla argüman göndermek ve ondan bir değer döndürmek istediğimizde.
ES5 sürümü:
var multiply = function (a, b) {
return a*b;
};
console.log(multiply(5, 6)); // 30
ES6 sürümü:
var multiplyArrow = (a, b) => a*b;
console.log(multiplyArrow(5, 6)); // 30
Not :
function anahtar kelimesi gerekli değildir. => gereklidir. {} isteğe bağlıdır; {} sağlamadığımızda, JavaScript otomatik olarak return ekler ve {} sağladığımızda, ihtiyaç duyarsak return eklememiz gerekir.
Varyant 2: Bir fonksiyona sadece bir argüman geçirmek ve ondan bazı değerler döndürmek istediğimizde.
ES5 sürümü:
var double = function(a) {
return a*2;
};
console.log(double(2)); // 4
ES6 sürümü:
var doubleArrow = a => a*2;
console.log(doubleArrow(2)); // 4
Not :
Sadece bir argüman verirken parantezleri, (), atlayabiliriz.
Varyant 3: Bir fonksiyona herhangi bir argüman geçirmek istemediğimizde ve herhangi bir değer dönmek istemediğimizde.
ES5 sürümü:
var sayHello = function() {
console.log("Hello");
};
sayHello(); // Hello
ES6 sürümü:
var sayHelloArrow = () => {console.log("sayHelloArrow");}
sayHelloArrow(); // sayHelloArrow
Varyant 4: Ok fonksiyonlarından açıkça döndürmek istediğimizde.
ES6 sürümü:
var increment = x => {
return x + 1;
};
console.log(increment(1)); // 2
Varyant 5: Ok fonksiyonlarından bir nesne döndürmek istediğimizde.
ES6 sürümü:
var returnObject = () => ({a:5});
console.log(returnObject());
Not :
Nesneyi parantez içine almamız gerekiyor, (). Aksi takdirde, JavaScript bir bloğu bir nesneden ayırt edemez.
Varyant 6: Ok fonksiyonlarının kendi arguments (dizi benzeri bir nesne) öğeleri yoktur. arguments için dış bağlama bağlıdırlar.
ES6 sürümü:
function foo() {
var abc = i => arguments[0];
console.log(abc(1));
};
foo(2); // 2
Not :
foo, bir arguments dizi benzeri nesnesine sahip bir ES5 fonksiyonudur ve ona geçirilen argüman 2'dir, bu yüzden foo için arguments[0] 2'dir.
abc, kendi arguments'ına sahip olmadığı için bir ES6 ok fonksiyonudur. Bu nedenle onun yerine dış bağlamı olan foo'nun arguments[0]'ını yazdırır.
Varyant 7: Ok fonksiyonlarının kendi this'i yoktur, this için dış bağlama bağlıdırlar
ES5 sürümü:
var obj5 = {
greet: "Hi, Welcome ",
greetUser : function(user) {
setTimeout(function(){
console.log(this.greet + ": " + user); // "this" here is undefined.
});
}
};
obj5.greetUser("Katty"); //undefined: Katty
Not :
setTimeout'a geçirilen geri arama, bir ES5 fonksiyonudur ve kendi this değerine sahiptir; bu değer bir use-strict ortamında tanımsızdır. Bu nedenle çıktıyı elde ederiz:
undefined: Katty
ES6 sürümü:
var obj6 = {
greet: "Hi, Welcome ",
greetUser : function(user) {
setTimeout(() => console.log(this.greet + ": " + user));
// This here refers to outer context
}
};
obj6.greetUser("Katty"); // Hi, Welcome: Katty
Not :
setTimeout'a geçirilen geri arama bir ES6 ok fonksiyonudur ve kendi this'ine sahip değildir, bu yüzden dış bağlamından alır; bu bağlam this'e sahip olan greetUser'dir. Bu da obj6dır ve bu yüzden çıktıyı alırız:
Hi, Welcome: Katty
Çeşitli:
- Ok fonksiyonlarla
newkullanamayız. - Ok fonksiyonlarının
prototypeözelliği yoktur. - Bir ok fonksiyonu
applyveyacallile çağrıldığındathisbağlamamız yoktur.
Cevap #5
Bu konudaki ilk cevabımda yazdığım her şeyin hâlâ arkasındayım. Ancak, kod stili konusundaki görüşüm o zamandan beri gelişti, bu yüzden bunu önceki cevabım üzerine inşa eden bu soruya yeni bir cevabım var.
Lexical this hakkında
Son cevabımda, bu dil hakkında sahip olduğum temel bir inancı kasıtlı olarak kaçındım, çünkü bu inanç yaptığım argümana doğrudan ilgili değildi. Bunun açıkça belirtilmemesine rağmen, birçok kişinin okları kullanmamayı tavsiye etme önerime neden tereddütle yaklaştığını anlayabiliyorum; çünkü okları çok faydalı buluyorlar.
İnanışım şudur: Öncelikle this kullanmamalıyız. Bu nedenle, bir kişi kasıtlı olarak kodunda this kullanmaktan kaçınıyorsa, ok işaretlerinin “lexical this” özelliği çok az veya hiç değer taşımaz. Ayrıca, this'in kötü bir şey olduğu ön kabulü altında, okların this ile olan muamelesi bir “iyi şey” olmaktan ziyade, başka bir kötü dil özelliği için bir tür hasar kontrolüdür.
Bunu bazı insanlara gelmiyor olabilir diye düşünüyorum, ama buna gelenler bile kendilerini bir dosyada this'in yüz kez geçtiği kod tabanlarında çalışırken bulacaklar ve biraz (ya da çok) hasar kontrolü, makul bir kişinin umut edebileceği tek şeydir. Bu yüzden oklar, kötü bir durumu iyileştirdiklerinde, bir bakıma iyi olabilir.
Kod yazarken oklarla this kullanmak oklar olmadan kullanmaktan daha kolay olsa bile, okların kullanım kuralları hâlâ çok karmaşıktır (bakınız: mevcut konu). Bu nedenle, yönergeler sizin talep ettiğiniz gibi ne “açık” ne de “tutarlı”dır. Programcılar okların belirsizliklerini bilseler bile, bence omuz silkip onları kabul ediyorlar, çünkü leksikal thisin değeri bu belirsizliklerin önüne geçiyor.
Tüm bunlar şu gerçeğe bir önsöz niteliğindedir: Eğer this kullanılmazsa, okların normalde neden olduğu this ile ilgili belirsizlik önemini kaybeder. Bu bağlamda oklar daha nötr hale gelir.
Kısa sözdizimi ile ilgili
İlk cevabımı yazdığımda, en iyi uygulamalara kölecesine bağlı kalmanın bile daha kusursuz kod üretebiliyorsam ödenmeye değer bir bedel olduğunu düşünüyordum. Ancak sonunda, özlülüğün kod kalitesini artırabilecek bir soyutlama biçimi olarak da hizmet edebileceğini fark ettim — öyle ki bazen en iyi uygulamalardan sapmayı haklı çıkaracak kadar.
Başka bir deyişle: kahretsin, ben de tek satırlık fonksiyonlar istiyorum!
Bir yönerge ile ilgili
this-nötr ok fonksiyonlarının olasılığı ve kısalığın peşinden gitmeye değer olması nedeniyle, aşağıdaki daha esnek yönergeyi sunuyorum:
ES6'da Fonksiyon Gösterimi için Yönerge:
thiskullanma.- Adıyla çağıracağınız fonksiyonlar için fonksiyon bildirimlerini kullanın (çünkü bunlar yukarı taşınır).
- Geri çağrılar için ok fonksiyonlarını kullanın (çünkü genellikle daha kısa olurlar).
