كيف يعمل JS: الطبقات والميراث ، transpilation في Babel و TypeScript

تعد الفصول الدراسية واحدة من أكثر الطرق شيوعًا لتنظيم مشروعات البرامج في هذه الأيام. يستخدم نهج البرمجة هذا أيضًا في JavaScript. ننشر اليوم ترجمة للجزء 15 من سلسلة النظام البيئي JS. ستناقش هذه المقالة طرقًا مختلفة لتطبيق الفئات في جافا سكريبت وآليات الميراث والنتح. سنبدأ بإخبارك بكيفية عمل النماذج الأولية وتحليل الطرق المختلفة لمحاكاة الميراث القائم على الفصل في المكتبات الشعبية. بعد ذلك ، سنتحدث عن كيف يمكن ، بفضل النقل ، كتابة برامج JS التي تستخدم ميزات غير متوفرة في اللغة أو ، على الرغم من وجودها في شكل معايير جديدة أو مقترحات في مراحل مختلفة من الموافقة ، لم يتم تنفيذها بعد في JS- المحركات. على وجه الخصوص ، سنتحدث عن فئات بابل و TypeScript وفئات ECMAScript 2015. بعد ذلك ، سنلقي نظرة على بعض الأمثلة التي توضح ميزات التنفيذ الداخلي للفئات في محرك V8 JS.
الصورة

[نصح القراءة] الأجزاء الـ 19 الأخرى من الدورة
الجزء 1: نظرة عامة على المحرك وآليات وقت التشغيل ومكدس المكالمة
الجزء 2: حول V8 الداخلية وتحسين التعليمات البرمجية
الجزء الثالث: إدارة الذاكرة وأربعة أنواع من تسرب الذاكرة والتعامل معها
الجزء 4: حلقة الأحداث ، عدم التزامن ، وخمس طرق لتحسين التعليمات البرمجية الخاصة بك مع عدم التزامن / انتظار
الجزء 5: WebSocket و HTTP / 2 + SSE. ماذا تختار؟
الجزء 6: ميزات ومجال WebAssembly
الجزء 7: عمال الويب وخمسة سيناريوهات الاستخدام
الجزء الثامن: عمال الخدمة
الجزء 9: دفع الإخطارات على شبكة الإنترنت
الجزء 10: تتبع التغييرات في DOM باستخدام MutationObserver
الجزء 11: محركات تقديم صفحات الويب ونصائح لتحسين أدائها
الجزء 12: نظام الشبكة الفرعي للمتصفحات ، مما يحسن من أدائها وأمانها
الجزء 12: نظام الشبكة الفرعي للمتصفحات ، مما يحسن من أدائها وأمانها
الجزء 13: الرسوم المتحركة مع CSS وجافا سكريبت
الجزء 14: كيف يعمل JS: أشجار الجملة المجردة ، التحليل والتحسين
الجزء 15: كيف يعمل JS: الطبقات والميراث ، transpilation في بابل و TypeScript
الجزء 16: كيف يعمل JS: التخزين
الجزء 17: كيف يعمل JS: تقنية Shadow DOM ومكونات الويب
الجزء 18: كيف يعمل JS: آليات الاتصال WebRTC و P2P
الجزء 19: كيف يعمل JS: العناصر المخصصة

مراجعة


في جافا سكريبت ، نواجه باستمرار كائنات ، حتى عندما يبدو أننا نعمل مع أنواع بيانات بدائية. على سبيل المثال ، أنشئ سلسلة حرفية:

const name = "SessionStack"; 

بعد ذلك ، يمكننا أن ننتقل على الفور إلى name لاستدعاء طرق مختلفة لكائن من نوع String ، والذي سيتم تحويل السلسلة الحرفية التي أنشأناها تلقائيًا.

 console.log(name.repeat(2)); // SessionStackSessionStack console.log(name.toLowerCase()); // sessionstack 

على عكس اللغات الأخرى ، في JavaScript ، بعد إنشاء متغير يحتوي ، على سبيل المثال ، على سلسلة أو رقم ، يمكننا ، دون إجراء تحويل صريح ، العمل مع هذا المتغير كما لو تم إنشاؤه في الأصل باستخدام الكلمة الرئيسية new والمنشئ المقابل. ونتيجة لذلك ، بسبب الإنشاء التلقائي لكائنات تغلف القيم البدائية ، يمكنك العمل مع هذه القيم كما لو كانت كائنات ، على وجه الخصوص ، تشير إلى أساليبها وخصائصها.

حقيقة أخرى جديرة بالملاحظة فيما يتعلق بنظام نوع JavaScript هي ، على سبيل المثال ، المصفوفات هي أيضًا كائنات. إذا نظرت إلى ناتج الأمر typeof للصفيف ، يمكنك أن ترى أنه يشير إلى أن الكيان قيد التحقيق لديه نوع بيانات object . ونتيجة لذلك ، اتضح أن مؤشرات عناصر الصفيف ليست سوى خصائص لكائن معين. لذلك ، عندما نصل إلى عنصر صفيف حسب الفهرس ، فإن الأمر يتعلق بالعمل مع خاصية كائن من نوع Array والحصول على قيمة هذه الخاصية. إذا تحدثنا عن كيفية تخزين البيانات داخل الكائنات والمصفوفات العادية ، فإن التركيبين التاليين يؤديان إلى إنشاء هياكل بيانات متطابقة تقريبًا:

 let names = ["SessionStack"]; let names = { "0": "SessionStack", "length": 1 } 

ونتيجة لذلك ، يتم الوصول إلى عناصر المصفوفة وخصائص الكائن بنفس السرعة. يقول مؤلف هذه المقالة أنه اكتشف في سياق حل مشكلة معقدة واحدة. وبالتحديد ، بمجرد أن يحتاج إلى إجراء تحسين جاد لجزء مهم جدًا من التعليمات البرمجية في المشروع. بعد تجربة العديد من الأساليب البسيطة ، قرر استبدال جميع الكائنات المستخدمة في هذا الرمز بمصفوفات. من الناحية النظرية ، يعد الوصول إلى عناصر الصفيف أسرع من العمل باستخدام مفاتيح جدول التجزئة. لدهشته ، لم يؤثر هذا الاستبدال على الأداء بأي شكل من الأشكال ، حيث أن العمل مع المصفوفات والعمل مع الكائنات في JavaScript يرجع إلى التفاعل مع مفاتيح جدول التجزئة ، والذي يتطلب في كلتا الحالتين نفس الوقت.

محاكاة الفئات باستخدام النماذج الأولية


عندما نفكر في الأشياء ، فإن أول ما يتبادر إلى الذهن هو الطبقات. ربما قام كل من يشارك في البرمجة اليوم بإنشاء تطبيقات يعتمد هيكلها على الطبقات والعلاقات بينها. على الرغم من أن الكائنات الموجودة في JavaScript يمكن العثور عليها حرفياً في كل مكان ، إلا أن اللغة لا تستخدم نظام وراثة تقليدي قائم على الفصل. تستخدم جافا سكريبت نماذج أولية لحل مشكلات مماثلة.


الكائن ونموذجه الأولي

في JavaScript ، يرتبط كل كائن بكائن آخر - مع النموذج الأولي الخاص به. عند محاولة الوصول إلى خاصية أو أسلوب كائن ، يتم البحث عن ما تحتاجه أولاً في الكائن نفسه. إذا كان البحث غير ناجح ، فسيستمر في النموذج الأولي للكائن.

خذ بعين الاعتبار مثال بسيط يصف وظيفة مُنشئ للفئة الأساسية Component :

 function Component(content) { this.content = content; } Component.prototype.render = function() {   console.log(this.content); } 

هنا نعين وظيفة render() لطريقة النموذج الأولي ، حيث نحتاج إلى كل مثيل من فئة Component لاستخدام هذه الطريقة. عندما يتم استدعاء طريقة render ، في أي مثيل من Component ، يبدأ البحث في الكائن نفسه الذي يتم استدعاؤه. ثم يستمر البحث في النموذج الأولي ، حيث يجد النظام هذه الطريقة.


النموذج الأولي ومثالين من فئة المكونات

الآن دعنا نحاول توسيع فئة Component . لنقم بإنشاء مُنشئ لفئة جديدة - InputField :

 function InputField(value) {   this.content = `<input type="text" value="${value}" />`; } 

إذا كنا بحاجة إلى فئة InputField لتوسيع وظائف فئة Component وأن نكون قادرين على استدعاء طريقة render ، فنحن بحاجة إلى تغيير نموذجها الأولي. عندما يتم استدعاء طريقة ما على مثيل لفئة تابعة ، فلا معنى للبحث عنها في نموذج أولي فارغ. نحتاج أن نجد في البحث عن هذه الطريقة في فئة Component . لذلك ، نحتاج إلى القيام بما يلي:

 InputField.prototype = Object.create(new Component()); 

الآن ، عند العمل مع مثيل لفئة InputField واستدعاء طريقة فئة Component ، سيتم العثور على هذه الطريقة في النموذج الأولي لفئة Component . لتطبيق نظام الوراثة ، تحتاج إلى توصيل النموذج الأولي InputField بمثيل لفئة Component . تستخدم العديد من المكتبات Object.setPrototypeOf () لحل هذه المشكلة.


توسيع فئة المكونات مع فئة InputField

ومع ذلك ، فإن الإجراءات المذكورة أعلاه ليست كافية لتنفيذ آلية مماثلة للميراث التقليدي. في كل مرة نقوم بتوسيع الفصل ، نحتاج إلى تنفيذ الإجراءات التالية:

  • اجعل النموذج الأولي للفئة المتحدرة مثيلًا للفئة الأم.
  • استدعاء ، في مُنشئ الفئة المنحدرة ، مُنشئ الفئة الأصل للتأكد من تهيئة الفئة الأصل بشكل صحيح.
  • قم بتوفير آلية لاستدعاء طرق الفصل الأصل في المواقف التي يتجاوز فيها الفصل التابع الأسلوب الأصلي ، ولكن هناك حاجة لاستدعاء التطبيق الأصلي لهذه الطريقة من الفصل الأصل.

كما ترى ، إذا أراد مطور JS استخدام إمكانات الميراث القائم على الفئة ، فسيتعين عليه باستمرار تنفيذ الخطوات المذكورة أعلاه. في حال كنت بحاجة إلى إنشاء العديد من الفئات ، يمكن عمل كل ذلك في شكل وظائف مناسبة لإعادة الاستخدام.

في الواقع ، تم حل مهمة تنظيم الميراث على أساس الطبقات في البداية في ممارسة تطوير JS بهذه الطريقة. على وجه الخصوص ، باستخدام مكتبات مختلفة. أصبحت هذه الحلول شائعة جدًا ، مما يشير بوضوح إلى وجود شيء ما مفقود بشكل واضح في JavaScript. هذا هو السبب في أن ECMAScript 2015 قد قدم تراكيب نحوية جديدة تهدف إلى دعم العمل مع الطبقات وتنفيذ آليات الميراث المقابلة.

نقل الصف


بعد اقتراح الميزات الجديدة لـ ECMAScript 2015 (ES6) ، أراد مجتمع JS الاستفادة منها في أقرب وقت ممكن ، دون انتظار اكتمال العملية الطويلة لإضافة دعم لهذه الميزات في محركات ومتصفحات JS. في حل مثل هذه المشاكل ، فإن transpilation جيد. في هذه الحالة ، يتم تقليل التجميع لتحويل رمز JS المكتوب وفقًا لقواعد ES6 إلى طريقة عرض يمكن فهمها للمتصفحات التي لا تدعم حتى الآن قدرات ES6. ونتيجة لذلك ، على سبيل المثال ، يصبح من الممكن الإعلان عن الفئات وتطبيق آليات الميراث القائمة على الفئة وفقًا لقواعد ES6 وتحويل هذه البنيات إلى تعليمات برمجية تعمل في أي متصفح. بشكل تخطيطي ، يمكن تمثيل هذه العملية ، باستخدام مثال معالجة وظيفة السهم عن طريق ناقل (ميزة لغة جديدة جديدة تحتاج إلى وقت للدعم) كما هو موضح في الشكل أدناه.


transpilation

Babel.js هي واحدة من أكثر الناشرين جافا سكريبت شعبية. دعنا نرى كيف يعمل من خلال إجراء تجميع لرمز إعلان فئة Component ، الذي تحدثنا عنه أعلاه. إذن هذا هو رمز ES6:

 class Component { constructor(content) {   this.content = content; } render() { console.log(this.content) } } const component = new Component('SessionStack'); component.render(); 

وإليك ما يتحول إليه هذا الرمز بعد عملية النقل:

 var Component = function () { function Component(content) {   _classCallCheck(this, Component);   this.content = content; } _createClass(Component, [{   key: 'render',   value: function render() {     console.log(this.content);   } }]); return Component; }(); 

كما ترون ، يتم الحصول على كود ECMAScript 5 عند إخراج ناقل الحركة ، والذي يمكن تشغيله في أي بيئة. بالإضافة إلى ذلك ، تتم إضافة مكالمات لبعض الوظائف التي تعد جزءًا من مكتبة Babel القياسية هنا.

نحن نتحدث عن _classCallCheck() و _createClass() المضمنة في الشفرة المنقولة. تم تصميم الوظيفة الأولى ، _classCallCheck() ، لمنع استدعاء وظيفة المُنشئ مثل دالة عادية. للقيام بذلك ، فإنه يتحقق ما إذا كان السياق الذي يتم استدعاء الدالة هو سياق مثيل لفئة Component . يتحقق الرمز لمعرفة ما إذا كانت هذه الكلمة الأساسية تشير إلى مثيل مماثل. _createClass() الوظيفة الثانية _createClass() خصائص الكائن التي يتم تمريرها إليها _createClass() من الكائنات التي تحتوي على مفاتيح وقيمها.

من أجل فهم كيفية عمل الوراثة ، نقوم بتحليل فئة InputField ، التي هي سلالة لفئة Component . إليك كيفية تجميع العلاقات الطبقية في ES6:

 class InputField extends Component {   constructor(value) {       const content = `<input type="text" value="${value}" />`;       super(content);   } } 

إليك نتيجة ترجمة هذا الرمز باستخدام Babel:

 var InputField = function (_Component) { _inherits(InputField, _Component); function InputField(value) {   _classCallCheck(this, InputField);   var content = '<input type="text" value="' + value + '" />';   return _possibleConstructorReturn(this, (InputField.__proto__ || Object.getPrototypeOf(InputField)).call(this, content)); } return InputField; }(Component); 

في هذا المثال ، يتم تغليف منطق آليات الوراثة في استدعاء _inherits() . يقوم بتنفيذ نفس الإجراءات التي وصفناها أعلاه ، والتي ارتبطت ، على وجه الخصوص ، بالكتابة إلى النموذج الأولي للفئة المتحدرة مثيلًا للفئة الأم.

من أجل تبديل الشفرة ، تقوم بابل بإجراء العديد من تحولاتها. أولاً ، يتم تحليل رمز ES6 وتحويله إلى تمثيل وسيط يسمى شجرة البنية المجردة . ثم يتم تحويل شجرة البناء المجردة الناتجة إلى شجرة أخرى ، يتم تحويل كل عقدة منها إلى مكافئ ES5 الخاص بها. ونتيجة لذلك ، يتم تحويل هذه الشجرة إلى رمز JS.

شجرة تركيبية مجردة في بابل


تحتوي شجرة البناء المجردة على عقد ، لكل منها عقدة رئيسية واحدة فقط. بابل لديها نوع أساسي للعقد. يحتوي على معلومات حول ما هي العقدة وأين يمكن العثور عليها في التعليمات البرمجية. هناك أنواع مختلفة من العقد ، على سبيل المثال ، العقد لتمثيل الحرف ، مثل السلاسل والأرقام والقيم null ، وما إلى ذلك. بالإضافة إلى ذلك ، هناك عُقد لتمثيل التعبيرات المستخدمة للتحكم في تدفق تنفيذ البرنامج (في if الإنشاء) ، والعقد للحلقات ( for while ،). هناك أيضًا نوع خاص من العقدة لتمثيل الفئات. هو سليل من الطبقة الأساسية Node . يقوم بتوسيع هذا الفصل بإضافة حقول لتخزين المراجع إلى الفئة الأساسية ونص الفئة كعقدة منفصلة.
تحويل جزء التعليمات البرمجية التالية إلى شجرة بناء جملة مجردة:

 class Component { constructor(content) {   this.content = content; } render() {   console.log(this.content) } } 

هنا كيف سيبدو تمثيله التخطيطي.


شجرة تركيبية مجردة

بعد إنشاء شجرة ، يتم تحويل كل عقدها إلى عقدة ES5 المقابلة لها ، وبعد ذلك يتم تحويل هذه الشجرة الجديدة إلى رمز يتوافق مع معيار ECMAScript 5. أثناء عملية التحويل ، ابحث أولاً عن العقدة التي تقع أبعد من العقدة الجذرية ، وبعد ذلك يتم تحويل هذه العقدة إلى رمز باستخدام القصاصات التي تم إنشاؤها لكل عقدة. بعد ذلك ، يتم تكرار العملية. هذه التقنية تسمى البحث العميق .

في المثال أعلاه ، سيتم إنشاء الكود ClassBody أولاً ، وبعد ذلك سيتم إنشاء ClassBody للعقدة ClassBody ، وأخيرًا ، الكود للعقدة ClassDeclaration .

transpiling TypeScript


نظام شائع آخر يستخدم transpilation هو TypeScript. هذه لغة برمجة يتم تحويل رمزها إلى كود ECMAScript 5 ومفهومة لأي محرك JS. يوفر بنية جديدة لكتابة تطبيقات JS. إليك كيفية تنفيذ فئة Component على TypeScript:

 class Component {   content: string;   constructor(content: string) {       this.content = content;   }   render() {       console.log(this.content)   } } 

هنا شجرة بناء الجملة المجردة لهذا الرمز.


شجرة تركيبية مجردة

يدعم TypeScript الوراثة.

 class InputField extends Component {   constructor(value: string) {       const content = `<input type="text" value="${value}" />`;       super(content);   } } 

إليك نتيجة نقل هذا الرمز:

 var InputField = /** @class */ (function (_super) {   __extends(InputField, _super);   function InputField(value) {       var _this = this;       var content = "<input type=\"text\" value=\"" + value + "\" />";       _this = _super.call(this, content) || this;       return _this;   }   return InputField; }(Component)); 

كما ترون ، هذا مرة أخرى رمز ES5 ، حيث ، بالإضافة إلى الإنشاءات القياسية ، هناك مكالمات لبعض الوظائف من مكتبة TypeScript. __extends() وظيفة __extends() مع تلك التي تحدثنا عنها في بداية هذه المادة.

بفضل التبني الواسع النطاق لـ Babel و TypeScript ، أصبحت آليات الإعلان عن الطبقات وتنظيم الميراث القائم على الطبقة أدوات قياسية لهيكلة تطبيقات JS. وقد ساهم ذلك في إضافة دعم لهذه الآليات في المتصفحات.

دعم فئة المستعرض


ظهر دعم الفصل في متصفح Chrome في عام 2014. يسمح هذا للمتصفح بالعمل مع إعلانات الصف دون استخدام transpilation أو أي مكتبات مساعدة.


العمل مع الفصول الدراسية في وحدة تحكم Chrome JS

في الواقع ، دعم المتصفح لهذه الآليات ليس أكثر من السكر النحوي. يتم تحويل هذه البنيات إلى نفس الهياكل الأساسية التي تدعمها اللغة بالفعل. ونتيجة لذلك ، حتى إذا كنت تستخدم بناء الجملة الجديد ، على مستوى أقل ، سيبدو كل شيء وكأنه إنشاء مُنشئين ومعالجة النماذج الأولية للكائنات:


دعم الطبقة هو السكر النحوي

دعم الفئة في V8


لنتحدث عن كيفية عمل دعم فئة ES6 في محرك V8 JS. في المادة السابقة المخصصة لأشجار النحو المجردة ، تحدثنا عن حقيقة أنه عند إعداد JS-code للتنفيذ ، يقوم النظام بتحليلها ويشكل شجرة بناء مجردة على أساسها. عند تحليل تركيبات تعريفات الفئة ، تقع العقد من النوع ClassLiteral في شجرة البنية المجردة.

تخزن هذه العقد بضعة أشياء مثيرة للاهتمام. أولاً ، هو مُنشئ كدالة منفصلة ، وثانياً ، قائمة خصائص الفئة. يمكن أن تكون طرق ، getters ، المستوطنين ، المجالات العامة أو الخاصة. بالإضافة إلى ذلك ، تخزن هذه العقدة إشارة إلى الفئة الرئيسية ، والتي تمتد إلى الفئة التي تم تشكيل العقدة من أجلها ، والتي ، مرة أخرى ، تخزن المنشئ وقائمة الخصائص ورابط لفصلها الأصلي.

بعد ClassLiteral عقدة ClassLiteral الجديدة إلى ClassLiteral برمجية ، يتم تحويلها إلى ClassLiteral تتكون من وظائف ونماذج أولية.

الملخص


يقول مؤلف هذه المادة أن SessionStack تسعى جاهدة لتحسين كود مكتبتها على أكمل وجه ممكن ، حيث يتعين عليها حل المهام الصعبة المتمثلة في جمع المعلومات حول كل ما يحدث على صفحات الويب. في سياق حل هذه المشاكل ، يجب ألا تبطئ المكتبة عمل الصفحة التي تم تحليلها. يتطلب تحسين هذا المستوى مراعاة أصغر التفاصيل لنظام جافا سكريبت البيئي التي تؤثر على الأداء ، على وجه الخصوص ، مع مراعاة ميزات كيفية ترتيب الفئات وآليات الميراث في ES6.

أعزائي القراء! هل تستخدم تراكيب بناء الجملة ES6 للعمل مع الفئات في JavaScript؟

Source: https://habr.com/ru/post/ar415377/


All Articles