مقدمة
أقوم حاليًا بتطوير محرر مخطط جافا سكريبت ، وأثناء عملية هذا العمل ، واجهت مشكلة ستركز عليها هذه المقالة ، وهي التسلسل وإلغاء تسلسل كائنات البيانات المعقدة.
دون الخوض في تفاصيل المشروع ، ألاحظ أنه وفقًا لفكرتي ، فإن المخطط عبارة عن مجموعة من العناصر (الذروات) الموروثة من الفئة الأساسية. تبعا لذلك ، تطبق كل فئة تابعة منطقها الخاص. بالإضافة إلى ذلك ، تحتوي القمم على روابط مع بعضها البعض (أسهم) ، والتي يجب أيضًا الحفاظ عليها. من الناحية النظرية ، يمكن أن تشير القمم إلى نفسها مباشرة أو من خلال القمم الأخرى. لا يمكن لـ JSON.stringify القياسي إجراء تسلسل لمثل هذا الصفيف ، لذلك قررت أن أصنع مُسلسل خاص بي يحل المشكلتين الموصوفتين:
- القدرة على حفظ معلومات الصف أثناء التسلسل واستعادتها أثناء إزالة التسلسل.
- القدرة على حفظ واستعادة الروابط للكائنات ، بما في ذلك دوري.
اقرأ المزيد عن بيان المشكلة وحلها تحت الخفض.
مشروع مُسلسل جيثب
رابط لمشروع جيثب: رابط .
توجد أمثلة معقدة أيضًا في مجلد test-src .
جهاز التسلسل العودي: رابط .
مُسلسل مُسطح: رابط .
بيان المشكلة
كما أشرت بالفعل ، فإن المهمة الأولية هي تسلسل الدوائر التعسفية للمحرر. حتى لا تضيع الوقت في وصف المحرر ، قمنا بتعيين المهمة بشكل أسهل. لنفترض أننا نريد تقديم وصف رسمي لمخطط خوارزمية بسيط باستخدام فئات ES6 Javascript ، ومن ثم إجراء تسلسل لهذا المخطط وإلغاء تسلسله.
على الإنترنت ، وجدت صورة مناسبة لأبسط خوارزمية لتحديد الحد الأقصى لقيمتين:

هنا يجب أن أقول إنني لست مطورًا لجافا سكريبت ، ولغتي "الأصلية" هي C # ، لذا فإن طريقة حل المشكلة تمليها تجربة التطوير الكائني في C #. بالنظر إلى هذا الرسم البياني ، أرى رؤوس الأنواع التالية (لا تلعب الأسماء الشرطية والأدوار الخاصة):
- Start vertex (ابدأ)
- الذروة النهائية (إنهاء)
- (الأعلى) Team (Command)
- التنازل عن قمة الرأس (دع)
- التحقق أعلى (إذا)
هذه القمم لها بعض الاختلافات عن بعضها البعض في مجموعة بياناتها أو دلالاتها ، لكنها كلها موروثة من القمة الأساسية (العقدة). في نفس المكان ، في فئة العقدة ، يتم وصف حقل الروابط ، والذي يحتوي على روابط إلى رؤوس أخرى ، وتسمح طريقة addLink بإضافة هذه الروابط. يمكن العثور على الرمز الكامل لجميع الفئات هنا .
دعونا نكتب الرمز الذي يجمع الدائرة من الصورة ، ونحاول إجراء تسلسل للنتيجة.
إذا قمنا بتسلسل هذا المخطط باستخدام JSON.stringify ، فإننا نحصل على شيء فظيع. سأعطي الأسطر القليلة الأولى من النتيجة ، التي أضفت فيها تعليقاتي:
نتيجة JSON.stringify [ { "id": "d9c8ab69-e4fa-4433-80bb-1cc7173024d6", "name": "Start", "links": { "2e3d482b-187f-4c96-95cd-b3cde9e55a43": { "id": "2e3d482b-187f-4c96-95cd-b3cde9e55a43", "target": { "id": "f87a3913-84b0-4b70-8927-6111c6628a1f", "name": "Command", "links": { "4f623116-1b70-42bf-8a47-da1e9be5e4b2": { "id": "4f623116-1b70-42bf-8a47-da1e9be5e4b2", "target": { "id": "94a47403-13ab-4c83-98fe-3b201744c8f2", "name": "If", "links": { ...
لأن احتوى الرأس الأول على رابط للثاني ، وهذا إلى الروابط اللاحقة ، ثم نتيجة تسلسله تم تسلسل الدائرة بأكملها. ثم تسلسل الذروة الثانية وكل شيء يعتمد عليها ، وهلم جرا. يمكنك استعادة الروابط الأصلية من هذه التجزئة فقط عن طريق المعرفات ، ولكنها لن تساعد إذا كان أي من القمم يشير إلى نفسه مباشرة أو من خلال القمم الأخرى. في هذه الحالة ، سوف يرسل المتسلسل نوعًا غير معروف: تحويل البنية الدائرية إلى خطأ JSON . إذا لم يكن الأمر واضحًا ، فإليك أبسط مثال يولد هذا الخطأ: https://jsfiddle.net/L4guo86w/ .
بالإضافة إلى ذلك ، لا يحتوي JSON على أي معلومات حول فئات المصدر ، لذلك لا توجد طريقة لفهم نوع كل قمة قبل التسلسل.
أدركت هذه المشاكل ، وبدأت في الاتصال بالإنترنت وبدأت في البحث عن حلول جاهزة. كان هناك الكثير ، لكن معظمها كان ضخمًا جدًا أو تطلب وصفًا خاصًا للفصول القابلة للتسلسل ، لذلك تقرر صنع دراجتك الخاصة. ونعم ، أحب الدراجات الهوائية.
مفهوم المسلسل
هذا القسم مخصص لأولئك الذين يرغبون في المشاركة في إنشاء خوارزمية تسلسل معي ، ولو بشكل افتراضي.
إحدى مشكلات جافا سكريبت هي نقص البيانات الوصفية التي يمكن أن تعمل معجزات في لغات مثل C # أو Java (السمات والتفكير). من ناحية أخرى ، لست بحاجة إلى تسلسل معقد للغاية مع القدرة على تحديد قائمة الحقول القابلة للتسلسل والتحقق والرقائق الأخرى. لذلك ، فإن الفكرة الرئيسية هي إضافة معلومات حول نوعه إلى الكائن وتسلسله باستخدام JSON.stringify العادي.
أثناء البحث عن حلول ، صادفت مقالة مثيرة للاهتمام يترجم عنوانها على أنها "6 طرق خاطئة لإضافة معلومات النوع في JSON" . في الواقع ، الطرق جيدة جدًا ، واخترت الطريقة تحت الرقم 5. إذا كنت كسولًا جدًا لقراءة المقال ، لكنني أوصي بشدة بفعل ذلك ، فسوف أصف هذه الطريقة بإيجاز: عند إجراء تسلسل لكائن ، نقوم بلفه في كائن آخر فقط حقل اسمه في التنسيق "@<type>"
، والقيمة هي بيانات الكائن. أثناء إلغاء التسلسل ، نقوم باستخراج اسم النوع ، وإعادة إنشاء الكائن من المُنشئ ، وقراءة بيانات الحقول الخاصة به.
إذا قمنا بإزالة الروابط من مثالنا أعلاه ، فإن JSON.stringify القياسي يقوم بتسلسل بيانات مثل هذا:
JSON.stringify [ { "id": "d04d6a58-7215-4102-aed0-32122e331cf4", "name": "Start", "links": {} }, { "id": "5c58c3fc-8ce1-45a5-9e44-90d5cebe11d3", "name": "Command", "links": {}, "command": " A, B" }, ... }
وسيقوم مُسلسلنا بلفها على النحو التالي:
نتيجة التسلسل [ { "@Schema.Start": { "id": "d04d6a58-7215-4102-aed0-32122e331cf4", "name": "Start", "links": {} } }, { "@Schema.Command": { "id": "5c58c3fc-8ce1-45a5-9e44-90d5cebe11d3", "name": "Command", "links": {}, "command": " A, B" } }, ... }
بالطبع ، هناك عيب: يجب أن يعرف المتسلسل الأنواع التي يمكنه إجراء تسلسل لها ، ويجب ألا تحتوي الكائنات نفسها على حقول يبدأ اسمها بكلب. ومع ذلك ، يتم حل المشكلة الثانية بالاتفاق مع المطورين أو عن طريق استبدال رمز الكلب بشيء آخر ، ويتم حل المشكلة الأولى في سطر واحد من التعليمات البرمجية (أدناه مثال). نحن نعرف بالضبط ما سنقوم بعمل تسلسل ، أليس كذلك؟
حل مشكلة الارتباط
لا يزال الأمر أبسط من حيث الخوارزمية ، ولكن من الصعب تنفيذه.
عند إجراء تسلسل لمثيلات الفئات المسجلة في المُسلسل ، سنقوم بتخزينها في ذاكرة التخزين المؤقت وتعيين رقم تسلسلي لها. إذا التقينا في المستقبل بهذا المثيل مرة أخرى ، فإننا في التعريف الأول سنضيف هذا الرقم (سيأخذ اسم الحقل الشكل "@<type>|<index>"
) ، وفي مكان التسلسل سنقوم بإدراج الرابط في شكل كائن
{ "@<type>": <index> }
وهكذا ، خلال عملية إزالة التسلسل ، ننظر إلى قيمة الحقل بالضبط. إذا كان هذا رقمًا ، فإننا نستخرج الكائن من ذاكرة التخزين المؤقت بهذا الرقم. خلاف ذلك ، هذا هو تعريفه الأول.
دعنا نرجع الرابط من أعلى المخطط الأول إلى الثاني ونلقي نظرة على النتيجة:
نتيجة التسلسل [ { "@Schema.Start": { "id": "a26a3a29-9462-4c92-8d24-6a93dd5c819a", "name": "Start", "links": { "25fa2c44-0446-4471-a013-8b24ffb33bac": { "@Schema.Link": { "id": "25fa2c44-0446-4471-a013-8b24ffb33bac", "target": { "@Schema.Command|1": { "id": "4f4f5521-a2ee-4576-8aec-f61a08ed38dc", "name": "Command", "links": {}, "command": " A, B" } } } } } } }, { "@Schema.Command": 1 }, ... }
لا يبدو الأمر واضحًا جدًا للوهلة الأولى ، لأنه يتم تعريف الرأس الثاني أولاً داخل الأول في كائن اتصال Link ، ولكن من المهم أن يعمل هذا النهج. بالإضافة إلى ذلك ، قمت بإنشاء الإصدار الثاني من المُسلسل ، والذي يتجاوز الشجرة ليس في العمق ، ولكن في العرض ، والذي يتجنب مثل هذه "السلالم".
إنشاء مُسلسِل
هذا القسم مخصص لأولئك الذين يرغبون في تنفيذ الأفكار الموضحة أعلاه.
الرقم التسلسلي فارغ
مثل أي دولة أخرى ، سيكون لدينا مُسلسل لدينا طريقتين رئيسيتين - التسلسل وإلغاء التسلسل. بالإضافة إلى ذلك ، سنحتاج إلى طريقة تخبر المُسلسل عن الفئات التي يجب أن يتم إجراء تسلسل لها (تسجيل) والفئات التي لا يجب (تجاهلها). هذا الأخير ضروري حتى لا يتم إجراء تسلسل لعناصر DOM أو كائنات JQuery أو أي أنواع بيانات أخرى لا يمكن إجراء تسلسل لها أو التي لا يلزم إجراء تسلسل لها. على سبيل المثال ، في المحرر الخاص بي ، أقوم بتخزين عنصر مرئي يتوافق مع قمة الرأس أو الرابط. يتم إنشاؤه أثناء التهيئة ، وبطبيعة الحال ، لا ينبغي أن تقع في قاعدة البيانات.
كود غلاف المسلسل export default class Serializer { constructor() { this._nameToCtor = [];
تفسيرات
لتسجيل فصل دراسي ، يجب عليك تمرير مُنشئه إلى طريقة التسجيل بإحدى طريقتين:
- تسجيل (MyClass)
- تسجيل ("MyNamespace.MyClass" ، MyClass)
في الحالة الأولى ، سيتم استخراج اسم الفئة من اسم وظيفة المُنشئ (غير مدعوم في IE) ، في الحالة الثانية ، تحدد الاسم بنفسك. الطريقة الثانية هي الأفضل يسمح لك باستخدام مساحات الأسماء ، والأول ، حسب التصميم ، مصمم لتسجيل أنواع جافا سكريبت المضمنة بمنطق تسلسل معاد تعريفه.
بالنسبة لمثالنا ، فإن تهيئة المتسلسل هي كما يلي:
import Schema from './schema'; ...
يحتوي كائن المخطط على أوصاف جميع فئات الرأس ، لذلك يتم احتواء رمز تسجيل الفصل في سطر واحد.
سياق التسلسل وإلغاء التسلسل
ربما لاحظت فصول SerializationContext و DeserializationContext المشفرة. إنهم هم الذين يقومون بكل العمل ، وهناك حاجة في المقام الأول من أجل فصل البيانات عن عمليات التسلسل / إزالة التسلسل المختلفة ، لأن لكل مكالمة يحتاجون إليها لتخزين المعلومات الوسيطة - ذاكرة تخزين مؤقت للكائنات المتسلسلة ورقم تسلسلي للارتباط.
التسلسل السياق
سأحلل بالتفصيل فقط متسلسل العودية ، لأنه نظيرهم "المسطح" أكثر تعقيدًا إلى حد ما ، ويختلف فقط في أسلوبه في معالجة شجرة الأشياء.
لنبدأ بالمنشئ:
constructor(ser) { this.__proto__.__proto__ = ser; this.cache = [];
this.__proto__.__proto__ = ser;
شرح الخط الغامض this.__proto__.__proto__ = ser;
عند إدخال المنشئ ، نقبل كائن المتسلسل نفسه ، ويرث هذا الخط فصلنا منه. هذا يسمح بالوصول إلى بيانات المتسلسل من خلال this
.
على سبيل المثال ، يشير this._ignore
إلى قائمة بالفئات التي تم تجاهلها this._ignore
نفسه ("القائمة السوداء") ، وهي مفيدة جدًا. خلاف ذلك ، سيكون علينا كتابة شيء من هذا القبيل.
طريقة التسلسل الرئيسية:
serialize(val) { if (Array.isArray(val)) {
تجدر الإشارة إلى أن هناك ثلاثة أنواع أساسية من البيانات التي نعالجها: المصفوفات والكائنات والقيم البسيطة. إذا كان مُنشئ كائن في "القائمة السوداء" ، فلن يتم إجراء تسلسل لهذا الكائن.
تسلسل الصفيف:
serializeArray(val) { let res = []; for (let item of val) { let e = this.serialize(item); if (typeof e !== 'undefined') res.push(e); } return res; }
يمكنك الكتابة بشكل أقصر عبر الخريطة ، لكن هذا ليس بالغ الأهمية. شيء واحد فقط مهم - التحقق من القيمة من أجل غير محدد. إذا كان هناك صنف غير قابل للتسلسل في الصفيف ، فبدون هذا الاختيار ، سيقع في الصفيف على أنه غير محدد ، وهو ليس جيدًا جدًا. أيضًا في عملية التنفيذ ، يتم إجراء تسلسل للمصفوفات بدون مفاتيح. نظريًا ، يمكنك تحسين الخوارزمية لتسلسل المصفوفات الترابطية ، ولكن لهذه الأغراض أفضل استخدام الكائنات. بالإضافة إلى ذلك ، لا يحب JSON.stringify المصفوفات الترابطية.
تسلسل الكائن:
كود serializeObject(val) { let name = this._ctorToName[val.constructor]; if (name) {
من الواضح أن هذا هو الجزء الأصعب من المتسلسل ، قلبه. لنفككها.
بادئ ذي بدء ، نتحقق مما إذا كان مُنشئ الصف مسجلاً في التسلسل. إذا لم يكن كذلك ، فهذا كائن بسيط يتم استدعاء أسلوب الأداة المساعدة serializeObjectInner
.
خلاف ذلك ، نتحقق مما إذا تم تعيين الكائن معرف فريد __uuid . هذا هو متغير عداد بسيط مشترك بين جميع المتسللين ، ويتم استخدامه للاحتفاظ بالإشارة إلى مثيل الفئة في ذاكرة التخزين المؤقت. يمكنك الاستغناء عنها ، وتخزين المثيل نفسه بدون مفتاح في ذاكرة التخزين المؤقت ، ولكن بعد ذلك للتحقق من تخزين الكائن في ذاكرة التخزين المؤقت ، سيتعين عليك المرور عبر ذاكرة التخزين المؤقت بالكامل ، وهنا يكفي التحقق من المفتاح. أعتقد أن هذا أسرع من حيث التنفيذ الداخلي للكائنات في المتصفحات. بالإضافة إلى ذلك ، أنا لا أقوم بتسلسل الحقول بدءاً بشرطة سفلية ، لذلك لن يقع الحقل _uuid في json الناتج ، مثل حقول الفئة الخاصة الأخرى. إذا كان هذا غير مقبول لمهمتك ، يمكنك تغيير هذا المنطق.
بعد ذلك ، من خلال قيمة __uuid ، نبحث عن كائن يصف مثيل الفئة في ذاكرة التخزين المؤقت (ذاكرة التخزين المؤقت ).
إذا كان مثل هذا الكائن موجودًا ، فهذا يعني أن القيمة قد تم إجراء تسلسل لها مسبقًا. في هذه الحالة ، نقوم بتعيين رقم تسلسلي للكائن ، إذا لم يتم ذلك من قبل:
if (!cached.index) {
تبدو الشفرة مربكة ، ويمكن تبسيطها عن طريق تعيين رقم لجميع الفئات التي نقوم بعمل تسلسل لها. ولكن من أجل تصحيح الأخطاء وإدراك النتيجة ، من الأفضل عندما يتم تعيين الرقم فقط لتلك الفئات التي توجد بها روابط في المستقبل.
عندما يتم تعيين الرقم ، نعيد الارتباط وفقًا للخوارزمية:
إذا تم إجراء تسلسل للكائن لأول مرة ، فإننا نقوم بإنشاء مثيل لذاكرة التخزين المؤقت الخاصة به:
let res; let cached = { ref: { [`@${name}`]: {} } }; this.cache[val.__uuid] = cached;
ثم قم بتسلسلها:
if (typeof val.serialize === 'function') {
هناك تدقيق لتطبيق واجهة التسلسل بواسطة الفئة (والتي سيتم مناقشتها لاحقًا) ، بالإضافة إلى إنشاء Object.keys(cached.ref)[0]
. الحقيقة هي أن cached.ref يخزن رابطًا إلى كائن المجمع { "@<type>[|<index>]": <> }
، ولكن اسم حقل الكائن غير معروف لنا ، لأن في هذه المرحلة ، لا نعرف حتى الآن ما إذا كان الاسم سيحتوي على رقم الكائن (الفهرس). يستخرج هذا البناء ببساطة الحقل الأول والوحيد للكائن.
أخيرًا ، طريقة المنفعة من تسلسل الكائن الداخلي:
serializeObjectInner(val) { let res = {}; for (let key of Object.getOwnPropertyNames(val)) { if (!(isString(key) && key.startsWith('__'))) {
نقوم بإنشاء كائن جديد ونسخ الحقول من القديم إليه.
سياق التسلسل
تعمل عملية إلغاء التسلسل بترتيب عكسي ولا تحتاج إلى تعليقات خاصة.
كود /** * */ class DeserializationContext { /** * * @param {Serializer} ser */ constructor(ser) { this.__proto__.__proto__ = ser; this.cache = []; // } /** * json * @param {any} val json * @returns {any} */ deserialize(val) { if (Array.isArray(val)) { // return this.deserializeArray(val); } else if (isObject(val)) { // return this.deserializeObject(val); } else { // return val; } } /** * * @param {Object} val * @returns {Object} */ deserializeArray(val) { return val.map(item => this.deserialize(item)); } /** * * @param {Array} val * @returns {Array} */ deserializeObject(val) { let res = {}; for (let key of Object.getOwnPropertyNames(val)) { let data = val[key]; if (isString(key) && key.startsWith('@')) { // if (isInteger(data)) { // res = this.cache[data]; if (res) { return res; } else { console.error(` ${data}`); return data; } } else { // let [name, id] = key.substr(1).split('|'); let ctor = this._nameToCtor[name]; if (ctor) { // res = new ctor(); // , if (id) this.cache[id] = res; if (typeof res.deserialize === 'function') { // res.deserialize(data); } else { // for (let key of Object.getOwnPropertyNames(data)) { res[key] = this.deserialize(data[key]); } } return res; } else { // console.error(` "${name}" .`); return val[key]; } } } else { // res[key] = this.deserialize(val[key]); } } return res; } }
ميزات إضافية
واجهة التسلسل
لا يوجد دعم واجهة في جافا سكريبت ، ولكن يمكننا الاتفاق على أنه إذا نفذ الفصل طرق التسلسل وإلغاء التسلسل ، فسيتم استخدام هذه الأساليب للتسلسل / إلغاء التسلسل ، على التوالي.
بالإضافة إلى ذلك ، يسمح لك Javascript بتنفيذ هذه الطرق للأنواع المضمنة ، على سبيل المثال ، للتاريخ:
تسلسل التاريخ إلى تنسيق ISO Date.prototype.serialize = function () { return this.toISOString(); }; Date.prototype.deserialize = function (val) { let date = new Date(val); this.setDate(date.getDate()); this.setTime(date.getTime()); };
الشيء الرئيسي هو تذكر تسجيل نوع التاريخ: serializer.register(Date);
.
النتيجة:
{ "@Date": "2018-06-02T20:41:06.861Z" }
القيد الوحيد: لا ينبغي أن تكون نتيجة التسلسل عددًا صحيحًا ، لأن في هذه الحالة ، سيتم تفسيره على أنه مرجع كائن.
وبالمثل ، يمكنك إجراء تسلسل للفئات البسيطة إلى سلاسل. مثال على إجراء تسلسل لفئة اللون ، التي تصف اللون ، إلى السطر #rrggbb
على github .
متسلسل مسطح
خاصة بالنسبة لك ، أيها القراء الأعزاء ، لقد كتبت النسخة الثانية من المُسلسِل ، الذي يجتاز شجرة الأشياء التي لا تتعمق بعمق بشكل متكرر ، ولكن بشكل متكرر في العرض باستخدام قائمة انتظار.
للمقارنة ، سأعطي مثالا على تسلسل الذروتين الأولين من مخططنا في كلتا الحالتين.
مُسلسل تكراري (تسلسل متعمق) [ { "@Schema.Start": { "id": "5ec74f26-9515-4789-b852-12feeb258949", "name": "Start", "links": { "102c3dca-8e08-4389-bc7f-68862f2061ef": { "@Schema.Link": { "id": "102c3dca-8e08-4389-bc7f-68862f2061ef", "target": { "@Schema.Command|1": { "id": "447f6299-4bd4-48e4-b271-016a0d47fc0e", "name": "Command", "links": {}, "command": " A, B" } } } } } } }, { "@Schema.Command": 1 } ]
متسلسل مسطح (تسلسل واسع) [ { "@Schema.Start": { "id": "1412603f-24c2-4513-836e-f2b0c0392483", "name": "Start", "links": { "b94ac7e5-d75f-44c1-960f-a02f52c994da": { "@Schema.Link": { "id": "b94ac7e5-d75f-44c1-960f-a02f52c994da", "target": { "@Schema.Command": 1 } } } } } }, { "@Schema.Command|1": { "id": "a93e452e-4276-4d6a-86a1-0681226d79f0", "name": "Command", "links": {}, "command": " A, B" } } ]
أنا شخصياً أحب الخيار الثاني أكثر من الخيار الأول ، ولكن يجب أن نتذكر أنه عند اختيار أحد الخيارات ، لا يمكنك استخدام الخيار الآخر. كل شيء عن الروابط. لاحظ أنه في المسلسل المسطح ، يذهب رابط إلى الرأس الثاني قبل وصفه.
إيجابيات وسلبيات التسلسل
الإيجابيات:
- كود المتسلسل بسيط للغاية ومضغوط (حوالي 300 سطر ، نصفها من التعليقات).
- إن المُسلسل سهل الاستخدام ولا يتطلب مكتبات تابعة لجهات خارجية.
- هناك دعم مدمج لواجهة التسلسل من أجل التسلسل التعسفي للفئات.
- والنتيجة مرضية للعين (IMHO).
- لا يعد تطوير مُسلسِل / مُسلسل مماثل بلغات أخرى مشكلة. قد يكون هذا مطلوبًا إذا تمت معالجة نتيجة التسلسل على الظهر.
السلبيات:
- يتطلب المتسلسل تسجيل الفئات التي يمكن إجراء تسلسل لها.
- هناك قيود طفيفة على أسماء الحقول للكائنات.
- المُسلسل مكتوب noob في جافا سكريبت ، لذلك قد يحتوي على أخطاء وأخطاء.
- قد يعاني الأداء على كميات كبيرة من البيانات.
أيضا ناقص هو أن الرمز مكتوب في ES6. بالطبع ، من الممكن التحويل إلى إصدارات سابقة من Javascript ، لكنني لم أتحقق من توافق الكود الناتج مع المتصفحات المختلفة.
منشوراتي الأخرى
- توطين المشاريع على .NET مع مترجم وظيفة
- تعبئة قوالب نصية بالبيانات المستندة إلى النموذج. تطبيق .NET باستخدام دالات بايت (IL) ديناميكية