تغيير ال context ب call و apply


السلام عليكم و رحمه الله تعالى و بركاته

الوظائف فى جافاسكربت بعد اعلانها تتحول لكائنات مشتقه من الصنف Function ، و بالتالى ترث كل وظائفه و من اهمها call و apply ، الاثنين يقومان بنفس العمل و هو تغير ال context اى الى ماذا تشير this داخل الوظيفه .

عليك فهم قواعد اشاره this العامه قبل الخوض فى apply و call ، داخل الوظيفه العاديه تشير this الى ال global object الذى فى بيئه المتصفح هو الكائن window كما يوضح المثال البسيط التالى :

function User(){
    return this;
}

قمت بإنشاء وظيفه جديده بإسم User تقوم بإرجاع this ، عندما نقوم بإختبارها كالتالى :

alert(User()); // object window

اذا تم استدعاء الوظيفه User فإنها تقوم بإرجاع this التى تشير الى ال global object window و بالتالى يمكن إستخدام وظائف window كما يوضح الكود البسيط التالى :

mm(); // return object window
mm().prompt("Do you believe me ?");

اما اذا تم استخدام الوظيفه User على انها function constructor فإن this بداخلها ستشير إلى الكائن الجديد الذى تم اشتقاقه منها ، كما يوضح المثال البسيط التالى :

var me = new User();

this داخل User فى هذه الحاله تشير إلى الكائن me الذى تم اشتقاقه منها ، لذلك this تستخدم داخل ال function constructor لعمل public attributes كما يوضح المثال البسيط التالى :

function User(name){
    this.name = name;
    this.sayHello = function(){
        alert("hello, my name's "+this.name);
   }
}

يمكننا استخدام User كالتالى :

var me = new User("Mostafa");
me.sayHello(); // hello, my name's Mostafa

طبعا اذا تم استخدام User بدون اشتقاق كائن جديد منها عن طريق new ، فإن this بداخلها ستشير إلى ال global object و الخصائص و الوظائف بداخلها سترتبط بال global object بالتالى سيحدث error كما يوضح الكود البسيط التالى :

User("Mostafa"); // assign name to global object
alert(name); // Mostafa
sayHello(); // hello, my name's Mostafa

الكود السابق يؤدى الى اضافه الكثير من المتغيرات الى ال global scope و قد يؤدى الى الكتابه على متغيرات هامه جدا ، و هو ما يجب اجتنابه تمام ، يمكننا تجنب ذلك عن طريق جعل اول حرف من الوظيفه capital حتى نتذكر انها constructor و اضافه تعليق يوضح ذلك ، او اضافه الكود البسبط التالى ليقوم بإختبار هل User استخدمت كوظيفه عاديه ام استخدامت ك function constructor كما يوضح الكود البسيط التالى :

function User(name){
    if(this === window){
        throw new Error("User is function constructor, must be used with [new]");
    }
    this.name = name;
    this.sayHello = function(){
        alert("hello, my name's "+this.name);
   }
}

قمت بإضافه شرط يفحص هل this تساوى ال global object window ، هذا الشرط صحيح اذا تم استخدام User كوظيفه عاديه و عليه اقوم بتعطيل الترجمه عن طريق انشاء كائن جديد من نوع Error و القاءه للمترجم الذى سيعطل البرنامج فورا ، اما اذا تم استخدام User بالطريقه الصحيحه ك function constructor سيعمل الكود كما يجب و لن يتم تلويث ال global scope بالمتغيرات كما يوضح الكود التالى :

User("Almhajer"); // Error: User is function constructor, must be used with [new]

الكود السابق يعطل الترجمه و يقف التطبيق فورا و الكود الذى بعده بالطبع لن يعمل ، اما الكود التالى فيعمل😀 جيدا :

var me = new User("Omar Al-Dolaimy");
me.sayHello(); // hello, my name's Omar Al-Dolaimy

this داخل Object prototype قد شرحتها هنا بالتفصيل .

بقى this داخل prototype ، تشير this داخل ال prototype الى الكائن الذى يشتق من ال function constructor عن طريق new ، كما يوضح الكود البسيط التالى :

function User(name){
    this.name = name;
}
User.prototype = {
    sayHello:function(){
        alert("hello, my name's "+this.name)
    }
}

يمكننا استخدام الكود السابق كما يوضح الكود الذى يسبقه بدون مشاكل .

كل القواعد السابقه يمكن قلبها رأسا على عقب عن طريق الوظيفه apply و call التى تورثها اى وظيفه من الصنف Function ، الاثنين يقوموا بتغير ال context اى الى ماذا تشير الكلمه this ، الوظيفتين يقبلا العباره الاولى الكائن الجديد الذى يحل محل this ، العباره التانيه العبارات التى تمرر للوظيفه التى يتم تغير ال context لها ، فى call العبارات تمرر داخل نص string ، انا فى apply العبارات تمرر داخل arry كما يوضح الكود البسيط التالى :

function changeColor(color){
    this.style.color = color;
    /* [this] point to window */
}

this داخل الوظيفه changeColor تشير إلى window ، يمكننا تغير اشاره this الى كائن مناسب كما يوضح الكود التالى :

var elem = document.getElementById("header");
changeColor.call(elem,"#C00");

او يمكننا تغير ال context عن طريق استخدام apply كما يوضح الكود البسيط التالى

changeColor.apply(elem,["#C00"]);

الوظيفه call تمرر العبارات للوظيفه التى يتم تغير ال context لها فى صوره نص ، اما apply فتمررها فى صوره مصفوفه array ، و فى كلتا الحالتين فإن الكود داخل الوظيفه عند استدعاء call او apply يتم ترجمته داخل محرك الجافاسكربت كالتالى :

elem.style.color = "#C00";

تستخدم call و apply ايضا بكثره مع arguments collection داخل الوظائف ، كما ذكرت من قبل داخل كل وظيفه يوجد الكلمه arguments التى تشير إلى العبارات التى مررت لهذه الوظيفه ، لها الخاصيه length لمعرفه عدد العبارات ، لكنها ليست مصفوفه Array لذلك لا يمكنك استخدام وظائف ال array معها مثل push و shift و unshift و pop وreverse و slice … الخ ، لكننا يمكننا استخدامها عن طريق call او apply كما يوضح الكود البسيط التالى :

function mm(){
    alert(Array.prototype.slice.call(arguments,"2"));
//  alert(Array.prototype.slice.apply(arguments,[2]));
}

mm(1,2,3,4,5); // 3,4,5
mm("i","will","dominate","the","world"); // dominate,the,world

لا يمكنك استخدام arguments.slice مباشرا داخل الوظيفه ، لأن arguments كما اشرت من نوع collection لا يمكن استخدام وظائف المصفوفه معها ، لكن الحل فى استدعاء وظائف المصفوفه عن طريق Array.prototype.slice و تغير ال context عن طريق call او apply كما يوضح المثال السابق ، و بالتالى this داخل slice ستتحول لتشير إلى arguments و فى نفس الوقت سيحدث تغيير للنوع type conversion و ستتحول arguments من collection الى array .

الوراثه و تغيير ال context

يستخدم ايضا تغيير ال context عن طريق call و apply من اجل عمل delegation ، ال delegation هو نقل المهمه من كائن لكائن اخر ليقوم بها نيابه عنه ، التى تنتشر كثيرا فى الوراثه كما يوضح المثال التالى :

function Person(name){
    this.name= name;
}
Person.prototype.getName =function(){
    return this.name;
};

function Author(name,books){
    Person.call(this,name); // equals this.name = name;
    this.books = books;
}

قمت بإنشاء اتنين function constructor الاول اسمه Person يحتوى على خاصيه عامه إسمها name ، و الثانى إسمه Author له خاصيتين الاولى books و الثانيه name ، هيا بنا ننشأ وظيفه جديده تسهل علينا مهمه الوراثه بإسم extend كما يوضح الكود البسيط التالى :

function extend(subClass,superClass){
    var F = function(){};
    F.prototype = superClass.prototype
    subClass.prototype = new F();
    subClass.prototype.constructor = subClass;
}

هذه الوظيفه تقوم بوراثه superClass و اعطائه ل subClass ، كما يوضح المثال البسيط التالى :

extend(Author,Person);
Author.prototype.getBooks = function(){
    return this.books;
}

يمكننا استخدام Author كما يوضح المثال التالى :

var me = new Author("Mostafa Farghaly","nothing yet!");
me.getName(); // Mostafa Farghaly (inherited)
me.getBooks(); // nothing yet!

لكن عيب هذه الطريقه فى الوراثه هو ان ال subClass و superClass مرتبطين ببعض جدا tightly coupled ، و من أهم أسس ال OO ان الاصناف لابد ان تكون loosely coupled اى غير مرتبطين ولا يعتمدوا على بعض ، فى الكود السابق يظهر الخلل هنا Person.call و اعطائه العبارات this,name ، ماذا اذا تغير person او ماذا اذا اردت ان ترث من كائن جديد نهائيا ؟ الحل فى تعديل كود الوراثه كما يوضح الكود التالى :

function extend(subClass,superClass){ /*Improved*/
    var F = function(){};
    F.prototype = superClass.prototype
    subClass.prototype = new F();
    subClass.prototype.constructor = subClass;
    subClass.superClass = superClass.prototype;
}

التحديث : قمت بإعطاء ال subClass كما يوضح الكود السابق static property بإسم superClass تشير إلى ال superClass.prototype التى ترث منه ، يوجد فى الكثير من لغات البرمجه الكلمه super التى تشير إلى الصنف الذى يرث منه الصنف الحالى ، و بالتالى تكون generic لا تشير الى صنف بإسم و عينه ، و عليه سنعدل ال Author constructor ليكون افضل و loosely coupled كما يوضح الكود البسيط التالى :

function Author(name,books){
    Author.superClass.constructor.call(this,name);
    /* insted of Person.call(this.name);*/
    this.books = books;
}
extend(Author,person);
Author.prototype.getBooks = function(){
    return this.book;
}

اضافه الخاصيه superClass إلى ال Author يمكننا من استدعاء وظائف الصنف الذى يرث منه مباشرا بدون ذكر اسمه و بهذا نحن نطبق قاعده ال OO الشهيره code to an interafce not implementation ، و يمكن عمل override للوظائف التى ترثها منه كما يوضح الكود التالى :

Author.prototype.getName = function(){
    var name = Author.superClass.getName.call(this);
    return "My Name's "+ name ;
}

و يمكننا استخدام Author كما يوضح الكود البسيط التالى :

var me = new Author("Mostafa Farghaly","Underground JavaScript");
me.getName(); // My Name's Mostafa Farghaly
me.getBooks(); // Underground JavaScript

للمزيد

Mozilla Developers Center » Function.call
Mozilla Developers Center » Function.apply

تم اضافه هذا الموضوع لدروس OOP Javascript .

الأوسمة: , , ,

رد واحد to “تغيير ال context ب call و apply”

  1. almhajer Says:

    روح ياشيخ الله يفتح عليك فتح العارفين ان شاء الله ويجعله في ميزان حسناتك
    انا اتوق لمثل هذه الدروس والشرح الوافي والطويل الذي يزيد خبره ربنا يفتح عليك ويارب تتحفنا بالمزيد وكما ذكرت اخي العملية كلها تقع على عاتق الوراثة وفكر المبرمج بصراحة هذه حيل من الطراز الاول وبجسب فهمي ان العملية تكمن في المؤشر ونقل المؤشر ف this يعتبر مؤشر يدل الى شي ما دون التطرق الى ماهيته وبذلك يحملك ان تستخدمه دون معرف ماذايحمل
    فالعمل كله يقع على عاتق المؤشر وهذا مايسمى المؤشر الذاتي والمؤشر الرجعي

    وخلال تطبيقي الى الاكواد حصلت على كود كالتالي
    function bk(){
    return this.document.getElementById;
    }

    bk()(“header”).value=2000000;
    نلاحظ انه يعمل على الطرفين متصفح موزلا وانترنت اكسبلور مع العلم ان الفاير بوك لفاير فوكس يقول يوجد هنا استثناء دون معرفة تحديد ماهو او تم تجاوز التعريف ولو ازلنا
    القوس من اما bk كالتالي
    bk(“header”).value=2000000;

    لعمل الكود على فاير فوكس بصحة دون اطلاق اي تنبيه بينما الفرحة التي لاتكتمل على وجه اكسبلور انه لايعمل شكرا جزيلا وبارك الله فيك وشرح اكثر من رائع

أضف تعليقاً

إملأ الحقول أدناه بالمعلومات المناسبة أو إضغط على إحدى الأيقونات لتسجيل الدخول:

WordPress.com Logo

أنت تعلق بإستخدام حساب WordPress.com. تسجيل خروج   / تغيير )

صورة تويتر

أنت تعلق بإستخدام حساب Twitter. تسجيل خروج   / تغيير )

Facebook photo

أنت تعلق بإستخدام حساب Facebook. تسجيل خروج   / تغيير )

Google+ photo

أنت تعلق بإستخدام حساب Google+. تسجيل خروج   / تغيير )

Connecting to %s


%d مدونون معجبون بهذه: