לרוב javascript היא שפה המיועדת לweb
אבל יש לשפה כמה תכונות מעניינות שנרצה לעסוק בהן.
ראשית, השפה היא duck type checking כלומר שהיא כמעט לא עושה בדיקת טיפוסים, היא רק בודקת האם כל הפונקציות שאובייקט מסויים מפעיל אכן מוגדרות אצל האובייקט הזה. נזכיר שפרדיגמות של type checking נוספות הן static type כמו ב OCaml שנעשות בזמן קומפילציה או dynamic type שבה ה Type Inference נעשה בזמן ריצה.
הטיפוסים והערכים של הפונקציה הם
Execution context – הסביבה בה מורץ קטע קוד.
כל פונקציה יוצרת execution context חדש עם scope של משתנים שונים
(הפונקציה נמצאת ב-stack, ה- execution context נמצא ב-heap).
Hoisting - סריקה של כל ההצהרות שנמצאות בקטע קוד לפני הרצת הקוד.
בשלב זה אין עדיין השמה של משתנים ולכן כל משתנה מהסוג var x= = 3; יוגדר כ-undefined עד לרגע הרצת שורת הקוד.
Scope chaining:
נסתכל על דוגמת הקוד הבא-
scope chaining היא דרך בתכנות לחפש מופע מתאים של משתנה שלא הוצהר בscope הנוכחי שבו הוגדרתי (עולים בscope עד שמגיעים לגלובלי). לכן בקוד הבא השורה הראשונה תדפיס 2 השנייה תדפיס 5 (כי מצאנו את x ב scope של הפונקציה) וכשנקרא ל b שוב נדפיס 2 בגלל שהscope הוא סטטי ובסביבה שבה b הוגדרה x=2.
נסתכל על הקוד הבא כדי להבין מה קורה בshadowing וב hoisting
var scope = "global"
function f() {
console.log(scope)
var scope = "local"
console.log(scope)
}
היינו מצפים שהפעלת f שהפלט יהיה global
ולאחר מכן scope
אבל זה לא המצב בגלל שבjavascript האינטרפטר מבצע הפרדה בין הצהרה על משתנה להשמה שלו באופן הבא
var scope = "global"
function f() {
var scope;
console.log(scope);
scope = "local";
consol.log(scope)
}
זה גורם לפלט של f להיות undefined ולאחר מכן local. מצב זה שבו יש שני שמות זהים בשני scopes שונים נקרא shadowing והפעולה של העלאת ההצהרה של המשתנה לתחילת ה scope נקראת hoisting.
אם היינו מורידים את שלב ה declaration כלומר בלי var בתוך f היינו מקבלים את הפלט הרצוי.
נשים לב ש undefined זה לא אותו דבר כמו המצב של משתנה שמוגדר לאחר השימוש. נסתכל למשל על הפונקציה הבאה
function f() {
consol.log(scope)
scope = "local"
console.log(scope)
}
בקטע הקוד הזה נקבל שגיאה בגלל שלא השתמשנו ב var. ברגע שהinterpeter מזהה את מילת המפתח var הוא מבצע hoisting למשתנה שזה העלת ההצהרה שלו לתחילת ה scope שלו.
ברגע שאין var הוא לא עושה זאת ולכן בשורת ההדפסה הראשונה אין משתנה שמוגדר כך גם לא מבחינת ההצהרה ולכן תזרק שגיאה.
כדי לתקן את זה, רק צריך לשים var בשורה שבה ביצענו השמה ל scope.
נראה דוגמה נוספת על ידי הסתכלות על שני קטעי הקוד הבאים, ההבדל בינהם הוא שבאחד יש var ובשני אין עבור ההצהרה על j .
בקוד השמאלי נקבל 0 ואז 0 בגלל ש j הוא ב global scope ובקוד השני תזרק שגיאה בגלל שהkeyword var מגדיר אותו להיות ב function scope של f.
התוכן של משתנים יכול להיות מהצורה ההבאה:
משתנים בscope גלובלי הם שדות של האובייקט הגלובלי. ניתן לגשת אליו באמצעות this בכל scope שהוא לא class scope.
שיוויון בין משתנים
ניתן לבצע שתי פעולות השוואה ב js
ההבדל בינהם הוא הוא שבהשוואה מסוג שוויון מתבצע type coercion כלומר המרה של ערך מטיפוס אחד לאחר.
לדוגמה אם
1==='1' //false
1=='1' //true
נסתכל על הקוד הבא
function makeCounter() {
var counter = 0;
function make() {
return ++counter;
}
return make;
}
אמרנו שאחד היכולות של Closures הוא היכולת להסתיר מידע של הפונקציה. כאן למשל עבור כל קריאה לmakeCounter יש לנו בתוך הscope של הפונקציה את המשתנה counter שהוא נגיש רק בתוך ה scope הזה. כל קריאה ל makeCounter תייצר scope חדש עם counter חדש אבל כל עוד נמשיך לקרוא לאותו make הcounter יגדל (ככה ניתן למדוד את מספר הקריאות לפונקציה למשל)
דוגמה נוספת:
המשתנה i מקושר פעם אחת לפונקציה ולכן כל הערכים שמתקבלים הם 10. בגלל שvar עובר hoisting לתחילת הפונקציה אז i הוא בסקופ של constfuncs ולכן ערך החזרה מכל הפונקציות במערך יהיה 10. קל להתבלבל ולחשוב שמדובר ב dynamic scope אבל עדיין מדובר ב static scope כי מה שקורה זה שפשוט הסביבה שבה הפונקציה הוגדרה היא הסביבה של constfuncs שבה i=10 בסיום הריצה של הפונקציה. אם js הייתה עובדת בשיטת dynamic scope היינו מקבלים שגיאה כי בסביבה שבה הפונקציה נקראת אין משתנה i בכלל.
איך נתקן?
נוכל ליצור closure חדש לכל משתנה i.
בעצם יצרנו פה scope חדש עבור i כאשר העברנו אותו כקלט ל g. בגלל שהעברנו את i כקלט לפונקציה חדשה by value אז עכשיו נשמר עבור כל
הפתרון השני הוא להחליף את var ל let , כי let הוא סגור גם ל loop scope ול if scopes כשעושים hoisting.
הצהרה:
// 1)
function f(x) {x*x}
// 2) way slower method
var f = new Function("x", "return x*x")
// 3)
var f = function(x){return x*x}
//also can be done like this:
//good for recursion
var f = function g(x) {return x*x}
// triggering g will cause ReferenceError from any scope outside g
שימוש בפונקציות
var foo = function() {
var a = 3
var b = 5
var bar = function () {
var b = 7
var c = 11
a= a+b+c
}
console.log(a,b)
bar()
console.log(a,b)
}
/**
3 5
21 5
/**
var o = { f: function() {return 5}}
o.f()
ניתן להשתמש בפונקציות אנונימיות כדי ליצור custom scope למשל
function (){var x =4;x} ייצור scope ספציפי למשתנה x שמחוץ לו הוא לא נגיש. כמו כן ניתן להפעיל פונקציות אנונימיות ישירות על ידי עטיפתן בסוגרים והפעלתן למשל
()(function (){var x =4;x})
בגלל שפונקציות הן גם משתנים אז גם עליהן js עושה hoisting. למשל בקטע הקוד למטה מודפס 8 כי הערך התחתון דורס את העליון בגלל hoisting ו shadowing
פונקציה היא אובייקט, לקריאה שלה יש שדה args.
js לא בודקת תקינות של מספר הארגומנטים.
נסתכל על הפונקציה הבאה
function f() {
if(arguments.length <= 1){
return 0
}else {
var result = 1
for(var i = 0; i< arguments.length; i++) {
result = result * arguments[i];
}
return result
}
}
ניתן לראות כאן שאנחנו משתמשים בשדה arguments שיש לכל פונקציה, למרות שהיא הוגדרה ללא חתימה עדיים אם נעביר מספר ארגומנטים הם יישמרו בפרמטר הזה.
עבור f בלי פרמטרים נקבל 0. אם נקרא למשל ל f(1,2) נקבל 2 כי מחשבים את מכפלת כל הארגומנטים.
arguments הוא שדה בתוך מופע של האובייקט של הפונקציה כלומר מופע שמייצג ריצה כלשהי של הפונקציה.
נשים לב ש arguments הוא אובייקט בפני עצמו והוא מכיל עוד שדות. שדה מעניין נוסף שיש לו נקרא callee שהוא מחזיר את הפונקציה עצמה וגם אותה ניתן להפעיל. למשל
function(x){if (x<=1) return 1; else return x * arguments.callee(x-1)}
callee מחזיק בתוכו גם פרמטר length אלא שהוא מייצג את מספר הפרמטרים שהפונקציה מקבלת כקלט בחתימה שלה.
function check (x,y) {
var actual = arguments.length
var expected = arguments.callee.length
console.log(actual)
console.log(expected)
}
check()
// will print 0 2
this keyword
מה קורה כששמים this בתוך פונקציה. נסתכל על הפונקציה הבאה
function f(x) {
return this.a + x;
}
הפעלה של f בצורה רגילה תגרום ל this להתייחס לאובייקט הגלובלי כפי שכבר אמרנו ותזרק שגיאה כי אין לאובייקט זה משתנה מסוג a.
אבל ב javascript בניגוד לשפות אחרות מונחות עצמים כמו java, אני יכול להצמיד לפונקציה את המופע הרלוונטי (בד״כ לכל אובייקט יש מתודות).
את זה ניתן לעשות על ידי שני פונקציות שיש לאובייקט function
שני הפונקציות הנ״ל מקבלות כקלט את האובייקט שיהיה this בתוך הפונקציה ואת הקלט לפונקציה עצמה למשל
o1 = {a:1};
o2 = {a:2};
f.call(o1,5)
f.call(o2,5)
f.apply(o2, [5])
כעת this לא יקרוס בגלל שלשני האובייקטים יש פרמטר a. ההבדל בין apply ל call זה שב apply הקלט לפונקציה מתקבל בתוך מערך.
כפי שאמרנו, this בתוך מתודה מתייחס למופע של האובייקט שבתוכו מוגדרת המתודה.
מכפלות עם שמות (variance) אבל שדות יכולים להיות עם פונקציות.
כדי לייצר אובייקט ניתן להשתמש בשני דרכים
//1)
var a = new Object()
var now = new Date(2024,2,25)
//2)
var circle = {x:0, y:0, radius:2}
סמנטיקה:
var book = new Object()
book.title = "catch 22"
book.numOfChapter = 3
// printing an object will print it JSON format
console.log(book)
// iterate every variable name in object
for (var name in book) {
//access the value:
book[name]
}
//remove the variable title from book
delete book.title
constructor
function Rectancle(w,h) {
this.width = w;
this.height = h;
}
var rect = new Rectangle(1,2)
consolt.log(rect)
// Rectangle {width: 1 , height: 2}
In JavaScript, every object has a prototype. The prototype is an internal reference that points to another object. When you try to access a property or method on an object that doesn't exist on the object itself, JavaScript will look for it in the object's prototype, and if it's not found there, it will continue searching up the prototype chain until it reaches the top-level Object.prototype.
נסתכל על הקוד הבא לדוגמה
הוספנו שתי פונקציות חדשות לprototype והן התווספו לכל המופעים של circle.
זה מאפשר לנו לממש ירושה:
כדי להגדיר ירושה אני צריך להגדיר שהprototype שלי הוא אובייקט מסוג מסויים למשל אם נרצה שאובייקט מסוג Complex יהיה אבא של אובייקט מסוג AdvancedComplex נצטרך להגדיר
ככה Advanced יקבל את כל התכונות של Complex.
לכל האובייקטים יש את השדות הבאות
__defineGetter__
__defineSetter__
__lookupGetter__
__lookupSetter__
__proto__
constructor
hasOwnProperty
isPrototypeOf
propertyIsEnumerable
toLocaleString
toString
valueOf
אלא כלים ש javascript משתמש בהם כדי לממש inheritance.