برمجة جافا سكريبت

Metaprogramming هو نوع من البرمجة المرتبطة بإنشاء البرامج التي تولد برامج أخرى نتيجة لعملهم ، أو البرامج التي تغير نفسها أثناء التنفيذ. (ويكيبيديا)

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


JavaScript ، بحكم طبيعتها ، لغة ديناميكية قوية جدًا وتسمح لك بكتابة كود مرن بشكل جيد:


/** *   save-    */ const comment = { authorId: 1, comment: '' }; for (let name in comment) { const pascalCasedName = name.slice(0, 1).toUpperCase() + name.slice(1); comment[`save${pascalCasedName}`] = function() { //   } } comment.saveAuthorId(); //  authorId comment.saveComment(); //  comment 

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


 <?php class Comment { public $authorId; public $comment; public function __construct($authorId, $comment) { $this->authorId = $authorId; $this->comment = $comment; } //       public function __call($methodName, $arguments) { foreach (get_object_vars($this) as $fieldName => $fieldValue) { $saveMethodName = "save" . strtoupper($fieldName[0]) . substr($fieldName, 1); if ($methodName == $saveMethodName) { //   } } } } $comment = new Comment(1, ''); $comment->saveAuthorId(); //  authorId $comment->saveComment(); //  comment 

بالإضافة إلى البنية المرنة ، لدينا أيضًا مجموعة من الوظائف المفيدة لكتابة التعليمات البرمجية الديناميكية: Object.create و Object.defineProperty و Function.apply وغيرها الكثير.


النظر فيها بمزيد من التفصيل.


  1. توليد الكود
  2. العمل مع الوظائف
  3. العمل مع الأشياء
  4. تعكس API
  5. الرموز
  6. الوكيل
  7. الخلاصة

1. إنشاء التعليمات البرمجية


الأداة القياسية لتنفيذ التعليمات البرمجية ديناميكيًا هي وظيفة Eval ، والتي تتيح لك تنفيذ التعليمات البرمجية من السلسلة التي تم تمريرها:


 eval('alert("Hello, world")'); 

لسوء الحظ ، يحتوي EV على العديد من الفروق الدقيقة:


  • إذا تمت كتابة الرمز الخاص بنا في وضع صارم ("استخدام صارم") ، فلن تكون المتغيرات المعلنة داخل Eval مرئية في كود تقييم الاستدعاء. في الوقت نفسه ، يمكن للكود نفسه داخل Eval أن يغير دائمًا المتغيرات الخارجية.
  • يمكن تنفيذ التعليمات البرمجية داخل Eval في كل من السياق العام (إذا تم استدعاؤها من خلال window.eval) وفي سياق الوظيفة التي تم إجراء المكالمة بداخلها (إذا تم تقييمها فقط ، بدون نافذة).
  • يمكن أن تنشأ مشاكل بسبب تصغير JS ، عندما يتم استبدال أسماء المتغيرات بأسماء أقصر لتقليل الحجم. لا يمس الرمز الذي يتم تمريره كسلسلة للتقييم المصغر ، ولهذا يمكننا البدء في الوصول إلى المتغيرات الخارجية باستخدام أسماء قديمة غير محددة ، مما سيؤدي إلى أخطاء دقيقة.

هناك بديل رائع لحل هذه المشاكل - وظيفة جديدة .


 const hello = new Function('name', 'alert("Hello, " + name)'); hello('') // alert("Hello, "); 

على عكس Eval ، يمكننا دائمًا تمرير المعلمات بشكل صريح من خلال وسيطات الدالة وإعطائها سياق هذا ديناميكيًا (عبر Function.apply أو Function.call ). بالإضافة إلى ذلك ، يتم استدعاء الوظيفة التي تم إنشاؤها دائمًا في النطاق العالمي.


في الأيام الخوالي ، غالبًا ما كان يتم استخدام Eval لتغيير الرمز بشكل ديناميكي ، مثل لم يكن لدى JavaScript سوى آليات قليلة للتفكير وكان من المستحيل الاستغناء عن التقييم. ولكن في معيار اللغة الحديثة ، ظهرت وظائف عالية المستوى ، ويتم استخدام التقييم الآن كثيرًا.


2. العمل مع الوظائف


توفر لنا JavaScript العديد من الأدوات الممتازة للعمل بشكل ديناميكي مع الوظائف ، مما يتيح لنا الحصول على معلومات متنوعة حول الوظيفة في وقت التشغيل وتغييرها:


  • Function.length - يسمح لك بمعرفة عدد الوسائط من دالة:


     const func = function(name, surname) { console.log(`Hello, ${surname} ${name}`) }; console.log(func.length) // 2 

  • Function.apply و Function.call - تسمح لك بتغيير سياق هذه الوظيفة ديناميكيًا:


     const person = { name: '', introduce: function() { return ` ${this.name}`; } } person.introduce(); //   person.introduce.call({ name: '' }); //   

    تختلف عن بعضها البعض فقط في ذلك ، في Function.apply ، يتم تقديم الحجج إلى الدالة كمصفوفة ، وفي Function.call ، مفصولة بفواصل. غالبًا ما تم استخدام هذه الميزة من قبل لتمرير قائمة الوسائط إلى الدالة كمصفوفة. مثال شائع هو دالة Math.max (افتراضيًا ، لا يمكنها العمل مع المصفوفات):


     Math.max.apply(null, [1, 2, 4, 3]); // 4 

    مع ظهور عامل الانتشار الجديد ، يمكنك ببساطة كتابة ما يلي:


     Math.max(...[1, 2, 4, 3]); // 4 

  • Function.bind - يسمح لك بإنشاء نسخة من دالة من دالة موجودة ، ولكن مع سياق مختلف:


     const person = { name: '', introduce: function() { return ` ${this.name}`; } } person.introduce(); //   const introduceEgor = person.introduce.bind({ name: '' }); introduceEgor(); //   

  • Function.caller - يسمح لك بالحصول على وظيفة الاتصال. لا ينصح باستخدامه ، لأنه غير موجود في معيار اللغة ولن يعمل في الوضع الصارم. ويرجع ذلك إلى حقيقة أنه إذا قامت العديد من محركات جافا سكريبت بتطبيق تحسين استدعاء المكالمة الموصوف في مواصفات اللغة ، فقد يؤدي استدعاء دالة. مثال للاستخدام:


     const a = function() { console.log(a.caller == b); } const b = function() { a(); } b(); // true 

  • Function.toString - إرجاع تمثيل سلسلة للدالة. هذه ميزة قوية للغاية تسمح لك بفحص كل من محتويات الدالة وحججها:


     const getFullName = (name, surname, middlename) => { console.log(`${surname} ${name} ${middlename}`); } getFullName.toString() /* * "(name, surname, middlename) => { * console.log(`${surname} ${name} ${middlename}`); * }" */ 

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


    • تحليل مجموعة من النظامي ونحصل على مستوى مقبول من الموثوقية (قد لا يعمل إذا لم نغطي جميع الأنواع المحتملة لإدخالات الوظائف).
    • نحصل على تمثيل السلسلة للدالة ووضعها في محلل جافا سكريبت النهائي (على سبيل المثال ، إسبريما أو الجوزة ) ، ثم نعمل مع AST المهيكل. مثال تحليل AST عبر esprima. يمكنني أن أنصح أيضًا بتقرير جيد عن المحللين من Alexei Okhrimenko.


أمثلة بسيطة مع تحليل الوظيفة العادية:


الحصول على قائمة الحجج الدالة
 /** *    . * @param fn  */ const getFunctionParams = fn => { const COMMENTS = /(\/\/.*$)|(\/\*[\s\S]*?\*\/)|(\s*=[^,\)]*(('(?:\\'|[^'\r\n])*')|("(?:\\"|[^"\r\n])*"))|(\s*=[^,\)]*))/gm; const DEFAULT_PARAMS = /=[^,]+/gm; const FAT_ARROW = /=>.*$/gm; const ARGUMENT_NAMES = /([^\s,]+)/g; const formattedFn = fn .toString() .replace(COMMENTS, "") .replace(FAT_ARROW, "") .replace(DEFAULT_PARAMS, ""); const params = formattedFn .slice(formattedFn.indexOf("(") + 1, formattedFn.indexOf(")")) .match(ARGUMENT_NAMES); return params || []; }; const getFullName = (name, surname, middlename) => { console.log(surname + ' ' + name + ' ' + middlename); }; console.log(getFunctionParams(getFullName)); // ["name", "surname", "middlename"] 


الحصول على وظائف الجسم
 /** *     . * @param fn  */ const getFunctionBody = fn => { const restoreIndent = body => { const lines = body.split("\n"); const bodyLine = lines.find(line => line.trim() !== ""); let indent = typeof bodyLine !== "undefined" ? (/[ \t]*/.exec(bodyLine) || [])[0] : ""; indent = indent || ""; return lines.map(line => line.replace(indent, "")).join("\n"); }; const fnStr = fn.toString(); const rawBody = fnStr.substring( fnStr.indexOf("{") + 1, fnStr.lastIndexOf("}") ); const indentedBody = restoreIndent(rawBody); const trimmedBody = indentedBody.replace(/^\s+|\s+$/g, ""); return trimmedBody; }; //       getFullName const getFullName = (name, surname, middlename) => { console.log(surname + ' ' + name + ' ' + middlename); }; console.log(getFunctionBody(getFullName)); 


من المهم أن نلاحظ أنه عند استخدام المصغر ، يمكن تحسين كل من الشفرة نفسها داخل الدالة المحللة وحججها وبالتالي تغييرها.


3. العمل مع الأشياء


تحتوي JavaScript على كائن كائن عمومي يحتوي على العديد من الطرق للعمل ديناميكيًا مع الكائنات.


معظم هذه الأساليب موجودة منذ فترة طويلة في اللغة وتستخدم على نطاق واسع.


خصائص الكائن


  • Object . عين - لنسخ خصائص كائن أو أكثر إلى الكائن المحدد بواسطة المعلمة الأولى:


     Object.assign({}, { a: 1 }, { b: 2 }, { c: 3 }) // {a: 1, b: 2, c: 3} 

  • Object.keys و Object.values - تُرجع إما قائمة مفاتيح أو قائمة قيم الكائن:


     const obj = { a: 1, b: 2, c: 3 }; console.log(Object.keys(obj)); // ["a", "b", "c"] console.log(Object.values(obj)); // [1, 2, 3] 

  • Object.entries - إرجاع قائمة بخصائصها بالصيغة [[key1، value1]، [key2، value2]] :


     const obj = { a: 1, b: 2, c: 3 }; console.log(Object.entries(obj)); // [["a", 1], ["b", 2], ["c", 3]] 

  • Object.prototype.hasOwnProperty - للتحقق مما إذا كانت الخاصية مضمنة في كائن (وليس في سلسلة النموذج الأولي):


     const obj = { a: 1 }; obj.__proto__ = { b: 2 }; console.log(obj.hasOwnProperty('a')); // true console.log(obj.hasOwnProperty('b')) // false 

  • Object.getOwnPropertyNames - تُرجع قائمة خصائصها الخاصة ، بما في ذلك تعدادها وغير تعدادها:


     const obj = { a: 1, b: 2 }; Object.defineProperty(obj, 'c', { value: 3, enumerable: false }); //    for (let key in obj) { console.log(key); } // "a", "b" console.log(Object.getOwnPropertyNames(obj)); // [ "a", "b", "c" ] 

  • Object.getOwnPropertySymbols - تُرجع قائمة خاصة بها (موجودة في الكائن ، وليس في سلسلة النموذج الأولي):


     const obj = {}; const a = Symbol('a'); obj[a] = 1; console.log(Object.getOwnPropertySymbols(obj)); // [ Symbol(a) ] 

  • Object.prototype.propertyIsEnumerable - التحقق مما إذا كانت الخاصية قابلة للتعداد (على سبيل المثال ، متوفرة في حلقات for-in ، for-of-loops):


     const arr = [ ' ' ]; console.log(arr.propertyIsEnumerable(0)); // true —  ' '   console.log(arr.propertyIsEnumerable('length')); // false —  length    


واصفات خاصية الكائن


تسمح لك الواصفات بتعديل معلمات الخاصية. باستخدامها ، يمكننا بسهولة أن نصنع اعتراضاتنا الخاصة أثناء قراءة / كتابة أي خاصية (getters and locers - get / set) ، وجعل الخصائص ثابتة أو غير قابلة للحساب ، وعدد من الأشياء الأخرى.


  • Object.defineProperty و Object.defineProperties - يقوم بإنشاء واحد أو أكثر من واصفات الخصائص. قم بإنشاء واصف خاص بك باستخدام getter و setter:


     const obj = { name: '', surname: '' }; Object.defineProperty(obj, 'fullname', { //     fullname get: function() { return `${this.name} ${this.surname}`; }, //     fullname (     delete obj.fullname) set: function(value) { const [name, surname] = value.split(' '); this.name = name; this.surname = surname; }, }); console.log(obj.fullname); //   obj.fullname = ' '; console.log(obj.name); //  console.log(obj.surname); //  

    في المثال أعلاه ، لم يكن لخاصية الاسم الكامل قيمته الخاصة ، ولكنه عمل ديناميكيًا مع خصائص الاسم واللقب. ليس من الضروري تحديد كل من getter و setter في نفس الوقت - يمكننا فقط ترك getter والحصول على خاصية للقراءة فقط. أو يمكننا إضافة إجراء إضافي في أداة التعيين جنبًا إلى جنب مع تعيين القيمة ، على سبيل المثال ، التسجيل.
    بالإضافة إلى خصائص get / set ، تحتوي الواصفات على العديد من الخصائص لتكوينها:


     const obj = {}; //      get/set,       value.    get/set  value.   — undefined. Object.defineProperty(obj, 'name', { value: '' }); // ,         (for-in, for-of, Object.keys).   — false. Object.defineProperty(obj, 'a', { enumerable: true }); //         defineProperty     delete.   — false. Object.defineProperty(obj, 'b', { configurable: false }); //      .   — false. Object.defineProperty(obj, 'c', { writable: true }); 

  • Object.getOwnPropertyDescriptor و Object.getOwnPropertyDescriptors - تسمح لك بالحصول على واصف الكائن المطلوب أو قائمته الكاملة:


     const obj = { a: 1, b: 2 }; console.log(Object.getOwnPropertyDescriptor(obj, "a")); // { configurable: true, enumerable: true, value: 1, writable: true } /** * { * a: { configurable: true, enumerable: true, value: 1, writable: true }, * b: { configurable: true, enumerable: true, value: 2, writable: true } * } */ console.log(Object.getOwnPropertyDescriptors(obj)); 


إنشاء قيود عند العمل مع الكائنات


  • Object.freeze - "يجمد" خصائص الكائن. نتيجة هذا "التجميد" هي الثبات التام لخصائص الكائن - لا يمكن تغييرها وحذفها ، إضافة خصائص جديدة ، تغيير الواصفات:


     const obj = Object.freeze({ a: 1 }); //       ,       . obj.a = 2; obj.b = 3; console.log(obj); // { a: 1 } console.log(Object.isFrozen(obj)) // true 

  • Object.seal - "يختم" خصائص الكائن. يشبه الختم Object.freeze ، ولكن لديه عدد من الاختلافات. نحن ، كما هو الحال في Object.freeze ، نحظر إضافة خصائص جديدة ، وحذف الخصائص الموجودة ، وتغيير الواصفات الخاصة بها ، ولكن في الوقت نفسه يمكننا تغيير قيم الخصائص:


     const obj = Object.seal({ a: 1 }); obj.a = 2; //  a   2 //     ,       . obj.b = 3; console.log(obj); // { a: 2 } console.log(Object.isSealed(obj)) // true 

  • Object.preventExtensions - يمنع إضافة خصائص / واصفات جديدة:


     const obj = Object.preventExtensions({ a: 1 }); obj.a = 2; //       ,       . obj.b = 3; console.log(obj); // { a: 2 } console.log(Object.isExtensible(obj)) // false 


نماذج الكائنات


  • Object.create - لإنشاء كائن بالنموذج الأولي المحدد في المعلمة. يمكن استخدام هذه الميزة لكل من وراثة النموذج الأولي ولإنشاء كائنات "نظيفة" ، بدون خصائص من Object.prototype :


     const pureObj = Object.create(null); 

  • Object.getPrototypeOf و Object.setPrototypeOf - للحصول على / تغيير النموذج الأولي لكائن:


     const duck = {}; const bird = {}; Object.setPrototypeOf(duck, bird); console.log(Object.getPrototypeOf(duck) === bird); // true console.log(duck.__proto__ === bird); // true 

  • Object.prototype.isPrototypeOf - للتحقق مما إذا كان الكائن الحالي موجودًا في سلسلة النموذج الأولي لكائن آخر:


     const duck = {}; const bird = {}; duck.__proto__ = bird; console.log(bird.isPrototypeOf(duck)); // true 


4. تعكس API


مع ظهور ES6 ، تمت إضافة كائن انعكاس عالمي إلى جافا سكريبت لتخزين مختلف الأساليب المتعلقة بالانعكاس والتأمل.


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


لم يسهّل نقل الوظائف إلى كائن Reflect البحث عن الأساليب الضرورية للتفكير وأعطى دلالات أكبر فحسب ، بل تجنب أيضًا المواقف غير السارة عندما لا يحتوي كائننا على Object.prototype في نموذجه الأولي ، ولكننا نريد استخدام الأساليب من هناك:


 let obj = Object.create(null); obj.qwerty = 'qwerty'; console.log(obj.__proto__) // null console.log(obj.hasOwnProperty('qwerty')) // Uncaught TypeError: obj.hasOwnProperty is not a function console.log(obj.hasOwnProperty === undefined); // true console.log(Object.prototype.hasOwnProperty.call(obj, 'qwerty')); // true 

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


تمت إضافة العديد من الطرق الجديدة أيضًا:


  • Reflect.construct هو بديل أكثر ملاءمة لـ Object.create ، والذي يسمح ليس فقط بإنشاء كائن بالنموذج الأولي المحدد ، ولكن أيضًا لتهيئته على الفور:


     function Person(name, surname) { this.name = this.formatParam(name); this.surname = this.formatParam(surname); } Person.prototype.formatParam = function(param) { return param.slice(0, 1).toUpperCase() + param.slice(1).toLowerCase(); } const oldPerson = Object.create(Person.prototype); // {} Person.call(oldPerson, '', ''); // {name: "", surname: ""} const newPerson = Reflect.construct(Person, ['', '']); // {name: "", surname: ""} 

  • Reflect.ownKeys - تُرجع مجموعة من الخصائص التي تنتمي إلى الكائن المحدد (وليس إلى كائنات في سلسلة النموذج الأولي):


     let person = { name: '', surname: '' }; person.__proto__ = { age: 30 }; console.log(Reflect.ownKeys(person)); // ["name", "surname"] 

  • Reflect.deleteProperty - بديل لعامل الحذف ، على شكل طريقة:


     let person = { name: '', surname: '' }; delete person.name; // person = {surname: ""} Reflect.deleteProperty(person, 'surname'); // person = {} 

  • Reflect.has - بديل لعامل التشغيل ، يتم في شكل طريقة:


     let person = { name: '', surname: '' }; console.log('name' in person); // true console.log(Reflect.has(person, 'name')); // true 

  • Reflect.get و Reflect.set - لقراءة / تغيير خصائص الكائن:


     let person = { name: '', surname: '' }; console.log(Reflect.get(person, 'name')); //  Reflect.set(person, 'surname', '') // person = {name: "", surname: ""} 


يمكن العثور على مزيد من التفاصيل حول التغييرات هنا .


تعكس البيانات الوصفية


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


يمكن أن تكون البيانات الوصفية أي معلومات مفيدة لا تتعلق مباشرة بالكائن ، على سبيل المثال:


  • تكتب TypeScript ، عند تشغيل علامة emitDecoratorMetadata ، معلومات حول أنواع البيانات الوصفية ، مما يسمح لك بالوصول إليها في وقت التشغيل. علاوة على ذلك ، يمكن الحصول على هذه المعلومات من خلال التصميم الرئيسي: النوع:
     const typeData = Reflect.getMetadata("design:type", object, propertyName); 
  • تقوم مكتبة InversifyJS الشائعة للتحكم في الانقلاب بتخزين معلومات متنوعة حول العلاقات الموضحة في البيانات الوصفية.

في الوقت الحالي ، يتم استخدام هذا الملف المتعدد للعمل في المتصفحات .


5. الرموز


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


  1. الرموز المحلية - لا يؤثر النص في معلمات وظيفة الرمز على التفرد وهو مطلوب فقط لتصحيح الأخطاء:


     const sym1 = Symbol('name'); const sym2 = Symbol('name'); console.log(sym1 == sym2); // false 

  2. الأحرف العامة - يتم تخزين الأحرف في السجل العام ، لذا فإن الأحرف بنفس المفتاح متساوية:


     const sym3 = Symbol.for('name'); const sym4 = Symbol.for('name'); const sym5 = Symbol.for('other name'); console.log(sym3 == sym4); // true,        'name' console.log(sym3 == sym5); // false,     


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


  • Symbol.iterator - يسمح لك بإنشاء قواعد خاصة بك لتكرار الكائنات باستخدام for-of أو ... spread :


     let arr = [1, 2, 3]; //       arr[Symbol.iterator] = function() { const self = this; let pos = this.length - 1; return { next() { if (pos >= 0) { return { done: false, value: self[pos--] }; } else { return { done: true }; } } } }; console.log([...arr]); // [3, 2, 1] 

  • Symbol.hasInstance هي طريقة تحدد ما إذا كان المنشئ يتعرف على كائن كمثيل له. مستخدم من قبل عامل التشغيل:


     class MyArray { static [Symbol.hasInstance](instance) { return Array.isArray(instance); } } console.log([] instanceof MyArray); // true 

  • Symbol.isConcatSpreadable - يشير إلى ما إذا كان يجب أن يتم تسوية الصفيف عند تسلسله في Array.concat:


     let firstArr = [1, 2, 3]; let secondArr = [4, 5, 6]; firstArr.concat(secondArr); // [1, 2, 3, 4, 5, 6] secondArr[Symbol.isConcatSpreadable] = false; console.log(firstArr.concat(secondArr)); // [1, 2, 3, [4, 5, 6]] 

  • Symbol.species - يسمح لك بتحديد المُنشئ الذي سيتم استخدامه لإنشاء كائنات مشتقة داخل الفئة.
    على سبيل المثال ، لدينا فئة مصفوفة قياسية للعمل مع المصفوفات ولها طريقة .map التي تنشئ مصفوفة جديدة بناءً على الصفيف الحالي. لمعرفة أي فئة تستخدم لإنشاء هذا الصفيف الجديد ، يستدعي Array this.constructor [Symbol.species] مثل هذا:


     Array.prototype.map = function(cb) { const ArrayClass = this.constructor[Symbol.species]; const result = new ArrayClass(this.length); this.forEach((value, index, arr) => { result[index] = cb(value, index, arr); }); return result; } 

    وبالتالي ، تجاوزًا لنوع Symbol.species ، يمكننا إنشاء صفنا الخاص للعمل مع المصفوفات ونقول أن جميع الطرق القياسية مثل .map ،.


     class MyArray extends Array { static get [Symbol.species]() { return this; } } const arr = new MyArray(1, 2, 3); // [1, 2, 3] console.log(arr instanceof MyArray); // true console.log(arr instanceof Array); // true //   Array.map     Array,    Symbol.species  this      MyArray const doubledArr = arr.map(x => x * 2); console.log(doubledArr instanceof MyArray); // true console.log(doubledArr instanceof Array); // true 

    بالطبع ، هذا لا يعمل فقط مع المصفوفات ، ولكن أيضًا مع الفئات القياسية الأخرى. علاوة على ذلك ، حتى لو قمنا ببساطة بإنشاء فصل خاص بنا بأساليب تعيد مثيلات جديدة من نفس الفئة ، يجب أن نستخدم this.constructor [Symbol.species] للحصول على مرجع إلى المنشئ.


  • Symbol.toPrimitive - يسمح لك بتحديد كيفية تحويل كائننا إلى قيمة بدائية. إذا كان في وقت سابق ، للحد من البدائية ، كنا بحاجة إلى استخدام toString مع valueOf ، الآن يمكن القيام بكل شيء في طريقة واحدة مناسبة:


     const figure = { id: 1, name: '', [Symbol.toPrimitive](hint) { if (hint === 'string') { return this.name; } else if (hint === 'number') { return this.id; } else { // default return this.name; } } } console.log(`${figure}`); // hint = string console.log(+figure); // hint = number console.log(figure + ''); // hint = default 

  • Symbol.match - يسمح لك بإنشاء فئات معالج خاص بك لطريقة دالة String.prototype.match :


     class StartAndEndsWithMatcher { constructor(value) { this.value = value; } [Symbol.match](str) { const startsWith = str.startsWith(this.value); const endsWith = str.endsWith(this.value); if (startsWith && endsWith) { return [this.value]; } return null; } } const testMatchResult = '||'.match(new StartAndEndsWithMatcher('|')); console.log(testMatchResult); // ["|"] const catMatchResult = '|'.match(new StartAndEndsWithMatcher('|')); console.log(catMatchResult) // null 

    Symbol.replace , Symbol.search Symbol.split String.prototype .



, ( reflect-metadata ) . - , , . :


 const validationRules = Symbol('validationRules'); const person = { name: '', surname: '' }; person[validationRules] = { name: ['max-length-256', 'required'], surname: ['max-length-256'] }; 

6. (Proxy)


Proxy , Reflect API Symbols ES6, // , , . , .


, data-binding MobX React, Vue . .


:


 const formData = { login: 'User', password: 'pass' }; const proxyFormData = new Proxy(formData, { set(target, name, value) { target[name] = value; this.forceUpdate(); //   React- } }); //       forceUpdate()    React proxyFormData.login = 'User2'; //     ,   -      proxyFormData.age = 20; 

, /:


 const formData = { login: 'User', password: 'pass' }; const proxyFormData = {}; for (let param in formData) { Reflect.defineProperty(proxyFormData, `__private__${param}`, { value: formData[param], enumerable: false, configurable: true }); Reflect.defineProperty(proxyFormData, param, { get: function() { return this[`__private__${param}`]; }, set: function(value) { this[`__private__${param}`] = value; this.forceUpdate(); //   React- }, enumerable: true, configurable: true }); } //       forceUpdate()    React proxyFormData.login = 'User2'; //                  -  Reflect.defineProperty proxyFormData.age = 20; 

-, — Proxy ( , ), / , delete obj[name] .


7.


JavaScript , ECMAScript 4, . , .


You Don't Know JS .

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


All Articles