npm install @aspnet/signalr-client --save
بررسی محتوای پوشهی node_modules\@aspnet\signalr-client
پس از نصب بستهی «aspnet/signalr-client@»، در مسیر node_modules\@aspnet\signalr-client\dist دو پوشهی src و browser را خواهید یافت. پوشهی src حاوی منبع کامل این کلاینت و همچنین فایلهای Typings مخصوص تایپاسکریپت است.
و پوشهی browser آن شامل دو گروه فایل است:
- در اینجا گروهی از فایلها، حاوی عبارت ES5 هستند و تعدادی خیر. SignalR JavaScript بر اساس ES 6 یا EcmaScript 2015 تهیه شدهاست و از مفاهیمی مانند Promises و arrow functions استفاده میکند. باید دقت داشت که تعدادی از مرورگرها مانند IE از این قابلیتها پیشتیبانی نمیکنند. در بین این فایلها، آنهایی که حاوی عبارت ES5 نیستند، یعنی بر اساس ES 6 تهیه شدهاند. سایر فایلها توسط قابلیت Transpile مربوط به TypeScript به ES5 ترجمه شدهاند. به علاوه حجم این فایلها نیز بیشتر میباشد؛ چون حاوی تعاریف وابستگیهایی هستند که در ES 5 وجود خارجی ندارند. بنابراین بسته به نوع مرورگر مدنظر، یکی از این دو گروه را باید انتخاب کرد؛ ES 6 برای مرورگرهای جدید و ES 5 برای مرورگرهای قدیمی.
- به علاوه در اینجا تعدادی از فایلها حاوی عبارت msgpackprotocol هستند. نگارش جدید SignalR از پروتکلهای هاب سفارشی مانند پروتکلهای باینری نیز پشتیبانی میکند. همچنین حاوی یک پیاده سازی توکار از پروتکلهای باینری بر اساس MessagePack نیز هست. چون حجم کدهای پشتیبانی کنندهی از این پروتکل ویژه بالا است، آنرا به یک فایل مجزا انتقال دادهاند تا در صورت نیاز مورد استفاده قرار گیرد. بنابراین اگر از این پروتکل استفاده نمیکنید، نیازی هم به الحاق آن در صفحات خود نخواهید داشت. فایل third-party-notices.txt نیز مربوط است به یادآوری مجوز استفادهی از MessagePack که MIT میباشد.
- در هر گروه نیز، دو فایل min و معمولی قابل مشاهدهاست. فایلهای min برای توزیع نهایی مناسب هستند و فایلهای غیرفشرده شده برای حالت دیباگ.
استفاده از کلاینت جاوا اسکریپتی SignalR Core
برای کار با کلاینت جاوا اسکریپتی SignalR Core از همان فایلهای موجود در پوشهی node_modules/@aspnet/signalr-client/dist/browser استفاده میکنیم. تفاوت این کلاینت با نگارش قبلی SignalR به صورت یک ذیل است:
1) ارجاع به فایل قدیمی signalR-2.2.1.min.js با فایل جدید signalR-client-1.0.0-alpha1.js جایگزین میشود. اگر میخواهید مرورگرهای قدیمی را پشتیبانی کنید، نگارش ES5 آنرا لحاظ کنید.
2) پروکسیها با new HubConnection جایگزین شدهاند.
3) برای ثبت callbackهای سمت کلاینت، از متد جدید on استفاده میشود.
4) بجای متد done مربوط به jQuery، در اینجا از متد then مربوط به ES6 کمک گرفته شدهاست.
5) کار فراخوانی متدهای هاب توسط متد invoke انجام میشود.
یک مثال: بازنویسی قسمت سمت کلاینت مثال «کار با SignalR Core از طریق یک کلاینت Angular» با jQuery
هرچند کلاینت جدید SignalR Core وابستگی به jQuery ندارد، اما جهت سهولت کار با DOM، کدهای سمت کلاینت مثال قبلی را با jQuery بازنویسی میکنیم. تمام کدهای سمت سرور این مثال با مطلب «کار با SignalR Core از طریق یک کلاینت Angular» یکی است؛ مانند ایجاد هاب، فعالسازی SiganlR در فایل آغازین برنامه و ثبت مسیرهاب. بنابراین در اینجا، این قسمت از کدهای سمت سرور را مجددا تکرار نمیکنیم و تمام نکات آن یکی هستند.
برای کار با کلاینت جاوا اسکریپتی SignalR Core، اینبار دستور ذیل را در ریشهی پروژهی وب اجرا میکنیم (یا هر پروژهای که قرار است مدیریت فایلهای سمت کلاینت و Viewهای برنامه را انجام دهد):
npm init npm install @aspnet/signalr-client --save bower install
مرحلهی بعدی کار، تنظیمات فایل bundleconfig.json است؛ تا تمام اسکریپتهای مورد نیاز جمعآوری و یکی شوند:
[ { "outputFileName": "wwwroot/css/site.min.css", "inputFiles": [ "wwwroot/lib/bootstrap/dist/css/bootstrap.min.css", "wwwroot/css/site.css" ] }, { "outputFileName": "wwwroot/js/site.min.js", "inputFiles": [ "wwwroot/lib/jquery/dist/jquery.min.js", "wwwroot/lib/bootstrap/dist/js/bootstrap.min.js", "node_modules/@aspnet/signalr-client/dist/browser/signalr-client-1.0.0-alpha1-final.min.js", "wwwroot/lib/jquery-validation/dist/jquery.validate.min.js", "wwwroot/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.min.js", "wwwroot/lib/jquery-ajax-unobtrusive/jquery.unobtrusive-ajax.min.js", "wwwroot/js/site.js" ], "minify": { "enabled": false, "renameLocals": false }, "sourceMap": false } ]
با توجه به خروجیهای نهایی فایل bundleconfig.json، تنها نیاز است مداخل ذیل را به فایل layout برنامه اضافه کرد:
<link href="~/css/site.min.css" rel="stylesheet" asp-append-version="true" /> <script src="~/js/site.min.js" type="text/javascript" asp-append-version="true"></script>
مرحلهی بعد، تغییر نام متد send قسمت قبل به broadcastMessage است:
public class MessageHub : Hub { public Task Send(string message) { return Clients.All.InvokeAsync("broadcastMessage", message); } }
در ادامه یک کنترلر ساده را به نام JsClientController با View ذیل ایجاد میکنیم:
<form method="post" asp-action="Index" asp-controller="Home" data-ajax="true" role="form"> <div class="form-group"> <label label-for="message">Message: </label> <input id="message" name="message" class="form-control"/> </div> <button class="btn btn-primary" type="submit">Send To Home/Index</button> <button class="btn btn-success" id="sendmessageDirect" type="button">Send To /message hub directly</button> </form> <div id="discussion"> </div>
از اولین دکمه برای ارسال یک پیام به کنترلر Home که در آن توسط <IHubContext<MessageHub پیامی به تمام کلاینتها ارسال میشود، استفاده شدهاست. دومین دکمه متد Send هاب را مستقیما فراخوانی میکند؛ با این کدهای سمت کلاینت:
@section Scripts { <script type="text/javascript" asp-append-version="true"> $(function() { var connection = new signalR.HubConnection('/message'); connection.on('broadcastMessage', function (message) { // Add the message to the page. var encodedMsg = $('<div />').text(message).html(); $('#discussion').append('<li>' + encodedMsg + '</li>'); }); connection.start().then(function () { console.log('connected.'); $('#sendmessageDirect').click(function () { // Call the Send method on the hub. connection.invoke('send', $('#message').val()); }); }); }); </script> }
- سپس در متد on هست که مشخص میکنیم متد سمت کلاینتی که قرار است از سمت سرور فراخوانی شود، چه نامی دارد. نام آنرا در این مثال broadcastMessage درنظر گرفتهایم. در اینجا پارامتر message از سمت سرور دریافت شده و سپس در صفحهی جاری نمایش داده میشود.
بدیهی است متد Send میتواند تعداد پارامترهای بیشتری را بپذیرد و همچنین متد broadcastMessage نیز محدودیتی از لحاظ تعداد پارامتر ندارد. اگر پارامترهای بیشتری را تعریف کردید، در همینجا باید قید شوند.
- در ادامه کار شروع این اتصال آغاز میشود. در متد then هست که باید کار اتصال دکمهی sendmessageDirect صورت گیرد. چون عملیات اتصال ممکن است زمانبر باشد و connection ارسالی هنوز آغاز نشده باشد. در اینجا نحوهی فراخوانی مستقیم متد Send سمت سرور را با یک پارامتر ملاحظه میکنید. این متد نیز میتواند بر اساس امضای متد Send سمت سرور، تعداد پارامترهای بیشتری را قبول کند.
کدهای کامل این مثال را از اینجا میتوانید دریافت کنید: SignalRCore2WebApp02.zip
برای اجرا آن باید این دستورات را به ترتیب وارد کنید:
dotnet restore npm install npm install -g bower bower install dotnet watch run
تزریق خودکار وابستگیها در برنامههای ASP.NET MVC
به صورت پیش فرض، ASP.NET MVC به کنترلرهایی نیاز دارد که سازنده آنها فاقد پارامتر باشند. از این جهت که بتواند به صورت خودکار آنها را وهله سازی کرده و مورد استفاده قرار دهد. بنابراین به نظر میرسد که در اینجا نیز به همان روش معروف استفاده از الگوی Service locator و تکرار مدام کدهایی مانند ObjectFactory.GetInstance در سراسر برنامه خواهیم رسید که آنچنان مطلوب نیست.
اما ... در ASP.NET MVC میتوان وهله ساز پیش فرض کنترلرها را با پیاده سازی کلاس DefaultControllerFactory به طور کامل تعویض کرد. یعنی اگر در اینجا بجای وهله ساز پیش فرض، از وهله سازی انجام شده توسط IoC Container خود بتوانیم استفاده کنیم، آنگاه کار تزریق وابستگیها در سازندههای کنترلرها نیز خودکار خواهد گردید.
public class StructureMapControllerFactory : DefaultControllerFactory { protected override IController GetControllerInstance(RequestContext requestContext, Type controllerType) { if (controllerType == null) throw new InvalidOperationException(string.Format("Page not found: {0}", requestContext.HttpContext.Request.Url.AbsoluteUri.ToString(CultureInfo.InvariantCulture))); return ObjectFactory.GetInstance(controllerType) as Controller; } }
برای استفاده از این ControllerFactory جدید تنها کافی است بنویسیم:
protected void Application_Start() { //Set current Controller factory as StructureMapControllerFactory ControllerBuilder.Current.SetControllerFactory(new StructureMapControllerFactory()); }
اکنون نوشتن یک چنین کنترلرهایی که سازنده آنها دارای پارامتر است، مجاز خواهد بود و تزریق وابستگیها در سازندهها به صورت خودکار توسط IoC Container مورد استفاده انجام میشود.
public partial class LoginController : Controller { readonly IUsersService _usersService; public LoginController(IUsersService usersService) { _usersService = usersService; }
روش دوم تزریق خودکار وابستگیها در برنامههای ASP.NET MVC
روش پیاده سازی و تعویض DefaultControllerFactory پیش فرض، متداولترین روش خودکار سازی تزریق وابستگیها در ASP.NET MVC است. روش دیگری نیز بر اساس پیاده سازی اینترفیس توکار IDependencyResolver معرفی شده در ASP.NET MVC 3.0 به بعد، وجود دارد. این روش علاوه بر ASP.NET MVC در کنترلرهای مخصوص Web API نیز کاربرد دارد. حتی SignalR نیز دارای کلاس پایهای به نام DefaultDependencyResolver با امضای مشابه IDependencyResolver است.
using System; using System.Collections.Generic; using System.Linq; using System.Web.Mvc; using StructureMap; namespace Prog { public class StructureMapDependencyResolver : IDependencyResolver { public object GetService(Type serviceType) { if (serviceType.IsAbstract || serviceType.IsInterface || !serviceType.IsClass) return ObjectFactory.TryGetInstance(serviceType); return ObjectFactory.GetInstance(serviceType); } public IEnumerable<object> GetServices(Type serviceType) { return ObjectFactory.GetAllInstances(serviceType).Cast<object>(); } } }
protected void Application_Start() { DependencyResolver.SetResolver(new StructureMapDependencyResolver()); }
دریافت مثال کامل بحث جاری:
DI06.zip
بایدها و نبایدهای استفاده از IoC Containers
تفاوت پایهای که بین یک فریم ورک IoC و سایر فریم ورکها وجود دارد، در معکوس شدن مسئولیتها است. در اینجا لایههای مختلف برنامه شما نیستند که فریم ورک IoC را فراخوانی میکنند؛ بلکه این فریم ورک IoC است که از جزئیات ارتباطات و وابستگیهای سیستم شما آگاه است و نهایتا کار کنترل وهله سازی اشیاء مختلف را عهده دار خواهد شد. طول عمر آنها را تنظیم کرده یا حتی در بعضی از موارد مانند برنامه نویسی جنبهگرا یا AOP، نسبت به تزئین این اشیاء یا دخالت در مراحل مختلف فراخوانی متدهای آنها نیز نقش خواهد داشت. نکتهی مهم در اینجا، نا آگاهی برنامه از حضور آنها است.
بنابراین در پروژه شما اگر ماژولها و لایههای مختلفی حضور دارند، تنها برنامه اصلی است که باید ارجاعی را به فریم ورک IoC داشته باشد و نه سایر لایههای سیستم. علت حضور آن در ریشه سیستم نیز تنها باید به اصطلاحا bootstrapping و اعمال تنظیمات مرتبط با آن خلاصه شود.
به عبارتی استفاده صحیح از یک فریم ورک IoC نباید به شکل الگوی Service Locator باشد؛ حالتی که در تمام قسمتهای برنامه مدام مشاهده میکنید resolver.Resolve، resolver.Resolve و الی آخر. باید از این نوع استفاده از فریم ورکهای IoC تا حد ممکن حذر شود و کدهای برنامه نباید وابستگی مستقیم ثانویهای را به نام خود فریم ورک IoC پیدا کنند.
var container = BootstrapContainer(); var finder = container.Resolve<IDuplicateFinder>(); var processor = container.Resolve<IArgumentsParser>(); Execute( args, processor, finder ); container.Dispose();
الف) در آغاز برنامه برای اعمال تنظیمات اولیه و bootstrapping
ب) پیش از اجرای عملی جهت وهله سازی وابستگیهای مورد نیاز
ج) پس از اجرای عمل مورد نظر جهت آزاد سازی منابع
نکته مهم اینجا است که در حین اجرای فرآیند، این فرآیند باید تا حد ممکن از حضور IoC container بیخبر باشد و کار تشکیل اشیاء باید خارج از منطق تجاری برنامه انجام شود: IoC container خود را صدا نزنید؛ او شما را صدا خواهد زد.
عنوان شد تا «حد ممکن». این تا حد ممکن به چه معنایی است؟ اگر کار وهله سازی اشیاء را میتوانید تحت کنترل قرار دهید، مثلا آیا میتوانید در نحوه وهله سازی کنترلرها در ASP.NET MVC دخل و تصرف کرده و در زمان وهله سازی، اینکار را به یک IoC Container واگذار کنید؟ اگر بلی، دیگر به هیچ عنوانی نباید داخل کلاسهای فراخوانی شده و تزریق شده به کنترلرهای برنامه اثری از IoC Container شما مشاهده شود. زیرا این فریم ورکها اینقدر توانمند هستند که بتوانند تا چندین لایه از سیستم را واکاوی کرده و وابستگیهای لازم را وهله سازی کنند.
اگر خیر (نمیتوانید کار وهله سازی اشیاء را مستقیما تحت کنترل قرار دهید)؛ مانند تهیه یک Role Provider سفارشی در ASP.NET MVC که کار وهله سازی این Role Provider راسا توسط موتور ASP.NET انجام میشود و در این بین امکان دخل و تصرفی هم در آن ممکن نیست، آنگاه مجاز است داخل این کلاس ویژه از متدهای container.Resolve استفاده کرد؛ چون چارهی دیگری وجود ندارد و IoC Container نیست که کار وهله سازی ابتدایی آنرا عهده دار شده است. باید دقت داشت به این حالت خاص دیگر تزریق وابستگیها گفته نمیشود؛ بلکه نام الگوی آن Service locator است. در Service locator یک کامپوننت خودش به دنبال وابستگیهای مورد نیازش میگردد. در حالت تزریق وابستگیها، یک کامپوننت وابستگیهای مورد نیاز را درخواست میکند.
یک مثال:
public class ExampleClass { private readonly IService _service; public ExampleClass() { _service = Container.Resolve<IService>(); } public void DoSomething(int id) { _service.DoSomething(id); } }
نمونه اصلاح شده کلاس فوق، تزریق وابستگیها در سازنده کلاس به نحو زیر است:
public class ExampleClass { private IService _service; public ExampleClass(IService service) { _service = service; } public void DoSomething(int id) { _service.DoSomething(id); } }
نمونه دیگری از کلاسی که خودش به دنبال یافتن و وهله سازی وابستگیهای مورد نیازش است مثال زیر میباشد:
public class Search { IDinner _dinner; public Search(): this(new Dinner()) { } public Search(IDinner dinner) { _dinner = dinner; } }
برای بررسی ویژگیهای جاوا اسکریپت مدرن، یک پروژهی جدید React را ایجاد میکنیم.
> create-react-app sample-02 > cd sample-02 > npm start
به علاوه چون در این قسمت خروجی UI نخواهیم داشت، تمام خروجی را در کنسول developer tools مرورگر خود میتوانید مشاهده کنید (فشردن دکمهی F12).
var، let و const
در اکثر زبانهای برنامه نویسی، متغیرها در محدودهی دید قطعه کدی که تعریف شدهاند (scope)، قابل دسترسی هستند. برای نمونه محتوای فایل index.js پروژه را به صورت زیر تغییر داده و با فرض اجرای دستور npm start، خروجی آنرا میتوان در کنسول مرورگر مشاهده کرد.
function sayHello() { for (var i = 0; i < 5; i++) { console.log(i); } console.log(i); } sayHello();
در آخرین پیمایش حلقه، i مساوی 5 شده و از حلقه خارج میشود. اما چون در اینجا برای تعریف متغیر از واژهی کلیدی var استفاده شدهاست، محدودهی دید آن به کل تابعی که در آن تعریف شدهاست، بسط پیدا میکند. به همین جهت در این خروجی، عدد 5 را نیز مشاهده میکند که حاصل دسترسی به i، خارج از حلقهاست.
برای یک دست سازی این رفتار با سایر زبانهای برنامه نویسی، در ES6، واژهی کلیدی جدیدی به نام let تعریف شدهاست که میدان دید متغیر را به قطعه کدی که در آن تعریف شدهاست، محدود میکند. اکنون اگر در حلقهی فوق بجای var از let استفاده شود، یک چنین خطایی در مرورگر ظاهر خواهد شد که عنوان میکند، i استفاده شدهی در خارج از حلقه، تعریف نشدهاست.
./src/index.js Line 14:15: 'i' is not defined no-undef Search for the keywords to learn more about each error.
علاوه بر let، واژهی کلیدی جدید const نیز به ES6 اضافه شدهاست که از آن برای تعریف ثوابت استفاده میشود. constها نیز همانند let، میدان دید محدود شدهای به قطعه کد تعریف شدهی در آن دارند؛ اما قابلیت انتساب مجدد را ندارند:
const x = 1; x = 2; // Attempting to override 'x' which is a constant.
به صورت خلاصه از این پس واژهی کلیدی var را فراموش کنید. همیشه با const جهت تعریف متغیرها شروع کنید. اگر به خطا برخوردید و نیاز به انتساب مجدد وجود داشت، آنرا به let تغییر دهید. بنابراین استفاده از const همیشه نسبت به let ارجحیت دارد.
اشیاء در جاوا اسکریپت
اشیاء در جاوا اسکریپت به صورت مجموعهای از key/valueها تعریف میشوند:
const person = { name: "User 1", walk: function() {}, // method talk() {} // concise method };
const person = { name: "User 1", walk() {}, talk() {} };
person.talk(); person.name = "User 3"; person["name"] = "User 2";
مورد آخر همان روش استفاده از key/valueها است که اساس اشیاء جاوا اسکریپتی را تشکیل میدهد. البته از این روش فقط زمانی استفاده کنید که قرار است یکسری کار پویا صورت گیرند (مقدار key به صورت متغیر دریافت شود) و از ابتدا مشخص نیست که کدام خاصیت یا متد قرار است تعریف و استفاده شود:
const targetMember = "name"; person[targetMember] = "User 2";
واژهی کلیدی this در جاوا اسکریپت
از واژهی کلیدی this، در قسمتهای بعدی زیاد استفاده خواهیم کرد. به همین جهت نیاز است تفاوتهای اساسی آنرا با سایر زبانهای برنامه نویسی بررسی کنیم.
همان شیء person را که پیشتر تعریف کردیم درنظر بگیرید. در متد walk آن، مقدار this را لاگ میکنیم:
const person = { name: "User 1", walk() { console.log(this); }, talk() {} }; person.walk();
شیء this در جاوا اسکریپت، همانند سایر زبانهای برنامه نویسی مانند سیشارپ و یا جاوا رفتار نمیکند. در سایر زبانهای نامبرده شده، this همواره ارجاعی را به وهلهای از شیء جاری، باز میگرداند؛ دقیقا همانند تصویری که در بالا مشاهده میکنید. در اینجا نیز this جاوا اسکریپتی لاگ شده، ارجاعی را به وهلهی جاری شیء person، بازگشت دادهاست. اما مشکل اینجا است که this در جاوا اسکریپت، همیشه به این صورت رفتار نمیکند!
برای نمونه در ادامه یک ثابت را به نام walk تعریف کرده و آنرا به person.walk مقدار دهی میکنیم:
const walk = person.walk; console.log(walk);
سؤال: اکنون اگر این function را با فراخوانی ()walk اجرا کنیم، چه خروجی را میتوان مشاهده کرد؟
اینبار this لاگ شده، به شیء person اشاره نمیکند و شیء استاندارد window مرورگر را بازگشت دادهاست!
اگر یک function به صورت متدی از یک شیء فراخوانی شود، مقدار this همواره اشارهگری به وهلهای از آن شیء خواهد بود. اما اگر این تابع به صورت متکی به خود و به صورت یک function و نه متد یک شیء، فراخوانی شود، اینبار this، شیء سراسری جاوا اسکریپت یا همان شیء window را بازگشت میدهد.
یک نکته: اگر strict mode جاوا اسکریپت را در پروژهی جاری فعال کنیم، بجای شیء window، مقدار undefined را در خروجی فوق شاهد خواهیم بود.
اتصال مجدد this به شیء اصلی در جاوا اسکریپت
تا اینجا دریافتیم که اگر یک function را به صورت متکی به خود و نه جزئی از یک شیء فراخوانی کنیم، شیء this در این حالت به شیء window سراسری مرورگر اشاره میکند و اگر strict mode فعال باشد، فقط undefined را بازگشت میهد. اکنون میخواهیم بررسی کنیم که چگونه میتوان این مشکل را برطرف کرد؛ یعنی صرفنظر از نحوهی فراخوانی متدها یا تابعها، this همواره ارجاعی را به شیء person بازگشت دهد.
در جاوا اسکریپت، تابعها نیز شیء هستند. برای مثال person.walk نوشته شده نیز یک شیء است. برای اثبات سادهی آن فقط یک دات را پس از person.walk قرار دهید:
همانطور که مشاهده میکنید، شیء person.walk مانند تمام اشیاء دیگر جاوا اسکریپت، به همراه متد bind نیز هست. کار آن، انقیاد یک تابع، به یک شیء است. یعنی هرچیزی را که به عنوان آرگومان آن، به آن ارسال کنیم، به عنوان مقدار شیء this درنظر میگیرد:
const walk2 = person.walk.bind(person); console.log(walk2); walk2();
Arrow functions
تابع زیر را درنظر بگیرید که به یک ثابت انتساب داده شدهاست:
const square = function(number) { return number * number; };
const square2 = (number) => { return number * number; };
const square2 = number => { return number * number; };
در ادامه اگر بدنهی این تابع، فقط حاوی یک return بود، میتوان آنرا به صورت زیر نیز خلاصه کرد (در اینجا {} به همراه واژهی کلیدی return حذف میشوند):
const square3 = number => number * number; console.log(square3(5));
اکنون مثال مفید دیگری را در مورد Arrow functions بررسی میکنیم که بیشتر شبیه به عبارات LINQ در #C است:
const jobs = [ { id: 1, isActive: true }, { id: 2, isActive: true }, { id: 3, isActive: true }, { id: 4, isActive: true }, { id: 5, isActive: false } ];
var activeJobs = jobs.filter(function(job) { return job.isActive; });
در ادامه میتوان این تابع را توسط arrow functions به صورت سادهتر زیر نیز نوشت:
var activeJobs2 = jobs.filter(job => job.isActive);
ارتباط بین arrow functions و شیء this
نکتهی مهمی را که باید در مورد arrow functions دانست این است که شیء this را rebind نمیکنند (rebind: مقدار دهی مجدد؛ ریست کردن).
در مثال زیر، ابتدا شیء user با متد talk که در آن شیء this، لاگ شده، ایجاد شده و سپس این متد فراخوانی گردیدهاست:
const user = { name: "User 1", talk() { console.log(this); } }; user.talk();
اکنون اگر متد لاگ کردن را داخل یک تایمر قرار دهیم چه اتفاقی رخ میدهد؟
const user = { name: "User 1", talk() { setTimeout(function() { console.log(this); }, 1000); } }; user.talk();
در این حالت خروجی console.log، مجددا همان شیء سراسری window مرورگر است و دیگر به وهلهی جاری شیء user اشاره نمیکند. علت اینجا است که پارامتر اول متد setTimeout که یک callback function نام دارد، جزئی از هیچ شیءای نیست. بنابراین دیگر مانند فراخوانی متد ()user.talk در مثال قبلی کار نمیکند؛ چون متکی به خود است. هر زمان که یک متد متکی به خود غیر وابستهی به یک شیء را اجرا کنیم، به صورت پیشفرض this آن، به شیء window مرورگر اشاره میکند.
سؤال: چگونه میتوان درون یک callback function متکی به خود، به this همان شیء user جاری دسترسی یافت؟
یک روش حل این مساله، ذخیره this شیء user در یک متغیر و سپس ارسال آن به متد متکی به خود setTimeout است:
const user2 = { name: "User 2", talk() { var self = this; setTimeout(function() { console.log(self); }, 1000); } }; user2.talk();
const user3 = { name: "User 3", talk() { setTimeout(() => console.log(this), 1000); } }; user3.talk();
کدهای کامل این قسمت را از اینجا میتوانید دریافت کنید: sample-02.zip
در قسمت بعد نیز بررسی پیشنیازهای جاوا اسکریپتی شروع به کار با React را ادامه خواهیم داد.
در ASP.NET MVC 3 دو موتور نمایشی به صورت پیش فرض نصب هستند (WebForms and Razor). بنابراین اگر صرفا از Razor استفاده میکنید، میتوان موتور اول را کلا از سیستم پردازشی برنامه حذف کرد. برای اینکار تنها کافی است در فایل global.asax.cs برنامه بنویسیم:
protected void Application_Start() { ViewEngines.Engines.Clear(); ViewEngines.Engines.Add(new RazorViewEngine()); ... }
پس از نصب از طریق NuGet، به صورت خودکار اسمبلیهای لازم به پروژه اضافه شده و همچنین فایل web.config برنامه نیز ویرایش میشود. در انتهای این فایل سطر ذیل مشخص میکند که Glimpse فعال باشد یا خیر.
<glimpse enabled="true" />
به این ترتیب یک کوکی به مرورگر اضافه شده و اکنون پس از بازگشت به صفحه اصلی برنامه و refresh کامل صفحه، در کنار سمت راست پایین صفحه، آیکن آن ظاهر خواهد شد.
بر روی این آیکن کلیک نمائید تا در برگهی View آن، انواع Viewهایی که درگیر نمایش صفحه جاری بودهاند، مشخص شوند:
همانطور که ملاحظه میکنید در اینجا دو موتور پیش فرض فعال بوده و پس از سعی و خطای صورت گرفته، در انتهای کار Razor انتخاب شده است. اکنون اگر نکته حذف موتورهای نمایشی اضافی را اعمال کنیم به تصویر زیر خواهیم رسید:
هم تعداد سعی و خطاها کمتر شده و هم تعداد فایلهایی که بررسی شده است به حداقل رسیده (برای مثال در حالتیکه موتور WebForms فعال باشد، چهار فایل با پسوندهای مختلف در مکانهای پیش فرض نیز حتما جستجو خواهند شد).
حدود یک سال قبل کامپیوتری را که داشتم (اینتل پنتیوم 4) به یک AMD دوهستهای ارتقاء دادم و هفتهی اول پس از ارتقاء، روزگار من سیاه شد! روزهای اول 2 بار کرش ویندوز و مشاهده صفحه آبی و روزهای بعد تا 7 بار این اتفاق تکرار میشد. حتی تا تعویض مادربرد جدید هم پیش رفتم ولی تاثیری نداشت. تست رم و غیره هم انجام شد، مشکلی نبود. خلاصه اینجا بود که از سر ناچاری به این فکر افتادم که آیا این پیغامهای صفحهی آبی ویندوز را میشود تفسیر کرد؟ مشکل دقیقا از کجاست؟ چون در این موارد به هر کسی که مراجعه کنید بر اساس تجربه قبلی یک نسخه برای شما خواهد پیچید. رمت خرابه! بایوست رو ارتقاء بده! (این مورد تاثیر داشت! ولی تعداد کرشها صفر نشد) مادربردت مشکل داره و ...
تمام اینها بر اساس تجربیات قبلی این افراد است و ارزشمند. ولی آیا این جوابها قانع کننده هستند؟ چرا باید رم را عوض کرد؟ از کجا فهمیدید مادربرد مشکل داره؟
شرکتهایی مثل apple برای اینکه با این نوع مشکلات مواجه نشوند، به صورت انحصاری با تولید کنندگان سخت افزار قرار داد میبندند و در نتیجه سیستم عاملی هم که تولید میکنند بسیار پایدار خواهد بود چون بر اساس سخت افزاری کاملا مشخص، طراحی و تست شده است. اما در مورد ویندوز اینطور نیست.
ضمنا هیچ الزامی هم ندارد که این صفحه آبی ویندوز بدلیل مشکلات سخت افزاری حاصل شود (وجود سخت افزار معیوب). ضعف برنامه نویسی و خصوصا درایورهای مشکل دار هم میتوانند سبب ایجاد این نوع صفحات آبی شوند که مشکل من هم دقیقا همین مورد بود که در ادامه نحوه بررسی آنرا توضیح خواهم داد. (البته سطح این مطلب را مقدماتی در نظر بگیرید)
در ویندوز این امکان وجود دارد که پس از هر بار کرش سیستم عامل و مشاهده صفحه آبی یک دامپ کرنل نیز به صورت خودکار حاصل شود. این فایل دامپ را میتوان پس از راه اندازی مجدد سیستم با یک سری ابزار آنالیز کرد و علت دقیق کرش ویندوز را بدست آورد.
برای اینکه این فایلهای دامپ تولید شوند باید مراحل زیر مطابق تصویر طی شوند:
اکنون بعد از هر کرش و صفحه آبی ویندوز یک فایل دامپ در دایرکتوری C:\WINDOWS\Minidump تشکیل میشود. برای آنالیز این فایلها به صورت زیر میشود عمل کرد:
ابتدا برنامه زیر را دانلود کنید:
Debugging Tools for Windows
پس از نصب، Debugging Tools for Windows را خواهید داشت که جهت دیباگ کردن سیستم و آنالیز فایلهای دامپ و غیره کاربرد دارد.
سپس مطالعه مقاله زیر در مورد نحوه استفاده از این ابزار بسیار مفید است:
http://support.microsoft.com/kb/315263
به صورت خلاصه :
یک فایل bat درست کنید با محتویات زیر و دقیقا به همین شکل:
c:\windbg\kd -y srv*c:\symbols*http://msdl.microsoft.com/download/symbols -i c:\windows\i386 -z %1
در این دستور سه مورد قابل ملاحظه است:
الف) مسیر فایل kd.exe که توسط پکیج Debugging Tools for Windows نصب میشود. (مطابق سیستم خودتان آنرا اصلاح کنید)
ب) مسیر c:\windows\i386 بدین معنا است که دایرکتوری i386 سی دی ویندوز را در این مسیر کپی کردهاید یا خواهید کرد (نیاز به یک ویندوز تر و تازه و نصب نشده خواهد بود).
ج) مسیر c:\symbols خودبخود ایجاد خواهد شد و فایلهای مربوطه از سایت مایکروسافت توسط برنامه kd.exe دانلود میشود (بنابراین باید دسترسی به اینترنت نیز داشت).
فرض کنید نام این فایل را test.bat گذاشتهاید.
برای آنالیز فایل Mini102607-07.dmp در دایرکتوری مینی دامپ ویندوز (07 در اینجا یعنی هفتمین کرش روز مربوطه!) دستور زیر را در خط فرمان صادر کنید:
test.bat C:\WINDOWS\Minidump\Mini102607-07.dmp
نتیجه یک نمونه از این آنالیزهای سیستم من به صورت زیر بود:
BAD_POOL_CALLER (c2)
The current thread is making a bad pool request. Typically this is at a bad IRQL level or double freeing the same allocation, etc.
Arguments:
Arg1: 00000007, Attempt to free pool which was already freed
Arg2: 00000cd4, (reserved)
Arg3: 02060008, Memory contents of the pool block
Arg4: 88b4a118, Address of the block of pool being deallocated
Debugging Details:
------------------
POOL_ADDRESS: 88b4a118
FREED_POOL_TAG: TCPc
BUGCHECK_STR: 0xc2_7_TCPc
CUSTOMER_CRASH_COUNT: 4
DEFAULT_BUCKET_ID: COMMON_SYSTEM_FAULT
PROCESS_NAME: System
LAST_CONTROL_TRANSFER: from 8054a583 to 804f9deb
STACK_TEXT:
ba4f3874 8054a583 000000c2 00000007 00000cd4 nt!KeBugCheckEx+0x1b
ba4f38c4 b043d3ff 88b4a118 00000000 ba4f390c nt!ExFreePoolWithTag+0x2a3
ba4f38d4 b043cca3 883ae760 883ae7f4 883ae7f4 tcpip!TCPClose+0x16
ba4f390c b02f3161 8a74fe20 883ae760 b02f2a6d tcpip!TCPDispatch+0x101
WARNING: Stack unwind information not available. Following frames may be wrong.
ba4f3984 b03e2046 00000001 00000000 ba4f39d8 vsdatant+0x45161
ba4f39d8 b03e921c 00000008 ba4f3aac 00000000 ipnat!NatpRedirectQueryHandler+0x250
ba4f3a70 00000000 8837d8e8 0000000d 000005ee ipnat!NatpDirectPacket+0xd2
STACK_COMMAND: kb
FOLLOWUP_IP:
vsdatant+45161
b02f3161 ?? ???
SYMBOL_STACK_INDEX: 4
SYMBOL_NAME: vsdatant+45161
FOLLOWUP_NAME: MachineOwner
MODULE_NAME: vsdatant
IMAGE_NAME: vsdatant.sys
DEBUG_FLR_IMAGE_TIMESTAMP: 46e0766a
FAILURE_BUCKET_ID: 0xc2_7_TCPc_vsdatant+45161
BUCKET_ID: 0xc2_7_TCPc_vsdatant+45161
Followup: MachineOwner
به لاگ حاصل از دو دیدگاه میتوان پرداخت: الف) اگر من برنامه نویس مربوطه باشم، با trace موجود در لاگ فایل، مشخص میشود که کجای کار مشکل داشته است ، ب) یا اینکه خیر. بنده توسعه دهنده درایور نیستم. حداقل اسم دقیق درایور یا پروسه مشکلزا را میتوان از این لاگ بدست آورد.
خوب! تا اینجا مشخص شد که دلیل کرش، درایور vsdatant.sys است. با جستجو در اینترنت مشخص شد که این درایور مربوط به فایروال زون آلارم است! (همین عبارت بالا یا نام درایور ذکر شده را مستقیما در گوگل جستجو کنید)
پس از آن زون آلارم را با outpost firewall جایگزین کردم و تا الان کرشی حاصل نشده است (حتی یکبار از سال قبل تا به امروز). جدا زندگی من مختل شده بود. تصور کنید سیستم شما روزی 7 بار کرش کند!! و چه تصورات نامربوطی را نسبت به فروشنده سخت افزار در ذهن خود مرور کرده باشید!
خلاصهی کلام:
صفحات آبی ویندوز قابل تفسیر هستند. پدید آمدن آنها الزاما بدلیل وجود سخت افزار معیوب نیست و به صرف اینکه شخصی به شما گفته "رمت خرابه!" اکتفا نکنید.
پ.ن.
لاگ فوق مربوط به یک سال قبل است و احتمالا شاید زون آلارمهای جدید این مشکل را نداشته باشند.
_logger.LogInformation("LogInformation");
- Success
- Error
- Warning
- Verbose
- Debug
- Information
cmdlet | Name | Id |
Write-Output | Success | 1 |
Write-Error | Error | 2 |
Write-Warning | Warning | 3 |
Write-Verbose | Verbose | 4 |
Write-Debug | Debug | 5 |
Write-Information | Information | 6 |
به جز دو مورد اول، بقیه cmdletها خروجی را به صورت پیشفرض درون کنسول نمایش نمیدهند. به عنوان مثال اسکریپت زیر را در نظر بگیرید:
Write-Output 'Output' Write-Error 'This is an error' Write-Warning 'This is a warning' Write-Verbose 'This is verbose' Write-Debug 'This is Debug' Write-Information 'This is information'
با اجرای اسکریپت فوق خروجی زیر را خواهیم داشت:
Output Write-Error: This is an error WARNING: This is a warning
همانطور که مشاهده میکنید سه cmdlet فوق، خروجی را درون کنسول نمایش ندادهاند. این رفتار توسط مفهومی تحت عنوان Action Preference قابل تنظیم است که در واقع یک Enum است با مقدار زیر:
6 | Break | |
رخداد به صورت عادی مدیریت شده و برنامه ادامه پیدا میکند | 2 | Continue |
به طور کلی از رخداد صرفنظر خواهد شد؛ بدون اینکه چیزی در استریم نمایش داده شود | 4 | Ignore |
سوال پرسیده خواهد شد که برنامه را ادامه دهد یا متوقف کند | 3 | Inquire |
به طور کلی از رخداد صرفنظر خواهد شد | 0 | SilentlyContinue |
دستور را متوقف خواهد کرد | 1 | Stop |
دستور به نوعی معلق خواهد شد | 5 | Suspend |
بنابراین با تغییر Action Preference برای هر کدام از cmdletها میتوانیم رفتار اسکریپت قبلی را تغییر دهیم:
Write-Output 'Output' Write-Error 'This is an error' Write-Warning 'This is a warning' $VerbosePreference = 'Continue' Write-Verbose 'This is verbose' $DebugPreference = 'Continue' Write-Debug 'This is Debug' $InformationPreference = 'Continue' Write-Information 'This is information'
اکنون اگر اسکریپت فوق را اجرا کنید، سه خروجی آخر را نیز مشاهده خواهید کرد:
Output Write-Error: This is an error WARNING: This is a warning VERBOSE: This is verbose DEBUG: This is Debug This is information
هر کدام از استریمهای فوق قابل redirect شدن نیز هستند؛ برای اینکار میتوانیم از redirect operatorهایی که در PowerShell پشتیبانی میشود استفاده کنیم:
> >> >&1
به عنوان مثال میتوانیم تمام خطاها یا هشدارهای درون یک اسکریپت را به یک فایل منتقل کنیم:
./script.ps1 2>&1 > .\logs.txt
یا میتوانیم تمام Success streamها را به یک فایل هدایت کنیم:
.\script.ps1 > script.log
ارسال تمام Success, Warning, Errorها به یک فایل:
&{ Write-Warning "hello" Write-Error "hello" Write-Output "hi" } 3>&1 2>&1 > C:\Temp\redirection.log
ارسال تمام استریمها به یک فایل:
.\script.ps1 *> script.log
همچنین میتوانیم استریمی را به اصطلاح suppress کنیم که در خروجی نمایش داده نشود:
./script.ps1 1> $null 2> $null ./script.ps1 *> $null
از تکنیک فوق برای drop کردن خروجیهایی که نمیخواهیم نمایش داده شوند، استفاده میشود. در کد فوق دو Idهای ۱ و ۲ را به متغیر ویژهی null هدایت کردهایم؛ همچنین میتوانستیم از یک رشتهی خالی نیز بجای null استفاده کنیم. در خط بعدی از * استفاده کردهایم که به معنای تمامی استریمهای موجود است؛ با اینکار چیزی در خروجی نمایش داده نخواهد شد. یک روش دیگر برای drop کردن، استفاده از دستور Out-Null است:
Get-ChildItem | Out-Null
لازم به ذکر است که این cmdlet تا قبل از نسخه ۶ خیلی کند بود؛ زیرا همانند دیگر cmdletهای درون pipeline میبایست یک ورودی (InputObject) را دریافت کند که باعث میشد هزینهی پردازشی بالایی داشته باشد. اما در نسخه ۶ به بعد این مشکل رفع شدهاست و پارزر به محض رسیدن به این keyword به صورت کلی خروجی را discard میکند بدون اینکه Out-Null را فراخوانی کند؛ در واقع این cmdlet یک hint برای پارزر است. روش دیگر برای drop کردن خروجی، انتساب نتیجه یک دستور به متغییر null است:
New-Item -Type Directory -Path $path | Out-Null $null = New-Item -Type Directory -Path $path
همچنین میتوانیم خروجی یک دستور را به void تبدیل کنیم؛ که نتیجه مشابه با تکنیکهای فوق دارد:
[void](New-Item -Name test -ItemType Directory)
یک نکته در مورد Out-Null
در loopهای بزرگ ممکن است Out-Null حتی در PowerShell 7.x هم کند عمل کند:
PS > Measure-Command { for($i=0; $i -lt 1mb; $i++) { $i | Out-Null } } | Select-Object TotalSeconds TotalSeconds ------------ 4.3056315 PS > Measure-Command { for($i=0; $i -lt 1mb; $i++) { $null = $i } } | Select-Object TotalSeconds TotalSeconds ------------ 1.1210884 PS > Measure-Command { for($i=0; $i -lt 1mb; $i++) { [void]$i } } | Select-Object TotalSeconds TotalSeconds ------------ 1.130507 PS > Measure-Command { for($i=0; $i -lt 1mb; $i++) { $i > $null } } | Select-Object TotalSeconds TotalSeconds ------------ 1.3832427
- توابع خط فرمان - Command Line API
- توابع کنسول - Console API
<div id="first" class="content">Content1 with css class and id</div> <div class="content"> Content2 with css class <a class="links" href="#">Link1</a> <a href="#">Link2</a> </div> <div> Content3 without css class and id </div> <input type="button" onclick="myFunc()" value="Run myFunc" /> <input type="text" id="myInput" /> <script type="text/javascript"> function myFunc() { loop(1000); loop(50000); } function loop(number) { for (var i = 0; i < number; i++) { } } </script>
$$("div.content");
profile("myFunc Testing"); myFunc(); profileEnd();
- $(id)
معادل دستور document.getElementById است که یک المنت با id داده شده بر میگرداند .
$("first");
- $$(selector)
آرایه ای از المنتهای مطابق با selector داده شده بر میگرداند .
$$("div.content")
به تفاوت دو دستور توجه کنید . خروجی دستور اول ، یک المنت است و خروجی دستور دوم یک آرایه از المنت که بین [ و ] قرار گرفته اند .
برای آشنایی بیشتر با CSS Seletorها به این لینک مراجعه کنید : http://www.w3.org/TR/css3-selectors
- $x(xPathExpression)
آرایه ای از المنت هایی را بر میگرداند که با xPath داده شده مطابقت داشته باشند .
var objects = $x("html/body/div[2]/a") for(var i = 0; i < objects.length; i++) { console.log(objects[i]); }
برای آشنایی بیشتر با عبارات xPath به این لینک مراجعه کنید : http://www.w3schools.com/xpath
- dir(object)
تمام خصوصیات شیء ارسال شده را لیست میکند .
var objects = $x("html/body/div[2]/a") dir(objects);
- dirxml(node)
سورس یک المنت را بصورت درختواره ( tree ) پرینت میکند . همچنین با کلیک بروی هر node ، فایرباگ آن node را در تب html نمایش میدهد .
var node = $("first"); dirxml(node);
توجه کنید که این دستور فقط یک node دریافت میکند . برای همین اگر از دستور $$("#first") استفاده میکنید ، چون این دستور یک آرایه بر میگرداند ، باید اولین عضو آرایه را دریافت و ارسال کنید .
یعنی :
var node = $$("#first")[0]; dirxml(node);
- clear()
این دستور محیط console را خالی میکند . عملکرد این دستور معادل کلیک دکمهی Clear ( در بالا - چپ تب کنسول ) است .
- inspect(object[,tabName])
توسط این دستور میتوانید یک شیء را در مناسبترین تب فایرباگ یا یکی از تبهای مورد نظر خود ، Inspect کنید .
var node = $("first"); inspect(node); // inspect in html tab inspect(node,'dom'); // inspect in dom tab
- keys(object)
آرایه ای از "نام" تمام خصوصیات شیء ارسال شده بر میگرداند .
var obj = $("first"); keys(obj)
- values(object)
آرایه ای از "مقدار" تمام خصوصیات شیء ارسال شده بر میگرداند .
var obj = $("first"); values(obj)
- debug(fn) and undebug(fn)
این متدها یک BreakPoint در ابتدای تابع مشخص شده اضافه/حذف میکنند . ( در تب Script ) . به همین ترتیب هنگامی که تابع مورد نظر فراخوانی شود ، در نقطه ای که BreakPoint قرار داده شده توقف خواهد کرد .
البته میشود BreakPoint را دستی هم قرار داد . در اصل این تابع ، این عملیات را سادهتر میکند .
debug(myFunc); myFunc(); undebug(myFunc);
- monitor(fn) and unmonitor(fn)
این متدها برای فعال/غیرفعال کردن Logging فراخوانیهای یک تابع استفاده میشوند .
در حالت عادی برای پی بردن به اینکه یک تابع اجرا میشود یا نه ، در تابع مورد نظر یک alert قرار میدهیم و تست میکنیم . که این روش در برنامه برنامههای بزرگ صحیح نیست . زیرا در این حالت باید بین حجم زیادی کد به دنبال تابع مورد نظر بگردیم و سپس alert را قرار بدهیم و بعد اطمینان از صحت عملکرد تابع مجدد آن را حذف کرد ، که با اتلاف زمان و به خطر انداختن کدها همراه است .
اما با استفاده از این متدها ، تنها نیاز به داشتن اسم تابع داریم ( و نه مکان تابع در کدهای برنامه ) .
تست monitor :
monitor(myFunc); // now click on "Run myFunc" button
unmonitor(myFunc); // now click on "Run myFunc" button
- monitorEvents(object[, types]) and unmonitorEvents(object[, types])
این متدها عملیات Event Logging برای یک شیء را فعال/غیرفعال میکنند . در کنار شیء مورد نظر ، میتوان نوع رویداد را هم به متد ارسال کرد . در این صورت عملیات Logging فقط برای همان گروه رویداد/رویداد ، فعال/غیرفعال میشود .
منظور از گروه رویداد ، مجموعه رویدادهای یک شیء است . مثلا mousemove , moseover , mousedown , ... در گروه mouse قرار میگیرند . یعنی میتوانید با ارسال کلمهی mouse فقط رویدادهای mouse را تحت نظر بگیرید یا اینکه فقط یک رویداد را مشخص کنید ، مثل mousedown .
راه سادهتر فعال کردن Event Logging ، رفتن به تب Html ، راست کلیک کردن بروی المنت مورد نظر و فعال کردن گزینهی Log Events میباشد .
var obj = $("myInput"); monitorEvents(obj,'keypress');
نتیجه پس از فشردن چند دکمهی کیبورد در myInput :
توضیحات بیشتر : http://getfirebug.com/wiki/index.php/MonitorEvents - profile([title]) and profileEnd()
این متدها ، JavaScript Profiler را فعال/غیرفعال میکنند . هنگام فعال کردن میتوانید یک عنوان هم برای پروفایل مشخص کنید . در قسمت قبلی مقاله در مورد این قابلیت توضیحاتی ارائه شد .
سه را برای اجرای Profiler وجود دارد :
1 - کلیک بروی دکمهی Profiler در بالای تب کنسول .
2 - استفاده از کد console.profile("ProfileTitle") در کدهای جاوا اسکریپت .
3 - استفاده از متد profile("Profile Title") در خط فرمان .
profile("myFunc Testing"); myFunc(); profileEnd();
ستونهای Profiler :Function : نام تابع اجرا شده .
Calls : تعداد دفعات فراخوانی تابع .
Percent : زمان اجرای تابع در زمان کل ، به درصد .
Own Time : زمان اجرای تابع به تنهایی . برای مثال در کد ما ، زمان اجرای تابع myFunc به تنهایی تقریبا صفر است زیرا عمیاتی در خود انجام نمیدهد و زمان صرف شده در این تابع ، برای اجرای 2 با تابع loop است . این زمان ( Own Time ) زمان اجرای تابع ، منهای زمان صرف شده برای فراخوانی توابع دیگر است .
Time : زمان اجرای تابع از نقطهی آغاز تا پایان . مجموع زمان اجرای خود تابع به همراه زمان اجرای توابع فراخوانی شده . در کد ما ، این زمان ، مجموع زمان اجرای خود تابع به همراه دو بار فراخوانی تابع loop است .
Avg : میانگین زمان اجرای هربار تابع . فرمول : Avg = Time / Calls
Min & Max : حداقل و حداکثر زمان اجرای تابع .
File : نام فایل و شماره خطی که تابع در آن قرار دارد .