במאמר הקודם דיברנו מעט על closure ב-JavaScript ועל היתרונות שלו (נוחות כתיבה משמעותית) והחסרונות שלו (יכול לפגוע משמעותית בביצועים). בסוף המאמר הבטחתי שאני אדבר על חלופה ל-closure שבמקרים מסוימים יכולה להאיץ את הביצועים ושווה להכיר אותה. החלופה הזו היא שימוש מסיבי ב-prototype במקום ב-closure.
נשאלת השאלה, מה זה prototype? האם זה פריימוורק חדש ומגניב של JavaScript? אלו מכם שמכירים היטב Vanilla JavaScript אמורים להכיר היטב מה זה prototype, אבל לכל השאר, ובמיוחד לפוחחי jQuery, אני אסביר שמדובר במנגנון של JavaScript שמאפשר להוסיף מתודות או תכונות לאובייקטים קיימים. נשמע סתום ולא קשור? בואו ונדגים. אני מניח שיש לכם ידע ולו בסיסי ב-JavaScript מונחה עצמים. אם לא, זה הזמן ללחוץ על הקישור ולקרוא קצת לפני שתמשיכו הלאה.
בואו וניצור אובייקט בסיסי ופשוט, לא ליטרלי בשם Person וניצור אותו עם האופרטור new:
function Person(name) {
this.name = name;
}
var c = new Person('John');
עד כאן הכל פשוט ויפה, יצרנו אובייקט ויצרנו לו אפילו איזה פעולת construct חמודה שרצה בהתחלה ויוצרת תכונת name לאובייקט שלנו, אם נריץ את הקוד הבא, נוכל לקבל את התכונה הזו:
console.log(c.name);
נניח ואני גם רוצה להוסיף מתודה לאובייקט החביב שלי. כפי שלמדנו קודם לכן, אני יכול להוסיף מתודה לאובייקט ובאמצעות closure אני יכול להיות בטוח שלמתודה הזו כל התכונות והמשתנים בקונטקסט יהיו זמינים. בואו וניצור get ו-set לשם:
var person = function(name) {
return {
'get_name': function() {
return name;
},
'set_name': function(newName) {
name = newName;
}
};
};
var c = new person('John');
console.log(c.get_name());
התוצאה בקונסולה תהיה השם שהכנסתי לקונסטרקטור, במקרה הזה John. איך זה עובד? כי ה-closure ידאג שלכל מתודה באובייקט תהיה גישה לכל המשתנים בקונטקסט, בדיוק כמו שלמדנו במאמר הקודם. כל עוד אני יכול לקרוא למתודות האלו מבחוץ (ואני יכול) מנוע הג'אווהסקריפט ישמור את כל המשתנים בקונטקסט בזכרון לכל instance ולא ישחרר אותם לעולם.
אבל יש דרך נוספת – להוסיף מתודות לאובייקט שלי לאחר ההגדרה שלו באמצעות prototype וכך בעצם לאפשר למנוע הג'אווהסקריפט לנהל את הזכרון יותר ביעילות, כיוון שאנו לא משתמשים ב-closure ומנוע הג'אווהסקריפט יכול למחוק את המשתנים המיותרים. איך זה עובד? שימו לב לדוגמה הבאה:
function Person(name) {
this.name = name;
}
Person.prototype.get_name = function() {
return this.name;
};
Person.prototype.set_name = function(name) {
this.name = name;
};
var c = new Person('John');
console.log(c.get_name());
קל לראות איך להוסיף מתודות באמצעות prototype – במקום להגדיר אותן בתוך האובייקט, אנו מעניקים לאובייקט שלנו את המתודות באמצעות JavaScript Prototype. האם יש לכך השלכה על הביצועים? כן, באופן דרמטי. אל תאמינו לי! שימו לב למבחן הרלוונטי שיש ב-jspref בנוגע ל-closure מול prototype:
זה אמור לשכנע אתכם. בטח תשאלו את השאלה – איך בדיוק אפשר להשתמש בזה ב-jQuery? התשובה היא שלמרבה הצער אי אפשר, jQuery מסתמכת באופן רציני ביותר על closure ופחות על prototype, אבל אם יש לכם בעית ביצועים רצינית בסקריפט, או הרבה אובייקטים, אתם יכולים לצאת קצת מאיזור הנוחות ולהתחיל לעבוד ב-vanilla JavaScript. כך למשל, במקום להשתמש באירוע click של jQuery בדוגמה במאמר הקודם ששומר את כל המשתנים באמצעות closure, אנחנו יכולים להשתמש ב-prototype. אני אדגים באופן גס ביותר.
הנה הקוד המקורי שאותו הבאתי במאמר הקודם, הקוד הזה משתמש ב-closure, על פניו הוא טוב:
// When the DOM loads, initialized the user interface.
$(
function() {
// Gather all the links in the document.
var jLinks = $("a");
// Loop over each link.
jLinks.each(
function(intLinkIndex, objLink) {
// Get a jQuery reference to this link.
var jThis = $(this);
// Bind the click handler.
jThis.click(
function(objEvent) {
// Alert the link index in the context of the
// greater document.
alert("I an link " + (intLinkIndex + 1) + " of " + jLinks.length);
// Prevent default link behavior.
return (false);
});
});
});
אגב, למי שמעוניין, מקור הקוד נמצא גם ב-jsFiddle.
יש כמה דברים טובים לומר על הקוד הזה – הוא קריא, הוא נחמד, קל לשנות אותו וקל להבין מה מתרחש בו. אבל בואו ונניח שיש לי בעית ביצועים קשה בדף שבו הוא נמצא. אם אני הבנתי את עניין ה-closure, ואני יודע לעבוד עם אובייקטים ועם prototype, אני יכול לייעל את הקוד באופן הבא:
//creating jThis object blue print
function jThis(_this) {
}
//Creating methods with protypes for handling two properties
jThis.prototype.getIndex = function() {
return this.index;
}
jThis.prototype.setIndex = function(index) {
this.index = index;
}
jThis.prototype.getLength = function() {
return this.len;
}
jThis.prototype.setLength = function(len) {
this.len = len;
}
// When the DOM loads, initialized the user interface.
$(
function() {
// Gather all the links in the document.
var jLinks = $("a");
// Loop over each link.
jLinks.each(
function(intLinkIndex, objLink) {
// Get a jQuery reference to this link.
// Bind the click handler.
var _jThis = new jThis(this);
_jThis.setIndex(intLinkIndex);
_jThis.setLength(jLinks.length);
this.onclick =
function(objEvent) {
// Alert the link index in the context of the
// greater document.
alert("I an link " + (_jThis.getIndex() + 1) + " of " + _jThis.getLength());
// Prevent default link behavior.
return (false);
}
});
});
לא הלכתי כאן ממש עד הסוף עם הייעול כמובן, אבל בשביל הדוגמה זה יהיה מצוין. למרות שהשארתי את ה-closure, כן שיניתי את הפונקציה האחרונה ובמקום להשתמש ב-jQuery הבזבזני וה-closure שלו, יצרתי, בתחילת הקוד, אובייקט משלי עם שתי תכונות שנשלטות כל אחת על ידי set ו-get בצורה מסודרת וכך חסכתי closure אחד ואיפשרתי ל-garbage collector לעבוד בצורה יעילה יותר. והתוצאה? אתם יכולים לראות באמצעות המבחן הרלוונטי שיצרתי באמצעות jsperf שמעמיד את שני הקודים למבחן.
אתם מוזמנים כמובן להכנס ל-jsFiddle ולעשות פורק לקוד 'המשופר' שלי ולנסות לשפר אותו עוד יותר, יש שם המון מה לעשות כמובן והדוגמה שהבאתי היא דוגמה פשוטה לשם לימוד והמחשה בלבד. אם אתם מעוניינים להתנסות, זו דרך נהדרת.
אז כך שיפרתי את היעילות באמצעות שינוי הקוד ומעבר ל-Vanilla. זה לא אומר, אגב, ש-jQuery נחותה בביצועים שלה. אבל ל-jQuery יש ארכיטקטורה שאם נשתמש בה בלי להבין אותה עד הסוף, אנו עלולים לגרום לעצמנו לצרות. תמיד טוב לנסות ולהבין איך הדברים עובדים, כפי שהסברתי במאמר הקודם, ואז לא להכנס לצרות מראש.
כמובן שעם כל הכבוד ליעילות, עדיף שלא לרוץ ולנסות ליעל סקריפטים באופן כזה, הקוד החדש שיצרתי הוא הרבה פחות קריא ומובן ומתכנתים חדשים יתקשו להכנס אליו אלא אם כן הם ממש יודעים מה הם עושים. אחד היתרונות הגדולים ב-jQuery היא הקריאות הרבה שלה שמקילה על התחזוקה. אם אין לכם בעית ביצועים, אני ממליץ להניח לסקריפט לנפשו. טוב להכיר את prototype ואת closures והמחיר שלהן, אבל הידע הזה מעולה לשימושים אחרים, ולאו דווקא לסבך דברים פשוטים שלא לצורך.
תגובה אחת
וואו.. כתבה כל כך גרועה.. אני מרחם על מי שרוצה ללמוד js ונכמס לכאן.. לא מוסבר כאן כלום חוץ מדוגמאות והתנשאות של הכותב מה עשה בכל דוגמא.. תעשה טובה, אל תנסה ללמד.. תשאר מתכנת קטן. תודה.