راهنمای توابع و scope در جاوااسکریپت

هنگام یادگیری برنامه نویسی سوالی که ممکن است با آن مواجه شویم این است که آیا تا به حال به این موضوع فکر کرده‌ایم که برنامه‌ها چگونه کارهایی که باید انجام دهند را به خاطر می‌آورند و آن‌ها را بارها و بارها تکرار می‌کنند؟ اینجاست که مفهوم توابع و scope در جاوااسکریپت مطرح می‌شوند.

مقدمه‌ای بر توابع و scope در جاوااسکریپت

توابع این امکان را به ما می‌دهند تا خطوط کد را باهم گروه‌بندی کنیم و یک نام به آن‌ها اختصاص بدهیم. این توابع مانند ابزارهای خاصی هستند که به ما کمک می‌کنند کد خود را سازماندهی کرده و هر زمان که به آن نیاز داشتیم اقدامات خاصی را انجام دهیم.

به جای نوشتن یک کد یکسان، می‌توانیم از توابع برای آسان‌تر کردن کدنویسی استفاده کنیم. می‌توانیم توابع را به عنوان برنامه‌های کوچکی در نظر بگیریم که برای سازماندهی و کارآمدتر کردن کد خود از آن‌ها بصورت مجدا استفاده می‌کنیم.

Scope مفهوم دیگری است که بر نحوه عملکرد کد ما تأثیر می‌گذارد. این مفهوم مانند مجموعه‌ای از قوانین است که تعیین می‌کند متغیرهای ما در کدام قسمت‌ها مجاز هستند تا در دسترس باشند. گاهی اوقات آن‌ها آزاد هستند تا در هر قسمتی از کد مورد استفاده قرار بگیرند، و گاهی اوقات مجاز هستند فقط در محدوده خاصی در دسترس باشند.

روش تعریف توابع

تعریف یک تابع مانند تعریف نام آن و هدف دادن به آن می‌باشد، اینجا قسمتی است که ما کدی را که می‌خواهیم تابع اجرا کند را قرار می‌دهیم. به عنوان مثال:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
// This code is a function
function greet(name) {
console.log(`Hello, ${name}!`);
}
greet("Cas"); // Output: Hello, Cas!
// This code is a function function greet(name) { console.log(`Hello, ${name}!`); } greet("Cas"); // Output: Hello, Cas!
// This code is a function 

function greet(name) {
  console.log(`Hello, ${name}!`);
}

greet("Cas"); // Output: Hello, Cas!

در مثال بالا، تابعی به نام

greet
greetیک پارامتر
name
nameمی‌گیرد و یک پیام تبریک را با استفاده از یک template literal ثبت می‌کند. سپس تابع
greet
greet را با آرگومان Cas فراخوانی می‌کنیم و
Hello, Cas!
Hello, Cas!را در خروجی می‌بینیم.

پارامترها و آرگومان‌های تابع

توابع را می‌توانیم به عنوان ماشین‌هایی تصور کنیم که ورودی‌ها (پارامترها) را می‌گیرند و خروجی را تولید می‌کنند. پارامترها مانند placeholderهایی برای این ورودی‌ها می‌باشند. اما آرگومان‌ها مقادیر واقعی هستند که به تابع می‌دهیم. مثلا:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
function addNumbers(a, b) { //a, b are parameters
return a + b;
}
const result = addNumbers(5, 7); //5,7 are arguments
console.log(result); // Output: 12
function addNumbers(a, b) { //a, b are parameters return a + b; } const result = addNumbers(5, 7); //5,7 are arguments console.log(result); // Output: 12
function addNumbers(a, b) {  //a, b are parameters
  return a + b;
}

const result = addNumbers(5, 7);  //5,7 are arguments
console.log(result); // Output: 12

Statementها و مقادیر بازگشتی در توابع

اگر بخواهیم این مقادیر را با دانیای واقعی مقایسه کنیم، فرض کنید دوست خود را به یک جستجو می‌فرستیم. او بیرون رفته، کار را کامل می‌کند و با یک کالای ارزشمند برمی‌گردد. در دنیای توابع، این «آیتم» همان چیز است که ما آن را مقدار بازگشتی می‌نامیم. به عنوان مثال:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
function multiply(a, b) {
const result = a * b;
return result; // The function gives back the 'result' as a gift
}
const product = multiply(3, 5); // The function is called, and the return value is captured
console.log(product); // Output: 15
function multiply(a, b) { const result = a * b; return result; // The function gives back the 'result' as a gift } const product = multiply(3, 5); // The function is called, and the return value is captured console.log(product); // Output: 15
function multiply(a, b) {
  const result = a * b;
  return result;  // The function gives back the 'result' as a gift
}

const product = multiply(3, 5);  // The function is called, and the return value is captured
console.log(product);  // Output: 15

در مثال بالا، تابع

multiply
multiplyمحاسبات خود را انجام می‌دهد، پاسخ را محاسبه می‌کند (حاصل ضرب ۳ و ۵)، و آن را با استفاده از دستور
return
returnتحویل می‌دهد.

بررسی توابع Anonymous

گاهی اوقات ما به یک تابع با نام نیاز نداریم. یک تابع anonymous نامی ندارد، در عوض، مستقیماً در جایی که به آن اختصاص داده شده است، تعریف می‌شود. توابع anonymous اغلب به عنوان توابع callback یا توابع یکبار مصرف مورد استفاده قرار می‌گیرند. به عنوان مثال:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
const multiply = function(x, y) {
return x * y;
}
const multiply = function(x, y) { return x * y; }
const multiply = function(x, y) {
  return x * y;
}

این کد یک تابع anonymous اختصاص داده شده به متغیر

multiply
multiplyرا تعریف می‌کند که دو پارامتر
x
xو
y
yرا می‌گیرد و هنگام فراخوانی تابع حاصل ضرب آن‌ها را return می‌کند.

منظور از Function Expression چیست؟

این مفهوم هنگام تخصیص توابع به متغیرها، ارسال توابع به عنوان آرگومان به توابع دیگر، یا return کردن توابع از توابع دیگر به کار می‌رود. به عبارت دیگر جایگزین برای روش رایج تعریف تابع می‌باشد. مثلا:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
const add = function(a, b) {
return a + b;
};
const result = add(5, 3); // Call the function
console.log(result); // Output: 8
const add = function(a, b) { return a + b; }; const result = add(5, 3); // Call the function console.log(result); // Output: 8
const add = function(a, b) {
  return a + b;
};

const result = add(5, 3);  // Call the function
console.log(result);  // Output: 8

در این مثال، یک عبارت تابع به نام

add
addتعریف شده و به متغیر
add
add اختصاص داده شده است. تابع دو پارامتر
a
aو
b
bرا می‌گیرد و مجموع این دو عدد را return می‌کند.

Arrow Functionها و تاثیر آن‌ها بر کلمه کلیدی this

این تابع در مورد کلمه کلیدی

this
thisرفتار متفاوتی دارد. برخلاف توابع معمولی، arrow functionها context
this
this را برای خود ایجاد نمی‌کنند. در عوض، آن‌ها مقدار
this
this را از کد اطراف خود ارث‌بری می‌کنند. به عنوان مثال:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
function regularFunction() {
console.log(this); // Refers to the caller
}
const arrowFunction = () => {
console.log(this); // Inherits from where it's defined
};
const obj = {
regular: regularFunction,
arrow: arrowFunction
};
obj.regular(); // 'this' refers to 'obj'
obj.arrow(); // 'this' still refers to 'obj', despite being in an arrow function
function regularFunction() { console.log(this); // Refers to the caller } const arrowFunction = () => { console.log(this); // Inherits from where it's defined }; const obj = { regular: regularFunction, arrow: arrowFunction }; obj.regular(); // 'this' refers to 'obj' obj.arrow(); // 'this' still refers to 'obj', despite being in an arrow function
function regularFunction() {
  console.log(this);  // Refers to the caller
}

const arrowFunction = () => {
  console.log(this);  // Inherits from where it's defined
};

const obj = {
  regular: regularFunction,
  arrow: arrowFunction
};

obj.regular();  // 'this' refers to 'obj'
obj.arrow();    // 'this' still refers to 'obj', despite being in an arrow function

این کد تفاوت بین توابع معمولی و arrow functionها را در مورد استفاده از کلمه کلیدی

this
thisنشان می‌دهد. arrow functionها context
this
this را از جایی که تعریف شده‌اند ارث‌بری می‌کنند، در حالی که توابع معمولی به caller اشاره دارند.

یکی دیگر از مزایای arrow functionها این است که آن‌ها ظرافت مختصری را به جاوااسکریپت می‌دهند. به این ترتیب هنگامی که با مقادیر پارامترهای پیش‌فرض ترکیب می‌شوند، کد ما را ساده‌تر می‌کنند. به عنوان مثال:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
const greet = (name = "friend") => {
console.log(`Hello, ${name}!`);
};
greet(); // Output: Hello, friend!
greet("Cas"); // Output: Hello, Cas!
const greet = (name = "friend") => { console.log(`Hello, ${name}!`); }; greet(); // Output: Hello, friend! greet("Cas"); // Output: Hello, Cas!
const greet = (name = "friend") => {
  console.log(`Hello, ${name}!`);
};

greet();        // Output: Hello, friend!
greet("Cas"); // Output: Hello, Cas!

در مثال بالا، پارامتر

name
nameدارای مقدار پیش فرض friend است. arrow functionها مخصوصاً زمانی مفید هستند که بخواهیم راهی سریع برای تعریف یک تابع با پارامترهای پیش‌فرض داشته باشیم.

تابع و Variable Hoisting چگونه کار می‌کند؟

Hoisting مانند آماده کردن صحنه قبل از شروع نمایش است. در جاوااسکریپت، تعریف توابع به بالای scope حاوی خود hoist می‌شود. این بدان معناست که ما می‌توانیم یک تابع را قبل از اینکه در کد خود تعریف کنیم فراخوانی نماییم. مثلا:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
// Function declaration (can be called anywhere)
sayHello(); // This code works
function sayHello() {
console.log("Hello!");
}
// Function declaration (can be called anywhere) sayHello(); // This code works function sayHello() { console.log("Hello!"); }
// Function declaration (can be called anywhere)
sayHello(); // This code works

function sayHello() {
  console.log("Hello!");
}

قطعه کد بالا باتوجه به مفهوم hoisting کار می‌کند. با این حال، hoisting بر روی عبارات تابع اعمال نمی‌شود:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
// Function expreesion (called before defined)
sayHi(); // Error
const sayHi = function() {
console.log("Hi!");
};
// Function expression (should be defined before calling)
const sayHello = function() {
console.log("Hello!");
};
sayHello(); // This works
// Function expreesion (called before defined) sayHi(); // Error const sayHi = function() { console.log("Hi!"); }; // Function expression (should be defined before calling) const sayHello = function() { console.log("Hello!"); }; sayHello(); // This works
// Function expreesion (called before defined)
sayHi();  // Error

const sayHi = function() {
  console.log("Hi!");
};


// Function expression (should be defined before calling)
const sayHello = function() {
  console.log("Hello!");
};

sayHello(); // This works

تابع

sayHi
sayHiیک خطا ایجاد می کند. چون قبل از این که تعریف شود، فراخوانی شده است. این بدان معنی است که ما باید یک عبارت تابع را قبل از فراخوانی آن تعریف کنیم.

hoisting با کلمات کلیدی

let
letو
const
constرفتار کمی متفاوت دارد. آن‌ها یک dead zone موقتی را تجربه می‌کنند، درست مانند بازیگرانی که در پشت صحنه منتظر نوبت خود هستند. dead zone در جاوااسکریپت به بازه زمانی بین ایجاد یک متغیر با استفاده از کلمات کلیدی
let
let یا
const
const و نقطه‌ای که متغیر مقداردهی می‌شود، اشاره دارد.

در این مدت، اگر سعی کنیم به متغیر دسترسی داشته باشید، با خطای reference مواجه می‌شویم. این رفتار نتیجه نحوه عملکرد variable hoisting جاوااسکریپت با این تعریف‌های block-scoped می‌باشد. در ادامه یک مثال دیگر داریم:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
console.log(myName); // Throws an error - myName is not defined
let myName = "Cas";
console.log(myName); // Throws an error - myName is not defined let myName = "Cas";
console.log(myName);  // Throws an error - myName is not defined
let myName = "Cas";

در کد بالا،

myName
myNamehoist شده است، اما تلاش برای دسترسی به آن قبل از تعریف واقعی، به دلیل dead zone با خطا مواجه می‌شود.

باید به این نکته توجه داشته باشیم در حالی که function hoisting می‌تواند مفید باشد، تمرین خوبی است که قبل از استفاده از توابع، آن‌ها را تعریف کنیم تا کدی که داریم خواناتر شود.

IIFE (Immediately Invoked Function Expression) چیست؟

ممکن است مواقعی پیش آمده باشد که بخواهیم یک تابع را بلافاصله پس از تعریف آن اجرا کنیم. اینجاست که IIFEها وارد عمل می‌شوند. آن‌ها مانند express lane جاوااسکریپت عمل می‌کنند.

تنها کاری که باید انجام دهیم این است که تابع را تعریف کنیم، آن را داخل پرانتز قرار دهیم و سپس یک جفت پرانتز دیگر اضافه کنیم تا بلافاصله آن را فراخوانی نماییم. می‌توانیم IIFE خود را با اضافه کردن یک پارامتر، شخصی‌سازی کنیم. به عنوان مثال:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
(function(name) {
console.log(`Hello, ${name}!`);
})("Cas");
(function(name) { console.log(`Hello, ${name}!`); })("Cas");
(function(name) {
  console.log(`Hello, ${name}!`);
})("Cas");

در این مثال، IIFE نام Cas را به عنوان پارامتر انتخاب کرده و بلافاصله آن را اجرا می‌کند.

استفاده از پارامترهای پیش‌فرض در تابع جاوااسکریپتی

در دنیای توابع جاوااسکریپت، انعطاف‌پذیری بسیار مهم است. گاهی اوقات، می‌خواهیم تابعی که داریم مقادیر missing یا undefined را بدون ایجاد خطا مدیریت کند. اینجاست که مقادیر پارامترهای پیش‌فرض به کمک ما می‌آیند. به عنوان مثال:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
function greet(name = "Guest") {
console.log(`Hello, ${name}!`);
}
greet(); // Output: Hello, Guest!
greet("Cas"); // Output: Hello, Cas!
function greet(name = "Guest") { console.log(`Hello, ${name}!`); } greet(); // Output: Hello, Guest! greet("Cas"); // Output: Hello, Cas!
function greet(name = "Guest") {
  console.log(`Hello, ${name}!`);
}

greet();          // Output: Hello, Guest!
greet("Cas");   // Output: Hello, Cas!

در تابع

greet
greet، پارامتر
name
nameدارای مقدار پیش‌فرض Guest است. اگر تابع را بدون ارائه آرگومان برای
name
name فراخوانی کنیم، از مقدار پیش‌فرض استفاده می‌کند. اما اگر آرگومان ارائه کنیم، استفاده از مقدار پیش‌فرض را لغو می‌کند.

نحوه استفاده از Rest Parameters و Spread Operator در توابع جاوااسکریپت

Rest Parameter و Spread Operator دو مفهوم مرتبط در جاوااسکریپت هستند که با مدیریت و دستکاری آرگومان‌ها و آرایه‌های تابع سروکار دارند.

اگر بخواهیم این مفاهیم را با دنیای واقعی مقایسه کنیم، تصور کنید در حال برگزاری یک پیکنیک هستیم و می‌خواهیم تمام غذاهایی را که سایر دوستانمان همراه خود می‌آورند، جمع‌آوری کنیم. rest parameter مانند یک جمع کننده ظروف است که تمام اقلامی را که دوستان ما آورده‌اند را می‌گیرد و آن‌ها را در آرایه‌ای قرار می‌دهد تا از آن استفاده کنیم. مثلا:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
function partyPlanner(mainDish, ...sideDishes) {
console.log(`Main dish: ${mainDish}`);
console.log(`Side dishes: ${sideDishes.join(', ')}`);
}
partyPlanner( "Jollof rice", "Fufu", "Pizza", "Salad", "Kpomo", "Fries");
// Output:
// Main dish: Jollof rice
// Side dishes: Fufu, Pizza, Salad, Kpomo, Fries
function partyPlanner(mainDish, ...sideDishes) { console.log(`Main dish: ${mainDish}`); console.log(`Side dishes: ${sideDishes.join(', ')}`); } partyPlanner( "Jollof rice", "Fufu", "Pizza", "Salad", "Kpomo", "Fries"); // Output: // Main dish: Jollof rice // Side dishes: Fufu, Pizza, Salad, Kpomo, Fries
function partyPlanner(mainDish, ...sideDishes) {
  console.log(`Main dish: ${mainDish}`);
  console.log(`Side dishes: ${sideDishes.join(', ')}`);
}

partyPlanner( "Jollof rice", "Fufu", "Pizza", "Salad", "Kpomo", "Fries");
// Output:
// Main dish: Jollof rice
// Side dishes: Fufu, Pizza, Salad, Kpomo, Fries

در این مثال، پارامتر

...sideDishes
...sideDishesتمام مقادیر اضافی را جمع‌آوری کرده و آن‌ها را در یک آرایه بسته‌بندی می‌کند و کار با تعداد ورودی‌های مختلف را آسان می‌کند.

Destruct کردن پارامترهای تابع

فرض کنید یک جعبه هدیه با آیتم‌های مختلف دریافت کرده‌ایم و می‌خواهیم آن‌ها را از بسته‌بندی خارج کرده و بلافاصله آیتم‌های مورد نیاز خود را انتخاب کنیم.

Destructuring به ما کمک می‌کند تا آیتم‌هایی را که از داده‌های پیچیده نیاز داریم، مانند آبجکت‌ها یا آرایه‌ها، باز کرده و از آن‌ها استفاده کنیم. به مثال زیر توجه کنید:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
function printPersonInfo({ firstName, lastName, age }) {
console.log(`First Name: ${firstName}`);
console.log(`Last Name: ${lastName}`);
console.log(`Age: ${age}`);
}
const person = {
firstName: 'Cas',
lastName: 'Nuel',
age: 30
};
printPersonInfo(person);
// Output:
// First Name: Cas
// Last Name: Nuel
// Age: 30
function printPersonInfo({ firstName, lastName, age }) { console.log(`First Name: ${firstName}`); console.log(`Last Name: ${lastName}`); console.log(`Age: ${age}`); } const person = { firstName: 'Cas', lastName: 'Nuel', age: 30 }; printPersonInfo(person); // Output: // First Name: Cas // Last Name: Nuel // Age: 30
function printPersonInfo({ firstName, lastName, age }) {
  console.log(`First Name: ${firstName}`);
  console.log(`Last Name: ${lastName}`);
  console.log(`Age: ${age}`);
}

const person = {
  firstName: 'Cas',
  lastName: 'Nuel',
  age: 30
};

printPersonInfo(person);
// Output:
// First Name: Cas
// Last Name: Nuel
// Age: 30

در این مثال، تابع

printPersonInfo
printPersonInfoیک پارامتر آبجکت می‌گیرد. به جای دسترسی به ویژگی‌های آبجکت با استفاده از
person.firstName
person.firstName،
person.lastName
person.lastName،
person.Age
person.Ageاز destructuring در لیست پارامترهای تابع برای استخراج مستقیم ویژگی‌ها استفاده می‌کنیم. این کار باعث می‌شود تا کدی که داریم تمیزتر و خواناتر شود. وقتی
printPersonInfo(person)
printPersonInfo(person)را فراخوانی می‌کنیم، تابع آبجکت
person
personرا destruct می‌کند و ویژگی‌های آن را چاپ می‌نماید.

توابع بازگشتی در جاوااسکریپت

تابع بازگشتی جایی است که یک تابع خود را فراخوانی می‌کند تا یک مسئله را با تجزیه آن به مشکلات فرعی کوچک‌تر و مشابه حل کند.

بازگشت شامل دو جزء اصلی است: یک شرط پایه که تعیین می‌کند بازگشت چه زمانی باید متوقف شود، و یک مورد بازگشتی که در آن تابع خود را با پارامترهای تغییر یافته، فراخوانی می‌کند. در ادامه یک مثال کد از یک تابع بازگشتی داریم که فاکتوریل یک عدد را محاسبه می‌کند:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
function factorial(n) {
// Base condition: factorial of 0 or 1 is 1
if (n === 0 || n === 1) {
return 1;
}
// Recursive case: call the function with a smaller sub-problem
return n * factorial(n - 1);
}
const num = 5;
const result = factorial(num);
console.log(`Factorial of ${num} is ${result}`);
function factorial(n) { // Base condition: factorial of 0 or 1 is 1 if (n === 0 || n === 1) { return 1; } // Recursive case: call the function with a smaller sub-problem return n * factorial(n - 1); } const num = 5; const result = factorial(num); console.log(`Factorial of ${num} is ${result}`);
function factorial(n) {
  // Base condition: factorial of 0 or 1 is 1
  if (n === 0 || n === 1) {
    return 1;
  }

  // Recursive case: call the function with a smaller sub-problem
  return n * factorial(n - 1);
}

const num = 5;
const result = factorial(num);
console.log(`Factorial of ${num} is ${result}`);

در این مثال، تابع

factorial
factorialفاکتوریل یک عدد
n
nرا محاسبه می‌کند. شرط پایه بررسی می‌کند که مقدار
n
n0 یا ۱ باشد. اگر اینطور باشد، تابع بلافاصله ۱ را return می‌کند، زیرا فاکتوریل ۰ یا ۱ برابر با ۱ است. حالت بازگشتی
n
n را با نتیجه فراخوانی تابع
factorial
factorial با
n - 1
n - 1ضرب می‌کند.

این کار یک زنجیره از callهای بازگشتی ایجاد می‌کند، که هر کدام مشکل را یک مرحله کاهش می‌دهد و زمانی که به شرایط پایه برسید متوقف می‌شود. همچنین مقادیر محاسبه شده به زنجیره برگردانده می‌شوند.

به عنوان مثال، هنگام فراخوانی

factorial(5)
factorial(5):

factorial(5)
factorial(5)مقدار
۵ * factorial(4)
۵ * factorial(4)را return می‌کند،

factorial(4)
factorial(4)مقدار
۴ * factorial(3)
۴ * factorial(3)را return می‌کند

factorial(3)
factorial(3)مقدار
۳ * factorial(2)
۳ * factorial(2)را return می‌کند

factorial(2)
factorial(2)مقدار
۲ * factorial(1)
۲ * factorial(1)را return می‌کند

factorial(1)
factorial(1) مقدار ۱ را return می‌کند

سپس این مقادیر با هم ضرب می‌شوند و نتیجه نهایی که ۱۲۰ است به دست می‌آید.

بازگشت یک تکنیک قدرتمند است، اما برای جلوگیری از ایجاد حلقه‌های بی‌نهایت، داشتن یک شرط پایه کاملاً ضروری است. هر فراخوانی بازگشتی باید به سمت حالت پایه حرکت کند و اطمینان حاصل کند که مشکل با هر تکرار، کوچک‌تر می‌شود.

Scope و Closure توابع در جاوااسکریپت

با استفاده از scope و closure در جاوااسکریپت می‌توانیم کد خود را سازماندهی کنیم، داده‌های private ایجاد کنیم و توابع قدرتمندی بسازیم.

مقایسه Scope سراسری و محلی

می‌توانیم scope سراسری را به عنوان محله‌ای در نظر بگیریم که همه همسایه‌های ما (متغیرها) در آن زندگی می‌کنند. متغیرهای تعریف شده در اینجا از هر قسمت کد قابل دسترسی می‌باشند. به عنوان مثال:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
const globalVariable = "I'm global!";
function globalScopeExample() {
console.log(globalVariable); // Accessing the global variable
}
globalScopeExample(); // Output: I'm global!
const globalVariable = "I'm global!"; function globalScopeExample() { console.log(globalVariable); // Accessing the global variable } globalScopeExample(); // Output: I'm global!
const globalVariable = "I'm global!";

function globalScopeExample() {
  console.log(globalVariable);  // Accessing the global variable
}

globalScopeExample();  // Output: I'm global!

این کد یک متغیر سراسری

globalVariable
globalVariableبا مقدار
string
stringتعریف می‌کند. سپس، یک تابع
globalScopeExample
globalScopeExampleوجود دارد که مقدار
globalVariable
globalVariableرا ثبت می‌کند. تابع فراخوانی می‌شود و در نتیجه مقدار متغیر سراسری به دست می‌آید.

از سوی دیگر، scope محلی مانند اتاق‌هایی در خانه ما است. متغیرهای تعریف شده در داخل توابع یا بلاک‌های کد، محلی هستند و فقط در آن تابع یا بلاک کد قابل دسترسی می‌باشند. مثلا:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
function localScopeExample() {
const localVariable = "I'm local!";
console.log(localVariable); // Accessing the local variable
}
localScopeExample(); // Output: I'm local!
// console.log(localVariable); // This would result in an error
function localScopeExample() { const localVariable = "I'm local!"; console.log(localVariable); // Accessing the local variable } localScopeExample(); // Output: I'm local! // console.log(localVariable); // This would result in an error
function localScopeExample() {
  const localVariable = "I'm local!";
  console.log(localVariable);  // Accessing the local variable
}

localScopeExample();  // Output: I'm local!
// console.log(localVariable);  // This would result in an error

این کد یک تابع

localScopeExample
localScopeExampleتعریف می‌کند که یک متغیر
localVariable
localVariableدر داخل تابع ایجاد کرده و سپس مقدار آن را چاپ می‌نماید. هنگامی که تابع فراخوانی می‌شود، مقدار
localVariable
localVariableرا در خروجی نشان می‌دهد. تلاش برای دسترسی به
localVariable
localVariableخارج از تابع منجر به ایجاد خطا می‌شود.

منظور از Scope و Closure Lexical چیست؟

Lexical scope کمی شبیه عروسک‌های تودرتو روسی است. هر عروسک می‌تواند به عروسک‌های داخل خود دسترسی داشته باشد، اما برعکس نه. به طور مشابه، در برنامه نویسی، این مفهوم به این معنی است که یک تابع درونی می‌تواند به متغیرها از تابع بیرونی خود دسترسی داشته باشد، اما برعکس نه. مثلا:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
function outer() {
const outerVar = "I'm from outer function!";
function inner() {
console.log(outerVar); // Accessing the outer variable
}
inner();
}
outer(); // Output: I'm from outer function!
function outer() { const outerVar = "I'm from outer function!"; function inner() { console.log(outerVar); // Accessing the outer variable } inner(); } outer(); // Output: I'm from outer function!
function outer() {
  const outerVar = "I'm from outer function!";
  
  function inner() {
    console.log(outerVar);  // Accessing the outer variable
  }

  inner();
}

outer();  // Output: I'm from outer function!

در این مثال یک تابع خارجی

outer
outerرا تعریف می‎‌کنیم که حاوی متغیر
outerVar
outerVarاست. داخل تابع
outer
outerیک تابع داخلی
inner
innerوجود دارد که مقدار
outerVar
outerVarرا ثبت می‌کند. هنگامی که
outer
outerفراخوانی می‌شود،
inner
innerرا نیز فراخوانی می‌کند و در نتیجه خروجی
I'm from outer function!
I'm from outer function!نمایش داده می‌شود.

Closureها چگونه کار می‌کنند و چرا مهم هستند؟

Closureها مانند کپسول‌های زمانی هستند که حتی پس از اتمام کارکرد متغیرها، روی متغیرها می‌مانند. آن‌ها ترکیبی از یک تابع و محیطی هستند که در آن ایجاد شده است. به عنوان مثال:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
function rememberMe() {
const secret = "I'm a secret!";
return function() {
console.log(secret); // This inner function remembers the 'secret'
};
}
const myClosure = rememberMe();
myClosure(); // Output: I'm a secret!
function rememberMe() { const secret = "I'm a secret!"; return function() { console.log(secret); // This inner function remembers the 'secret' }; } const myClosure = rememberMe(); myClosure(); // Output: I'm a secret!
function rememberMe() {
  const secret = "I'm a secret!";
  return function() {
    console.log(secret);  // This inner function remembers the 'secret'
  };
}

const myClosure = rememberMe();
myClosure();  // Output: I'm a secret!

کد بالا یک تابع

memoryMe()
memoryMe()را تعریف می‌کند که تابع دیگری را ایجاد و return می‌کند. این تابع بازگشتی که به عنوان closure شناخته می‌شود، به متغیر
secret
secretاز scope تابع parent خود دسترسی دارد. هنگامی که تابع
myClosure
myClosureفراخوانی می شود، مقدار متغیر
secret
secret را ثبت می‌کند.

Closureها برای ایجاد داده‌ها یا توابع private که فقط بخش خاصی از کد ما می‌تواند به آن‌ها دسترسی داشته باشد عالی هستند. به عنوان مثال:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
function counter() {
let count = 0;
return function() {
return ++count;
};
}
const increment = counter();
console.log(increment()); // Output: 1
console.log(increment()); // Output: 2
function counter() { let count = 0; return function() { return ++count; }; } const increment = counter(); console.log(increment()); // Output: 1 console.log(increment()); // Output: 2
function counter() {
  let count = 0;
  return function() {
    return ++count;
  };
}

const increment = counter();
console.log(increment());  // Output: 1
console.log(increment());  // Output: 2

کد مثال بالا یک تابع

counter
counterایجاد می‌کند که هر بار که فراخوانی می‌شود یک شمارنده افزایشی ایجاد می‌کند و به این ترتیب استفاده از closure را نشان می‌دهد.

اجرای Context و Call Stack

هر بار که یک تابع فراخوانی می‌شود، جاوااسکریپت یک context اجرایی ایجاد می‌کند، که یک نوع محیط برای اجرای آن تابع است. این context متغیرها، رفرنس‌ها و جایی که تابع از آن‌جا فراخوانی شده است را پیگیری می‌کند.

می‌توانیم context را به عنوان یک منطقه پشت صحنه که در آن کد تابع اجرا می‌شود، در نظر بگیریم. تمام متغیرها، توابع و پارامترها در اینجا ذخیره می‌شوند. مثلا:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
function first() {
console.log("Hello from first!");
second(); // Calling another function
}
function second() {
console.log("Hello from second!");
}
first(); // Output: Hello from first! Hello from second!
function first() { console.log("Hello from first!"); second(); // Calling another function } function second() { console.log("Hello from second!"); } first(); // Output: Hello from first! Hello from second!
function first() {
  console.log("Hello from first!");
  second();  // Calling another function
}

function second() {
  console.log("Hello from second!");
}

first();  // Output: Hello from first! Hello from second!

در مثال بالا، تابع اول تابع دوم را فراخوانی می‌کند و یک context اجرایی جدید برای تابع دوم ایجاد می‌نماید.

Call Stack مانند لیستی از وظایف است که در انتظار اجرا هستند. هنگامی که یک تابع فراخوانی می‌شود، به بالای پشته اضافه می‌شود. وقتی تمام شد، حذف می‌گردد.

این پشته از contextها چیزی است که کد ما را ردیابی می‌کند.

دیباگ و عیب‌یابی در جاوااسکریپت

هنگامی که کدنویسی می‌کنیم، احتمالاً با مسائل پیچیده‌ای مواجه می‌شویم که می‌تواند باعث شود کد ما رفتار غیرمنتظره‌ای داشته باشد. در ادامه برخی از اشکالات و خطاهای رایج را باهم بررسی می‌کنیم.

متغیرهای سراسری تصادفی

مثال زیر را در نظر بگیرید:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
function oops() {
myVariable = "I'm global!"; // Oops, forgot 'var', 'let', or 'const'!
}
oops();
console.log(myVariable); // Output: I'm global!
function oops() { myVariable = "I'm global!"; // Oops, forgot 'var', 'let', or 'const'! } oops(); console.log(myVariable); // Output: I'm global!
function oops() {
  myVariable = "I'm global!";  // Oops, forgot 'var', 'let', or 'const'!
}

oops();
console.log(myVariable);  // Output: I'm global!

در این مثال،

myVariable
myVariableمتغیر سراسری می‌شود زیرا برای تعریف آن از
var
var،
let
letیا
const
constاستفاده نکرده‌ایم.

Shadowing

مثال زیر را در نظر بگیرید:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
const x = 10;
function shadowExample() {
const x = 5; // This 'x' is different from the outer 'x'
console.log(x); // Output: 5
}
shadowExample();
console.log(x); // Output: 10
const x = 10; function shadowExample() { const x = 5; // This 'x' is different from the outer 'x' console.log(x); // Output: 5 } shadowExample(); console.log(x); // Output: 10
const x = 10;

function shadowExample() {
  const x = 5;  // This 'x' is different from the outer 'x'
  console.log(x);  // Output: 5
}

shadowExample();
console.log(x);  // Output: 10

در این مثال، متغیر

x
xداخلی باعث ایجاد shadow بر روی متغیر
x
xخارجی می‌شود و این موضوع باعث می‌شود تا مقدار متغیر
x
xدر داخل و خارج از تایع مقادیر متفاوتی داشته باشد.

بررسی ابزارها و تکنیک‌های دیباگ کردن کد

مرورگرهای مدرن مانند کروم مجهز به ابزارهای توسعه دهنده هستند که به ما این امکان را می‌دهند تا breakpointها را تعیین کنیم، متغیرها را بررسی کرده و کد خود را خط به خط مرور نماییم.

تنظیم breakpointها شامل استفاده از ابزارهای توسعه دهنده مرورگر برای مکث کد در نقاط خاص (breakpointها) و بررسی مقادیر متغیرها می‌باشد. این کار به ما کمک می‌کند تا مشخص کنیم که کارها تا کدام قسمت پیش می‌روند.

Console logging شامل استفاده از عبارت

console.log()
console.log()برای چاپ پیام خاص یا مقادیر متغیرها در کنسول است. این موضوع می‌تواند به ما کمک کند تا جریان کد خود را ردیابی کرده و رفتار غیر منتظره را شناسایی نماییم.

استراتژی‌های شناسایی و رفع خطاها

پرداختن به مسائل مربوط به خطاها نیاز به رویکردی علمی دارد. به این ترتیب که:

  • بررسی محلی: دیباگ کردن کد را با بررسی scope متغیرها شروع می‌کنیم. آیا آن‌ها در جای مناسب قرار دارند؟ آیا آن‌ها متغیرهای دیگر را تحت تاثیر خود قرار می‌دهند؟
  • بررسی گام به گام: از یک دیباگر مانند Dev tools مرورگر، دیباگر کد ویژوال استودیو یا Node.js inspector استفاده می‌کنیم تا کد خود را به صورت گام به گام مرور کنیم. این کار به ما کمک می‌کند تا متغیرها را در مراحل مختلف پیدا کرده و تغییرات غیرمنتظره را مشاهده نماییم.
  • بررسی مشکل به صورت انفرادی: اگر تابعی مطابق انتظاری که داریم رفتار نمی‌کند، آن را جدا کرده و جداگانه مورد آزمایش قرار می‌دهیم. این کار به ما کمک می‌کند تا روی بخش مشکل‌ساز تمرکز کنیم.
  • مرور کد: یک بار دیگر کد خود را بررسی می‌کنیم. نگاه دوم ممکن است چیزی را نشان دهد که بار اول نسبت به آن بی‌توجه بوده‌ایم.

پیمایش مسائل مربوط به scope ممکن است مانند باز کردن یک گره به نظر برسد، اما با تمرین، می‌توانیم به مهارتی دست پیدا کنیم که ما را قادر می‌سازد حتی سخت‌ترین مشکلات را هم برطرف کنیم.

جمع‌بندی

در این مقاله ما توابع و scope در را جاوااسکریپت بررسی کردیم. این که توابع چگونه می‌توانند به عنوان ابزار قدرتمند عمل کنند و به ما این اجازه را بدهند تا کدهای سازمان‌یافته با قابلیت استفاده مجدد ایجاد کنیم. همچنین یاد گرفتیم که scope مانند مجموعه‌ای از قوانین است و تعیین می‌کند که متغیرها بتوانند آزادانه در همه قسمت‌های کدی که داریم در دسترس باشند یا در محدوده‌های خاص بمانند.

اکنون با داشتن این بینش‌ها و استراتژی‌ها، به خوبی آماده ساخت کدهای جاوااسکریپت کارآمدتر و سازماندهی شده با استفاده از توابع و scope هستیم. در نتیجه به راحتی می‌توانیم بر چالش‌ها غلبه کرده و برنامه‌های کاربردی پویا بسازیم.

دیدگاه‌ها:

افزودن دیدگاه جدید