Kaynak kodunu incelemek, şüphesiz ki geliştirici kariyerinizin gidişatını değiştirebilir. Sadece bir seviyenin altına bakmak bile sizi çoğu ortalama geliştiriciden ayırabilir.
Ustalığa giden ilk adım!
İşte kişisel bir hikaye: Bir AI/ML girişimindeki mevcut işimde, ekip Neo4j verilerini sunucudan görselleştirme için ön uca nasıl aktaracağını çözemedi ve 12 saat içinde bir sunumları vardı. Serbest çalışan olarak işe alındım ve paniği açıkça görebiliyordunuz. Sorun, Neo4j tarafından döndürülen verilerin görselleştirme aracı neo4jd3 tarafından beklenen doğru biçimde olmamasıydı.
Şunu hayal edin: Neo4jd3 bir üçgen bekliyor ve Neo4j bir kare döndürüyor. İşte tam burada uyumsuz bir uyumsuzluk var!
Yakında Mastered'da JavaScript ve Neo4j ile grafik veri bilimi yapabiliriz! Bu görüntü nostaljik.
Sadece iki seçenek vardı: Neo4j'in tüm arka ucunu yeniden yapmak veya Neo4jd3'ün kaynak kodunu incelemek, beklenen formatı bulmak ve ardından kareyi üçgene dönüştüren bir adaptör oluşturmak.
neo4jd3 <- adapter <- Neo4j
Beynim kaynak kodunu okumaya başladı ve bir adaptör oluşturdum: neo4jd3-ts .
import createNeoChart, { NeoDatatoChartData } from "neo4jd3-ts";
Bağdaştırıcı NeoDatatoChartData
ve diğer her şey tarih oldu. Bu dersi ciddiye aldım ve her fırsatta kullandığım her araçta bir seviye daha aşağı iniyorum. O kadar yaygınlaştı ki bazen belgeleri bile okumuyorum.
Bu yaklaşım kariyerimi muazzam bir şekilde değiştirdi. Yaptığım her şey sihir gibi görünüyor. Birkaç ay içinde, kaynağa doğru bir adım attığım için kritik sunucu geçişleri ve projeleri yönetiyordum.
Bu dizi tam da bununla ilgili: API'ye razı olmamak, bunun ötesine geçmek, bu araçları yeniden yaratmayı öğrenmek. Yapay zeka çılgınlığının olduğu bu dünyada ortalama olmaktan çıkmak, bir geliştiriciyi ortalamanın ötesinde değerli kılan şeydir!
Bu seriyle ilgili planım, popüler JavaScript kütüphanelerini ve araçlarını inceleyerek bunların nasıl çalıştığını ve bunlardan ne gibi kalıplar öğrenebileceğimizi tek tek anlamak.
Ben çoğunlukla arka uç mühendisi olduğumdan (evet, tam yığın, ama zamanımın %90'ını arka uçla ilgilenerek geçiriyorum), başlamak için Express.js'den daha iyi bir araç yok.
Benim varsayımım, programlama deneyiminiz ve programlamanın temellerine dair iyi bir kavrayışınız olduğudur! İleri seviye başlangıç seviyesinde sınıflandırılabilirsiniz.
Temelleri öğretirken kaynak kodunu öğrenmeye/öğretmeye çalışmak gerçekten zor ve sıkıcı olacak. Seriye katılabilirsiniz, ancak zor olacağını bekleyin. Her şeyi kapsayamam, ancak elimden geldiğince deneyeceğim.
Bu makalenin Express öncesi olmasının bir nedeni var: Express'in bağımlı olduğu çok küçük bir kütüphane olan merge-descriptors'ı ele almaya karar verdim. Bunu yazdığım sırada bu kütüphane 27.181.495 kez indirilmişti ve yalnızca 26 satır kod içeriyordu.
Bu bize bir yapı kurma ve JavaScript modülleri oluşturmada kritik öneme sahip olan nesne temellerini tanıtma fırsatı verecek.
Devam etmeden önce, Express kaynak kodunun ve birleştirme tanımlayıcılarının sisteminizde olduğundan emin olun. Bu şekilde, onu bir IDE'de açabilir ve nerede aradığımıza dair satır numaralarıyla size rehberlik edebilirim.
Express güçlü bir kütüphanedir. Başka bir araca geçmeden önce birkaç makalede elimizden geldiğince çok şey ele alacağız.
IDE'nizde Express kaynak kodunuzu tercihen satır numaralarıyla açın, lib
klasörüne gidin ve giriş dosyası olan express.js
dosyasını açın.
17. satırda ilk kütüphanemiz var:
var mixin = require('merge-descriptors');
Kullanım 42. ve 43. satırlardadır:
mixin(app, EventEmitter.prototype, false); mixin(app, proto, false);
Burada neler olduğunu keşfetmeden önce, bir adım geri çekilip veri yapısının ötesinde JavaScript'teki nesneler hakkında konuşmamız gerekiyor. Kompozisyon, kalıtım, prototipler ve karışımları tartışacağız—bu makalenin başlığı.
Express kaynak kodunu kapatın ve bu önemli nesne temellerini öğrenirken takip edebileceğimiz yeni bir klasör oluşturun.
Bir nesne , Nesne Yönelimli Programlamanın (OOP) özünde yer alan veri ve davranışın bir kapsüllenmesidir. İlginç bilgi: JavaScript'teki hemen hemen her şey bir nesnedir.
const person = { // data name: "Jane", age: 0, // behavior grow(){ this.age += 1; } };
person
nesnesindeki açılış ve kapanış parantezleri arasındaki her şeye nesnenin kendi özellikleri denir. Bu önemlidir.
Kişinin kendi özellikleri, doğrudan doğruya nesne üzerinde bulunan özelliklerdir. name
, age
, grow
person
kendi özellikleridir.
Bu önemlidir çünkü her JavaScript nesnesinin bir prototype
özelliği vardır. Yukarıdaki nesneyi, person
nesnelerini dinamik olarak oluşturmamıza izin verecek bir fonksiyon planına kodlayalım.
function createNewPerson(name, age){ this.name = name; this.age = age; } createNewPerson.prototype.print = function(){ console.log(`${this.name} is ${this.age}`); }; const john = new createNewPerson("John", 32);
Prototip, JavaScript nesnelerinin diğer nesnelerden özellikleri ve yöntemleri nasıl devraldığıdır. Own Properties
ve Prototype
arasındaki fark, bir nesnedeki bir özelliğe erişirken ortaya çıkar:
john.name; // access
JavaScript, yüksek önceliğe sahip oldukları için önce Own Properties
bakacaktır. Özelliği bulamazsa, null bulana ve bir hata fırlatana kadar nesnenin kendi prototype
nesnesine yinelemeli olarak bakar.
Bir prototip nesnesi, kendi prototipi aracılığıyla başka bir nesneden miras alabilir. Buna prototip zinciri denir.
console.log(john.hasOwnProperty('name')); // true console.log(john.hasOwnProperty('print')); // false, it's in the prototype
Ancak, john
üzerinde print
çalışmaları var:
john.print(); // "John is 32"
JavaScript'in prototip tabanlı bir dil olarak tanımlanmasının nedeni budur. Prototiplerle miras gibi özellikler ve yöntemler eklemekten daha fazlasını yapabiliriz.
Mirasın "merhaba dünya"sı memeli nesnesidir. Bunu JavaScript ile yeniden yaratalım.
// our Mammal blueprint function Mammal(name) { this.name = name; } Mammal.prototype.breathe = function() { console.log(`${this.name} is breathing.`); };
JavaScript'te Object
nesnesinin içinde statik bir fonksiyon vardır:
Object.create();
{}
ve new functionBlueprint
benzer bir nesne oluşturur, ancak fark create
miras alacağı bir prototipi parametre olarak alabilmesidir.
// we use a cat blueprint function here (implemented below) Cat.prototype = Object.create(Mammal.prototype); // correction after we inherited all the properties Cat.prototype.constructor = Cat;
Şimdi Cat
Mammal
bulunan breathe
yöntemine sahip olacak, ancak bilinmesi gereken önemli şey Cat
prototipi olarak Mammal
işaret etmesidir.
Memeli Planı : Öncelikle Mammal
fonksiyonunu tanımlıyoruz ve prototipine bir breathe
metodu ekliyoruz.
Cat Kalıtımı : Cat
fonksiyonunu oluşturuyoruz ve Cat.prototype
Object.create(Mammal.prototype)
olarak ayarlıyoruz. Bu, Cat
prototipinin Mammal
kalıtım almasını sağlar, ancak constructor
işaretçisini Mammal
olarak değiştirir.
Yapıcıyı Düzeltme : Cat.prototype.constructor
Cat
geri işaret edecek şekilde düzeltiyoruz, böylece Cat
nesnesinin Mammal
yöntemleri miras alırken kimliğini koruduğundan emin oluyoruz. Son olarak, Cat
bir meow
yöntemi ekliyoruz.
Bu yaklaşım, Cat
nesnesinin hem Mammal
(örneğin breathe
) hem de kendi prototipinden (örneğin meow
) gelen yöntemlere erişmesine olanak tanır.
Bunu düzeltmemiz gerekiyor. Tam örneği oluşturalım:
function Cat(name, breed) { this.name = name; this.breed = breed; } Cat.prototype = Object.create(Mammal.prototype); // cat prototype pointing to mammal // correction after we inherited all the properties Cat.prototype.constructor = Cat; // we are re-pointing a pointer, the inherited properties are still there Cat.prototype.meow = function() { console.log(`${this.name} is meowing.`); };
Cat.prototype.constructor = Cat
anlamak için işaretçiler hakkında bilgi sahibi olmanız gerekir. Object.create
ile Mammal
miras aldığımızda, Cat
prototipimizin işaretçisini Mammal
olarak değiştirir, bu da yanlıştır. Ebeveyni Mammal
olmasına rağmen Cat
kendi bireyi olmasını istiyoruz.
İşte bu yüzden bunu düzeltmemiz gerekiyor.
Bu örnekte Cat
, prototip zincirini kullanarak Mammal
miras alır. Cat
nesnesi hem breathe
hem de meow
yöntemlerine erişebilir.
const myCat = new Cat("Misty", "Ragdoll"); myCat.breathe(); // Misty is breathing. myCat.meow(); // Misty is meowing.
Memeliden miras alan bir köpek de yaratabiliriz:
function Dog(name, breed) { this.name = name; this.breed = breed; } Dog.prototype = Object.create(Mammal.prototype); Dog.prototype.constructor = Dog; Dog.prototype.bark = function() { console.log(`${this.name} is barking.`); }; const myDog = new Dog('Buddy', 'Golden Retriever'); myDog.breathe(); // Buddy is breathing. myDog.bark(); // Buddy is barking.
Temel klasik kalıtım oluşturduk, ama bu neden önemli? Kaynak kodunu ele aldığımızı sanıyordum!
Evet, doğru, ancak prototipler, kalıtımın ötesinde, verimli ve esnek modüller oluşturmanın çekirdeğidir. Basit, iyi yazılmış modüller bile prototip nesneleriyle doludur. Biz sadece temelleri atıyoruz.
Kalıtımın alternatifi, iki veya daha fazla nesneyi alıp birleştirerek "üst" bir nesne oluşturan nesne kompozisyonudur.
Mixin'ler nesnelerin miras kullanmadan diğer nesnelerden metot ödünç almasına izin verir. İlgisiz nesneler arasında davranışı paylaşmak için kullanışlıdırlar.
İlk araştırmamızın yaptığı şey de bu: İlk önce ele almayı vaat ettiğimiz merge-descriptors
kütüphanesi.
Express'te nerede ve nasıl kullanıldığını zaten gördük. Şimdi bunu nesne kompozisyonu için biliyoruz.
17. satırda ilk kütüphanemiz şu şekilde:
var mixin = require('merge-descriptors');
Kullanım 42. ve 43. satırlardadır:
mixin(app, EventEmitter.prototype, false); mixin(app, proto, false);
Öğrendiklerimize göre mixin
EventEmitter.prototype
ve proto
birleştirerek app
adında bir nesne oluşturduğunu söyleyebiliriz.
Express'ten bahsederken app
geçeceğiz.
merge-descriptors
tüm kaynak kodu şudur:
'use strict'; function mergeDescriptors(destination, source, overwrite = true) { if (!destination) { throw new TypeError('The `destination` argument is required.'); } if (!source) { throw new TypeError('The `source` argument is required.'); } for (const name of Object.getOwnPropertyNames(source)) { if (!overwrite && Object.hasOwn(destination, name)) { // Skip descriptor continue; } // Copy descriptor const descriptor = Object.getOwnPropertyDescriptor(source, name); Object.defineProperty(destination, name, descriptor); } return destination; } module.exports = mergeDescriptors;
Başlangıçta her zaman fonksiyonun nasıl kullanıldığına ve aldığı parametrelere bakın:
// definition mergeDescriptors(destination, source, overwrite = true) // usage var mixin = require('merge-descriptors'); mixin(app, EventEmitter.prototype, false); mixin(app, proto, false);
App
hedefimizdir. mixin
nesne kompozisyonu anlamına geldiğini biliyoruz. Kabaca, bu paketin yaptığı şey kaynak nesneyi hedef nesneye birleştirmek ve üzerine yazma seçeneği sunmaktır.
Üzerine yazma, varsayıma göre, app
(hedefin) kaynağın sahip olduğu tam bir özelliğe sahip olması durumunda gerçekleşir; true
üzerine yazmada, aksi takdirde o özellik olduğu gibi bırakılır ve atlanır.
Nesnelerin aynı özelliğe iki kez sahip olamayacağını biliyoruz. Anahtar-değer çiftlerinde (nesnelerde), anahtarlar benzersiz olmalıdır.
Express ile üzerine yazma işlemi false
olur.
Aşağıda temel ev idaresi yer almaktadır, beklenen hataları her zaman ele alın:
if (!destination) { throw new TypeError('The `destination` argument is required.'); } if (!source) { throw new TypeError('The `source` argument is required.'); }
İşte burada ilginçleşiyor: 12. satır.
for (const name of Object.getOwnPropertyNames(source)) {
Yukarıda OwnProperty
ne anlama geldiğini biliyoruz, dolayısıyla getOwnPropertyNames
açıkça kendi özelliklerinin anahtarlarını almak anlamına geliyor.
const person = { // data name: "Jane", age: 0, // behavior grow() { this.age += 1; } }; Object.getOwnPropertyNames(person); // [ 'name', 'age', 'grow' ]
Anahtarları bir dizi olarak döndürüyor ve aşağıdaki örnekte bu anahtarlar üzerinde döngü oluşturuyoruz:
for (const name of Object.getOwnPropertyNames(source)) {
Aşağıdaki, hedef ve kaynağın şu anda üzerinde döngü kurduğumuz aynı anahtara sahip olup olmadığını kontrol ediyor:
if (!overwrite && Object.hasOwn(destination, name)) { // Skip descriptor continue; }
Eğer overwrite false ise, bu özelliği atlayın; üzerine yazmayın. continue
yaptığı şey budur—döngüyü bir sonraki yinelemeye iter ve alttaki kodu çalıştırmaz, bu da şu koddur:
// Copy descriptor const descriptor = Object.getOwnPropertyDescriptor(source, name); Object.defineProperty(destination, name, descriptor);
getOwnProperty
ne anlama geldiğini zaten biliyoruz. Yeni kelime descriptor
. Bu fonksiyonu kendi person
nesnemizde test edelim:
const person = { // data name: "Jane", age: 0, // behavior grow() { this.age += 1; } }; Object.getOwnPropertyDescriptor(person, "grow"); // { // value: [Function: grow], // writable: true, // enumerable: true, // configurable: true // }
Bu, grow
fonksiyonumuzu değer olarak döndürür ve bir sonraki satır kendini açıklar:
Object.defineProperty(destination, name, descriptor);
Tanımlayıcımızı kaynaktan alıp hedefe yazıyor. Kaynağın kendi özelliklerini hedef nesnemize kendi özellikleri olarak kopyalıyor.
person
nesnemizde bir örnek yapalım:
const val = { value: function isAlien() { return false; }, enumerable: true, writable: true, configurable: true, }; Object.defineProperty(person, "isAlien", val);
Artık person
isAlien
özelliği tanımlanmış olmalı.
Özetle, çok indirilen bu modül, kaynak nesnenin kendi özelliklerini, üzerine yazma seçeneğiyle birlikte hedefe kopyalar.
Bu kaynak kod katmanındaki ilk modülümüzü başarıyla tamamladık, daha heyecan verici şeyler de yolda.
Bu bir giriş niteliğindeydi. Modülü anlamak için gereken temelleri ele alarak başladık ve bir yan ürün olarak, çoğu modüldeki kalıpları, yani nesne kompozisyonunu ve kalıtımı anladık. Son olarak, merge-descriptors
modülünde gezindik.
Bu kalıp çoğu makalede yaygın olacaktır. Ele alınması gereken temellerin olduğunu düşünürsem, bunları ilk bölümde ele alacağız ve ardından kaynak kodunu ele alacağız.
Neyse ki, merge-descriptors
Express'te kullanılıyor ve bu bizim başlangıç kaynak kodu çalışmamızdaki odak noktamız. Bu yüzden Express'in yeterince iyi bir çalışmasını elde ettiğimizi hissedene kadar daha fazla Express.js kaynak kodu makalesi bekleyin, ardından Node.js gibi başka bir modüle veya araca geçin.
Bu arada bir meydan okuma olarak yapabileceğiniz şey, birleştirme tanımlayıcılarındaki test dosyasına gitmek, tüm dosyayı okumaktır. Kaynağı kendi başınıza okumak önemlidir, ne yaptığını ve ne test ettiğini anlamaya çalışın, sonra bozun, evet ve tekrar düzeltin veya daha fazla test ekleyin!
Programlama becerilerinizi geliştirmek için daha özel, pratik ve uzun içeriklerle ilgileniyorsanız, Ko-fi'de daha fazlasını bulabilirsiniz.