Kaynak Kodu Okuyarak JavaScript Bilginizi Geliştirin
Yayınlanan: 2022-03-10Sık kullandığınız bir kitaplığın veya çerçevenin kaynak kodunu ilk kez derinlemesine incelediğinizi hatırlıyor musunuz? Benim için o an, üç yıl önce bir ön uç geliştirici olarak ilk işim sırasında geldi.
E-öğrenme kursları oluşturmak için kullandığımız eski bir iç çerçeveyi yeniden yazmayı yeni bitirmiştik. Yeniden yazmanın başlangıcında, Mithril, Inferno, Angular, React, Aurelia, Vue ve Polymer dahil olmak üzere bir dizi farklı çözümü araştırmak için zaman harcadık. Daha çok acemi olduğum için (gazetecilikten web geliştirmeye yeni geçmiştim), her bir çerçevenin karmaşıklığından korktuğumu ve her birinin nasıl çalıştığını anlamadığımı hatırlıyorum.
Seçtiğimiz çerçeve Mithril'i daha derinlemesine araştırmaya başladığımda anlayışım arttı. O zamandan beri, iş yerinde veya kendi projelerimde her gün kullandığım kitaplıkların derinliklerine inmek için harcadığım saatler, JavaScript ve genel olarak programlama bilgime büyük ölçüde yardımcı oldu. Bu yazıda, favori kütüphanenizi veya çerçevenizi alıp bir eğitim aracı olarak kullanmanın bazı yollarını paylaşacağım.
Kaynak Kodu Okumanın Faydaları
Kaynak kodu okumanın en büyük faydalarından biri, öğrenebileceğiniz şeylerin sayısıdır. Mithril'in kod tabanına ilk baktığımda, sanal DOM'un ne olduğu hakkında belirsiz bir fikrim vardı. Bitirdiğimde, sanal DOM'nin, kullanıcı arabiriminizin nasıl görünmesi gerektiğini tanımlayan bir nesne ağacı oluşturmayı içeren bir teknik olduğu bilgisine ulaştım. Bu ağaç daha sonra document.createElement
gibi DOM API'leri kullanılarak DOM öğelerine dönüştürülür. Güncellemeler, kullanıcı arabiriminin gelecekteki durumunu açıklayan yeni bir ağaç oluşturularak ve ardından eski ağaçtan nesnelerle karşılaştırılarak gerçekleştirilir.
Bunların hepsini çeşitli makalelerde ve eğitimlerde okumuştum ve yardımcı olsa da, gönderdiğimiz bir uygulama bağlamında iş yerinde gözlemleyebilmek benim için çok aydınlatıcı oldu. Ayrıca farklı çerçeveleri karşılaştırırken hangi soruları sormam gerektiğini de öğretti. Örneğin GitHub yıldızlarına bakmak yerine, "Her çerçevenin güncellemeleri gerçekleştirme şekli performansı ve kullanıcı deneyimini nasıl etkiler?" gibi sorular sormayı biliyordum.
Başka bir fayda, iyi uygulama mimarisini takdir etme ve anlamada bir artıştır. Çoğu açık kaynak projesi genellikle depolarıyla aynı yapıyı takip ederken, her biri farklılıklar içerir. Mithril'in yapısı oldukça düzdür ve API'sine aşina iseniz, render
, router
ve request
gibi klasörlerdeki kod hakkında doğru tahminlerde bulunabilirsiniz. Öte yandan, React'in yapısı yeni mimarisini yansıtıyor. Bakımcılar, UI güncellemelerinden sorumlu modülü ( react-reconciler
-reconciler) DOM öğelerini oluşturmaktan sorumlu modülden ( react-dom
) ayırdı.
Bunun faydalarından biri, geliştiricilerin react-reconciler
paketine bağlanarak kendi özel oluşturucularını yazmalarının artık daha kolay olmasıdır. Son zamanlarda üzerinde çalıştığım bir modül paketleyici olan Parcel, React gibi bir packages
klasörüne de sahip. Anahtar modül parcel-bundler
olarak adlandırılır ve demetler oluşturmaktan, etkin modül sunucusunu çalıştırmaktan ve komut satırı aracından sorumlu kodu içerir.
Benim için hoş bir sürpriz olarak gelen bir başka fayda da, dilin nasıl çalıştığını tanımlayan resmi JavaScript spesifikasyonunu okurken daha rahat olmanızdır. Spesifikasyonu ilk okuduğumda, throw Error
throw new Error
arasındaki farkı araştırıyordum (spoiler uyarısı - hiçbiri yok). Bunu araştırdım çünkü Mithril'in m
işlevinin uygulanmasında throw Error
kullandığını fark ettim ve onu throw new Error
üzerinde kullanmanın bir yararı olup olmadığını merak ettim. O zamandan beri, mantıksal operatörlerin &&
ve ||
mutlaka boolean döndürmez, ==
eşitlik operatörünün değerleri nasıl zorladığını yöneten kuralları ve Object.prototype.toString.call({})
öğesinin '[object Object]'
döndürmesinin nedenini buldu.
Kaynak Kodu Okuma Teknikleri
Kaynak koduna yaklaşmanın birçok yolu vardır. Başlamanın en kolay yolunun, seçtiğiniz kitaplıktan bir yöntem seçmek ve onu çağırdığınızda ne olduğunu belgelemek olduğunu buldum. Her adımı belgelemeyin, genel akışını ve yapısını belirlemeye çalışın.
Bunu yakın zamanda ReactDOM.render
ile yaptım ve sonuç olarak React Fiber ve uygulamasının arkasındaki bazı nedenler hakkında çok şey öğrendim. Neyse ki React popüler bir framework olduğu için aynı konuda diğer geliştiriciler tarafından yazılmış birçok makaleye rastladım ve bu süreci hızlandırdı.
Bu derin dalış beni aynı zamanda işbirlikli zamanlama kavramları, window.requestIdleCallback
yöntemi ve bağlantılı listelerin gerçek dünyadan bir örneğiyle tanıştırdı (React, güncellemeleri, önceliklendirilmiş güncellemelerin bağlantılı bir listesi olan bir kuyruğa koyarak işler). Bunu yaparken kütüphaneyi kullanarak çok temel bir uygulama oluşturmanız tavsiye edilir. Bu, diğer kitaplıkların neden olduğu yığın izleriyle uğraşmak zorunda kalmadığınız için hata ayıklamayı kolaylaştırır.
Derinlemesine inceleme yapmıyorsam, üzerinde çalıştığım bir projede /node_modules
klasörünü açarım veya GitHub deposuna giderim. Bu genellikle bir hata veya ilginç bir özellikle karşılaştığımda olur. GitHub'da kod okurken, en son sürümden okuduğunuzdan emin olun. Dalları değiştirmek için kullanılan butona tıklayıp “etiketler”i seçerek en son sürüm etiketine sahip komisyonlardan gelen kodu görüntüleyebilirsiniz. Kitaplıklar ve çerçeveler sonsuza kadar değişime uğrar, bu nedenle bir sonraki sürümde bırakılabilecek bir şey hakkında bilgi edinmek istemezsiniz.
Kaynak kodu okumanın daha az ilgili bir yolu, 'kursiyer bakış' yöntemi olarak adlandırmaktan hoşlandığım yöntemdir. Kod okumaya başladığımda, express.js dosyasını kurdum, /node_modules
klasörünü açtım ve bağımlılıklarını inceledim. README
bana tatmin edici bir açıklama getirmediyse, kaynağı okurum. Bunu yapmak beni şu ilginç bulgulara götürdü:
- Express, her ikisi de nesneleri birleştiren, ancak bunu çok farklı şekillerde yapan iki modüle bağlıdır.
merge-descriptors
yalnızca doğrudan kaynak nesnede bulunan özellikleri ekler ve ayrıca numaralandırılamayan özellikleri birleştirirken,utils-merge
yalnızca bir nesnenin numaralandırılabilir özellikleri ve prototip zincirinde bulunanlar üzerinde yinelenir.merge-descriptors
Object.getOwnPropertyNames()
veObject.getOwnPropertyDescriptor()
kullanırkenutils-merge
for..in
kullanır; -
setprototypeof
modülü, somutlaştırılmış bir nesnenin prototipini ayarlamak için platformlar arası bir yol sağlar; -
escape-html
, HTML içeriğinde enterpolasyon yapılabilmesi için bir içerik dizisinden kaçmak için 78 satırlık bir modüldür.
Bulguların hemen faydalı olması muhtemel olmasa da, kütüphaneniz veya çerçeveniz tarafından kullanılan bağımlılıklar hakkında genel bir anlayışa sahip olmak faydalıdır.
Ön uç kodunda hata ayıklama söz konusu olduğunda, tarayıcınızın hata ayıklama araçları en iyi arkadaşınızdır. Diğer şeylerin yanı sıra, programı istediğiniz zaman durdurmanıza ve durumunu incelemenize, bir işlevin yürütülmesini atlamanıza veya bir işleve girip çıkmanıza izin verir. Bazen bu, kod küçültülmüş olduğu için hemen mümkün olmayacaktır. Onu küçültmeye ve küçültülmemiş kodu /node_modules
klasöründeki ilgili dosyaya kopyalamaya eğilimliyim.
Vaka Çalışması: Redux'un Bağlantı İşlevi
React-Redux, React uygulamalarının durumunu yönetmek için kullanılan bir kütüphanedir. Bunun gibi popüler kütüphanelerle uğraşırken, uygulanması hakkında yazılmış makaleleri arayarak başlıyorum. Bu vaka çalışması için bunu yaparken, bu makaleye rastladım. Bu, kaynak kodunu okumakla ilgili bir başka iyi şey. Araştırma aşaması genellikle sizi bunun gibi yalnızca kendi düşünce ve anlayışınızı geliştiren bilgilendirici makalelere yönlendirir.
connect
, React bileşenlerini bir uygulamanın Redux deposuna bağlayan bir React-Redux işlevidir. Nasıl? Eh, belgelere göre, aşağıdakileri yapar:
"...geçirdiğiniz bileşeni saran yeni, bağlı bir bileşen sınıfı döndürür."
Bunu okuduktan sonra şu soruları sorardım:
- İşlevlerin bir girdi aldığı ve ardından aynı girdiyi ek işlevlerle sarılmış olarak döndürdüğü herhangi bir kalıp veya kavram biliyor muyum?
- Böyle bir kalıp biliyorsam, bunu belgelerde verilen açıklamaya dayanarak nasıl uygularım?
Genellikle bir sonraki adım, connect
kullanan çok basit bir örnek uygulama oluşturmak olacaktır. Ancak bu vesileyle, en sonunda bir üretim ortamına girecek olan bir uygulama bağlamında connect
anlamak istediğim için Limejump'ta geliştirdiğimiz yeni React uygulamasını kullanmayı seçtim.
Odaklandığım bileşen şöyle görünüyor:
class MarketContainer extends Component { // code omitted for brevity } const mapDispatchToProps = dispatch => { return { updateSummary: (summary, start, today) => dispatch(updateSummary(summary, start, today)) } } export default connect(null, mapDispatchToProps)(MarketContainer);
Dört küçük bağlantılı bileşeni saran bir kap bileşenidir. connect
yöntemini dışa aktaran dosyada ilk karşılaştığınız şeylerden biri şu yorumdur: connect, connectAdvanced üzerinden bir cephedir . Çok ileri gitmeden ilk öğrenme anımıza sahibiz: cephe tasarım desenini çalışırken gözlemleme fırsatı . Dosyanın sonunda, connect
createConnect
adlı bir işlevin çağrısını dışa aktardığını görüyoruz. Parametreleri, şu şekilde tahrip edilmiş bir grup varsayılan değerdir:
export function createConnect({ connectHOC = connectAdvanced, mapStateToPropsFactories = defaultMapStateToPropsFactories, mapDispatchToPropsFactories = defaultMapDispatchToPropsFactories, mergePropsFactories = defaultMergePropsFactories, selectorFactory = defaultSelectorFactory } = {})
Yine, başka bir öğrenme anı ile karşılaşıyoruz: çağrılan işlevleri dışa aktarma ve varsayılan işlev argümanlarını yok etme . Yıkıcı kısım bir öğrenme anı çünkü kod şöyle yazılmıştı:
export function createConnect({ connectHOC = connectAdvanced, mapStateToPropsFactories = defaultMapStateToPropsFactories, mapDispatchToPropsFactories = defaultMapDispatchToPropsFactories, mergePropsFactories = defaultMergePropsFactories, selectorFactory = defaultSelectorFactory })
Uncaught TypeError: Cannot destructure property 'connectHOC' of 'undefined' or 'null'.
Bunun nedeni, işlevin geri dönecek varsayılan bir argümanı olmamasıdır.
Not : Bununla ilgili daha fazla bilgi için David Walsh'un makalesini okuyabilirsiniz. Dil bilginize bağlı olarak bazı öğrenme anları önemsiz görünebilir ve bu nedenle daha önce görmediğiniz veya hakkında daha fazla şey öğrenmeniz gereken şeylere odaklanmak daha iyi olabilir.
createConnect
kendisi, işlev gövdesinde hiçbir şey yapmaz. Burada kullandığım connect
adlı bir işlevi döndürür:
export default connect(null, mapDispatchToProps)(MarketContainer)
Tamamı isteğe bağlı olmak üzere dört bağımsız değişken alır ve ilk üç bağımsız değişkenin her biri, bağımsız değişkenlerin mevcut olup olmadığına ve değer türlerine göre davranışlarını tanımlamaya yardımcı olan bir match
işlevinden geçer. Şimdi, match
için sağlanan ikinci argüman connect
içine aktarılan üç işlevden biri olduğu için, hangi iş parçacığını takip edeceğime karar vermem gerekiyor.
Bu argümanlar işlevlerse connect
için ilk argümanı sarmak için kullanılan proxy işlevi, düz nesneleri kontrol etmek için kullanılan isPlainObject
yardımcı programı veya hata ayıklayıcınızı tüm istisnaları kıracak şekilde nasıl ayarlayabileceğinizi gösteren warning
modülü ile öğrenme anları vardır. Eşleştirme işlevlerinden sonra, React bileşenimizi alıp connectHOC
işlevine geliyoruz. Bu, aslında bileşeni mağazaya bağlamayı işleyen wrapWithConnect
işlevini döndüren başka bir işlev çağrısıdır.
connectHOC
uygulamasına baktığımda, uygulama ayrıntılarını gizlemek için neden connect
gerektiğini anlayabiliyorum. React-Redux'un kalbidir ve connect
aracılığıyla açığa çıkarılması gerekmeyen mantığı içerir. Derin dalışı burada bitirecek olsam da, devam etmiş olsaydım, bu, kod tabanının inanılmaz derecede ayrıntılı bir açıklamasını içerdiğinden daha önce bulduğum referans materyaline başvurmak için mükemmel bir zaman olurdu.
Özet
Kaynak kodu okumak başta zordur ama her şeyde olduğu gibi zamanla daha kolay hale gelir. Amaç her şeyi anlamak değil, farklı bir bakış açısı ve yeni bilgilerle yola çıkmaktır. Anahtar, tüm süreç hakkında bilinçli olmak ve her şeyi yoğun bir şekilde merak etmektir.
Örneğin, isPlainObject
işlevini ilginç buldum çünkü verilen argümanın düz bir nesne olduğundan emin olmak için if (typeof obj !== 'object' || obj === null) return false
kullanıyor. Uygulamasını ilk okuduğumda, neden daha az kod olan ve nesneler ile Date gibi nesne alt türleri arasında ayrım yapan Object.prototype.toString.call(opts) !== '[object Object]'
kullanmadığını merak ettim. nesne. Bununla birlikte, sonraki satırı okumak, örneğin connect
kullanan bir geliştiricinin bir Date nesnesi döndürmesi gibi pek olası olmayan bir durumda, bunun Object.getPrototypeOf(obj) === null
denetimi tarafından ele alınacağını ortaya çıkardı.
isPlainObject
bir başka entrika parçası şu koddur:
while (Object.getPrototypeOf(baseProto) !== null) { baseProto = Object.getPrototypeOf(baseProto) }
Bazı Google aramaları beni bu StackOverflow dizisine ve bu kodun bir iFrame'den kaynaklanan nesnelere karşı kontrol gibi durumları nasıl ele aldığını açıklayan Redux sorununa yönlendirdi.
Kaynak Kodu Okuma Üzerine Faydalı Linkler
- “Mühendis Çerçeveleri Nasıl Tersine Çevirilir,” Max Koretskyi, Medium
- “Kod Nasıl Okunur?” Aria Stewart, GitHub