CoffeeScript #15
قسمتهای اصلاح نشده
در ادامهی مطالب قسمت قبل، به برخی دیگر از معایب طراحی در جاوااسکریپت که در CoffeeScript نیز اصلاح نشدهاند میپردازیم.استفاده از parseInt
تابع ()parseInt در جاوااسکریپت، در صورتیکه یک مقدار رشتهای را به آن ارسال کنید و پایهی مناسب آن را تعیین نکنید، نتایج غیره منتظرهای (unexpected) را باز میگرداند . برای مثال:
# Returns 8, not 10! parseInt('010') is 8
# Use base 10 for the correct result parseInt('010', 10) is 10
Strict mode
Strict mode یکی از قابلیتهای ECMAScript 5 است که به شما اجازه میدهد تا یک برنامه یا تابع جاوااسکریپت را در محیطی محدود اجرا کنید. این محدودیت موجب نمایش بیشتر خطاها و هشدارها نسبت به حالت نرمال میشود و به توسعه دهندگان این امکان را میدهد تا از نوشتن کدهای غیر قابل بهینه سازی برای اشتباهات رایج جلوگیری کنند.
به عبارت دیگر Strict mode باعث کاهش اشکالات، افزایش امنیت، بهبود عملکرد و حذف برخی از سختیهای استفاده از ویژگیهای زبان میشود.
در حال حاضر Strict mode، در مرورگرهای زیر پشتیبانی میشود:
- Chrome >= 13.0
- Safari >= 5.0
- Opera >= 12.0
- Firefox >= 4.0
- IE >= 10.0
با این حال، Strict mode به طور کامل با مرورگرهای قدیمی سازگار است.
تغییرات Strict mode
- خطا در پروپرتیها و نام آرگومانهای تابع تکراری
- خطا در عدم استفادهی صحیح از delete
- خطا در زمان دسترسی به arguments.caller و arguments.callee (به دلایل عملکرد)
- استفاده از عمگر with سبب بروز خطای نحوی میشود
- متغیرهای خاص مانند undefined که قابل نوشتن نیستند
- معرفی کلمات کلیدی رزرو شده مانند implements, interface, let, package, private, protected, public, static و yield.
با این حال، برخی از رفتارهای زمان اجرای Strict mode نیز تغییر کرده است:
- متغییرهای سراسری به صورت صریح و روشن هستند (کلمه کلیدی var نیاز است). مقدار سراسری this نیز به صورت undefined است.
- eval نمیتواند متغیر جدیدی را در حوزهی محلی خود تعریف کند.
- بدنهی هر تابع باید قبل از استفاده تعریف شده باشد (قبلا گفتم که در جاوااسکریپت شما میتوانید قبل از تعریف تابع آن را فراخوانی کنید).
- آرگومانها تغییر ناپذیر هستند.
CoffeeScript در حال حاضر بسیاری از الزامات Strict mode را پیاده سازی کردهاست مانند: همیشه از کلمه کلیدی var برای تعریف متغیر استفاده میکند؛ اما فعال کردن Strict mode در برنامههای CoffeeScript نیز بسیار مفید خواهد بود. در واقع CoffeeScript بر روی انطباق برنامهها با Strict mode در زمان کامپایل را، در برنامههای آینده خود دارد.
استفاده از Strict mode
برای فعال کردن بررسی محدودیت، کد و توابع خود را با این رشته شروع کنید:-> "use strict" # ... your code ...
do -> "use strict" console.log(arguments.callee)
Strict mode دسترسی به arguments.callee و arguments.caller، که تاثیر بدی را بر روی عملکرد کد شما دارند، حذف میکند و استفادهی از آنها سبب بروز خطا میشود.
در مثال زیر در حالت strict mode سبب بروز خطای TypeError میشود، اما در حالت نرمال به خوبی اجرا شده و یک متغیر سراسری را ایجاد میکند.
do -> "use strict" class @Spine
do -> "use strict" class window.Spine
شما میتوانید در زمان توسعه برنامه جاوااسکریپت خود Strict mode را فعال کنید و در زمان انتشار، بدون Strict mode برنامهی خود را منتشر کنید.
JavaScript Lint
JavaScript Lint یک ابزار بررسی کیفیت کدهای جاوااسکریپت است و اجرای برنامهی شما از طریق این راه عالی باعث بهبود کیفیت و بهترین شیوهی کد نویسی میشود. این پروژه براساس ابزار JSLint است. شما میتوانید چک لیست سایت JSLint را که شامل موضوعاتی است که باید آنها در نظر داشته باشید، مانند متغیرهای سراسری، فراموش کردن نوشتن سمی کالن، کیفیت ضعیف عمل مقایسه را نام برد.
خبر خوب این است که CoffeeScript تمام موارد گفته شدهی در چک لیست را انجام میدهد. بنابراین کد تولیدی CoffeeScript با JavaScript Lint کاملا سازگار است. در واقع ابزار coffee از lint ،option پشتیبانی میکند.
coffee --lint index.coffee index.coffee: 0 error(s), 0 warning(s)
Thread.CurrentThread.CurrentUICulture = new CultureInfo("fa-IR"); Thread.CurrentThread.CurrentUICulture = CultureInfo.CreateSpecificCulture("fa-IR");
[Required(ErrorMessageResourceName = "ResourceKeyName", ErrorMessageResourceType = typeof(<SolutionName>.Resources.<ResourceClassName>))]
public class LocalizationDisplayNameAttribute : DisplayNameAttribute { private readonly DisplayAttribute _display; public LocalizationDisplayNameAttribute(string resourceName, Type resourceType) { _display = new DisplayAttribute { ResourceType = resourceType, Name = resourceName }; } public override string DisplayName { get { try { return _display.GetName(); } catch (Exception) { return _display.Name; } } } }
public class LocalizationDisplayNameAttribute : DisplayNameAttribute { private readonly PropertyInfo nameProperty; public LocalizationDisplayNameAttribute(string displayNameKey, Type resourceType = null) : base(displayNameKey) { if (resourceType != null) nameProperty = resourceType.GetProperty(base.DisplayName, BindingFlags.Static | BindingFlags.Public); } public override string DisplayName { get { if (nameProperty == null) base.DisplayName; return (string)nameProperty.GetValue(nameProperty.DeclaringType, null); } } }
[LocalizationDisplayName("ResourceKeyName", typeof(<SolutionName>.Resources.<ResourceClassName>))]
public class BaseController : Controller { private const string LanguageCookieName = "MyLanguageCookieName"; protected override void ExecuteCore() { var cookie = HttpContext.Request.Cookies[LanguageCookieName]; string lang; if (cookie != null) { lang = cookie.Value; } else { lang = ConfigurationManager.AppSettings["DefaultCulture"] ?? "fa-IR"; var httpCookie = new HttpCookie(LanguageCookieName, lang) { Expires = DateTime.Now.AddYears(1) }; HttpContext.Response.SetCookie(httpCookie); } Thread.CurrentThread.CurrentUICulture = CultureInfo.CreateSpecificCulture(lang); base.ExecuteCore(); } }
public class LocalizationActionFilterAttribute : ActionFilterAttribute { private const string LanguageCookieName = "MyLanguageCookieName"; public override void OnActionExecuting(ActionExecutingContext filterContext) { var cookie = filterContext.HttpContext.Request.Cookies[LanguageCookieName]; string lang; if (cookie != null) { lang = cookie.Value; } else { lang = ConfigurationManager.AppSettings["DefaultCulture"] ?? "fa-IR"; var httpCookie = new HttpCookie(LanguageCookieName, lang) { Expires = DateTime.Now.AddYears(1) }; filterContext.HttpContext.Response.SetCookie(httpCookie); } Thread.CurrentThread.CurrentUICulture = CultureInfo.CreateSpecificCulture(lang); base.OnActionExecuting(filterContext); } }
<select id="langs" onchange="languageChanged()"> <option value="fa-IR">فارسی</option> <option value="en-US">انگلیسی</option> </select> <script type="text/javascript"> function languageChanged() { setCookie("MyLanguageCookieName", $('#langs').val(), 365); window.location.reload(); } document.ready = function () { $('#langs').val(getCookie("MyLanguageCookieName")); }; function setCookie(name, value, exdays, path) { var exdate = new Date(); exdate.setDate(exdate.getDate() + exdays); var newValue = escape(value) + ((exdays == null) ? "" : "; expires=" + exdate.toUTCString()) + ((path == null) ? "" : "; path=" + path) ; document.cookie = name + "=" + newValue; } function getCookie(name) { var i, x, y, cookies = document.cookie.split(";"); for (i = 0; i < cookies.length; i++) { x = cookies[i].substr(0, cookies[i].indexOf("=")); y = cookies[i].substr(cookies[i].indexOf("=") + 1); x = x.replace(/^\s+|\s+$/g, ""); if (x == name) { return unescape(y); } } } </script>
GET https://www.dntips.ir HTTP/1.1 ... Accept-Language: fa-IR,en-US;q=0.5 ...
Accept-Language: fa-IR,fa;q=0.8,en-US;q=0.5,ar-BH;q=0.3
<system.web> <globalization enableClientBasedCulture="true" uiCulture="auto" culture="auto"></globalization> </system.web>
var langs = filterContext.HttpContext.Request.UserLanguages;
routes.MapRoute( "Localization", // Route name "{lang}/{controller}/{action}/{id}", // URL with parameters new { controller = "Home", action = "Index", id = UrlParameter.Optional } // Parameter defaults );
public class BaseController : Controller { protected override void ExecuteCore() { var lang = RouteData.Values["lang"]; if (lang != null && !string.IsNullOrWhiteSpace(lang.ToString())) { Thread.CurrentThread.CurrentUICulture = CultureInfo.CreateSpecificCulture(lang.ToString()); } base.ExecuteCore(); } }
public class BaseController : Controller { protected override void OnActionExecuted(ActionExecutedContext context) { var view = context.Result as ViewResultBase; if (view == null) return; // not a view var viewName = view.ViewName; view.ViewName = GetGlobalizationViewName(viewName, context); base.OnActionExecuted(context); } private static string GetGlobalizationViewName(string viewName, ControllerContext context) { var cultureName = Thread.CurrentThread.CurrentUICulture.Name; if (cultureName == "en-US") return viewName; // default culture if (string.IsNullOrEmpty(viewName)) return context.RouteData.Values["action"] + "." + cultureName; // "Index.fa" int i; if ((i = viewName.IndexOf('.')) > 0) // ex: Index.cshtml return viewName.Substring(0, i + 1) + cultureName + viewName.Substring(i); // "Index.fa.cshtml" return viewName + "." + cultureName; // "Index" ==> "Index.fa" } }
public sealed class RazorGlobalizationViewEngine : RazorViewEngine { protected override IView CreatePartialView(ControllerContext controllerContext, string partialPath) { return base.CreatePartialView(controllerContext, GetGlobalizationViewPath(controllerContext, partialPath)); } protected override IView CreateView(ControllerContext controllerContext, string viewPath, string masterPath) { return base.CreateView(controllerContext, GetGlobalizationViewPath(controllerContext, viewPath), masterPath); } private static string GetGlobalizationViewPath(ControllerContext controllerContext, string viewPath) { //var controllerName = controllerContext.RouteData.GetRequiredString("controller"); var request = controllerContext.HttpContext.Request; var lang = request.Cookies["MyLanguageCookie"]; if (lang != null && !string.IsNullOrEmpty(lang.Value) && lang.Value != "en-US") { var localizedViewPath = Regex.Replace(viewPath, "^~/Views/", string.Format("~/Views/Globalization/{0}/", lang.Value)); if (File.Exists(request.MapPath(localizedViewPath))) viewPath = localizedViewPath; } return viewPath; }
protected void Application_Start() { ViewEngines.Engines.Clear(); ViewEngines.Engines.Add(new RazorGlobalizationViewEngine()); }
<?xml version="1.0" encoding="utf-8"?> <root> <!-- Microsoft ResX Schema ... --> <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata"> ... </xsd:schema> <resheader name="resmimetype"> <value>text/microsoft-resx</value> </resheader> <resheader name="version"> <value>2.0</value> </resheader> <resheader name="reader"> <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> </resheader> <resheader name="writer"> <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> </resheader> <data name="RightToLeft" xml:space="preserve"> <value>false</value> <comment>RightToleft is false in English!</comment> </data> </root>
ICriteria API در NHibernate پیاده سازی الگوی Query Object است. مشکلی هم که این روش دارد استفاده از رشتهها جهت ایجاد کوئریهای متفاوت است؛ به عبارتی Type safe نیست. ایرادی هم به آن وارد نیست چون پیاده سازی اولیه آن از جاوا صورت گرفته و مباحث Lambda Expressions و Extension Methods هنوز در آن زبان به صورت رسمی ارائه نشده است (در JDK 7 تحت عنوان Closures قرار است اضافه شود). NHibernate 3.0 از ویژگیهای جدید زبانهای دات نتی جهت ارائهی محصور کنندهای Type safe حول ICriteria API استاندارد به نام QueryOver API سود جسته است. این پیاده سازی بسیار شبیه به عبارات LINQ است اما نباید با آن اشتباه گرفته شود زیرا LINQ to NHibernate یک ویژگی دیگر جدید، یکپارچه و استاندارد NHibernate 3.0 به شمار میرود.
برای نمونه در یک ICriteria query متداول، فراخوانیهای ذیل متداول است:
.Add(Expression.Eq("Name", "Smith"))
.Where<Person>(p => p.Name == "Smith")
مزیتهای این روش (strongly-typed fluent API) به شرح زیر است:
- خبری از رشتهها جهت استفاده از یک خاصیت وجود ندارد. برای مثال در اینجا خاصیت Name کلاس Person تحت کنترل کامپایلر قرار میگیرد و اگر در کلاس Person تغییراتی حاصل شود، برای مثال Name به LName تغییر کند، برنامه دیگر کامپایل نخواهد شد. اما در حالت ICriteria API یا باید به نتایج حاصل از Unit testing مراجعه کرد یا باید به نتایج بازخورد کاربران برنامه مانند: "باز برنامه رو تغییر دادی، یکجای دیگر از کار افتاد!" دقت نمود!
- اگر در حین ویرایش کلاس Person از ابزارهای Refactoring استفاده شود، تغییرات حاصل به صورت خودکار به تمام برنامه نیز اعمال خواهد شد. بدیهی است این اعمال تغییرات تنها در صورتی میسر است که خاصیت مورد نظر به صورت رشته معرفی نگردیده و ارجاعات به اشیاء تعریف شده به سادگی قابل parse باشند.
- در این حالت امکان بررسی نوع خواص تغییر کرده نیز توسط کامپایلر به سادگی میسر است و اگر ارجاعات تعریف شده به نحو صحیحی از این نوع جدید استفاده نکنند باز هم برنامه تا رفع این مشکلات کامپایل نخواهد شد که این هم مزیت مهمی در نگهداری سادهتر یک برنامه است.
- با بکارگیری Extension methods و پیاده سازی Fluent API جدید، مدت زمان یادگیری این روش نیز به شدت کاهش یافته، زیرا Intellisense موجود در VS.NET بهترین راهنمای استفاده از امکانات فراهم شده است. برای مثال جهت استفاده از ویژگی جدید QueryOver به سادگی میتوان پس از ساختن یک session جدید به صورت زیر عمل نمود:
IList<Cat> cats = session.QueryOver<Cat>().Where(c => c.Name == "Max").List();
جهت مشاهدهی معرفی کامل آن میتوان به مستندات NHibernate 3.0 مراجعه کرد.
نکته : آشنایی با کد نویسی و مفاهیم #F برای درک بهتر مطالب توصیه میشود.
معرفی پروژه FSharpX
این قسمتها عبارتند از :
FSharpx.Core : شامل مجموعه ای کامل از توابع عمومی، پرکاربرد و ساختاری است که برای این زبان توسعه داده شده اند و با تمام زبانهای دات نت سازگاری دارند؛
FSharpx.Http : استفاده از #F در برنامه نویسی مدل Http؛
FSharpx.TypeProvider : این پروژه خود شامل چندین بخش است که در این جا چند مورد از آنها را عنوان میکنم:
- FSharpx.TypeProviders.AppSetting : متد خواندن و نوشتن (setter و getter) را برای فایلهای تنظیمان پروژه (Application Setting File) فراهم میکند.
- FSharpx.TypeProviders.Vector : برای محاسبات با ساختارهای برداری استفاده میشود.
- FSharpx.TypeProviders.Machine : برای دسترسی و اعمال تغییرات در رجیستری و فایلهای سیستمی استفاده میشود.
- FSharpx.TypeProviders.Xaml : با استفاده از این افزونه میتوانیم از فایلهای Xaml، در پروژههای #F استفاده کنیم و WPF Designer نرم افزار VS.Net هم برای این زبان قابل استفاده خواهد شد.
- FSharpx.TypeProviders.Regex : امکان استفاده از عبارات با قاعده را در این پروژه فراهم میکند.
یک مثال از عبارات با قاعده:
type PhoneRegex = Regex< @"(?<AreaCode>^\d{3})-(?<PhoneNumber>\d{3}-\d{4}$)"> PhoneRegex.IsMatch "425-123-2345" |> should equal true PhoneRegex().Match("425-123-2345").CompleteMatch.Value |> should equal "425-123-2345" PhoneRegex().Match("425-123-2345").PhoneNumber.Value |> should equal "123-2345"
ایتدا یک پروژه از نوع F# Console Application ایجاد کنید. از قسمت Project Properties (بر روی پروژه کلیک راست کنید و گزینه Properties را انتخاب کنید) نوع پروژه را به Windows Application تغییر دهید(قسمت Out Put Type). اسمبلیهای زیر را به پروژه ارجاع دهید:
- PresentationCore
- PresentationFramework
- WindowBase
- System.Xaml
با استفاده از پنجره Package Manager Console دستور نصب زیر را اجرا کنید(آخرین نسخه این پکیج 1.8.31 و حجم آن کمتر از یک مگابایت است):
PM> Install-Package FSharpx.TypeProviders.Xaml
حال یک فایل Xaml به پروژه اضافه کنید و کدهای زیر را در آن کپی کنید:
<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="WPF F# Sample By Masoud Pakdel" Height="350" Width="525"> <Grid Name="MainGrid"> <StackPanel Name="StackPanel1" Margin="50"> <Button Name="Button1">Who are you?</Button> </StackPanel> </Grid> </Window>
open System open System.Windows open System.Windows.Controls open FSharpx type MainWindow = XAML<"MainWindow.xaml"> let loadWindow() = let window = MainWindow() window.Button1.Click.Add(fun _ -> MessageBox.Show("Masoud Pakdel") |> ignore) window.Root [<STAThread>] (new Application()).Run(loadWindow()) |> ignore
در تابع loadWindow یک نمونه از کلاس MainWindow ساخته میشود و برای button1 آن رویداد کلیک تعریف میکنیم. دستورات زیر معادل دستورات شروع برنامه در فایل program پروژههای #C است.
[<STAThread>] (new Application()).Run(loadWindow()) |> ignore
پروژه را اجرا کنید و بر روی تنهای Button موجود در صفحه، کلیک کنید و پیغام مورد نظر را مشاهده خواهید کرد. به صورت زیر: