توابع جاوا اسکریپت جاوا اسکریپت بیانی: توابع جاوا اسکریپت که مقادیر چندگانه را از یک تابع برمی گرداند

مردم فکر می کنند که علم کامپیوتر یک هنر برای نوابغ است. در واقعیت، برعکس است - فقط بسیاری از مردم چیزهایی می سازند که روی هم قرار می گیرند، گویی دیواری از سنگریزه های کوچک درست می کنند.

دونالد کنوت

قبلاً تماس هایی با عملکردهایی مانند هشدار دیده اید. توابع نان و کره برنامه نویسی جاوا اسکریپت هستند. ایده بسته بندی یک برنامه و نامیدن آن به عنوان یک متغیر بسیار محبوب است. این ابزاری برای ساختار برنامه های بزرگ، کاهش تکرار، نامگذاری زیربرنامه ها و جداسازی برنامه های فرعی از یکدیگر است.

واضح ترین کاربرد توابع ایجاد یک فرهنگ لغت جدید است. اختراع کلمات برای نثر معمولی انسان شکل بدی است. این در یک زبان برنامه نویسی ضروری است.

یک فرد بالغ روسی زبان به طور متوسط ​​تقریباً 10000 کلمه می داند. یک زبان برنامه نویسی کمیاب شامل 10000 دستور داخلی است. و واژگان یک زبان برنامه نویسی با وضوح بیشتری تعریف شده است، بنابراین انعطاف پذیری کمتری نسبت به زبان انسانی دارد. بنابراین، معمولاً برای جلوگیری از تکرار بی مورد، باید کلمات خود را به آن اضافه کنیم.

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

Var مربع = تابع (x) ( بازگشت x * x; ); console.log(square(12)); // → 144

یک تابع توسط عبارتی ایجاد می شود که با کلمه کلیدی تابع شروع می شود. توابع دارای مجموعه ای از پارامترها هستند (در این مورد فقط x) و بدنه ای حاوی دستورالعمل هایی هستند که باید هنگام فراخوانی تابع اجرا شوند. بدنه یک تابع همیشه در پرانتزهای فرفری محصور می شود، حتی اگر از یک عبارت واحد تشکیل شده باشد.

یک تابع ممکن است چندین پارامتر داشته باشد یا اصلاً هیچ پارامتری نداشته باشد. در مثال زیر، makeNoise لیستی از پارامترها ندارد، اما power دارای دو پارامتر است:

Var makeNoise = function() ( console.log("Shit!"); ); makeNoise(); // ← خریا! قدرت var = تابع (پایه، توان) (نتیجه var = 1؛ برای (تعداد var = 0؛ تعداد< exponent; count++) result *= base; return result; }; console.log(power(2, 10)); // → 1024

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

پارامترها و scope پارامترهای تابع یکسان هستند، اما مقادیر اولیه آنها هنگام فراخوانی تابع تنظیم می شود، نه در کد آن.

یک ویژگی مهم توابع این است که متغیرهای ایجاد شده در یک تابع (شامل پارامترها) محلی برای آن تابع هستند. این بدان معناست که در مثال قدرت، هر بار که تابع فراخوانی می شود، متغیر نتیجه ایجاد می شود و این تجسم های فردی آن هیچ ارتباطی با یکدیگر نخواهند داشت.

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

کد زیر این موضوع را نشان می دهد. دو تابع را تعریف و فراخوانی می کند که مقداری را به متغیر x اختصاص می دهند. اولین آن را به عنوان محلی اعلام می کند، بنابراین فقط متغیر محلی را تغییر می دهد. دومی اعلام نمی کند، بنابراین کار با x در داخل تابع به متغیر سراسری x تعریف شده در ابتدای مثال اشاره دارد.

Var x = "خارج"; var f1 = function() ( var x = "inside f1"; ); f1(); console.log(x); // → var f2 = function() ( x = "inside f2"; ); f2(); console.log(x); // → داخل f2

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

جاوا اسکریپت دامنه تودرتو بیش از فقط بین متغیرهای سراسری و محلی تمایز قائل می شود. توابع را می توان در داخل توابع تعریف کرد که در نتیجه چندین سطح محلی ایجاد می شود.

به عنوان مثال، تابع نسبتاً بی‌معنی زیر حاوی دو تابع دیگر است:

Var landscape = function() (var result = ""; var flat = function(size) ( for (var count = 0; count< size; count++) result += "_"; }; var mountain = function(size) { result += "/"; for (var count = 0; count < size; count++) result += """; result += "\\"; }; flat(3); mountain(4); flat(6); mountain(1); flat(1); return result; }; console.log(landscape()); // → ___/""""\______/"\_

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

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

افرادی که سایر زبان های برنامه نویسی را مطالعه کرده اند ممکن است فکر کنند که هر بلوکی که در بریس های فرفری محصور شده باشد، محیط محلی خود را ایجاد می کند. اما در جاوا اسکریپت فقط توابع محدوده ایجاد می کنند. می توانید از بلوک های مستقل استفاده کنید:

Var چیزی = 1; ( var something = 2; // کاری با متغیر something... ) // از بلوک خارج شد...

اما چیزی در داخل بلوک همان متغیر خارجی است. اگرچه چنین بلوک هایی مجاز هستند، اما استفاده از آنها فقط برای دستورات if و حلقه ها منطقی است.

اگر این برای شما عجیب به نظر می رسد، تنها شما نیستید که چنین فکر می کنید. در نسخه جاوا اسکریپت 1.7 ظاهر شد کلمه کلیدی let، که مانند var کار می کند اما متغیرهایی را ایجاد می کند که محلی برای هر بلوک معین هستند، نه فقط تابع.

توابع به عنوان ارزش نام توابع معمولاً به عنوان نامی برای یک برنامه استفاده می شود. چنین متغیری یک بار تنظیم می شود و تغییر نمی کند. بنابراین به راحتی می توان یک تابع و نام آن را اشتباه گرفت.

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

Var launchMissiles = تابع (مقدار) ( ‎missileSystem.launch("یا!")؛ ); if (safeMode) launchMissiles = function(value) (/* cancel */);

در فصل 5، کارهای شگفت انگیزی را که می توانید با ارسال فراخوانی تابع به دیگر توابع انجام دهید، مورد بحث قرار خواهیم داد.

اعلان توابع نسخه کوتاه تری از عبارت "var Square = تابع..." وجود دارد. کلمه کلیدی تابع را می توان در ابتدای یک عبارت استفاده کرد:

تابع مربع (x) (برگردان x * x؛ )

این یک اعلان تابع است. عبارت متغیر مربع را تعریف می کند و تابع داده شده را به آن اختصاص می دهد. تا اینجای کار خیلی خوبه. در چنین تعریفی تنها یک دام وجود دارد.

Console.log("آینده می گوید:"، future()); تابع future() ( بازگشت "ما هنوز هیچ ماشین پرنده ای نداریم."؛)

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

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

مثال تابع () ( تابع a() () // عادی اگر (چیزی) ( تابع b() () // Ay-yay-yay! ) )

پشته تماس نگاهی دقیق تر به نحوه عملکرد دستور اجرا با توابع مفید است. اینجا برنامه سادهبا فراخوانی چند تابع:

تابع greet(who) ( console.log("Hello, " + who); ) greet("Semyon"); console.log ("Pokeda");

چیزی شبیه به این پردازش می شود: فراخوانی greet باعث می شود که پاس به ابتدای تابع بپرد. تابع داخلی console.log را فراخوانی می کند که کنترل را قطع می کند، کار خود را انجام می دهد و کنترل را برمی گرداند. سپس به آخر سلام می رسد و به جایی که از آنجا خوانده شده برمی گردد. خط بعدی دوباره console.log را فراخوانی می کند.

این را می توان به صورت شماتیک به صورت زیر نشان داد:

Top greet console.log سلام top console.log بالا

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

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

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

تابع chicken() ( return egg(); ) function egg() ( return chicken(); ) console.log(chicken() + " first first."); // → ??

آرگومان های اختیاری کد زیر کاملا قانونی است و بدون مشکل اجرا می شود:

هشدار ("سلام"، "عصر بخیر"، "سلام به همه!");

به طور رسمی، تابع یک آرگومان می گیرد. با این حال، هنگامی که این گونه به چالش کشیده می شود، او شکایت نمی کند. او بقیه استدلال ها را نادیده می گیرد و "سلام" را نشان می دهد.

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

نقطه ضعف این رویکرد این است که ممکن است - و حتی احتمال دارد - تعداد اشتباهی از آرگومان ها را به یک تابع بدون شکایت کسی ارسال کرد.

مزیت این است که می توانید توابعی ایجاد کنید که آرگومان های اختیاری را دریافت کنند. به عنوان مثال، در نسخه بعدی تابع توان، می توان آن را با دو یا یک آرگومان فراخوانی کرد - در حالت دوم، توان برابر با دو خواهد بود و تابع مانند یک مربع عمل می کند.

توان تابع (پایه، توان) (اگر (شار == ​​تعریف نشده) توان = 2؛ نتیجه var = 1؛ برای (تعداد var = 0؛ تعداد< exponent; count++) result *= base; return result; } console.log(power(4)); // → 16 console.log(power(4, 3)); // → 64

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

Console.log("R"، 2، "D"، 2); // → R 2 D 2

بسته شدن توانایی استفاده از فراخوانی تابع به عنوان متغیر، همراه با این واقعیت که هر بار که یک تابع فراخوانی می شود، متغیرهای محلی دوباره ایجاد می شوند، ما را به یک سوال جالب هدایت می کند. وقتی یک تابع از کار می افتد چه اتفاقی برای متغیرهای محلی می افتد؟

مثال زیر این موضوع را نشان می دهد. تابع wrapValue را اعلام می کند که یک متغیر محلی ایجاد می کند. سپس تابعی را برمی گرداند که این متغیر محلی را می خواند و مقدار آن را برمی گرداند.

تابع wrapValue(n) ( var localVariable = n; return function() ( return localVariable; ) var wrap1 = wrapValue(1); var wrap2 = wrapValue(2); console.log(wrap1()); // → 1 console.log(wrap2()); // → 2

این معتبر است و همانطور که باید کار می کند - دسترسی به متغیر باقی می ماند. علاوه بر این، چندین نمونه از یک متغیر می‌توانند به طور همزمان وجود داشته باشند، که بیشتر این واقعیت را تأیید می‌کند که متغیرهای محلی با هر فراخوانی تابع دوباره ایجاد می‌شوند.

این توانایی برای کار با ارجاع به یک نمونه از یک متغیر محلی، بسته شدن نامیده می شود. تابعی که متغیرهای محلی را می بندد، closure نامیده می شود. نه تنها شما را از نگرانی در مورد طول عمر متغیر رها می کند، بلکه به شما امکان می دهد از توابع خلاقانه استفاده کنید.

با کمی تغییر، مثال خود را به تابعی تبدیل می کنیم که اعداد را در هر عدد داده شده ضرب می کند.

ضریب تابع (ضریب) ( تابع بازگشتی (عدد) (عدد بازگشتی * ضریب؛ ) var double = ضرب (2); console.log(twice(5)); // → 10

یک متغیر جداگانه مانند localVariable از مثال wrapValue دیگر مورد نیاز نیست. از آنجایی که پارامتر خود یک متغیر محلی است.

برای شروع به این روش فکر کردن نیاز به تمرین دارد. گزینه خوبیهمدل ذهنی این است که تصور کنید که تابع کد را در بدن خود منجمد می کند و آن را در بسته بندی می پیچد. وقتی تابع بازگشت (...) (...) را مشاهده می کنید، آن را به عنوان یک کنترل پنل برای یک قطعه کد منجمد شده برای استفاده بعدی در نظر بگیرید.

در مثال ما، ضرب‌کننده یک قطعه کد منجمد شده را برمی‌گرداند که آن را در متغیر double ذخیره می‌کنیم. آخرین خط تابع موجود در متغیر را فراخوانی می کند که باعث می شود کد ذخیره شده فعال شود (شماره بازگشت * فاکتور؛). همچنان به متغیر عاملی که هنگام فراخوانی ضریب تعریف شده است دسترسی دارد و همچنین به آرگومان ارسال شده در حین یخ زدایی (5) به عنوان یک پارامتر عددی دسترسی دارد.

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

تابع قدرت(پایه، توان) (اگر (نما == 0) برگرداند 1؛ در غیر این صورت، پایه * توان (پایه، توان - 1) را برگرداند؛ ) console.log(power(2، 3)); // → 8

تقریباً ریاضیدانان قدرت را اینگونه تعریف می کنند و شاید این مفهوم را زیباتر از یک چرخه توصیف می کند. این تابع بارها خود را با آرگومان های مختلف فراخوانی می کند تا به ضرب چندگانه برسد.

با این حال، یک مشکل در این اجرا وجود دارد: محیط معمولیجاوا اسکریپت 10 برابر کندتر از نسخه دارای حلقه است. راه رفتن در یک حلقه ارزان تر از فراخوانی یک تابع است.

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

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

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

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

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

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

در اینجا یک معما وجود دارد: شما می توانید تعداد نامتناهی اعداد را با شروع با عدد 1 بدست آورید و سپس 5 را با آن جمع کنید یا در 3 ضرب کنید. که منجر به یک عدد معین می شود؟ برای مثال می توان عدد 13 را با ضرب 1 در 3 و سپس دوبار جمع کردن 5 بدست آورد. و اصلا عدد 15 را نمی توان از این طریق بدست آورد.

راه حل بازگشتی:

تابع findSolution(target) ( تابع find(شروع، تاریخچه) (اگر (شروع == هدف) تاریخچه را برمی گرداند؛ در غیر این صورت اگر (شروع > هدف) را برگرداند null؛ در غیر این صورت بازگشت find(start + 5، "(" + history + " + 5)") || find(start * 3, "(" + history + " * 3)"); ) return find(1, "1"); ) console.log(findSolution(24)); // → (((1 * 3) + 5) * 3)

این مثال لزوما کوتاه ترین راه حل را پیدا نمی کند - هر کدام راضی می شود. من انتظار ندارم که شما فوراً نحوه عملکرد برنامه را درک کنید. اما بیایید این تمرین عالی در تفکر بازگشتی را درک کنیم.

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

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

برای درک بهتر اینکه تابع چگونه به اثر دلخواه خود می رسد، به فراخوانی هایی که برای یافتن راه حلی برای عدد 13 می دهد نگاهی بیاندازیم.

Find(1, "1") find(6, "(1 + 5)") find(11, "((1 + 5) + 5)") find(16, "((1 + 5) + 5 (( 3، "(1 * 3)") find(8، "((1 * 3) + 5)") find(13، "(((1 * 3) + 5) + 5)") یافت شد!

تورفتگی عمق پشته تماس را نشان می دهد. اولین بار، تابع find دو بار خود را فراخوانی می کند تا راه حل هایی را که با (1 + 5) و (1 * 3) شروع می شود بررسی کند. اولین فراخوانی به دنبال راه حلی می گردد که با (1 + 5) شروع می شود و از بازگشت برای بررسی همه راه حل هایی که عددی کمتر یا مساوی با عدد مورد نیاز تولید می کنند، استفاده می کند. آن را پیدا نمی کند و null برمی گرداند. سپس اپراتور || و به فراخوانی تابعی می رود که گزینه (1 * 3) را بررسی می کند. ما در اینجا خوش شانس هستیم، زیرا در سومین فراخوانی بازگشتی 13 دریافت می کنیم. این فراخوانی یک رشته را برمی گرداند و هر یک از || در طول مسیر از این خط بالاتر می گذرد و در نتیجه یک محلول برمی گردد.

توابع در حال رشد دو روش کم و بیش طبیعی برای معرفی توابع به یک برنامه وجود دارد.

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

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

این که نام‌گذاری یک تابع برای شما دشوار است نشان می‌دهد که چقدر عملکرد آن را درک کرده‌اید. بیایید یک مثال بزنیم. ما باید برنامه ای بنویسیم که دو عدد، تعداد گاوها و جوجه های مزرعه را چاپ کند و به دنبال آن کلمات "گاو" و "مرغ" را چاپ کند. باید صفرها را به اعداد جلو اضافه کنید تا هر کدام دقیقاً سه موقعیت را اشغال کند.

007 گاو 011 جوجه

بدیهی است که ما به یک تابع با دو آرگومان نیاز داریم. بیایید شروع به کدنویسی کنیم.
// چاپ تابع FarmInventory printFarmInventory(گاو، مرغ) ( var cowString = String(cows)؛ while (cowString.length< 3) cowString = "0" + cowString; console.log(cowString + " Коров"); var chickenString = String(chickens); while (chickenString.length < 3) chickenString = "0" + chickenString; console.log(chickenString + " Куриц"); } printFarmInventory(7, 11);

اگر .length را به یک رشته اضافه کنیم، طول آن را بدست می آوریم. معلوم می شود که در حالی که حلقه هاصفرهای ابتدایی را به اعداد اضافه کنید تا زمانی که یک رشته 3 کاراکتری به دست آورید.

آماده! اما درست زمانی که می خواستیم کد را برای کشاورز بفرستیم (البته به همراه یک چک سنگین)، او زنگ می زند و به ما می گوید که در مزرعه خود خوک دارد و آیا می توانیم نمایشی از تعداد خوک ها را به آن اضافه کنیم. برنامه؟

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

// خروجی با افزودن صفر و تابع برچسب printZeroPaddedWithLabel(شماره، برچسب) ( var numberString = String(number)؛ while (numberString.length< 3) numberString = "0" + numberString; console.log(numberString + " " + label); } // вывестиИнвентаризациюФермы function printFarmInventory(cows, chickens, pigs) { printZeroPaddedWithLabel(cows, "Коров"); printZeroPaddedWithLabel(chickens, "Куриц"); printZeroPaddedWithLabel(pigs, "Свиней"); } printFarmInventory(7, 11, 3);

آثار! اما نام printZeroPaddedWithLabel کمی عجیب است. این سه چیز - خروجی، اضافه کردن صفر و یک برچسب - را در یک تابع ترکیب می کند. به جای درج یک قطعه تکرار شونده کامل در یک تابع، بیایید یک مفهوم را برجسته کنیم:

// اضافه کردن تابع صفر zeroPad(عدد، عرض) ( var string = string(number)؛ while (string.length< width) string = "0" + string; return string; } // вывестиИнвентаризациюФермы function printFarmInventory(cows, chickens, pigs) { console.log(zeroPad(cows, 3) + " Коров"); console.log(zeroPad(chickens, 3) + " Куриц"); console.log(zeroPad(pigs, 3) + " Свиней"); } printFarmInventory(7, 16, 3);

یک تابع با نام زیبا و واضح zeroPad درک کد را آسان تر می کند. و می تواند در بسیاری از موقعیت ها استفاده شود، نه تنها در مورد ما. به عنوان مثال، برای نمایش جداول فرمت شده با اعداد.

ویژگی ها چقدر باید هوشمند و همه کاره باشند؟ ما می توانیم یک تابع ساده بنویسیم که عددی را با صفر تا سه موقعیت قرار می دهد، یا یک تابع پیچیده. همه منظورهبرای قالب بندی اعداد، پشتیبانی از کسرها، اعداد منفی، تراز نقطه، پر کردن با کاراکترهای مختلف و غیره.

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

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

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

یک تابع خالص نوع خاصی از تابع برگردان ارزش است که نه تنها عوارض جانبی ندارد، بلکه به عوارض جانبی بقیه کد نیز بستگی ندارد - به عنوان مثال، با متغیرهای سراسری که ممکن است تصادفی باشند کار نمی کند. جای دیگری تغییر کرد یک تابع خالص، هنگامی که با همان آرگومان ها فراخوانی می شود، همان نتیجه را برمی گرداند (و هیچ کار دیگری انجام نمی دهد) - که بسیار خوب است. کار کردن با او آسان است فراخوانی چنین تابعی را می توان به طور ذهنی با نتیجه کار آن جایگزین کرد، بدون اینکه معنای کد تغییر کند. هنگامی که می خواهید چنین تابعی را آزمایش کنید، می توانید به سادگی آن را فراخوانی کنید و مطمئن باشید که اگر در یک زمینه مشخص کار کند، در هر زمینه ای کار خواهد کرد. عملکردهای کمتر خالص ممکن است بسته به عوامل بسیاری نتایج متفاوتی را ارائه دهند و دارای عوارض جانبی باشند که آزمایش و محاسبه آنها دشوار است.

با این حال، نباید از نوشتن توابعی که کاملاً خالص نیستند، یا شروع به پاکسازی کد مقدس چنین توابعی خجالت بکشید. عوارض جانبی اغلب مفید هستند. هیچ راهی برای نوشتن یک نسخه تمیز از تابع console.log وجود ندارد و این تابع کاملاً مفید است. بیان برخی از عملیات ها با استفاده از عوارض جانبی آسان تر است.

خلاصه این فصل به شما نشان داد که چگونه توابع خود را بنویسید. هنگامی که کلمه کلیدی تابع به عنوان یک عبارت استفاده می شود، یک اشاره گر به فراخوانی تابع برمی گرداند. هنگامی که به عنوان یک دستورالعمل استفاده می شود، می توانید یک متغیر را با اختصاص یک فراخوانی تابع به آن اعلام کنید.

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

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

ExercisesMinimum در فصل قبل به تابع Math.min اشاره کردیم که کوچکترین آرگومان های خود را برمی گرداند. حالا ما می توانیم چنین تابعی را خودمان بنویسیم. یک تابع min بنویسید که دو آرگومان را بگیرد و حداقل آنها را برگرداند.

Console.log(min(0, 10)); // → 0 console.log(min(0, -10)); // → -10

بازگشت دیدیم که عملگر % (مدول) می تواند برای تعیین زوج بودن عدد (%2) استفاده شود. در اینجا روش دیگری برای تعریف آن وجود دارد:

صفر زوج است.
واحد فرد است.
هر عدد N برابر با N-2 است.

طبق این قوانین یک تابع بازگشتی isEven بنویسید. باید یک عدد را بپذیرد و یک مقدار بولی برگرداند.

آن را در 50 و 75 تست کنید. سعی کنید به آن -1 بدهید. چرا او اینگونه رفتار می کند؟ امکانش هست یه جوری درستش کرد؟

آن را روی 50 و 75 تست کنید. ببینید در -1 چگونه رفتار می کند. چرا؟ آیا می توانید راهی برای رفع این مشکل فکر کنید؟

Console.log(isEven(50)); // → true console.log(isEven(75)); // → false console.log(isEven(-1)); // → ??

شمارش لوبیاها

کاراکتر شماره N یک رشته را می توان با اضافه کردن .charAt(N) ("string".charAt(5)) به آن به دست آورد - به روشی مشابه به دست آوردن طول یک رشته با استفاده از .length. مقدار بازگشتی رشته ای متشکل از یک کاراکتر خواهد بود (به عنوان مثال "k"). اولین کاراکتر رشته دارای موقعیت 0 است، به این معنی که آخرین کاراکتر دارای string.length - 1 موقعیت است. به عبارت دیگر، یک رشته از دو کاراکتر دارای طول 2 است و موقعیت کاراکترهای آن 0 و 1 خواهد بود.

یک تابع countBs بنویسید که یک رشته را به عنوان آرگومان می گیرد و تعداد کاراکترهای "B" موجود در رشته را برمی گرداند.

سپس تابعی به نام countChar بنویسید که چیزی شبیه به countB ها عمل می کند، اما پارامتر دوم را می گیرد - کاراکتری که در رشته به دنبال آن خواهیم بود (به جای اینکه فقط تعداد کاراکترهای "B" را بشماریم). برای انجام این کار، تابع countBs را دوباره کار کنید.

توابع یکی از مهم ترین بلوک های سازنده کد در جاوا اسکریپت هستند.

توابع از مجموعه ای از دستورات تشکیل شده و معمولاً یکی را انجام می دهند وظیفه خاص(مثلاً جمع اعداد، محاسبه ریشه ها و ...).

کد قرار داده شده در یک تابع تنها پس از فراخوانی صریح این تابع اجرا می شود.

اعلامیه عملکرد

1. نحو:

//اعلان تابع functionFunctionname(ln1, ln2)( کد تابع) // فراخوانی functionFunctionname(ln1,lr2);

2. نحو:

//اعلان تابع var name function=function(ln1, ln2)(کد تابع) // فراخوانی تابع نام (ln1,lr2);

functionname نام تابع را مشخص می کند. هر تابع در صفحه باید یک نام منحصر به فرد داشته باشد. نام تابع باید با حروف لاتین مشخص شود و نباید با اعداد شروع شود.

ln1 و ln2 متغیرها یا مقادیری هستند که می توانند به تابع منتقل شوند. تعداد نامحدودی از متغیرها را می توان به هر تابع ارسال کرد.

لطفاً توجه داشته باشید: حتی اگر هیچ متغیری به تابع ارسال نشده باشد، فراموش نکنید که پرانتز "()" را بعد از نام تابع درج کنید.

لطفاً توجه داشته باشید که نام توابع در جاوا اسکریپت به حروف کوچک و بزرگ حساس است.

مثال تابع جاوا اسکریپت

تابع messageWrite() در مثال زیر تنها پس از کلیک روی دکمه اجرا می شود.

توجه داشته باشید که این مثال از رویداد onclick استفاده می کند. رویدادهای جاوا اسکریپتبعداً در این کتاب درسی به تفصیل مورد بحث قرار خواهد گرفت.

// تابع متنی را در تابع صفحه می نویسد messageWrite() ( document.write("این متن با استفاده از جاوا اسکریپت روی صفحه نوشته شد!")؛ )

انتقال متغیرها به توابع

شما می توانید تعداد نامحدودی از متغیرها را به توابع ارسال کنید.

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

/* بیایید تابعی تعریف کنیم که 10 را به متغیر پاس شده اضافه کند و نتیجه را در صفحه نمایش دهد */ function plus(a)(a=a+10; document.write("خروجی تابع: " + a+"
"); ) var a=25; document.write("مقدار متغیر قبل از فراخوانی تابع: "+a+"
")؛ // فراخوانی تابع با ارسال متغیر plus(a)؛ document.write("مقدار متغیر پس از فراخوانی تابع: "+a+"
");

مشاهده سریع

برای دسترسی به یک متغیر سراسری از یک تابع به جای کپی از آن، از window.variable_name استفاده کنید.

تابع plus(a)( window.a=a+10; ) var a=25; document.write("مقدار متغیر قبل از فراخوانی تابع: "+a+"
"); plus(a); document.write("مقدار متغیر پس از فراخوانی تابع: "+a+"
");

مشاهده سریع

دستور بازگشت

با دستور return می توانید مقادیری را از توابع برگردانید.

//تابع sum مجموع متغیرهای ارسال شده به آن تابع sum(v1,v2)( return v1+v2; ) document.write("5+6=" + sum(5,6) + " را برمی گرداند.
"); document.write("10+4=" + sum(10,4) + "
");

مشاهده سریع

توابع داخلی

جاوا اسکریپت علاوه بر توابع تعریف شده توسط کاربر، توابع داخلی نیز دارد.

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

Document.write(isFinite(40)+"
"); document.write(isFinite(-590)+"
"); document.write(isFinite(90.33)+"
"); document.write(isFinite(NaN)+"
"); document.write(isFinite("این یک رشته است")+"
");

مشاهده سریع

توجه داشته باشید: لیست کاملساخته شده است توابع جاوا اسکریپتشما می توانید آن را در ما پیدا کنید.

متغیرهای محلی و جهانی

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

پس از اتمام اجرای کد تابع، چنین متغیرهایی از بین می روند. این بدان معناست که متغیرهایی با نام یکسان را می توان در توابع مختلف تعریف کرد.

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

اگر یک متغیر را بدون var در داخل یک تابع اعلام کنید، آن نیز جهانی می شود.

متغیرهای سراسری تنها پس از بسته شدن صفحه از بین می روند.

//اعلان متغیرهای جهانی var1 و var2 var var1="var1 وجود دارد"; var var2; تابع func1() (//تخصیص var2 یک مقدار در داخل تابع func1 var var2="var2 وجود دارد"؛ ) //از یک تابع دیگر، محتویات متغیر var1 و var2 را به تابع صفحه ()func2 (//Output کنید محتویات متغیر var1 document.write(var1 + "
"); //خروجی محتوای متغیر var2 document.write(var2); )

مشاهده سریع

توجه داشته باشید که وقتی روی صفحه چاپ می شود، var2 یک مقدار خالی خواهد داشت زیرا func1 در "نسخه" محلی var2 عمل می کند.

استفاده از توابع ناشناس

توابعی که هنگام اعلام نام حاوی نام نیستند، ناشناس نامیده می شوند.

توابع ناشناس اساساً اعلام می‌شوند که متعاقباً مانند توابع معمولی از کد فراخوانی نمی‌شوند، بلکه به عنوان یک پارامتر به توابع دیگر ارسال می‌شوند.

تابع arrMap(arr,func)(var res=آرایه جدید؛ برای (var i=0;i