node.js - Hata sınıflarını alt sınıflarken Object.setPrototypeOf’un performans sorunları hakkında endişelenmeli miyim?
Bu MDN uyarısı nedeniyle Object.setPrototypeOf(this, new.target.prototype) fonksiyonu hakkında sorularım var:
Uyarı: Bir nesnenin
[[Prototype]]'ünü değiştirmek, modern JavaScript motorlarının özellik erişimlerini optimize etme şekline bağlı olarak, şu anda her tarayıcıda ve JavaScript motorunda çok yavaş bir işlemdir. Ayrıca, mirası değiştirmenin etkileri ince ve yaygın olup, sadeceObject.setPrototypeOf(...)ifadesinde geçirilen süre ile sınırlı değildir, aynı zamanda[[Prototype]]'ü değiştirilmiş herhangi bir nesneye erişimi olan herhangi bir koda da yayılabilir. Daha fazla bilgi için JavaScript motoru temellerini: prototipleri optimize etme başlığı altında okuyabilirsiniz.Bu özellik dilin bir parçası olduğu için, bu özelliği performanslı bir şekilde uygulamak motor geliştiricilerinin yükümlülüğüdür (ideal olarak). Motor geliştiricileri bu sorunu ele alana kadar, performans konusunda endişeleriniz varsa bir nesnenin
[[Prototype]]'ünü ayarlamaktan kaçınmalısınız. Bunun yerine,Object.create()kullanarak istenen[[Prototype]]'e sahip yeni bir nesne oluşturun.
TypeScript (Node.js) içinde Object.setPrototypeOf(this, new.target.prototype) kullanmadan prototip stringini geri yüklemenin en iyi yolu ne olurdu (ama sınıfları kullanarak)? Bunun hepsi, Express'te uygulanan bir hata yönetimi ara yazılımı yüzünden; burada hatanın kaynağını belirlemek ve buna göre uygun bir yanıt döndürebilmek için instanceof kullanmam gerekiyor, ancak Error sınıfından türeyen bir sınıf yaptığımda error instanceof Error true dönüyor, ama error instanceof CustomError false dönüyor. Küçük bir araştırma yapınca bunu resmi TypeScript belgelendirmesinde buldum:
new.targetdesteği
new.targetmeta-özelliği ES2015'te tanıtılmış yeni bir sözdizimidir. Bir yapıcının örneğinewile oluşturulduğunda,new.targetdeğeri, örneği tahsis etmek için ilk kullanılan yapıcı işlevine bir referans olacak şekilde ayarlanır. Bir işlevnewile oluşturulmak yerine çağrılırsa,new.targetundefinedolarak ayarlanır.
new.target, bir sınıf yapıcısındaObject.setPrototypeOfveya__proto__ayarlanması gerektiğinde kullanışlıdır. Bu tür bir kullanım örneği, NodeJS v4 ve üzeri sürümlerdeError'dan miras almaktır.Örnek
class CustomError extends Error { constructor(message?: string) { super(message); // 'Error' breaks prototype chain here Object.setPrototypeOf(this, new.target.prototype); // restore prototype chain } }Bu, oluşturulan JS ile sonuçlanır
var CustomError = (function(_super) { __extends(CustomError, _super); function CustomError() { var _newTarget = this.constructor; var _this = _super.apply(this, arguments); // 'Error' breaks prototype chain here _this.__proto__ = _newTarget.prototype; // restore prototype chain return _this; } return CustomError; })(Error);
Ve düşündüm ki tamam her şey mükemmel çünkü kod dönüştürüldüğü anda, derleyicinin yavaş olan bu fonksiyonla ilgileneceğini ve onu daha verimli başka bir prosedürle değiştireceğini varsayıyorum, ama şaşırtıcı bir şekilde durum öyle değil ve performans konusunda biraz endişeliyim (büyük bir proje üzerinde çalışıyorum).
Node 14.5.0'da TypeScript sürüm 3.9.6 kullanıyorum, yaptığım testlerin ekran görüntüleri bunlar:
- Node ile TypeScript
- TypeScript Oyun Alanı
- TypeScript derleyici sonuçları
Cevap #1
Bunun için endişelenmezdim.
Her şeyden önce, genel bir kural olarak, programınızın çalışma süresinin çoğunu hata nesneleri oluşturmakla geçireceğini beklememelisiniz: bunun yerine, başarılı bir şekilde istekleri yerine getirecektir. Nadiren çalışacak bir kodu mikro-optimize etmemeniz gerekir: kapsamlı testler yapmadığınız sürece, bu tür mikro-optimizasyonlar sadece çok, çok daha sonra ortaya çıkacak hataların eklenme riskini taşır.
Uyarı, yalnızca nesnenin prototipini gereksiz yere değiştirmekten sizi caydırmayı amaçlamaktadır. Yani bunu, kaçınılabilir bir şekilde (veri yapılarını yeniden tasarlayarak) ve nesne zaten yoğun bir şekilde kullanılacak diğer yerlere verildikten sonra yapmak anlamına gelir. Bunun nedeni, prototipleri değiştirmenin, o uyarı kutusunda bağlantısı verilen makalede açıklandığı gibi, özellik arama önbelleklerinin dayandığı değişmezlikleri bozmasıdır. Makalenin yazarı bile, bunu makalenin özetinde şu şekilde yazmaktadır (vurgular bana aittir):
Prototiplerinize dokunmayın! Ya da eğer gerçekten prototiplere dokunmanız gerekiyorsa, diğer kod çalışmadan önce dokunun, böylece en azından kodunuz çalışırken motor içindeki tüm optimizasyonları geçersiz kılmazsınız.
Errorsınıfından türetme durumunda, doğruluk açısından nesnenin prototipini bir şekilde değiştirmeniz gerçekten gerekir (isterObject.setPrototypeOf, ister eski yöntem__proto__ile) ve bu kaçınılmazdır; aksi takdirdeinstanceofdüzgün çalışmaz (soruda da belirtildiği gibi). Ve bunu kurucuda, yani nesnenin ömrünün başlarında yaptığınız için, performans açısından da nispeten zararsızdır; çünkü söz konusu özellik arama önbelleklerinin o noktada muhtemelen henüz oluşturulmamış olması beklenir.Bunu
Object.hasOwn'in aşağıdaki yeniden uygulanışı ile karşılaştırın:function hasOwn(obj, key) { var proto = Object.getPrototypeOf(obj); Object.setPrototypeOf(obj, null); try { return key in obj; } finally { Object.setPrototypeOf(obj, proto); } }Gerçek
Object.hasOwn(veyaObject.prototype.hasOwnProperty) yerine onu kullanmak, programınızın performansını temelde yok edecek korkunç bir fikir olurdu.Son olarak ama önemlisi:
Erroralt sınıflarının doğru prototipe sahip olmamasıyla ilgili sorunlar, yalnızca alt sınıflama ECMAScript 5 özellikleri kullanılarak taklit edildiğinde, yaniError.applyçağrıldığında ortaya çıkar. Yerel ECMAScript 6 alt sınıflaması kullanıldığında, bu sorun ortadan kalkar (daha sonra belirtildiği gibi, eğer TypeScriptFunction.prototype.applyyerineReflect.constructkullanmış olsaydı, sorun yine ortadan kalkardı, fakat şu ana kadar bunu yapmamıştır). Node 14 hedeflediğiniz için, TypeScript'i ECMAScript 6 kodu üretmesi için yapılandırabilir ve bu konuyu unutabilirsiniz. HattaObject.setPrototypeOfçağrısını olduğu gibi bırakabilirsiniz; böylece o sınıfa ECMAScript 5'e derlenecek bir kodda ihtiyaç duyulursa hazır olur; prototip baştan doğru şekilde ayarlanmışsa, çağrı boş bir işlem haline gelir ve performans üzerinde herhangi bir etkisi önemsiz olur.
