ما هي ميتابرومجة في جافا سكريبت؟ باللغة الإنجليزية لو سمحت.

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

يعد Metaprogramming في JavaScript أحد المفاهيم التي قد لا يكون الكثير منا على دراية بها. في هذه المقالة ، سوف نتعرف على Metaprogramming وكيف أنها مفيدة لنا.

مع ES6 (ECMAScript 2015)، لدينا دعم ل Reflectو Proxyالكائنات التي تسمح لنا للقيام Metaprogramming بكل سهولة. في هذه المقالة ، سوف نتعلم كيفية استخدامها مع الأمثلة.

ما هي ميتابرومجة؟

Metaprogrammingليس أقل من السحر في البرمجة ! ماذا عن كتابة برنامج يقرأ ويعدل ويحلل وحتى يولد البرنامج؟ ألا يبدو هذا ساحرًا وقويًا؟

إليك كيف يمكنني وصف Metaprogramming كمطور يستخدمها طوال الوقت:

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

ببساطة ، Metaprogramming تتضمن كتابة كود يمكن ذلك

  • إنشاء التعليمات البرمجية
  • التلاعب بنيات اللغة في وقت التشغيل. تُعرف هذه الظاهرة باسم Reflective Metaprogrammingأو Reflection.

ما هو الانعكاس في Metaprogramming؟

Reflectionهو فرع من Metaprogramming. للانعكاس ثلاثة فروع فرعية:

  1. الاستبطان : الكود قادر على فحص نفسه. يتم استخدامه لاكتشاف معلومات منخفضة المستوى للغاية حول الكود.
  2. التعديل الذاتي : كما يوحي الاسم ، فإن الكود قادر على تعديل نفسه.
  3. الشفاعة : النيابة عن الغير. يمكن تحقيق ذلك عن طريق الالتفاف ، والالتفاف ، والاعتراض.

يعطينا ES6 Reflectالكائن (المعروف أيضًا باسم Reflect API) لتحقيقه Introspection. و Proxyالهدف من ES6 يساعدنا مع Intercession. لن نتحدث كثيرًا عن   Self-Modificationذلك لأننا نريد الابتعاد عنه قدر الإمكان.

انتظر لحظة! فقط للتوضيح ، لم يتم تقديم Metaprogramming في ES6. بدلا من ذلك ، كانت متوفرة باللغة منذ نشأتها. ES6 جعله أسهل كثيرًا في الاستخدام.

عصر ما قبل ES6 من Metaprogramming

هل تذكر eval؟ دعونا نلقي نظرة على كيفية استخدامه:

const blog = { name: 'freeCodeCamp' } console.log('Before eval:', blog); const key = 'author'; const value = 'Tapas'; testEval = () => eval(`blog.${key} = '${value}'`); // Call the function testEval(); console.log('After eval magic:', blog); 

كما قد تلاحظ ، evalساعد في إنشاء رمز إضافي. في هذه الحالة ، blogتم تعديل الكائن بخاصية إضافية في وقت التنفيذ.

Before eval: {name: freeCodeCamp} After eval magic: {name: "freeCodeCamp", author: "Tapas"} 

استبطان - سبر غور

قبل تضمين العامل Reflect objectES6 في ES6 ، لا يزال بإمكاننا القيام بعملية الاستبطان. فيما يلي مثال على قراءة هيكل البرنامج:

var users = { 'Tom': 32, 'Bill': 50, 'Sam': 65 }; Object.keys(users).forEach(name => { const age = users[name]; console.log(`User ${name} is ${age} years old!`); }); 

نحن هنا نقرأ usersبنية الكائن ونسجل قيمة المفتاح في جملة.

User Tom is 32 years old! User Bill is 50 years old! User Sam is 65 years old! 

التعديل الذاتي

لنأخذ كائن مدونة له طريقة لتعديل نفسه:

var blog = { name: 'freeCodeCamp', modifySelf: function(key, value) {blog[key] = value} } 

و blogيمكن الكائن تعديل نفسها من خلال ذلك:

blog.modifySelf('author', 'Tapas'); 

شفاعة

Intercessionهو العمل نيابة عن شيء آخر عن طريق تغيير دلالات اللغة. على   Object.defineProperty()الطريقة يمكن تغيير دلالات الكائن:

var sun = {}; Object.defineProperty(sun, 'rises', { value: true, configurable: false, writable: false, enumerable: false }); console.log('sun rises', sun.rises); sun.rises = false; console.log('sun rises', sun.rises); 

انتاج،

sun rises true sun rises true 

كما ترى ، sunتم إنشاء الكائن ككائن عادي ثم تم تغيير المعنى بحيث لا يكون قابلاً للكتابة.

الآن دعونا القفز إلى فهم Reflectو Proxyالكائنات مع الأعراف الخاصة بكل منها.

انعكاس API

في ES6 ، يعد Reflect عنصرًا جديدًا Global Object(مثل الرياضيات) يوفر عددًا من وظائف الأداة المساعدة ، يبدو أن العديد منها يتداخل مع طرق ES5 المحددة على المستوى العام Object.

كل هذه الوظائف هي وظائف استبطان حيث يمكنك الاستعلام عن بعض التفاصيل الداخلية حول البرنامج في وقت التشغيل.

فيما يلي قائمة بالطرق المتاحة من Reflectالكائن. يرجى زيارة هذه الصفحة للاطلاع على مزيد من التفاصيل حول كل طريقة من هذه الطرق.

// Reflect object methods Reflect.apply() Reflect.construct() Reflect.get() Reflect.has() Reflect.ownKeys() Reflect.set() Reflect.setPrototypeOf() Reflect.defineProperty() Reflect.deleteProperty() Reflect.getOwnPropertyDescriptor() Reflect.getPrototypeOf() Reflect.isExtensible() 

لكن انتظر ، إليك سؤال: لماذا نحتاج إلى كائن واجهة برمجة تطبيقات جديد بينما يمكن أن يكون موجودًا بالفعل أو يمكن إضافته Objectأو Function؟

مشوش؟ دعنا نحاول معرفة ذلك.

كل ذلك في مساحة اسم واحدة

يدعم JavaScript بالفعل انعكاس الكائن. لكن واجهات برمجة التطبيقات هذه لم يتم تنظيمها ضمن مساحة اسم واحدة. منذ ES6 هم الآن تحت Reflect.

على عكس معظم الكائنات العالمية ، فإن Reflect ليس منشئًا. لا يمكنك استخدامه مع عامل تشغيل جديد أو استدعاء كائن Reflect كدالة. كل الخصائص والأساليب Reflectهي staticمثل الكائن الرياضيات.

سهلة الاستخدام

و introspectionطرق Objectبطرح استثناء عندما فشلوا في إتمام العملية. هذا عبء إضافي على المستهلك (المبرمج) للتعامل مع هذا الاستثناء في الكود.

You may prefer to handle it as a boolean(true | false) instead of using exception handling. The Reflect object helps you do that.

Here's an example with Object.defineProperty:

 try { Object.defineProperty(obj, name, desc); // property defined successfully } catch (e) { // possible failure and need to do something about it }

And with the Reflect API:

if (Reflect.defineProperty(obj, name, desc)) { // success } else { // failure (and far better) } 

The impression of the First-Class operation

We can find the existence of a property for an object as (prop in obj). If we need to use it multiple times in our code, we must explicitly wrap this operation in a function and pass the operation around as a first-class value.

In ES6, we already had those as part of the Reflect API as the first-class function. For example, Reflect.has(obj, prop) is the functional equivalent of (prop in obj).

Let's look at another example: Delete an object property.

const obj = { bar: true, baz: false}; // delete object[key] function deleteProperty(object, key) { delete object[key]; } deleteProperty(obj, 'bar'); 

With the Reflect API:

// With Reflect API Reflect.deleteProperty(obj, 'bar'); 

A more reliable way of using the apply() method

In ES5, we can use the apply() method to call a function with a given this value and passing an array as an argument.

Function.prototype.apply.call(func, obj, arr); // or func.apply(obj, arr); 

This is less reliable because func could be an object that would have defined its own apply method.

In ES6 we have a more reliable and elegant way of solving this:

Reflect.apply(func, obj, arr); 

In this case, we will get a TypeError if func is not callable. Also, Reflect.apply() is less verbose and easier to understand.

Helping other kinds of reflection

Wewill see what this means in a bit when we learn about the Proxy object. The Reflect API methods can be used with Proxy in many use cases.

The Proxy Object

ES6's Proxy object helps in intercession.

The proxy object defines custom behaviors for fundamental operations (for example, property lookup, assignment, enumeration, function invocation, and so on).

Here are a few useful terms you need to remember and use:

  • The target: An object which the proxy virtualizes.
  • The handler: A placeholder object which contains traps.
  • The trap: Methods that provide property access to the target object.

It is perfectly fine if you don't quite understand yet from the description above. We will get a grasp of it through code and examples in a minute.

The syntax to create a Proxy object is as follows:

let proxy = new Proxy(target, handler); 

There are many proxy traps (handler functions) available to access and customize a target object. Here is the list of them. You can read a more detailed description of traps here.

handler.apply() handler.construct() handler.get() handler.has() handler.ownKeys() handler.set() handler.setPrototypeOf() handler.getPrototypeOf() handler.defineProperty() handler.deleteProperty() handler.getOwnPropertyDescriptor() handler.preventExtensions() handler.isExtensible() 

Note that each of the traps has a mapping with the Reflect object's methods. This means that you can use Reflect and Proxy together in many use cases.

How to get unavailable object property values

Let's look at an example of an employee object and try to print some of its properties:

const employee = { firstName: 'Tapas', lastName: 'Adhikary' }; console.log(employee.firstName); console.log(employee.lastName); console.log(employee.org); console.log(employee.fullName); 

The expected output is the following:

Tapas Adhikary undefined undefined 

Now let's use the Proxy object to add some custom behavior to the employee object.

Step 1: Create a Handler that uses a get trap

We will use a trap called get which lets us get a property value. Here is our handler:

let handler = { get: function(target, fieldName) { if(fieldName === 'fullName' ) { return `${target.firstName} ${target.lastName}`; } return fieldName in target ? target[fieldName] : `No such property as, '${fieldName}'!` } }; 

The above handler helps to create the value for the fullName property. It also adds a better error message when an object property is missing.

Step 2: Create a Proxy Object

As we have the target employee object and the handler, we will be able to create a Proxy object like this:

let proxy = new Proxy(employee, handler); 

Step 3: Access the properties on the Proxy object

Now we can access the employee object properties using the proxy object, like this:

console.log(proxy.firstName); console.log(proxy.lastName); console.log(proxy.org); console.log(proxy.fullName); 

The output will be:

Tapas Adhikary No such property as, 'org'! Tapas Adhikary 

Notice how we have magically changed things for the employee object!

Proxy for Validation of Values

Let's create a proxy object to validate an integer value.

Step 1: Create a handler that uses a set trap

The handler looks like this:

const validator = { set: function(obj, prop, value) { if (prop === 'age') { if(!Number.isInteger(value)) { throw new TypeError('Age is always an Integer, Please Correct it!'); } if(value < 0) { throw new TypeError('This is insane, a negative age?'); } } } }; 

Step 2: Create a Proxy Object

Create a proxy object like this:

let proxy = new Proxy(employee, validator); 

Step 3: Assign a non-integer value to a property, say, age

Try doing this:

proxy.age = 'I am testing a blunder'; // string value 

The output will be like this:

TypeError: Age is always an Integer, Please Correct it! at Object.set (E:\Projects\KOSS\metaprogramming\js-mtprog\proxy\userSetProxy.js:28:23) at Object. (E:\Projects\KOSS\metaprogramming\js-mtprog\proxy\userSetProxy.js:40:7) at Module._compile (module.js:652:30) at Object.Module._extensions..js (module.js:663:10) at Module.load (module.js:565:32) at tryModuleLoad (module.js:505:12) at Function.Module._load (module.js:497:3) at Function.Module.runMain (module.js:693:10) at startup (bootstrap_node.js:188:16) at bootstrap_node.js:609:3 

Similarly, try doing this:

p.age = -1; // will result in error 

How to use Proxy and Reflect together

Here is an example of a handler where we use methods from the Reflect API:

const employee = { firstName: 'Tapas', lastName: 'Adhikary' }; let logHandler = { get: function(target, fieldName) { console.log("Log: ", target[fieldName]); // Use the get method of the Reflect object return Reflect.get(target, fieldName); } }; let func = () => { let p = new Proxy(employee, logHandler); p.firstName; p.lastName; }; func();

A few more Proxy use cases

There are several other use-cases where this concept can be used.

  • To protect the ID field of an object from deletion (trap: deleteProperty)
  • To trace Property Accesses (trap: get, set)
  • For Data Binding (trap: set)
  • With revocable references
  • To manipulate the in operator behavior

... and many more.

Metaprogramming Pitfalls

While the concept of Metaprogramming gives us lots of power, the magic of it can go the wrong way sometimes.

Be careful of:

  • Too much magic! Make sure you understand it before you apply it.
  • Possible performance hits when you're making the impossible possible
  • Could be seen as counter-debugging.

In Summary

To summarize,

  • Reflect and Proxy are great inclusions in JavaScript to help with Metaprogramming.
  • Lots of complex situations can be handled with their help.
  • كن على دراية بالجوانب السلبية أيضًا.
  • يمكن أيضًا استخدام رموز ES6 مع الفئات والكائنات الموجودة لديك لتغيير سلوكها.

أتمنى أن تكون قد وجدت هذه المقالة ثاقبة. يمكن العثور على جميع التعليمات البرمجية المصدر المستخدمة في هذه المقالة في مستودع GitHub الخاص بي.

يرجى مشاركة المقالة حتى يتمكن الآخرون من قراءتها أيضًا. يمكنك @ me على Twitter (tapasadhikary) مع التعليقات ، أو لا تتردد في متابعتي.