- پشتیبانی از تمامی Calendar System هایی که noda time ساپورت میکند؛ اعم از شمسی، قمری، میلادی و ...
- پشتیبانی از تمامی Locale هایی که NET. پشتیبانی میکند؛ اعم از فارسی، عربی، انگلیسی و ...
- قابلیت ارائه Template سفارشی شده برای UI
- سازگار شده با MVVM
- دریافت و نصب پکیج Bit.CSharpClient.Controls از nuget.
- اضافه کردن کدهای زیر در کلاس AppDelegate پروژهی iOS و کلاس Main Activity در Android و کلاس App پروژهی UWP قبل از Forms.Init .
BitCSharpClientControls.Init();
- اضافه کردن namepsace های زیر در فرم Xaml:
-
xmlns:bitControls="clr-namespace:Bit.CSharpClient.Controls;assembly=Bit.CSharpClient.Controls" xmlns:noda="clr-namespace:NodaTime;assembly=NodaTime"
مثال تقویم شمسی:
<bitControls:BitDatePicker Text="لطفا یک روز را انتخاب کنید" Culture="Fa" DateDisplayFormat="dd MMM yyyy" CalendarSystem="{x:Static noda:CalendarSystem.PersianArithmetic}" FlowDirection="RightToLeft" SelectedDate =" {Binding SelectedDate}"/>
برای درک بهتر از نحوهی استفادهی از این Date Picker، نگاهی به Sample های آن بیاندازید. همچنین میتوانید در همینجا نیز سؤالات خود را بپرسید؛ یا این که در Stackoverflow.com سوال ایجاد کنید و یا در GitHub یک Issue را ثبت کنید.
پیشنیازها
در اینجا فرض بر این است که موارد ذیل را نصب کردهاید:
- آخرین نگارش مرورگر Chrome
- افزونهی Debugger for Chrome که از آن برای دیباگ کدهای جاوا اسکریپتی و تایپاسکریپتی یکپارچهی با VSCode میتوان استفاده کرد.
تنظیمات VSCode جهت دیباگ برنامههای مبتنی بر Angular CLI
کدهای مطلب «فرمهای مبتنی بر قالبها در Angular - قسمت پنجم - ارسال اطلاعات به سرور» از انتهای بحث آن قابل دریافت هستند. این کدها را دریافت کرده و سپس پوشهی اصلی آنرا در VSCode باز کنید. اگر در ویندوز هستید با کلیک راست بر روی پوشه، گزینهی Open With Code ظاهر میشود و یا حتی میتوان از طریق خط فرمان به پوشهی اصلی برنامه مراجعه کرده و سپس دستور . code را صادر نمود.
در ادامه نیاز است دیباگر VSCode را تنظیم کنیم. اگر پیشتر هیچ تنظیمی را نداشته باشید، پس از مراجعهی به برگهی debug آن، بر روی دکمهی سبز رنگ Start Debugging آن کلیک کنید. در ادامه در منوی باز شده، گزینهی Chrome را انتخاب کنید:
اینکار سبب میشود تا فایل جدید vscode\launch.json. به پوشهی جاری اضافه شده و سپس تنظیمات دیباگر کروم در آن قرار گیرند.
حالت دوم شبیه به حالتی است که برنامهی مورد بحث جاری دارد: پیشتر دیباگری مانند NET Core. در آن تنظیم شدهاست. در این حالت، منوی drop down مربوط به دیباگرهای تنظیم شده را گشوده و سپس گزینهی آخر آن یعنی Add configuration را انتخاب کنید:
در اینجا نیز منوی Intellisense آن ظاهر شده و امکان انتخاب گزینهی Chrome را میدهد:
در نهایت هر دو حالت به ایجاد فایل جدید vscode\launch.json. و یا ویرایش آن منتهی میشوند. در اینجا تنها کاری را که باید انجام داد، تغییر پورت پیش فرض آن است:
- اگر از دستور ng serve استفاده میکنید، این پورت را به 4200 تغییر دهید (پورت پیش فرض این دستور است؛ برای تغییر آن، از پارامتر port-- در دستور ng serve استفاده کنید):
{ "version": "0.2.0", "configurations": [ { "type": "chrome", "request": "launch", "name": "Launch Chrome with ng serve", "url": "http://localhost:4200/#", "webRoot": "${workspaceRoot}" } ] }
- اگر از دستورات dotnet watch run و سپس ng build -watch استفاده میکنید (اولی وب سرور آزمایشی NET Core. را راه اندازی میکند و دومی کار ساخت پیوستهی برنامهی Angular را انجام میدهد)، این پورت را بر روی 5000 تنظیم کنید:
{ "version": "0.2.0", "configurations": [ { "type": "chrome", "request": "launch", "name": "Launch Chrome for dotnet build & ng build", "url": "http://localhost:5000/#", "webRoot": "${workspaceRoot}" } ] }
آزمایش یک break point و بررسی مقادیر دریافتی از سرور
تا اینجا کاری را که انجام دادیم، به افزودن یک قطعه تنظیم جدید به فایل vscode\launch.json. خلاصه میشود.
در ادامه به کامپوننت employee-register.component.ts مراجعه کرده و سطر this.languages = data را تبدیل به یک سطر مستقل درون {} میکنیم تا بتوانیم بر روی آن break point قرار دهیم:
ngOnInit() { this.formPoster.getLanguages().subscribe( data => { this.languages = data; }, err => console.log("get error: ", err) ); }
پس از آن از طریق خط فرمان به ریشهی پروژه وارد شده و دو پنجرهی کنسول مجزا را باز کنید. در اولی دستورات
>npm install >ng build --watch
>dotnet restore >dotnet watch run
سپس به برگهی دیباگ مراجعه کرده و گزینهی جدید Launch Chrome for dotnet build & ng build را انتخاب و سپس بر روی دکمهی سبز رنگ اجرای آن کلیک کنید:
اکنون اگر صفحهی مشاهدهی فرم را در مرورگر کروم باز شده درخواست کنیم، به این break point خواهیم رسید؛ اما ... حاوی اطلاعات data نیست. برای رفع این مشکل نیاز است تنظیمات پیشفرض را به صورت ذیل بهبود بخشید:
{ "version": "0.2.0", "configurations": [ { "type": "chrome", "request": "launch", "name": "Launch Chrome for dotnet build & ng build", "url": "http://localhost:5000", "runtimeArgs": [ "--user-data-dir", "--remote-debugging-port=9222", "--disable-session-crashed-bubble" ], "sourceMaps": true, "trace": true, "webRoot": "${workspaceRoot}/wwwroot/", "userDataDir": "${workspaceRoot}/.vscode/chrome" } ] }
- تنظیم sourceMaps و همچنین مشخص سازی محل دقیق پوشهی قرارگیری فایلهای نهایی ng build که همان پوشهی wwwroot است در webRoot سبب خواهند شد تا اینبار break point ما حاوی اطلاعات واقعی data دریافتی از سرور باشند (با نزدیک کردن اشارهگر ماوس به data، اطلاعات تکمیلی آن ظاهر میشود):
- اگر از دستور ng serve استفاده میکنید، در این تنظیمات پورت را به 4200 و محل پوشهی wwwroot را به dist تغییر دهید (مطابق تنظیمات فایل angular-cli.json.).
استفاده از pjax بجای ajax در ASP.NET MVC
از کمک شما ممنون
بالاخره خطا رو پیدا کردم
The following sections have been defined but have not been rendered for the layout page "~/Views/Shared/_PjaxLayout.cshtml": "Scripts".
ولی دلیلش چی میتونه باشه مگه فقط نمیاد قسمت مثلا main در کد زیر را جایگذاری کنه؟
<div id="main"> @RenderBody() </div>
//********** @Scripts.Render("~/bundles/jquery")
@Scripts.Render("~/bundles/bootstrap")
@RenderSection("Scripts", required: false)
<script type="text/javascript"> $(function () { $(document).pjax('a[withpjax]', '#main', { timeout: 5000 });
و لینک هم به اینصورت:
@Html.ActionLink("ارتباط با ما","Contact", "Home" , null,new { withpjax="with-pjax" })
public MenuItem GetMenuItem(RequestContext requestContext) { return new MenuItem { Name = "بیوگرافی", Url = new UrlHelper(requestContext).Action("Index", "Home", new { area = "Biography"}), Icon = "fa fa-child" }; }
public class BiographyAreaRegistration : AreaRegistration { public override string AreaName { get { return "Biography"; } } public override void RegisterArea(AreaRegistrationContext context) { context.MapRoute( "Biography_default", "Biography/{controller}/{action}/{id}", new { controller = "Home", action = "Index", id = UrlParameter.Optional }, namespaces:new []{$"{this.GetType().Namespace}.Controllers"} ); }
ابتدا فایل yml زیر را در پوشهی github\workflows\deploy.yml. قرار دهید (پوشهای را به این نام، در ریشهی پروژهی خود ایجاد کنید):
name: Deploy to GitHub Pages # Run workflow on every push to the main branch on: push: branches: [ main ] jobs: deploy-to-github-pages: # use ubuntu-latest image to run steps on runs-on: ubuntu-latest steps: # uses GitHub's checkout action to checkout code form the main branch - uses: actions/checkout@v2 # sets up .NET Core SDK - name: Setup .NET Core SDK uses: actions/setup-dotnet@v1 with: dotnet-version: 5.0.302 # publishes Blazor project to the release-folder - name: Publish .NET Core Project run: dotnet publish ./src/DNTPersianComponents.Blazor.WasmSample/Server/DNTPersianComponents.Blazor.WasmSample.Server.csproj -c Release -o release --nologo # changes the base-tag in index.html from '/' to 'DNTPersianComponents.Blazor' to match GitHub Pages repository subdirectory - name: Change base-tag in index.html from / to DNTPersianComponents.Blazor run: sed -i 's/<base href="\/" \/>/<base href="\/DNTPersianComponents.Blazor\/" \/>/g' release/wwwroot/index.html # copy index.html to 404.html to serve the same file when a file is not found - name: copy index.html to 404.html run: cp release/wwwroot/index.html release/wwwroot/404.html # add .nojekyll file to tell GitHub pages to not treat this as a Jekyll project. (Allow files and folders starting with an underscore) - name: Add .nojekyll file run: touch release/wwwroot/.nojekyll - name: Commit wwwroot to GitHub Pages uses: JamesIves/github-pages-deploy-action@3.7.1 with: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} BRANCH: github-pages FOLDER: release/wwwroot
- نام شاخهی اصلی پروژه؛ که یا main است و یا master.
- شماره نگارش دات نت مورد استفاده.
- مسیر فایل csproj پروژهی wasm.
- نام اصلی مخزن کد.
سپس آنرا به مخزن کد خود commit کنید. بعد به قسمت settings->pages در github مراجعه کرده و source را بر روی نام شاخهی جدید github-pages (فوق در قسمت آخر کار) قرار داده و آنرا ذخیره کنید. الان سایت دموی شما در مسیری که در همین قسمت pages پس از ذخیره سازی، نمایش میدهد، آمادهاست.
یک نکتهی مهم
چون base href، توسط action فوق اصلاح میشود تا به پوشهی نسبی محل قرارگیری برنامه اشاره کند، نیاز است navlinkها با href شروع شدهی با / نباشند؛ چون به ریشهی سایت اشاره میکنند و نه مسیر نسبی محل قرارگیری برنامه. کلا در هر قسمتی از برنامه، این نکته باید رعایت شود. مثلا اگر فونت وبی را در فایل app.css تعریف کردهاید، مسیر آن نباید با / شروع شود؛ وگرنه یافت نخواهد شد. یک مثال:
فایل app.css برنامه در مسیر wwwroot\css\app.css قرار دارد و داخل آن فایل، فونتهای پوشهی دیگر wwwroot\lib\samim-font را به صورت زیر تعریف کردهایم؛ که یعنی مسیر فونت را از ریشهی سایت پیدا کن:
src: url('/lib/samim-font/Samim-Bold.eot?v=4.0.5');
src: url('../lib/samim-font/Samim-Bold.eot?v=4.0.5');
box-shadow به شما امکان تعریف چندین سایه را بر روی یک عنصر، با تعیین مقادیر رنگ، اندازه، میزان تیرگی (بلور) و میزان جابجایی (افست) آن، میدهد.
box-shadow: inset horizontal vertical blur spread color;
box-shadow: 10px 10px; box-shadow: 10px 10px 5px #888; box-shadow: inset 2px 2px 2px 2px black; box-shadow: 10px 10px #888, -10px -10px #f4f4f4, 0px 0px 5px 5px #cc6600;
Browser Support
میتوان گفت تمام مرورگرها، خصوصیت box-shadow را پشتیبانی میکنند.- Internet Explorer 9.0 و بالاتر
- Firefox 3.5 و بالاتر
- Chrome 1.0 و بالاتر
- Safari 3 و بالاتر
- Opera 10.5 و بالاتر
برای استفاده از box-shadow در مرورگرهای نسخه پایینتر (بدون تغییر در نحوه تعریف)، باید از پیشوندهای هر مرورگر استفاده کنید. برای Firefox از moz-، برای Chrome و Safari از webkit- و Opera نیاز به پیشوند ندارد.
CSS مشترک برای افکتهای زیر
.box { width:70%; height:200px; background:#FFF; margin:40px auto; } .box h3{ text-align:center; position:relative; top:80px; }
Effect 1
با انتساب افکت سایه استاندارد به این المنت، ظاهری شبیه به یک جعبه برجسته را پیدا میکند.
کد HTML
<div class="box effect1"> <h3>Effect 1</h3> </div>
کد CSS
.effect1{ box-shadow: 0 10px 6px -6px #777; }
Effect 2
این افکت بر روی گوشههای المنت اثر خواهد گذاشت و ظاهر بلند کردن گوشههای جعبه را نمایش میدهد. با استفاده از دو خصوصیت before: و after: این افکت پیاده سازی میشود.
کد HTML
<div class="box effect2"> <h3>Effect 2</h3> </div>
کد CSS
.effect2 { position: relative; } .effect2:before, .effect2:after { z-index: -1; position: absolute; content: ""; bottom: 15px; left: 10px; width: 50%; top: 80%; max-width:300px; background: #777; box-shadow: 0 15px 10px #777; transform: rotate(-3deg); } .effect2:after { transform: rotate(3deg); right: 10px; left: auto; }
transform: rotate(8deg);
Effect 3
این افکت بر روی گوشهی پایین چپ المنت اثر خواهد گذاشت و ظاهر بلند کردن گوشه جعبه را نمایش میدهد. با استفاده از خصوصیت before: این افکت پیاده سازی میشود.
کد HTML
<div class="box effect3"> <h3>Effect 3</h3> </div>
کد CSS
.effect3 { position: relative; } .effect3:before { z-index: -1; position: absolute; content: ""; bottom: 15px; left: 10px; width: 50%; top: 80%; max-width:300px; background: #777; box-shadow: 0 15px 10px #777; transform: rotate(-3deg); }
Effect 4
این افکت بر روی گوشهی پایین راست المنت اثر خواهد گذاشت و ظاهر بلند کردن گوشه جعبه را نمایش میدهد. با استفاده از خصوصیت before: این افکت پیاده سازی میشود.
کد HTML
<div class="box effect4"> <h3>Effect 4</h3> </div>
کد CSS
.effect4 { position: relative; } .effect4:after { z-index: -1; position: absolute; content: ""; bottom: 15px; right: 10px; left: auto; width: 50%; top: 80%; max-width:300px; background: #777; box-shadow: 0 15px 10px #777; transform: rotate(3deg); }
Effect 5
این افکت یک سایه منحنی شکل را ایجاد میکند.
کد HTML
<div class="box effect5"> <h3>Effect 5</h3> </div>
کد CSS
.effect5 { position:relative; box-shadow:0 1px 4px rgba(0, 0, 0, 0.3), 0 0 40px rgba(0, 0, 0, 0.1) inset; } .effect5:before, .effect5:after { content:""; position:absolute; z-index:-1; box-shadow:0 0 20px rgba(0,0,0,0.8); top:50%; bottom:0; left:10px; right:10px; border-radius:100px / 10px; }
.effect5:before, .effect5:after { top:0; } .effect5:after { right:10px; left:auto; transform:skew(8deg) rotate(3deg); }
Effect 6
این افکت سایه را در دو طرف جعبه ایجاد میکند.
کد HTML
<div class="box effect6"> <h3>Effect 6</h3> </div>
کد CSS
.effect6 { position:relative; box-shadow:0 1px 4px rgba(0, 0, 0, 0.3), 0 0 40px rgba(0, 0, 0, 0.1) inset; } .effect6:before, .effect6:after { content:""; position:absolute; z-index:-1; box-shadow:0 0 20px rgba(0,0,0,0.8); top:10px; bottom:10px; left:0; right:0; border-radius:100px / 10px; } .effect6:after { right:10px; left:auto; transform:skew(8deg) rotate(3deg); }
ساخت (Build) برنامههای Angular
Angular CLI کار ساخت و کامپایل برنامه را به صورت خودکار انجام داده و خروجی را در مسیری مشخص درج میکند. در اینجا میتوان گزینههایی را بر اساس نوع کامپایل مدنظر مانند کامپایل برای حالت توسعه و یا کامپایل برای حالت توزیع نهایی، انتخاب کرد. همچنین مباحث bundling و یکی کردن تعداد بالای ماژولهای برنامه در آن لحاظ میشوند تا برنامه در حالت توزیع نهایی، سبب 100ها رفت و برگشت به سرور برای دریافت ماژولهای مختلف آن نشود. به علاوه مباحث uglification (به نوعی obfuscation کدهای جاوا اسکریپتی نهایی) و tree-shaking (حذف کدهایی که در برنامه استفاده نشدهاند؛ یا کدهای مرده) نیز پیاده سازی میشوند. با انجام tree-shaking، نه تنها اندازهی توزیع نهایی به کاربر کاهش پیدا میکند، بلکه مرورگر نیز حجم کمتری از کدهای جاوااسکریپتی را باید تفسیر کند.
برای شروع میتوان از دستور ذیل برای مشاهدهی تمام گزینههای مهیای ساخت برنامه استفاده کرد:
> ng build --help
"apps": [ { "outDir": "dist",
فایل | توضیح |
inline.bundle.js | WebPack runtime از آن برای بارگذاری ماژولهای برنامه و چسباندن قسمتهای مختلف به یکدیگر استفاده میشود. |
main.bundle.js | شامل تمام کدهای ما است. |
polyfills.bundle.js | Polyfills - جهت پشتیبانی از مرورگرهای مختلف. |
styles.bundle.js | شامل بسته بندی تمام شیوه نامههای برنامه است |
vendor.bundle.js | کدهای کتابخانههای ثالث مورد استفاده و همچنین خود Angular، در اینجا بسته بندی میشوند. |
روشی برای بررسی محتوای bundleهای تولید شده
تولید bundleها در جهت کاهش رفت و برگشتهای به سرور و بالا بردن کارآیی برنامه ضروری هستند؛ اما دقیقا این بسته بندیها شامل چه اطلاعاتی میشوند؟ این اطلاعات را میتوان از فایلهای source map تولیدی استخراج کرد و برای این منظور میتوان از برنامهی source-map-explorer استفاده کرد.
روش نصب عمومی آن:
> npm install -g source-map-explorer
> source-map-explorer dist/main.bundle.js
یک مثال: ساخت برنامهی مثال قسمت چهارم - تنظیمات مسیریابی در حالت dev
در ادامه، کار Build همان مثالی را که در قسمت قبل توضیح داده شد، بررسی میکنیم. برای این منظور از طریق خط فرمان به ریشهی پوشهی اصلی پروژه وارد شده و دستور ng build را صادر کنید. یک چنین خروجی را مشاهده خواهید کرد:
D:\Prog\angular-routing>ng build Hash: 123cae8bd8e571f44c31 Time: 33862ms chunk {0} polyfills.bundle.js, polyfills.bundle.js.map (polyfills) 158 kB {4} [initial] [rendered] chunk {1} main.bundle.js, main.bundle.js.map (main) 14.7 kB {3} [initial] [rendered] chunk {2} styles.bundle.js, styles.bundle.js.map (styles) 9.77 kB {4} [initial] [rendered] chunk {3} vendor.bundle.js, vendor.bundle.js.map (vendor) 2.34 MB [initial] [rendered] chunk {4} inline.bundle.js, inline.bundle.js.map (inline) 0 bytes [entry] [rendered]
<!doctype html> <html> <head> <meta charset="utf-8"> <title>AngularRouting</title> <base href="/"> <meta name="viewport" content="width=device-width, initial-scale=1"> <link rel="icon" type="image/x-icon" href="favicon.ico"> </head> <body> <app-root>Loading...</app-root> <script type="text/javascript" src="inline.bundle.js"> </script><script type="text/javascript" src="polyfills.bundle.js"> </script><script type="text/javascript" src="styles.bundle.js"> </script><script type="text/javascript" src="vendor.bundle.js"> </script><script type="text/javascript" src="main.bundle.js"></script> </body> </html>
یک نکته: زمانیکه دستور ng serve -o صادر میشود، در پشت صحنه دقیقا همین دستور ng build صادر شده و اطلاعات را درون حافظه تشکیل میدهد. اما اگر کار ng build را دستی انجام دهیم، اینبار ng serve -o اطلاعات را از پوشهی dist دریافت میکند. بنابراین در حین کار با ng serve -o نیازی به build دستی پروژه نیست.
سؤال: چرا حجم فایل endor.bundle.js اینقدر بالا است و شامل چه اجزایی میشود؟
نکتهای که در اینجا وجود دارد، حجم بالای فایل vendor.bundle.js آن است که 2.34 MB میباشد:
چون دستور ng build بدون پارامتری ذکر شدهاست، برنامه را برای حالت توسعه Build میکند و به همین جهت هیچگونه بهینه سازی در این مرحله صورت نخواهد گرفت. برای بررسی محتوای این فایل میتوان دستور ذیل را در ریشهی اصلی پروژه صادر کرد:
> source-map-explorer dist/vendor.bundle.js
همانطور که مشاهده میکنید، در حالت بهینه سازی نشده و Build برای توسعه، کامپایلر Angular حدود 41 درصد حجم فایل vendor.bundle.js را تشکیل میدهد. به علاوه ماژولها و قسمتهایی را ملاحظه میکنید که اساسا برنامهی فعلی مثال ما از آنها استفاده نمیکند؛ مانند http، فرمها و غیره.
سفارشی سازی Build برای محیطهای مختلف
اگر به پروژهی تولید شدهی توسط Angular CLI دقت کنید، حاوی پوشهای است به نام src\environments
هدف از فایلهای environment برای نمونه تغییر آدرس توزیع برنامه در حالت توسعه و ارائه نهایی است.
همچنین در اینجا میتوان نحوهی بهینه سازی فایلهای تولیدی را توسط Build Targets مشخص کرد و اینکار توسط ذکر پرچم prod-- (مخفف production) صورت میگیرد.
در ادامه، تفاوتهای دستورهای ng build و ng build --prod را ملاحظه میکنید:
- با اجرای ng build، از فایل environment.ts استفاده میشود؛ برخلاف حالت اجرای ng build --prod که از فایل environment.prod.ts استفاده میکند.
- Cache-busting در حالت ارائهی نهایی، به تمام اجزای پروژه اعمال میشود؛ اما در حالت توسعه فقط برای تصاویر قید شدهی در فایلهای css.
- فایلهای source map فقط برای حالت توسعه تولید میشوند.
- در حالت توسعه، cssها داخل فایلهای js تولیدی قرار میگیرند؛ اما در حالت ارائهی نهایی به صورت فایلهای css بسته بندی میشوند.
- در حالت توسعه برخلاف حالت ارائهی نهایی، کار uglification انجام نمیشود.
- در حالت توسعه برخلاف حالت ارائهی نهایی، کار tree-shaking یا حذف کدهای مرده و بدون ارجاع، انجام نمیشود.
- در حالت توسعه برخلاف حالت ارائهی نهایی، کار AOT انجام نمیشود. در اینجا AOT به معنای Ahead of time compilation است.
- در هر دو حالت توسعه و ارائهی نهایی کار bundling و دسته بندی فایلها انجام خواهد شد.
به همین جهت است که ng build سریع است؛ اما حجم بالاتری را هم تولید میکند. چون بسیاری از بهینه سازیهای حالت ارائهی نهایی را به همراه ندارد.
دستورات build برای حالت توسعه و ارائهی نهایی
برای حالت توسعه، هر 4 دستور ذیل یک مفهوم را دارند و به همین جهت مورد ng build متداولتر است:
>ng build --target=development --environment=dev >ng build --dev -e=dev >ng build --dev >ng build
برای حالت ارائهی نهایی، هر 3 دستور ذیل یک مفهوم را دارند و به همین جهت مورد ng build --prod متداولتر است:
>ng build --target=production --environment=prod >ng build --prod -e=prod >ng build --prod
همچنین هر کدام از این دستورات را توسط پرچمهای ذیل نیز میتوان سفارشی سازی کرد:
پرچم | مخفف | توضیح |
sourcemap-- | sm- | تولید سورسمپ |
aot-- | Ahead of Time compilation | |
watch-- | w- | تحت نظر قرار دادن فایلها و ساخت مجدد |
environment-- | e- | محیط ساخت |
target-- | t- | نوع ساخت |
dev-- | مخفف نوع ساخت جهت توسعه | |
prod-- | مخفف نوع ساخت جهت ارائه نهایی |
برای مثال در حالت prod، سورسمپها تولید نخواهند شد. اگر علاقمندید تا این فایلها نیز تولید شوند، پرچم souremap را نیز ذکر کنید.
و یا اگر برای حالت dev میخواهید AOT را فعالسازی کنید، پرچم aot-- را در آنجا قید کنید.
یک مثال: ساخت برنامهی مثال قسمت چهارم - تنظیمات مسیریابی در حالت prod
تا اینجا خروجی حالت dev ساخت برنامهی قسمت چهارم را بررسی کردیم. در ادامه دستور ng build --prod را در ریشهی پروژه صادر میکنیم:
D:\Prog\angular-routing>ng build --prod Hash: f5bd7fd555a85af8a86f Time: 39932ms chunk {0} polyfills.18173234f9641113b9fe.bundle.js (polyfills) 158 kB {4} [initial] [rendered] chunk {1} main.c6958def7c5f51c45261.bundle.js (main) 50.3 kB {3} [initial] [rendered] chunk {2} styles.d41d8cd98f00b204e980.bundle.css (styles) 69 bytes {4} [initial] [rendered] chunk {3} vendor.b426ba6883193375121e.bundle.js (vendor) 1.37 MB [initial] [rendered] chunk {4} inline.8cec210370dd3af5f1a0.bundle.js (inline) 0 bytes [entry] [rendered]
همانطور که ملاحظه میکنید، اینبار نه تنها حجم فایلها به میزان قابل ملاحظهای کاهش پیدا کردهاند، بلکه این نامها به همراه یک سری hash هم هستند که کار cache-busting (منقضی کردن کش مرورگر، با ارائهی نگارشی جدید) را انجام میدهند.
در ادامه اگر بخواهیم مجددا برنامهی source-map-explorer را جهت بررسی محتوای فایلهای js اجرا کنیم، به خطای عدم وجود sourcemapها خواهیم رسید (چون در حالت prod، به صورت پیش فرض غیرفعال هستند). به همینجهت برای این مقصود خاص نیاز است از پرچم فعالسازی موقت آن استفاده کرد:
> ng build --prod --sourcemap > source-map-explorer dist/vendor.b426ba6883193375121e.bundle.js
همانطور که در تصویر نیز مشخص است، اینبار کامپایلر Angular به همراه تمام ماژولهایی که در برنامه ارجاعی به آنها وجود نداشتهاست، حذف شدهاند و کل حجم بستهی Angular به 366 KB کاهش یافتهاست.
بررسی دستور ng serve
تا اینجا برای اجرای برنامه در حالت dev از دستور ng serve -o استفاده کردهایم. کار ارائهی برنامه توسط این دستور، از محتوای کامپایل شدهی درون حافظه با مدیریت webpack انجام میشود. به همین جهت بسیار سریع بوده و قابلیت live reload را ارائه میدهد (نمایش آنی تغییرات در مرورگر، با تغییر فایلها).
همانند تمام دستورات دیگر، اطلاعات بیشتری را در مورد این دستور، از طریق راهنمای آن میتوان به دست آورد:
> ng serve --help
که شامل این موارد هستند (علاوه بر تمام مواردی را که در حالت ng build میتوان مشخص کرد؛ مثلا ng serve --prod -o):
پرچم | مخفف | توضیح |
open-- | o- | بازکردن خودکار مرورگر پیش فرض. حالت پیش فرض آن گشودن مرورگر توسط خودتان است و سپس مراجعهی دستی به آدرس برنامه. |
port-- | p- | تغییر پورت پیش فرض مانند ng server -p 8626 |
live-reload-- | lr- |
فعال است مگر اینکه آنرا با false مقدار دهی کنید. |
ssl-- | ارائه به صورت HTTPS | |
proxy-config-- | pc- | Proxy configuration file |
استخراج فایل تنظیمات webpack از Angular CLI
Angular CLI برای مدیریت build، در پشت صحنه از webpack استفاده میکند. فایل تنظیمات آن نیز جزئی از فایلهای توکار این ابزار است و قرار نیست به صورت پیش فرض و مستقیم توسط پروژهی جاری ویرایش شود. به همین جهت آنرا در ساختار پروژهی تولید شده، مشاهده نمیکنید.
اگر علاقمند به سفارشی سازی بیشتر این تنظیمات پیش فرض باشید، ابتدا باید آنرا اصطلاحا eject کنید و سپس میتوان آنرا ویرایش کرد:
> ng eject Ejection was successful. To run your builds, you now need to do the following commands: - "npm run build" to build. - "npm run test" to run unit tests. - "npm start" to serve the app using webpack-dev-server. - "npm run e2e" to run protractor. Running the equivalent CLI commands will result in an error. ============================================ Some packages were added. Please run "npm install".
در این حالت است که فایل webpack.config.js به ریشهی پروژه جهت سفارشی سازی شما اضافه خواهد شد. همچنین فایلهای .angular-cli.json، package.json نیز جهت درج این تغییرات ویرایش میشوند.
و اگر در این لحظه پشیمان شدهاید (!) فقط کافی است تا این مرحلهی جدید commit شدهی به مخزن کد را لغو کنید و باز هم به همان Angular CLI قبلی میرسید.
Media Type یا MIME Type نشان دهنده فرمت یک مجموعه داده است. در HTTP، مدیا تایپ بیان کننده فرمت message body یک درخواست / پاسخ است و به دریافت کننده اعلام میکند که چطور باید پیام را بخواند. محل استاندارد تعیین Mime Type در هدر Content-Type است. درخواست کننده میتواند با استفاده از هدر Accept لیستی از MimeTypeهای قابل قبول را به عنوان پاسخ، به سرور اعلام کند.
Asp.net Web API از MimeType برای تعیین نحوه serialize یا deserialize کردن محتوای دریافتی / ارسالی استفاده میکند
MediaTypeFormatter
Web API برای خواندن/درج پیام در بدنه درخواست/پاسخ از MediaTypeFormmaterها استفاده میکند. اینها کلاسهایی هستند که نحوهی Serialize کردن و deserialize کردن اطلاعات به فرمتهای خاص را تعیین میکنند. Web API به صورت توکار دارای formatter هایی برای نوعهای XML ، JSON، BSON و Form-UrlEncoded میباشد. همه اینها کلاس پایه MediaTypeFormatter را پیاده سازی میکنند.
مسئله
یک پروژه Web API بسازید و view model زیر را در آن تعریف کنید:
public class NewProduct { [Required] public string Name { get; set; } public double Price { get; set; } public byte[] Pic { get; set; } }
همانطور که میبینید یک فیلد از نوع byte[] برای تصویر محصول در نظر گرفته شده است.
حالا یک کنترلر API ساخته و اکشنی برای دریافت اطلاعات محصول جدید از کاربر مینویسیم :public class ProductsController : ApiController { [HttpPost] public HttpResponseMessage PostProduct(NewProduct model) { if (ModelState.IsValid) { // ثبت محصول return new HttpResponseMessage(HttpStatusCode.Created); } return Request.CreateErrorResponse(HttpStatusCode.BadRequest, ModelState); } }
و یک صفحه html به نام index.html که حاوی یک فرم برای ارسال اطلاعات باشد :
<!DOCTYPE html> <html> <head> <title></title> </head> <body> <h1>ساخت MediaTypeFormatter برای Multipart/form-data</h1> <h2>محصول جدید</h2> <form id="newProduct" method="post" action="/api/products" enctype="multipart/form-data"> <div> <label for="name">نام محصول : </label> <input type="text" id="name" name="name" /> </div> <div> <label for="price">قیمت : </label> <input type="number" id="price" name="price" /> </div> <div> <label for="pic">تصویر : </label> <input type="file" id="pic" name="pic" /> </div> <div> <button type="submit">ثبت</button> </div> </form> </body> </html>
زمانی که فرم حاوی فایلی برای آپلود باشد مشخصه encType باید برابر با Multipart/form-data مقداردهی شود تا اطلاعات فایل به درستی کد شوند. در زمان ارسال فرم Content-type درخواست برابر با Multipart/form-data و فرمت اطلاعات درخواست ارسالی به شکل زیر خواهد بود :
همانطور که میبینید هر فیلد در فرم، در یک بخش جداگانه قرار گرفته است که با خط چین هایی از هم جدا شده اند. هر بخش، headerهای جداگانه خود را دارد.
- Content-Disposition که نام فیلد و نام فایل را شامل میشود .
- content-type که mime type مخصوص آن بخش از دادهها را مشخص میکند.
پس از اینکه فرم را تکمیل کرده و ارسال کنید ، با پیام خطای زیر مواجه میشوید :
خطای روی داده اعلام میکند که Web API فاقد MediaTypeFormatter برای خواندن اطلاعات ارسال شده با فرمتMultiPart/Form-data است. Web API برای خواندن و بایند کردن پارامترهای complex Type از درون بدنه پیام یک درخواست از MediaTypeFormatter استفاده میکند و همانطور که گفته شد Web API فاقد Formatter توکار برای deserialize کردن دادههای با فرمت Multipart/form-data است.
راه حلها :روشی که در سایت asp.net برای آپلود فایل در web api استفاده شده، عدم استفاده از پارامترها و خواندن محتوای Request در درون کنترلر است. که به طبع در صورتی که بخواهیم کنترلرهای تمیز و کوچکی داشته باشیم روش مناسبی نیست. از طرفی امتیاز parameter binding و modelstate را هم از دست خواهیم داد.
روش دیگری که میخواهیم در اینجا پیاده سازی کنیم ساختن یک MediaTypeFormatter برای خواندن فرمت Multipart/form-data است. با این روش کد موردنیاز کپسوله شده و امکان استفاده از binding و modelstate را خواهیم داشت.
برای ساختن یک MediaTypeFormatter یکی از 2 کلاس MediaTypeFormatter یا BufferedMediaTypeFormatter را باید پیاده سازی کنیم . تفاوت این دو در این است که BufferedMediaTypeFormatter برخلاف MediaTypeFormatter از متدهای synchronous استفاده میکند.
یک کلاس به نام MultiPartMediaTypeFormatter میسازیم و کلاس MediaTypeFormatter را به عنوان کلاس پایه آن قرار میدهیم .
public class MultiPartMediaTypeFormatter : MediaTypeFormatter { ... }
ابتدا در تابع سازنده کلاس فرمت هایی که میخواهیم توسط این کلاس خوانده شوند را تعریف میکنیم :
public MultiPartMediaTypeFormatter() { SupportedMediaTypes.Add(new MediaTypeHeaderValue("multipart/form-data")); }
سپس با پیاده سازی توابع CanReadType و CanWriteType مربوط به کلاس MediaTypeFormatter مشخص میکنیم که چه مدلهایی را میتوان توسط این کلاس serialize / deserialize کرد. در اینجا چون میخواهیم این کلاس محدود به یک مدل خاص نباشد، از یک اینترفیس برای شناسایی کلاسهای مجاز استفاده میکنیم .
public interface INeedMultiPartMediaTypeFormatter { }
و آنرا به کلاس newProduct اضافه میکنیم :
public class NewProduct : INeedMultiPartMediaTypeFormatter { ... }
public override bool CanReadType(Type type) { return typeof(INeedMultiPartMediaTypeFormatter).IsAssignableFrom(type); } public override bool CanWriteType(Type type) { return false; }
و اما تابع ReadFromStreamAsync که کار خواندن محتوای ارسال شده و بایند کردن آنها به پارامترها را برعهده دارد
public async override Task<object> ReadFromStreamAsync(Type type, Stream stream, HttpContent content, IFormatterLogger formatterLogger)
ابتدا محتوای ارسال شده را خوانده و اطلاعات فرم را استخراج میکنیم و از طرف دیگر با استفاده از کلاس Activator یک نمونه از مدل جاری را ساخته و لیست propertyهای آنرا استخراج میکنیم.
MultipartMemoryStreamProvider provider = await content.ReadAsMultipartAsync(); IEnumerable<HttpContent> formData = provider.Contents.AsEnumerable(); var modelInstance = Activator.CreateInstance(type); IEnumerable<PropertyInfo> properties = type.GetProperties();
سپس در یک حلقه به ترتیب برای هر property متعلق به مدل، در میان اطلاعات فرم جستجو میکنیم. برای پیدا کردن اطلاعات متناظر با هر property در هدر Content-Disposition که در بالا توضیح داده شد، به دنبال فیلد همنام با property میگردیم .
foreach (PropertyInfo prop in properties) { var propName = prop.Name.ToLower(); var propType = prop.PropertyType; var data = formData.FirstOrDefault(d => d.Headers.ContentDisposition.Name.ToLower().Contains(propName));
گفتیم که هر فیلد یک هدر، Content-Type هم میتواند داشته باشد. این هدر به صورت پیش فرض معادل text/plain است و برای فیلدهای عادی قرار داده نمیشود . در این مثال چون فقط یک
فیلد غیر رشته ای داریم فرض را بر این گرفته ایم که در صورت وجود Content-Type، فیلد مربوط به تصویر است. در صورتیکهContentType وجود داشته باشد، محتوای فیلد را به شکل Stream
خوانده به byte[] تبدیل و با استفاده از متد SetValue در property مربوطه قرار میدهیم.
if (data != null) { if (data.Headers.ContentType != null) { using (var fileStream = await data.ReadAsStreamAsync()) { using (MemoryStream ms = new MemoryStream()) { fileStream.CopyTo(ms); prop.SetValue(modelInstance, ms.ToArray()); } } }
در صورتی که Content-Type غایب باشد بدین معنی است که محتوای فیلد از نوع رشته است ( عدد ، تاریخ ، guid ، رشته ) و باید به نوع مناسب تبدیل شود. ابتدا آن را به صورت یک رشته میخوانیم و با استفاده از Convert.ChangeType آنرا به نوع مناسب تبدیل میکنیم و در property متناظر قرار میدهیم .
if (data != null) { if (data.Headers.ContentType != null) { //... } else { string rawVal = await data.ReadAsStringAsync(); object val = Convert.ChangeType(rawVal, propType); prop.SetValue(modelInstance, val); } }
return modelInstance;
config.Formatters.Add(new MultiPartMediaTypeFormatter());
برنامه نویسی شیء گرا
در این بخش میخواهیم به بررسی یکسری از ویژگیها و نکات ریز برنامه نویسی شیء گرا در جاوا اسکریپت بپردازیم که یک برنامه نویس حرفهای جاوا اسکریپت حتما باید بر آنها واقف باشد تا بتواند کتابخانهها و Framework های موثرتر و بهینهتری را ایجاد کند. لازم به ذکر است که در این مجموعه مقالات، پیادهسازی اشیاء و شیوهی کد نویسی، بر اساس استاندارد ECMAScript 5 یا ES5 انجام خواهد شد. بنابراین از قابلیتهای جدیدی که در ES6 اضافه شدهاست، صحبت نخواهیم کرد. پس از پایان این مجموعه مقالات و پس از آگاهی کامل از قابلیتهای جاوا اسکریپت، در مجموعه مقالاتی به بررسی قابلیتهای جدید ES6 خواهیم پرداخت که مرتبط به مقالات جاری است.
همانطور که قبلا اشاره شد، در زبانهای برنامه نویسی شیء گرا، مفهومی به نام کلاس وجود دارد که ساختاری را جهت ایجاد اشیاء معرفی میکند و میتوانیم اشیاء مختلفی را از این کلاسها ایجاد نماییم. اما در جاوا اسکریپت مفهوم کلاس وجود ندارد و فقط میتوانیم از اشیاء استفاده کنیم که نسبت به زبانهای مبتنی بر کلاس متفاوت میباشد.
بر اساس تعریفی که از اشیاء در استاندارد ECMAScript صورت گرفته است، هرشیء، شامل مجموعهای از ویژگیهاست، که هر یک از آنها میتواند حاوی یک مقدار پایه، شیء و یا تابع باشد. به عبارت دیگر هر شیء شامل آرایهای از مقادیر است. هر ویژگی ( Property ) یا تابع (که در برنامه نویسی شیء گرا متد نیز نامیده میشود) توسط نام خود شناسایی میشوند که به یک مقدار دادهای نگاشت یا Map شدهاند. به همین دلیل میتوان هر شیء را به عنوان یک Hash Table تصور کرد که دادهها را به صورت یک زوج کلید مقدار یا key-value pairs نگهداری مینماید. در اینصورت نام ویژگیها و متدها به عنوان key و مقدار آنها به عنوان value در نظر گرفته میشوند.
مفهوم شیء
همانطور که قبلا اشاره شد، جهت تعریف اشیاء میتوان از دو روش استفاده نمود. در روش اول، ایجاد شیء با استفاده از شیء Object و در روش دوم، با استفاده از Object Literal Notation انجام خواهد شد. روش دوم جدیدتر و بین برنامه نویسان جاوا اسکریپت محبوبتر است. مثال دیگری را جهت یادآوری در این مورد ذکر میکنم:
var person = new Object(); person.firstName = "Meysam"; person.birth = new Date(1982, 11, 8); person.getAge = function () { var now = new Date(); return now.getFullYear() - this.birth.getFullYear(); } alert(person.firstName + ": " + person.getAge()); // Meysam: 34
var person = { firstName: "Meysam", birth: new Date(1982, 11, 8), getAge: function () { var now = new Date(); return now.getFullYear() - this.birth.getFullYear(); } }; alert(person.firstName + ": " + person.getAge()); // Meysam: 34
انواع Property ها
در ECMAScript 5 ، صفاتی برای Property ها معرفی شده است که از طریق Attribute های داخلی به Property ها اختصاص مییابد. این Attribute ها توسط موتور جاوا اسکریپت بر روی Property ها پیاده سازی میشوند و به صورت مستقیم قابل دسترسی نمیباشند. در طی فرآیند آموزش این مطالب، Attribute های داخلی را در [[]] قرار میدهیم، مثل [[Enumarable]] ، تا از سایر دستورات تفکیک شوند. به صورت کلی دو نوع ویژگی داریم که شامل Data Properties و Accessor Properties میباشند که به شرح آنها میپردازیم.
Data Properties
Data Property ها، 4 صفت یا Attribute را توصیف میکنند که عبارتند از:
[[Configurable]]
مشخص میکند یک Property اجازه حذف، تعریف مجدد و یا تغییر نوع را دارد یا خیر. بصورت پیش فرض، زمانی که یک شیء بصورت مستقیم ساخته میشود، مقدار این ویژگی True میباشد.
[[Enumarable]]
مشخص میکند که آیا امکان پیمایش یک Property توسط حلقه for-in وجود دارد یا خیر. بصورت پیش فرض، زمانیکه یک شیء بصورت مستقیم ساخته میشود، مقدار این ویژگی True میباشد.
[[Writable]]
مشخص میکند که آیا مقدار یک Property قابل تغییر میباشد یا خیر. بصورت پیش فرض، زمانیکه یک شیء بصورت مستقیم ساخته میشود، مقدار این ویژگی True میباشد.
[[Value]]
شامل مقدار واقعی یک Property و محل مقداردهی یا برگرداندن مقدار Property ها میباشد. مقدار پیش فرض آن نیز undefined میباشد.
زمانیکه یک Property به صورت عادی به یک شیء اضافه میشود، مانند مثالهای قبلی، سه Attribute اول به true تنظیم میشوند و [[Value]] با مقدار اولیه Property تنظیم میگردد. در این حالت آن Property ، قابل بروزرسانی و پیمایش میباشد. جهت تغییر ساختار یک Property و تنظیم Attribute های آن، باید آن Property را با استفاده از متد defineProperty() تعریف نماییم . شکل کلی تعریف Property با استفاده از این متد به صورت زیر میباشد:
Object.defineProperty(obj, prop, descriptor)
var person = {}; Object.defineProperty(person, "name", { writable: false, value:"Meysam" }); alert(person.name); // Meysam person.name = "Arash"; alert(person.name); // Meysam
var person = {}; Object.defineProperty(person, "name", { configurable: false, value: "Meysam" }); alert(person.name); // Meysam delete person.name; alert(person.name); // Meysam
لازم به ذکر است که میتوانید متد defineProperty() را چندین بار برای یک Property فراخوانی نموده و در هر مرحله صفات متفاوتی را تنظیم و یا صفات قبلی را تغییر دهید.
علاوه بر متد فوق، متد دیگری به نام defineProperties() وجود دارد که میتوان چند Property را بصورت همزمان تعریف و صفات آن را تنظیم نمود. شکل کلی این متد به صورت زیر است:
Object.defineProperties(obj, props)
آرگومان props یک شیء میباشد که ویژگیهای آن، نام همان Property هایی هستند که باید به obj اضافه شوند. همچنین هر ویژگی خود یک شیء میباشد که میتوان صفات آن ویژگی را تنظیم نمود. به مثال زیر توجه کنید:
var person = {}; Object.defineProperties(person, { "name": { configurable: false, value: "Meysam" }, "age": { writable:false, value:34 } });
Accessor Properties
این صفات شامل توابع getter و setter میباشند که یک یا هر دوی آنها میتوانند برای یک Property تنظیم شوند. زمانی که مقداری را از یک Property میخوانید، تابع getter فراخوانی میشود و مقدار Property مربوطه را بر میگرداند. این تابع میتواند قبل از برگرداندن مقدار، پردازش هایی را بر روی آن Property انجام داده و یک نتیجهی معتبر را برگرداند. زمانیکه Property را مقداردهی مینمایید، تابع setter فراخوانی میشود و Property را با مقدار جدید تنظیم مینماید. این تابع میتواند قبل از مقداردهی به Property ، دادهی مورد نظر را اعتبارسنجی نماید تا از ورود مقادیر نامعتبر جلوگیری کند. Accessor Properties شامل 2 صفت زیر میباشد:
[[Get]]
یک تابع میباشد و زمانی فراخوانی میگردد که مقدار یک Property را بخوانیم و مقدار پیش فرض آن undefined میباشد.
[[Set]]
یک تابع میباشد و زمانی فراخوانی میگردد که یک Property را مقداردهی نماییم و مقدار پیش فرض آن undefined میباشد. این تابع شامل یک آرگومان ورودی است که حاوی مقدار ارسالی به Property است.
مثال زیر یک پیاده سازی ساده از شیء تاریخ شمسی میباشد که هنوز از لحاظ طراحی دارای نواقصی هست و در ادامه کارآیی و کد آن را بهبود میبخشیم.
var date = { _year: 1, _month: 1, _day: 1, isLeap: function () { switch (this.year % 33) { case 1: case 5: case 9: case 13: case 17: case 22: case 26: case 30: return true; default: return false; } } }; Object.defineProperties(date, { "year": { "get": function () { return this._year; }, "set": function (newValue) { if (newValue < 1 || newValue > 9999) throw new Error("Year must be between 1 and 9999"); this._year = newValue; } }, "month": { "get": function () { return this._month; }, "set": function (newValue) { if (newValue < 1 || newValue > 12) throw new Error("Month must be between 1 and 12"); this._month = newValue; } }, "day": { "get": function () { return this._day; }, "set": function (newValue) { if (newValue < 1 || newValue > 31) throw new Error("Day must be between 1 and 31"); if (this.month === 12 && !this.isLeap() && newValue > 29) throw new Error("Day must be between 1 and 29"); if (this.month > 6 && newValue > 30) throw new Error("Day must be between 1 and 30"); this._day = newValue; } } });
دقت داشته باشید که لازم نیست حتما accessor های getter و setter با هم برای یک Property تنظیم شوند و شما میتوانید فقط یکی از آنها را برای Property به کار ببرید. اگر فقط تابع getter به یک Property اختصاص یابد، آن Property فقط خواندنی میشود و امکان تغییر مقدار آن وجود ندارد. در این صورت هر دستوری که اقدام به تغییر Property نماید، بیتاثیر خواهد بود. همچنین اگر فقط تابع setter به یک Property اختصاص یابد، آن Property فقط نوشتنی میشود و امکان خواندن مقدار آن وجود ندارد. در این صورت هر دستوری که اقدام به خواندن Property نماید، مقدار undefined برای آن برگردانده میشود.
نکتهی دیگری که باید به آن توجه کنید این است که اگر یک Property با استفاده از متد defineProperty() تعریف گردد، Attribute هایی که مقداردهی نشدهاند، مثل [[Configurable]] ، [[Enumarable]] و [[Writable]] با false مقداردهی میگردند و [[Value]] ، [[Get]] و [[Set]] مقدار undefined را بر میگردانند. در مبحث بعدی، در مورد این نکته مثالی ارائه شده است.
خواندن Attribute های مربوط به یک Property
با استفاده از متد getOwnPropertyDescriptor() میتوان، Attribute های اختصاص داده شده به Property ها را خواند و از مقدار آنها مطلع شد. این متد شامل 2 آرگومان میباشد، که آرگومان اول، شیء ای است که میخواهیم Attribute آن را بخوانیم و آرگومان دوم، نام Attribute میباشد. خروجی متد getOwnPropertyDescriptor() یک شیء از نوع PropertyDescriptor میباشد که ویژگیهای آن، همان Attribute هایی هستند که برای یک Property تنظیم شدهاند. به مثال زیر جهت خواندن Attribute های شیء تاریخ شمسی توجه کنید:
var descriptor = Object.getOwnPropertyDescriptor(date, "_year"); alert(descriptor.value); // 1 alert(descriptor.configurable); // true alert(typeof descriptor.get); // undefined descriptor = Object.getOwnPropertyDescriptor(date, "year"); alert(descriptor.value); // undefined alert(descriptor.configurable); // false alert(typeof descriptor.get); // function