سلام ؛
سال نو مبارک ! امیدوارم سال بسیار خوبی در پیش داشته باشید :)
از زمانی که استفاده از ORMهای Code First رایج شده ، اجرای اسکریپتهای طولانی جهت ایجاد دیتابیس خیلی استفاده ندارد، اما حالت خاص همیشه پیش میآید.
مثلا قصد داریم پیش از آغاز برنامه پس از ایجاد دیتابیس توسط Entity Framework به یک سری جداول فیلدی با نوع دادهی Geometry اضافه کنیم. یا باید به دیتابیس یک سری Stored Procedure و View اضافه کرد.
Scriptها ی Generate شده توسط SQL Server حاوی دستور Go هستند. ADO.NET اجرای Script که حاوی Go باشد را پشتیبانی نمیکند.
اما روشها و ترفندهای زیادی برای اجرای یک فایل Script طولانی حاوی دستور Go روی دیتابیس وجود دارد :
private static string GetScript() { string path = AppDomain.CurrentDomain.BaseDirectory + @"Scripts\script.sql"; var file = new FileInfo(path); string script = file.OpenText().ReadToEnd(); return script; } private static void ExecuteScript() { string script = GetScript(); //split the script on "GO" commands var splitter = new[] {"\r\nGO\r\n"}; string[] commandTexts = script.Split(splitter, StringSplitOptions.RemoveEmptyEntries); foreach (string commandText in commandTexts) { using (var ctx = new DbContext()) { if (!string.IsNullOrEmpty(commandText)) { ctx.Database.ExecuteSqlCommand(commandText); } } } }
در اینجا به جای آنکه تلاش کنیم یک فایل را روی دیتابیس یک جا اجرا کنیم دستورات را جدا کرده و تک به تک اجرا میکنیم.
بروزرسانی:
پاسخ به بازخوردهای پروژهها
خطای property not found
بصورت زیر لیست موردنیازم را میسازم و به گزارش ارسال میکنم:
var dataSource = new List<WeekDayClassSessionSemiRow>(); for (int i = 0; i < classesList.Count; i++) { int clsId = classesList[i].Id; var sessions = sessionList.Where(p => p.Class_Id == clsId).ToList(); for (int w = 0; w < 17; w++) { var row = new WeekDayClassSessionSemiRow(); row.Class = classesList[i]; row.ClassName = classesList[i].Name; row.ClassFullName = classesList[i].Name + "( " + classesList[i].EducationalPlace.Name + " )"; row.IsSelected = true; row.RowNumber = (w + 1).ToString(); row.WeekNumber = w + 1; row.WeekTitle = "هفته " + (w + 1); row.WD0 = createCell(sessionList, clsId, w + 1, 0); row.WD1 = createCell(sessionList, clsId, w + 1, 1); row.WD2 = createCell(sessionList, clsId, w + 1, 2); row.WD3 = createCell(sessionList, clsId, w + 1, 3); row.WD4 = createCell(sessionList, clsId, w + 1, 4); row.WD5 = createCell(sessionList, clsId, w + 1, 5); row.WD6 = createCell(sessionList, clsId, w + 1, 6); dataSource.Add(row); } }
private WeekDayClassSessionSemi createCell(List<Session> sessionList, int classId, int weekNumber, int day) { var sessions = sessionList .Where(p => p.Class_Id == classId && p.Day == day && p.Week == weekNumber) .ToList(); var ss = new WeekDayClassSessionSemi(); ss.DayNumber = day; ss.HasSession = sessions.Count > 0; int minuteSum = sessions.Sum(p => p.TimeBaseOnMinute); ss.Percent = (minuteSum * 100) / 720; ss.Date = getDateFromCalander(weekNumber, day); ss.SemiDate = ss.Date.Length == 10 ? ss.Date.Substring(5, 5) : ""; ss.IsHoliday = isHoliday(ss.Date); ss.Text = "";// makeFillText(sessionList); ss.WeekNumber = weekNumber + 1; return ss; }
و بصورت زیر به گزارش ارسال میکنم :
var rpt = new Rpt_ClassPrograms(dataSource, reportParameter); IPdfReportData rptData = rpt.Create();
بالاخره گوگل کار تهیه API مخصوص ابزار Analytics خود را به پایان رساند و اکنون برنامه نویسها میتوانند همانند سایر سرویسهای گوگل از این ابزار گزارشگیری نمایند.
خلاصه کاربردی این API ، دو صفحه تعاریف پروتکل (+) و ریز مواردی (+) است که میتوان گزارشگیری نمود.
هنوز کتابخانه google-gdata جهت استفاده از این API به روز رسانی نشده است؛ بنابراین در این مقاله سعی خواهیم کرد نحوه کار با این API را از صفر بازنویسی کنیم.
مطابق صفحه تعاریف پروتکل، سه روش اعتبارسنجی جهت دریافت اطلاعات API معرفی شده است که در اینجا از روش ClientLogin که مرسومتر است استفاده خواهیم کرد.
مطابق مثالی که در آن صفحه قرار دارد، اطلاعاتی شبیه به اطلاعات زیر را باید ارسال و دریافت کنیم:
POST /accounts/ClientLogin HTTP/1.1
User-Agent: curl/7.15.1 (i486-pc-linux-gnu) libcurl/7.15.1
OpenSSL/0.9.8a zlib/1.2.3 libidn/0.5.18
Host: www.google.com
Accept: */*
Content-Length: 103
Content-Type: application/x-www-form-urlencoded
accountType=GOOGLE&Email=userName@google.com&Passwd=myPasswrd&source=curl-tester-1.0&service=analytics
HTTP/1.1 200 OK
Content-Type: text/plain
Cache-control: no-cache
Pragma: no-cache
Date: Mon, 02 Jun 2008 22:08:51 GMT
Content-Length: 497
SID=DQ...
LSID=DQAA...
Auth=DQAAAG8...
string getSecurityToken()
{
if (string.IsNullOrEmpty(Email))
throw new NullReferenceException("Email is required!");
if (string.IsNullOrEmpty(Password))
throw new NullReferenceException("Password is required!");
WebRequest request = WebRequest.Create("https://www.google.com/accounts/ClientLogin");
request.Method = "POST";
string postData = "accountType=GOOGLE&Email=" + Email + "&Passwd=" + Password + "&service=analytics&source=vahid-testapp-1.0";
byte[] byteArray = Encoding.ASCII.GetBytes(postData);
request.ContentType = "application/x-www-form-urlencoded";
request.ContentLength = byteArray.Length;
using (Stream dataSt = request.GetRequestStream())
{
dataSt.Write(byteArray, 0, byteArray.Length);
}
string auth = string.Empty;
using (WebResponse response = request.GetResponse())
{
using (Stream dataStream = response.GetResponseStream())
{
using (StreamReader reader = new StreamReader(dataStream))
{
string responseFromServer = reader.ReadToEnd().Trim();
string[] tokens = responseFromServer.Split('\n');
foreach (string token in tokens)
{
if (token.StartsWith("SID="))
continue;
if (token.StartsWith("LSID="))
continue;
if (token.StartsWith("Auth="))
{
auth = token.Substring(5);
}
else
{
throw new AuthenticationException("Error authenticating Google user " + Email);
}
}
}
}
}
return auth;
}
همانطور که ملاحظه میکنید به آدرس https://www.google.com/accounts/ClientLogin ، اطلاعات postData با متد POST ارسال شده (دقیقا مطابق توضیحات گوگل) و سپس از پاسخ دریافتی، مقدار نشانه Auth را جدا نموده و در ادامه عملیات استفاده خواهیم کرد. وجود این نشانه در پاسخ دریافتی به معنای موفقیت آمیز بودن اعتبار سنجی ما است و مقدار آن در طول کل عملیات باید نگهداری شده و مورد استفاده مجدد قرار گیرد.
سپس مطابق ادامه توضیحات API گوگل باید لیست پروفایلهایی را که ایجاد کردهایم پیدا نمائیم:
string getAvailableProfiles(string authToken)
{
return fetchPage("https://www.google.com/analytics/feeds/accounts/default", authToken);
}
متد fetchPage را از پیوست این مقاله میتوانید دریافت نمائید. خروجی یک فایل xml است که با انواع و اقسام روشهای موجود قابل آنالیز است، از کتابخانههای XML دات نت گرفته تا Linq to xml و یا روش serialization که من روش آخر را ترجیح میدهم.
مرحله بعد، ساخت URL زیر و دریافت مجدد اطلاعات مربوطه است:
string url = string.Format("https://www.google.com/analytics/feeds/data?ids={0}&metrics=ga:pageviews&start-date={1}&end-date={2}", id, from, to);
return fetchPage(url, auth);
فایلهای کلاسهای مورد استفاده را از اینجا دریافت نمائید.
مثالی در مورد نحوه استفاده از آن:
CGoogleAnalytics cga = new CGoogleAnalytics
{
Email = "username@gmail.com",
Password = "password",
From = DateTime.Now.Subtract(TimeSpan.FromDays(1)),
To = DateTime.Now.Subtract(TimeSpan.FromDays(1))
};
List<CGoogleAnalytics.SitePagePreviews> pagePreviews =
cga.GetTotalNumberOfPageViews();
foreach (var list in pagePreviews)
{
//string site = list.Site;
//int pw = list.PagePreviews;
}
برای کار با React، نیاز است با ES6 آشنایی داشته باشید که در این سایت، یک سری کامل بررسی مقدمات آنرا پیشتر مرور کردهایم. علاوه بر توصیهی مطالعهی این سری (اینکار الزامی است)، در این قسمت خلاصهی بسیار سریع و کاربردی آنرا که بیشتر در برنامههای مبتنی بر React مورد استفاده قرار میگیرند، با هم مرور خواهیم کرد. در قسمتهای بعدی، اهمیت ذکر این خلاصه بیشتر مشخص میشود.
برای بررسی ویژگیهای جاوا اسکریپت مدرن، یک پروژهی جدید React را ایجاد میکنیم.
سپس تمام کدهای داخل index.js را نیز حذف میکنیم. اکنون تمام کدهای خالص جاوا اسکریپتی خود را داخل این فایل خواهیم نوشت.
به علاوه چون در این قسمت خروجی UI نخواهیم داشت، تمام خروجی را در کنسول developer tools مرورگر خود میتوانید مشاهده کنید (فشردن دکمهی F12).
var، let و const
در اکثر زبانهای برنامه نویسی، متغیرها در محدودهی دید قطعه کدی که تعریف شدهاند (scope)، قابل دسترسی هستند. برای نمونه محتوای فایل index.js پروژه را به صورت زیر تغییر داده و با فرض اجرای دستور npm start، خروجی آنرا میتوان در کنسول مرورگر مشاهده کرد.
در این مثال متغیر i، مخصوص قطعه کد حلقه، تعریف شدهاست. بنابراین به ظاهر نباید خارج از این حلقه نیز قابل دسترسی باشد. اما خروجی آن به صورت زیر است:
در آخرین پیمایش حلقه، i مساوی 5 شده و از حلقه خارج میشود. اما چون در اینجا برای تعریف متغیر از واژهی کلیدی var استفاده شدهاست، محدودهی دید آن به کل تابعی که در آن تعریف شدهاست، بسط پیدا میکند. به همین جهت در این خروجی، عدد 5 را نیز مشاهده میکند که حاصل دسترسی به i، خارج از حلقهاست.
برای یک دست سازی این رفتار با سایر زبانهای برنامه نویسی، در ES6، واژهی کلیدی جدیدی به نام let تعریف شدهاست که میدان دید متغیر را به قطعه کدی که در آن تعریف شدهاست، محدود میکند. اکنون اگر در حلقهی فوق بجای var از let استفاده شود، یک چنین خطایی در مرورگر ظاهر خواهد شد که عنوان میکند، i استفاده شدهی در خارج از حلقه، تعریف نشدهاست.
علاوه بر let، واژهی کلیدی جدید const نیز به ES6 اضافه شدهاست که از آن برای تعریف ثوابت استفاده میشود. constها نیز همانند let، میدان دید محدود شدهای به قطعه کد تعریف شدهی در آن دارند؛ اما قابلیت انتساب مجدد را ندارند:
اگر یک چنین قطعه کدی را اجرا کنیم، خطای x is const را در مرورگر میتوان مشاهده کرد.
به صورت خلاصه از این پس واژهی کلیدی var را فراموش کنید. همیشه با const جهت تعریف متغیرها شروع کنید. اگر به خطا برخوردید و نیاز به انتساب مجدد وجود داشت، آنرا به let تغییر دهید. بنابراین استفاده از const همیشه نسبت به let ارجحیت دارد.
اشیاء در جاوا اسکریپت
اشیاء در جاوا اسکریپت به صورت مجموعهای از key/valueها تعریف میشوند:
در اینجا امکان تعریف یک تابع نیز وجود دارد که چون درون یک شیء قرار میگیرد، اینبار «متد» نامیده میشود. همچنین در ES6 میتوان این متدها را به صورت معمولی، مانند متد talk نیز تعریف کرد که به آنها concise method میگویند. بنابراین نحوهی تعریف فوق را به نحو زیر نیز میتوان خلاصه کرد:
پس از تعریف این شیء، روش دسترسی به اجزای آن به صورت زیر است:
به دو مورد اول، روش dot notation میگویند که از همان ابتدا دقیقا مشخص است کدامیک از خواص و متدهای شیء تعریف شده، مورد استفاده قرار میگیرند.
مورد آخر همان روش استفاده از key/valueها است که اساس اشیاء جاوا اسکریپتی را تشکیل میدهد. البته از این روش فقط زمانی استفاده کنید که قرار است یکسری کار پویا صورت گیرند (مقدار key به صورت متغیر دریافت شود) و از ابتدا مشخص نیست که کدام خاصیت یا متد قرار است تعریف و استفاده شود:
واژهی کلیدی this در جاوا اسکریپت
از واژهی کلیدی this، در قسمتهای بعدی زیاد استفاده خواهیم کرد. به همین جهت نیاز است تفاوتهای اساسی آنرا با سایر زبانهای برنامه نویسی بررسی کنیم.
همان شیء person را که پیشتر تعریف کردیم درنظر بگیرید. در متد walk آن، مقدار this را لاگ میکنیم:
خروجی این قطعه، به صورت زیر است:
شیء this در جاوا اسکریپت، همانند سایر زبانهای برنامه نویسی مانند سیشارپ و یا جاوا رفتار نمیکند. در سایر زبانهای نامبرده شده، this همواره ارجاعی را به وهلهای از شیء جاری، باز میگرداند؛ دقیقا همانند تصویری که در بالا مشاهده میکنید. در اینجا نیز this جاوا اسکریپتی لاگ شده، ارجاعی را به وهلهی جاری شیء person، بازگشت دادهاست. اما مشکل اینجا است که this در جاوا اسکریپت، همیشه به این صورت رفتار نمیکند!
برای نمونه در ادامه یک ثابت را به نام walk تعریف کرده و آنرا به person.walk مقدار دهی میکنیم:
دقت داشته باشید که در اینجا از () استفاده نشدهاست (متد walk اجرا نشدهاست). یعنی صرفا «ارجاعی» از متد walk شیء person را به ثابت walk نسبت دادهایم. بنابراین اکنون ثابت walk نیز یک function است که حاصل console.log آن به صورت زیر است:
سؤال: اکنون اگر این 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 درنظر میگیرد:
در اینجا متد bind، یک وهلهی جدید از person.walk را بازگشت میدهد که در آن شیء person را به عنوان شیء this، تنظیم کردهاست. به همین جهت اینبار فراخوانی walk2 که به شیء person متصل شدهاست، به this صحیحی بجای window سراسری اشاره میکند. از این روش در برنامههای مبتنی بر React زیاد استفاده میشود.
Arrow functions
تابع زیر را درنظر بگیرید که به یک ثابت انتساب داده شدهاست:
در ES6، روش سادهتر و تمیزتری برای این نوع تعاریف، ذیل ویژگی جدید Arrow functions اضافه شدهاست. برای تبدیل قطعه کد فوق به یک arrow function، ابتدا واژهی کلیدی function را حذف میکنیم. سپس بین پارامتر تابع و {}، یک علامت <= (که به آن fat arrow هم میگویند!) قرار میدهیم:
اگر مانند اینجا تنها یک تک پارامتر وجود داشته باشد، میتوان پرانتزهای ذکر شده را نیز حذف کرد:
و اگر این متد پارامتری نداشت، از () استفاده میشود.
در ادامه اگر بدنهی این تابع، فقط حاوی یک return بود، میتوان آنرا به صورت زیر نیز خلاصه کرد (در اینجا {} به همراه واژهی کلیدی return حذف میشوند):
این یک سطر ساده شده، دقیقا معادل اولین const square ای است که نوشتیم. نحوهی فراخوانی آن نیز مانند قبل است.
اکنون مثال مفید دیگری را در مورد Arrow functions بررسی میکنیم که بیشتر شبیه به عبارات LINQ در #C است:
در اینجا آرایهای از اشیاء job را مشاهده میکنید که مورد آخر آن، فعال نیست. اکنون میخواهیم لیست کارهای فعال را گزارشگیری کنیم:
متد filter در جاوا اسکریپت، بر روی تک تک عناصر آرایهی jobs حرکت میکند. سپس هر job را به پارامتر متد ارسالی آن که predicate نام دارد، جهت دریافت یک خروجی true و یا false، ارائه میدهد. اگر خروجی این متد true باشد، آن job انتخاب خواهد شد و در لیست نهایی گزارش، ظاهر میشود.
در ادامه میتوان این تابع را توسط arrow functions به صورت سادهتر زیر نیز نوشت:
ابتدا واژهی کلیدی function را حذف میکنیم. سپس چون یک تک پارامتر را دریافت میکند، نیازی به ذکر پرانتزهای آن نیز نیست. در ادامه چون یک تک return را داریم، { return } را با یک <= جایگزین خواهیم کرد. در اینجا نیازی به ذکر سمیکالن انتهای return هم نیست. نوشتن یک چنین کدی تمیزتر و خواندن آن، سادهتر است.
ارتباط بین arrow functions و شیء this
نکتهی مهمی را که باید در مورد arrow functions دانست این است که شیء this را rebind نمیکنند (rebind: مقدار دهی مجدد؛ ریست کردن).
در مثال زیر، ابتدا شیء user با متد talk که در آن شیء this، لاگ شده، ایجاد شده و سپس این متد فراخوانی گردیدهاست:
همانطور که انتظار میرود، this ای که در اینجا لاگ میشود، دقیقا ارجاعی را به وهلهی جاری شیء user دارد.
اکنون اگر متد لاگ کردن را داخل یک تایمر قرار دهیم چه اتفاقی رخ میدهد؟
متد setTimeout، متدی را که به عنوان آرگومان اول آن دریافت کرده، پس از 1 ثانیه اجرا میکند.
در این حالت خروجی console.log، مجددا همان شیء سراسری window مرورگر است و دیگر به وهلهی جاری شیء user اشاره نمیکند. علت اینجا است که پارامتر اول متد setTimeout که یک callback function نام دارد، جزئی از هیچ شیءای نیست. بنابراین دیگر مانند فراخوانی متد ()user.talk در مثال قبلی کار نمیکند؛ چون متکی به خود است. هر زمان که یک متد متکی به خود غیر وابستهی به یک شیء را اجرا کنیم، به صورت پیشفرض this آن، به شیء window مرورگر اشاره میکند.
سؤال: چگونه میتوان درون یک callback function متکی به خود، به this همان شیء user جاری دسترسی یافت؟
یک روش حل این مساله، ذخیره this شیء user در یک متغیر و سپس ارسال آن به متد متکی به خود setTimeout است:
این روشی است که سالها است وجود دارد؛ با ارائهی arrow functions، دیگر نیازی به اینکار نیست و میتوان از روش زیر استفاده کرد:
در اینجا callback function را تبدیل به یک arrow function کردهایم و چون arrow functions شیء this را rebind نمیکنند، یعنی شیء this را به ارث میبرند. بنابراین console.log مثال فوق، دقیقا به this شیء user اشاره میکند و دوباره آنرا مقدار دهی مجدد نمیکند و از همان نمونهی موجود قطعه کد تعریف شده، استفاده خواهد کرد.
کدهای کامل این قسمت را از اینجا میتوانید دریافت کنید: sample-02.zip
در قسمت بعد نیز بررسی پیشنیازهای جاوا اسکریپتی شروع به کار با React را ادامه خواهیم داد.
برای بررسی ویژگیهای جاوا اسکریپت مدرن، یک پروژهی جدید 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 را ادامه خواهیم داد.
در Angular مکانیزمی وجود دارد که بر اساس آن میتوان از توابع و خواص تعریف شده در یک کنترلر در سایر کنترلرها نیز استفاده کرد که در واقع از ان به عنوان ارث بری کنترلرها عنوان میشود؛ ولی نکته ای که وجود دارد این است که در جاوااسکریپ OOP پشتیبانی نمیشود پس چگونه یک آبجکت کنترلر توابع و خصوصیات کنترلر دیگر را به ارث میبرد؟ با ذکر یک مثال این مورد را بررسی خواهیم کرد.
ابتدا دو کنترلر به صورت زیر ایجاد میکنیم:
در کدهای بالا دو کنترلر به نام parentController و childController ایجاد کردم. از parentController به عنوان کنترلر والد استفاده خواهد شد و قصد داریم که از title آن در کنترلر child استفاده نماییم. View متناظر را به صورت زیر ایجاد خواهیم کرد.
اولین نکته این است که تگ div تعریف شده برای کنترلر child در محدوده تگ div کنترلر parent قرار گرفته است. دومین نکته این است که از روش controller as برای مقید سازی خواص به المانهای صفحه استفاده شده است. اگر برنامه را اجرا نمایید خروجی زیر ار مشاهده خواهید کرد.
حال برای دسترسی به title کنترلر parent در سایر کنترلرها کافیست از نام مستعار parentCtrl استفاده نماییم. از آن جا که محدوده کنترلر child در داخل محدوده کنترلر parent است دسترسی به کنترلر parent امکان پذیر میباشد.
خروجی:
ابتدا دو کنترلر به صورت زیر ایجاد میکنیم:
var app = angular.module('myApp', []); app.controller('parentController', function () { this.title = 'Title from parent controller'; }) app.controller('childController', function () { this.title = 'Title from child controller'; })
<div ng-app="myApp"> <div ng-controller="parentController as parentCtrl"> <div ng-controller="childController as childCtrl"> <input type="text" ng-model="parentCtrl.title" size="100"/> <input type="text" ng-model="childCtrl.title" size="100"/> </div> </div> </div>
هر دو کنترلر دارای یک title مجزا هستند ولی با استفاده از یک اشاره گر توانستیم این دو title را تفکیک نماییم.
حال برای دسترسی به title کنترلر parent در سایر کنترلرها کافیست از نام مستعار parentCtrl استفاده نماییم. از آن جا که محدوده کنترلر child در داخل محدوده کنترلر parent است دسترسی به کنترلر parent امکان پذیر میباشد.
<div ng-app="myApp"> <div ng-controller="parentController as parentCtrl"> <div ng-controller="childController as childCtrl"> <input type="text" ng-model="parentCtrl.title" size="100"/> <input type="text" ng-model="parentCtrl.title" size="100"/> </div> </div> </div>
نکته: دسترسی به کنترلر child در کنترلر parent امکان پذیر نیست.
__arglist __reftype __makeref __refvalue کلمات کلیدی
در حالیکه، ویرایشگر Visual Studio این کلمات را به صورت رنگی و جزء کلمات کلیدی نمایش میدهد، ولی به دلیل عدم وجود مستندات برای این کلمات کلیدی، برای استفاده از آنها باید مراقب باشید؛ چرا که ممکن است به اندازه کافی تست نشده باشند.
شما میتوانید با استفاده از کلمه کلیدی makeref__ یک TypeReference را از یک متغیر، ایجاد کنید. با استفاده از کلمه کلیدی reftype__ میتوانید نوع اصلی از متغیری را که TypeReference را از آن ایجاد کرده اید، استخراج کنید. در انتها میتوانید با استفاده از کلمه کلیدی refvalue__ مقدار متغیر را از TypeReference ایجاد شده، بدست آورد. با استفاده از کلمه کلیدی arglist__ همانند کلمه کلیدی params میتوانید به لیستی از پارامترهای یک تابع دسترسی داشته باشید.
var i = 28; TypedReference tr = __makeref( i ); Type t = __reftype( tr ); Console.WriteLine( t ); int rv = __refvalue( tr, int ); Console.WriteLine( rv ); ArglistTest.DisplayNumbers( __arglist( 1, 2, 3, 5, 6 ) );
public static class ArglistTest { public static void DisplayNumbers( __arglist ) { var ai = new ArgIterator( __arglist ); while ( ai.GetRemainingCount() > 0 ) { var tr = ai.GetNextArg(); Console.WriteLine( TypedReference.ToObject( tr ) ); } } }
Environment.NewLine
رشته خط جدید (↵ Enter) تعریف شده در محیط در حال استفاده را میتوان با استفاده از این دستور بدست آورد.
Console.WriteLine( "NewLine: {0}first line{0}second line{0}third line", Environment.NewLine );
ExceptionDispatchInfo
ExceptionDispatchInfo بیان کننده یک استثناء در یک نقطه خاص از کد، که وضعیت آن قبلا کپچر شدهاست، میباشد. شما میتوانید با استفاده از متد ExceptionDispatchInfo.Throw (در فضای نام System.Runtime.ExceptionServices) یک استثناء را (با حفظ Stack Trace اصلی) ایجاد کنید.
ExceptionDispatchInfo possibleException = null; try { int.Parse( "a" ); } catch ( FormatException ex ) { possibleException = ExceptionDispatchInfo.Capture( ex ); } possibleException?.Throw();
Debug.Assert & Debug.WriteIf & Debug.Indent
Debug.Assert – بررسی صحت شرط تعیین شده و در صورت false بودن شرط، نمایش پیام نوشته شده به همراه call stack مربوطه میشود.
Debug.Assert(1 == 0, "عدد 1 برابر با 0 نیست");
و خروجی آن در تصویر زیر قابل مشاهده است:
Debug.WriteIf( 1 == 1, "display message in output window :D" );
Debug.Indent/Debug.Unindent - برای افزایش/کاهش یک واحد تورفتگی در خروجی نمایش داده شده در پنجره Output، استفاده میشود.
Debug.WriteLine("تست تورفتگی"); Debug.Indent(); Debug.WriteLine("یک واحد افزایش داده شد"); Debug.Unindent(); Debug.WriteLine("یک واحد کاهش داده شد"); Debug.WriteLine("پایان تست");
قطعا به عنوان برنامه نویس JavaScript کم و بیش با ecmaScript 6 آشنایی دارید.
فکر میکنم کدهای فوق احتیاج به توضیح بیشتری نداشته باشند. اکنون فرض کنید نام فایل آن code.js است و در پوشهی src قرار دارد. همانطور که پیشتر عرض کردم، برای تبدیل آن به کدی از نوع es5 از gulp استفاده میکنیم.
بعد بر روی پوشهی root پروژهتان رفته و با استفاده از خط فرمان، npm init را ارسال کنید تا package.json برای شما ساخته شود.
با استفاده از این فرمان، gulp را در مسیر جاری پروژهتان و در پوشهی node-modules نصب کرده و همچنین فرمان --save-dev نیز آن را به وابستگیهای پروژه اضافه میکند.
حال در مسیر اصلی پروژه، فایل gulpfile.js را ساخته و کدهای زیر را مینویسیم:
برای توضیح کدهای بالا باید عرض کنم در قسمت gulp.task، عملیاتی را که لازم میدانیم gulp برای ما انجام دهد، شرح میدهیم.
به صورت مستقیم میتوانیم هر فایلی را که به صورت استاندارد نوشته شده است، کامپایل کرده و تبدیل به es6 نماییم.
فرض کنید آن را با نام es5.js در مسیر اصلی پروژه ذخیره کردهایم. با استفاده از خط فرمان خود آن را تبدیل به es6 خواهیم کرد؛ بدین شکل:
فایل مورد نظر ما به نام es6.js ذخیره خواهد شد و بدین صورت کامپایل خواهد شد
تبدیل es5 به es6 انجام شد؛ اما باید خدمتتان عرض کنم آنچنان هم نباید انتظار داشت که بعد از این تبدیل و فشار دادن دکمهی F5، پروژهی شما بدون هیچ خطایی اجرا شود (البته در صورتیکه هنوز در اوایل پروژه هستید شاید اینگونه شود) اما کمی refactoring برای کدهای کامپایل شده را به es 6، در خاطر داشته باشید.
با وجود ویژگیهای منحصر به فردی که دارد شاید تنها دلیلی که برخی از برنامه نویسان هنوز تصمیم به عدم استفاده از این زبان را دارند، مرورگرهایی میباشند که هنوز از es6 پشتیبانی نمیکنند: es6 compatibility table
اما راهکاری مناسب، برای اینکه بتوان هم از es6 استفاده کرد و هم کاربران را مجبور به استفادهی از مرورگرهای مدرن نکنیم نیز وجود دارد:به صورت مستقیم میتوان با استفاده از Babel، کدهای نوشته شدهی با es6 را کامپایل و تبدیل به es5 کنیم. برای اینکار معمولا از gulp استفاده میکنیم.
gulp در وبسایت رسمی آن، خودش را اینچنین تعریف کرده است : automate and enhance your workflow
حال کافیست با چند خط کد es6 شروع به کار کنیم. بنده معمولا از visual studio code استفاده میکنم.
class firstCLs { doFirst(str) { console.log(str); } } class secondCls extends firstCLs { doSecond(str) { super.doFirst(str); } } let str = 'string'; new secondCls().doSecond(`this is some ${str}`);
ابتدا از طریق خط فرمان، خود gulp را نصب میکنیم.
npm install -g gulp
برای نصب gulp در پروژهی local خود نیز این فرمان را ارسال کنید:
npm install gulp --save-dev
حال احتیاج به نصب gulp-babel داریم که با استفاده از خط فرمان، خود آن را نصب مینماییم:
npm install --save-dev gulp-babel babel-preset-es2015
const gulp = require('gulp'); const babel = require('gulp-babel'); gulp.task('default', () => { return gulp.src('src/code.js') .pipe(babel({ presets: ['es2015'] })) .pipe(gulp.dest('dist')); });
gulp.src مسیر فایلی را که تصمیم داریم کامپایل شده و به es5 تبدیل شود، معین میکند ( لازم به ذکر است که میتوان بطور مثال همهی اسکریپتهای درون یک پوشه را انتخاب کرد که توضیح آن در این مقاله نمیگنجد).
بعد در قسمت اولین pipe فوق است که میخواهیم تبدیلی را به es2015 داشته باشیم و در آخرین pipe نیز با استفاده از متد dest، آدرس مسیری را که میخواهیم آن فایل کامپایل شده در آن قرار بگیرد، مینویسیم. بطور مثال من پوشهی dist را به عنوان آدرس قرار دادهام.
مراحل انجام شده را save نمایید. خط فرمان خود را باز کرده و دستور gulp را type نمایید. بعد از پایان یافتن عملیات، به پوشهی dist رجوع کنید. فایل مورد نظر مشاهد میشود.
اما حال فرض کنید پروژه ای در اختیار داشته و کدها از نوع es5 میباشند و تصمیم به تبدیل آنها به es6 داشته باشید.
برای اینکار از lebab (که دقیقا عکس نوشتاری bable میباشد) استفاده میکنیم.
ابتدا از طریق خط فرمان خود آن را نصب مینماییم
npm install -g lebab
کدهای زیر را در نظر بگیرید
'use strict'; // Let/const var name = 'Bob', time = 'yesterday'; time = 'today'; // Template string console.log('Hello ' + name + ', how are you ' + time + '?'); var bob = { // Object shorthand name: name, // Object method sayMyName: function () { console.log(this.name); } }; // Classes var SkinnedMesh = function SkinnedMesh() { }; SkinnedMesh.prototype.update = function (camera) { camera = camera || createCamera(); this.camera = camera; }; Object.defineProperty(SkinnedMesh.prototype, 'name', { set: function (geometry) { this.geometry = geometry; }, get: function () { return this.geometry; } }); // Commonjs var lebab = require('lebab'); module.exports = SkinnedMesh; // Arrow functions var render = function () { // ... requestAnimationFrame(render); };
lebab es5.js -o es6.js
const name = 'Bob'; let time = 'yesterday'; time = 'today'; // Template string console.log(`Hello ${name}, how are you ${time}?`); const bob = { // Object shorthand name, // Object method sayMyName() { console.log(this.name); } }; class SkinnedMesh { update(camera=createCamera()) { this.camera = camera; } set name(geometry) { this.geometry = geometry; } get name() { return this.geometry; } } import lebab from 'lebab'; export default SkinnedMesh; // Arrow functions const render = () => { // ... requestAnimationFrame(render); };
برای اجرای متد درون یک کلاس از طریق Reflection ابتدا نوع آن کلاس را به دست میآوریم و سپس از طریق کلاس Activator.CreateInstance یک نمونه از آن کلاس را ساخته و در متغیری از نوع object ذخیره کرده و با استفاده از GetMethod اطلاعات متد مورد نظر خود را در متغیری ذخیره کرده و سپس از طریق دستور Invoke آن متد را اجرا میکنیم. دستور Invoke دو سربارگذاری دارد که در یک نوع از آن، متغیر حاوی نمونه کلاس و پارامترهای متد مورد نظر، در قالب یک آرایه از نوع object، به عنوان آرگومان پذیرفته میشود. با امضای زیر
public Object Invoke(Object obj, Object[] parameters)
به مثال زیر که چگونگی این عملیات را شرح میدهد، توجه کنید:
public class TestMath { public int Squar(int i) { return i*i; } } static void Main(string[] args) { Type type = typeof (TestMath);//به دست آوردن نوع کلاس object obj = Activator.CreateInstance(type);//ساختن نمونهای از نوع مورد نظر MethodInfo methodInfo = type.GetMethod("Squar");//یافتن اطلاعات متد مورد نظر Console.WriteLine(methodInfo.Invoke(obj, new object[] { 100 }));// و نمایش نتیجه object[] ارسال عدد 100 به صورت Console.Read(); }
توجه کنید که دو متد GetMethod و Invoke در فضای نام System.Reflection قرار دارند.
روش دیگر
در شیوه دیگر برای انجام این کار، نیازی به استفاده از GetMethod و Invoke نیست و فراخوانی متد مورد نظر بسیار شبیه فراخوانی عادی متدهاست و نیازی به ساخت متغیر ویژهای از نوع []object برای ارسال پارامترها نیست. برای انجام این کار فقط کافیست نوع متغیری که نوع نمونهسازی شده را نگهمیدارد (در اینجا نمونه ای از کلاس را نگهمیدارد) به صورت dynamic باشد:
static void Main(string[] args) { Type type = typeof (TestMath); dynamic obj = Activator.CreateInstance(type); Console.WriteLine(obj.Square(100)); Console.Read(); }
توجه کنید که بعد از تعریف obj، با درج نقطه در کنار آن، منوی Code Insight متد Square را شامل نمیشود اما کامپایلر آن را میپذیرد.