تقديم الخوادم الوكيلة ES2015

آدي عثمانية
آدي عثمانية

توفّر الخوادم الوكيلة ES2015 (في Chrome 49 والإصدارات الأحدث) واجهة برمجة تطبيقات للاستجابة للغة JavaScript، ما يتيح لنا رصد جميع العمليات على عنصر مستهدَف أو اعتراضها وتعديل آلية عمل هذا الهدف.

هناك عدد كبير من الاستخدامات للخوادم الوكيلة، منها:

  • قطْع التمريرة
  • المحاكاة الافتراضية للعناصر
  • إدارة الموارد
  • إنشاء ملف تعريف أو تسجيل لتصحيح الأخطاء
  • الأمان والتحكم في الوصول
  • عقود استخدام الأغراض

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

var target = { /* some properties */ };
var handler = { /* trap functions */ };
var proxy = new Proxy(target, handler);

يتم التحكّم في سلوك الخادم الوكيل من خلال المعالج، الذي يمكنه تعديل السلوك الأصلي لكائن target بعدة طرق مفيدة. يحتوي المعالج على طرق اختيارية (مثل .get() و.set() و.apply()) يتم استدعاؤها عند تنفيذ العملية المقابلة على الخادم الوكيل.

قطْع التمريرة

لنبدأ بأخذ كائن عادي وإضافة البرمجيات الوسيطة الخاصة بالاعتراض إليه باستخدام واجهة برمجة التطبيقات Proxy API. تذكر أن المعلمة الأولى التي يتم تمريرها إلى الدالة الإنشائية هي الهدف (الكائن يتم إنشاء وكيل له) والثانية هي المعالج (الوكيل نفسه). هذا هو المكان الذي يمكننا فيه إضافة خطافات لمربعات الضرب أو أدوات التصفية أو السلوكيات الأخرى.

var target = {};

var superhero = new Proxy(target, {
    get: function(target, name, receiver) {
        console.log('get was called for:', name);
        return target[name];
    }
});

superhero.power = 'Flight';
console.log(superhero.power);

عند تشغيل الرمز أعلاه في Chrome 49، نحصل على ما يلي:

get was called for: power  
"Flight"

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

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

var target = {};

var proxy = new Proxy(target, {});
    // operation forwarded to the target
proxy.paul = 'irish';
// 'irish'. The operation has been  forwarded
console.log(target.paul);

لقد نظرنا للتو في إنشاء وكيل للكائنات العادية، ولكن يمكننا ببساطة وكيل كائن الدالة، حيث تكون الدالة هي هدفنا. سنستخدم هذه المرة فخ handler.apply():

// Proxying a function object
function sum(a, b) {
    return a + b;
}

var handler = {
    apply: function(target, thisArg, argumentsList) {
        console.log(`Calculate sum: ${argumentsList}`);
        return target.apply(thisArg, argumentsList);
    }
};

var proxy = new Proxy(sum, handler);
proxy(1, 2);
// Calculate sum: 1, 2
// 3

تحديد الخوادم الوكيلة

يمكن ملاحظة هوية الخادم الوكيل باستخدام عاملَي المساواة في JavaScript (== و===). وكما نعلم، عند تطبيقها على كائنَين، تقارن هذه العوامل هويات الكائنات. يوضح المثال التالي هذا السلوك. تؤدي مقارنة خادمين وكيلين منفصلين إلى عرض خطأ على الرغم من تشابه الأهداف الأساسية. على نفس المنوال، يختلف الكائن المستهدف عن أي من الخوادم الوكيلة الخاصة به:

// Continuing previous example

var proxy2 = new Proxy (sum, handler);
(proxy==proxy2); // false
(proxy==sum); // false

من المفترَض ألا تتمكّن من تمييز الوكيل عن أي كائن غير تابع لخادم وكيل، وبالتالي لا يؤثر تفعيل خادم وكيل في نتائج تطبيقك. وهذا هو أحد الأسباب التي تجعل من واجهة برمجة تطبيقات Proxy API لا تتضمّن طريقة للتحقّق مما إذا كان أحد العناصر وكيلاً ولا يوفِّر فخاخًا لجميع العمليات على العناصر.

حالات الاستخدام

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

الخوادم الوكيلة كمعالجات

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

var validator = {
    set: function(obj, prop, value) {
    if (prop === 'yearOfBirth') {
        if (!Number.isInteger(value)) {
        throw new TypeError('The yearOfBirth is not an integer');
        }

        if (value > 3000) {
        throw new RangeError('The yearOfBirth seems invalid');
        }
    }

    // The default behavior to store the value
    obj[prop] = value;
    }
};

var person = new Proxy({}, validator);

person.yearOfBirth = 1986;
console.log(person.yearOfBirth); // 1986
person.yearOfBirth = 'eighties'; // Throws an exception
person.yearOfBirth = 3030; // Throws an exception

قد تأخذ الأمثلة الأكثر تعقيدًا لهذا النمط في الاعتبار جميع معالِجات الوكيل المختلفة للعمليات التي يمكن أن تعترضها. يمكن للمرء أن يتخيل عملية تنفيذ تحتاج إلى تكرار نمط فحص الوصول وإعادة توجيه العملية في كل فخ.

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

إضافة العنصر

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

function extend(sup,base) {

    var descriptor = Object.getOwnPropertyDescriptor(base.prototype,"constructor");

    base.prototype = Object.create(sup.prototype);

    var handler = {
    construct: function(target, args) {
        var obj = Object.create(base.prototype);
        this.apply(target,obj, args);
        return obj;
    },

    apply: function(target, that, args) {
        sup.apply(that,args);
        base.apply(that,args);
    }
    };

    var proxy = new Proxy(base, handler);
    descriptor.value = proxy;
    Object.defineProperty(base.prototype, "constructor", descriptor);
    return proxy;
}

var Vehicle = function(name){
    this.name = name;
};

var Car = extend(Vehicle, function(name, year) {
    this.year = year;
});

Car.prototype.style = "Saloon";

var Tesla = new Car("Model S", 2016);

console.log(Tesla.style); // "Saloon"
console.log(Tesla.name); // "Model S"
console.log(Tesla.year);  // 2016

التحكم في الوصول

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

استخدام الانعكاس مع الخوادم الوكيلة

Reflect هو كائن مدمَج جديد يوفر طرقًا لإجراء عمليات JavaScript قابلة للاعتراض، وهو مفيد جدًا للعمل مع الخوادم الوكيلة. وفي الواقع، تتشابه طرق الانعكاس مع طرق معالجات الخادم الوكيل.

لطالما قدمت اللغات المكتوبة بشكل ثابت مثل Python أو C# واجهة برمجة تطبيقات للانعكاس، ولكن JavaScript لم تكن بحاجة إلى واحدة لكونها لغة ديناميكية. يمكن القول بأنّ ES5 يتضمّن عددًا قليلاً جدًا من ميزات الانعكاس، مثل Array.isArray() أو Object.getOwnPropertyDescriptor() التي تُعتبر انعكاسًا بلغات أخرى. تقدم ES2015 واجهة برمجة تطبيقات الانعكاس التي ستضم طرقًا مستقبلية لهذه الفئة، مما يسهل شرحها. هذا منطقي حيث من المفترض أن يكون الكائن نموذجًا أوّليًا أساسيًا بدلاً من مجموعة لطرق الانعكاس.

باستخدام Reflect، يمكننا تحسين مثالنا السابق على البطل الخارق من أجل اعتراض الميدان بشكل صحيح أثناء الاستحواذ على الفخاخ وتحديدها على النحو التالي:

// Field interception with Proxy and the Reflect API

var pioneer = new Proxy({}, {
    get: function(target, name, receiver) {
        console.log(`get called for field: ${name}`);
        return Reflect.get(target, name, receiver);
    },

    set: function(target, name, value, receiver) {
        console.log(`set called for field: ${name} and value: ${value}`);
        return Reflect.set(target, name, value, receiver);
    }
});

pioneer.firstName = 'Grace';
pioneer.secondName = 'Hopper';
// Grace
pioneer.firstName

ما المخرجات:

set called for field: firstName and value: Grace
set called for field: secondName and value: Hopper
get called for field: firstName

مثال آخر هو المكان الذي قد يرغب المرء فيه:

  • يمكنك لف تعريف خادم وكيل داخل دالة إنشاء مخصّصة لتجنب إنشاء خادم وكيل جديد يدويًا في كل مرة نريد فيها العمل بمنطق محدّد.

  • أضف إمكانية "حفظ" التغييرات، ولكن فقط إذا تم تعديل البيانات بالفعل (نظريًا لأن عملية الحفظ مكلفة للغاية).

function Customer() {

    var proxy = new Proxy({
    save: function(){
        if (!this.dirty){
        return console.log('Not saving, object still clean');
        }
        console.log('Trying an expensive saving operation: ', this.changedProperties);
    },

    }, {

    set: function(target, name, value, receiver) {
        target.dirty = true;
        target.changedProperties = target.changedProperties || [];

        if(target.changedProperties.indexOf(name) == -1){
        target.changedProperties.push(name);
        }
        return Reflect.set(target, name, value, receiver);
    }

    });

    return proxy;
}


var customer = new Customer();

customer.name = 'seth';
customer.surname = 'thompson';
// Trying an expensive saving operation:  ["name", "surname"]
customer.save();

لمزيد من أمثلة واجهة برمجة التطبيقات Reflect، يُرجى الاطّلاع على خوادم ES6 الوكيلة في Tagtree.

Polyfilling Object.observe()

على الرغم من أنّه سيتم إيقاف خدمة Object.observe()، أصبح بالإمكان الآن تعويضها باستخدام الخوادم الوكيلة ES2015. كتب سايمون بلاكويل Object.observe() shim يستند إلى Proxy، مؤخرًا يستحق التحقق. كتب "إريك أرفيدسون" أيضًا نسخة كاملة من المواصفات إلى حد ما منذ عام 2012.

المتصفحات المتوافقة

تكون الخوادم الوكيلة ES2015 متوافقة في Chrome 49 وOpera وMicrosoft Edge وFirefox. وقد كانت لدى Safari إشارات عامة بشأن الميزة، لكننا ما زلنا متفائلين. يتوفّر Reflect حاليًا في متصفّح Chrome وOpera وFirefox وهو في مرحلة التطوير لمتصفّح Microsoft Edge.

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

محتوى إضافي للقراءة