اشتراکها
یک JavaScript UI Library خوب
اشتراکها
وبلاگ بیل گیتس
نظرات مطالب
بررسی تغییرات ASP.NET MVC 5 beta1
دمویی از تغییرات انجام شده در MVC5 و Web API 2
Jon Galloway: Bleeding edge ASP.NET: See what is new and next for MVC, Web API, SignalR and more
دریافت از اینجا: (^)
Jon Galloway: Bleeding edge ASP.NET: See what is new and next for MVC, Web API, SignalR and more
دریافت از اینجا: (^)
اشتراکها
کتابخانه angular-calendar-heatmap
Angular directive for d3.js heatmap representing time series data. Demo
در ادامهی مباحث پشتیبانی از XML در SQL Server، به کارآیی فیلدهای XML ایی و نحوهی ایندکس گذاری بر روی آنها خواهیم پرداخت. این مساله در تولید برنامههایی سریع و مقیاس پذیر، بسیار حائز اهمیت است.
در SQL Server، کوئریهای انجام شده بر روی فیلدهای XML، توسط همان پردازشگر کوئریهای رابطهای متداول آن، خوانده و اجرا خواهند شد و امکان تعریف یک XQuery خارج از یک عبارت SQL و یا T-SQL وجود ندارد. متدهای XQuery بسیار شبیه به system defined functions بوده و Query Plan یکپارچهای را با سایر قسمتهای رابطهای یک عبارت SQL دارند.
مفهوم Node table
دادههای XML ایی برای اینکه توسط SQL Server قابل استفاده باشند، به صورت درونی تبدیل به یک node table میشوند. به این معنا که نودهای یک سند XML، به یک جدول رابطهای به صورت خودکار تجزیه میشوند. این جدول درونی در صورت بکارگیری XML Indexes در جدول سیستمی sys.internal_tables قابل مشاهده خواهد بود. SQL Server برای انجام اینکار از یک XmlReader خاص خودش استفاده میکند. در مورد XMLهای ایندکس نشده، این تجزیه در زمان اجرا صورت میگیرد؛ پس از اینکه Query Plan آن تشکیل شد.
بررسی Query Plan فیلدهای XML ایی
جهت فراهم کردن مقدمات آزمایش، ابتدا جدول xmlInvoice را با یک فیلد XML ایی untyped درنظر بگیرید:
سپس 6 ردیف را به آن اضافه میکنیم:
همچنین برای مقایسه، دقیقا جدول مشابهی را اینبار با یک XML Schema مشخص ایجاد میکنیم.
سپس مجددا همان 6 رکورد قبلی را در این جدول جدید نیز insert خواهیم کرد.
در این جدول دوم، حالت پیش فرض content قبلی، به document تغییر کردهاست. با توجه به اینکه میدانیم اسناد ما چه فرمتی دارند و بیش از یک root element نخواهیم داشت، انتخاب document سبب خواهد شد تا Query Plan بهتری حاصل شود.
در ادامه برای مشاهدهی بهتر نتایج، کش Query Plan و اطلاعات آماری جدول xmlInvoice را حذف و به روز میکنیم:
به علاوه در management studio بهتر است از منوی Query، گزینهی Include actual execution plan را نیز انتخاب کنید (یا فشردن دکمههای Ctrl+M) تا پس از اجرای کوئری، بتوان Query Plan نهایی را نیز مشاهده نمود. برای خواندن یک Query Plan عموما از بالا به پایین و از راست به چپ باید عمل کرد. در آن نهایتا باید به عدد estimated subtree cost کوئری، دقت داشت.
کوئریهایی را که در این قسمت بررسی خواهیم کرد، در ادامه ملاحظه میکنید. بار اول این کوئریها را بر روی xmlInvoice و بار دوم، بر روی نگارش دوم دارای اسکیمای آن اجرا خواهیم کرد:
کوئری 1
همانطور که عنوان شد، از منوی Query گزینهی Include actual execution plan را نیز انتخاب کنید (یا فشردن دکمههای Ctrl+M) تا پس از اجرای کوئری، بتوان Query Plan نهایی را نیز مشاهده کرد.
در کوئری 1، با استفاده از متد exist به دنبال رکوردهایی هستیم که دارای ویژگی InvoiceId مساوی 1003 هستند. پس از اجرای کوئری، تصویر Query Plan آن به شکل زیر خواهد بود:
برای خواندن این تصویر، از بالا به پایین و چپ به راست باید عمل شود. هزینهی انجام کوئری را نیز با نگه داشتن کرسر ماوس بر روی select نهایی سمت چپ تصویر میتوان مشاهده کرد. البته باید درنظر داشت که این اعداد از دیدگاه Query Processor مفهوم پیدا میکنند. پردازشگر کوئری، بر اساس اطلاعاتی که در اختیار دارد، سعی میکند بهترین روش پردازش کوئری دریافتی را پیدا کند. برای اندازه گیری کارآیی، باید اندازه گیری زمان اجرای کوئری، مستقلا انجام شود.
در این کوئری، مطابق تصویر اول، ابتدا قسمت SQL آن (چپ بالای تصویر) پردازش میشود و سپس قسمت XML آن. قسمت XQuery این عبارت در دو قسمت سمت چپ، پایین تصویر مشخص شدهاند. Table valued functionها جاهایی هستند که node table ابتدای بحث جاری در آنها ساخته میشوند. در اینجا دو مرحلهی تولید Table valued functionها مشاهده میشود. اگر به جمع درصدهای آنها دقت کنید، هزینهی این دو قسمت، 98 درصد کل Query plan است.
سؤال: چرا دو مرحلهی تولید Table valued functionها در اینجا قابل مشاهده است؟ یک مرحلهی آن مربوط است به انتخاب نود Invoice و مرحلهی دوم مربوط است به فیلتر داخل [] ذکر شد برای یافتن ویژگیهای مساوی 1003.
در اینجا و در کوئریهای بعدی، هر Query Plan ایی که تعداد مراحل تولید Table valued function کمتری داشته باشد، بهینهتر است.
کوئری 5
اگر کوئری پلن شماره 5 را بررسی کنیم، به 3 مرحله تولید Table valued functionها خواهیم رسید. یک XML Reader برای خارج از [] (اصطلاحا به آن predicate گفته میشود) و دو مورد برای داخل [] تشکیل شدهاست؛ یکی برای انتخاب نود متنی و دیگری برای تساوی.
کوئری 7
اگر کوئری پلن شماره 7 را بررسی کنیم، به 3 مرحله تولید Table valued functionها خواهیم رسید که بسیار شبیه است به مورد 5. بنابراین در اینجا عمق بررسی و سلسله مراتب اهمیتی ندارد.
کوئری 9
کوئری 9 دقیقا معادل است با کوئری 1 نوشته شده؛ با این تفاوت که از روش FLOWR استفاده کردهاست. نکتهی جالب آن، وجود تنها یک XML reader در Query plan آن است که باید آنرا بخاطر داشت.
کوئری 2
کوئری 3
کوئری 4
کوئری 6
کوئری 8
اگر به این 5 کوئری یاد شده دقت کنید، از یک دات به معنای self استفاده کردهاند (یعنی پردازش بیشتری را انجام نده و از همین نود جاری برای پردازش نهایی استفاده کن). با توجه به بکارگیری متد exist، معنای کوئریهای یک و دو، یکیاست. اما در کوئری شماره 2، تنها یک XML Reader در Query plan نهایی وجود دارد (همانند عبارت FLOWR کوئری شماره 9).
یک نکته: اگر میخواهید بدانید بین کوئریهای 1 و 2 کدامیک بهتر عمل میکنند، از بین تمام کوئریهای موجود، دو کوئری یاد شده را انتخاب کرده و سپس با فرض روش بودن نمایش Query plan، هر دو کوئری را با هم اجرا کنید.
در این حالت، کوئری پلنهای هر دو کوئری را با هم یکجا میتوان مشاهده کرد؛ به علاوهی هزینهی نسبی آنها را در کل عملیات صورت گرفته. در حالت استفاده از دات و وجود تنها یک XML Reader، این هزینه تنها 6 درصد است، در مقابل هزینهی 94 درصدی کوئری شماره یک.
بنابراین از دیدگاه پردازشگر کوئریهای SQL Server، کوئری شماره 2، بسیار بهتر است از کوئری شماره 1.
در کوئریهای 3 و 4، شماره نود مدنظر را دقیقا مشخص کردهایم. این مورد در حالت سوم تفاوت محسوسی را از لحاظ کارآیی ایجاد نمیکند و حتی کارآیی را به علت اضافه کردن یک XML Reader دیگر برای پردازش عدد نود وارد شده، کاهش میدهد. اما کوئری 4 که عدد اولین نود را خارج از پرانتز قرار دادهاست، تنها در کل یک XML Reader را به همراه خواهد داشت.
سؤال: بین کوئریهای 2، 3 و 4 کدامیک بهینهتر است؟
بله. اگر هر سه کوئری را با هم انتخاب کرده و اجرا کنیم، میتوان در قسمت کوئری پلنها، هزینهی هر کدام را نسبت به کل مشاهده کرد. در این حالت کوئری 4 بهتر است از کوئری 2 و تنها یک درصد هزینهی کل را تشکیل میدهد.
کوئری 10
کوئری 10 اندکی متفاوت است نسبت به کوئریهای دیگر. در اینجا بجای متد exist از متد value استفاده شدهاست. یعنی ابتدا صریحا مقدار ویژگی InvoiceId استخراج شده و با 1003 مقایسه میشود.
اگر کوئری پلن آنرا با کوئری 4 که بهترین کوئری سری exist است مقایسه کنیم، کوئری 10، هزینهی 70 درصدی کل عملیات را به خود اختصاص خواهد داد، در مقابل 30 درصد هزینهی کوئری 4. بنابراین در این موارد، استفاده از متد exist بسیار بهینهتر است از متد value.
استفاده از Schema collection و تاثیر آن بر کارآیی
تمام مراحلی را که در اینجا ملاحظه کردید، صرفا با تغییر نام xmlInvoice به xmlInvoice2، تکرار کنید. xmlInvoice2 دارای ساختاری مشخص است، به همراه ذکر صریح document حین تعریف ستون XML ایی آن.
تمام پاسخهایی را که دریافت خواهید کرد با حالت بدون Schema collection یکی است.
برای مقایسه بهتر، یکبار نیز سعی کنید کوئری 1 جدول xmlInvoice را با کوئری 1 جدول xmlInvoice2 با هم در طی یک اجرا مقایسه کنید، تا بهتر بتوان Query plan نسبی آنها را بررسی کرد.
پس از این بررسی و مقایسه، به این نتیجه خواهید رسید که تفاوت محسوسی در اینجا و بین این دو حالت، قابل ملاحظه نیست. در SQL Server از Schema collection بیشتر برای اعتبارسنجی ورودیها استفاده میشود تا بهبود کارآیی کوئریها.
بنابراین به صورت خلاصه
- متد exist را به value ترجیح دهید.
- اصطلاحا ordinal (همان مشخص کردن نود 1 در اینجا) را در آخر قرار دهید (نه در بین نودها).
- مراحل اجرایی را با معرفی دات (استفاده از نود جاری) تا حد ممکن کاهش دهید.
و ... کوئری 4 در این سری، بهترین کارآیی را ارائه میدهد.
در SQL Server، کوئریهای انجام شده بر روی فیلدهای XML، توسط همان پردازشگر کوئریهای رابطهای متداول آن، خوانده و اجرا خواهند شد و امکان تعریف یک XQuery خارج از یک عبارت SQL و یا T-SQL وجود ندارد. متدهای XQuery بسیار شبیه به system defined functions بوده و Query Plan یکپارچهای را با سایر قسمتهای رابطهای یک عبارت SQL دارند.
مفهوم Node table
دادههای XML ایی برای اینکه توسط SQL Server قابل استفاده باشند، به صورت درونی تبدیل به یک node table میشوند. به این معنا که نودهای یک سند XML، به یک جدول رابطهای به صورت خودکار تجزیه میشوند. این جدول درونی در صورت بکارگیری XML Indexes در جدول سیستمی sys.internal_tables قابل مشاهده خواهد بود. SQL Server برای انجام اینکار از یک XmlReader خاص خودش استفاده میکند. در مورد XMLهای ایندکس نشده، این تجزیه در زمان اجرا صورت میگیرد؛ پس از اینکه Query Plan آن تشکیل شد.
بررسی Query Plan فیلدهای XML ایی
جهت فراهم کردن مقدمات آزمایش، ابتدا جدول xmlInvoice را با یک فیلد XML ایی untyped درنظر بگیرید:
CREATE TABLE xmlInvoice ( invoiceId INT IDENTITY PRIMARY KEY, invoice XML )
INSERT INTO xmlInvoice VALUES(' <Invoice InvoiceId="1000" dept="hardware"> <CustomerName>Vahid</CustomerName> <LineItems> <LineItem><Description>Gear</Description><Price>9.5</Price></LineItem> </LineItems> </Invoice> ') INSERT INTO xmlInvoice VALUES(' <Invoice InvoiceId="1002" dept="garden"> <CustomerName>Mehdi</CustomerName> <LineItems> <LineItem><Description>Shovel</Description><Price>19.2</Price></LineItem> </LineItems> </Invoice> ') INSERT INTO xmlInvoice VALUES(' <Invoice InvoiceId="1003" dept="garden"> <CustomerName>Mohsen</CustomerName> <LineItems> <LineItem><Description>Trellis</Description><Price>8.5</Price></LineItem> </LineItems> </Invoice> ') INSERT INTO xmlInvoice VALUES(' <Invoice InvoiceId="1004" dept="hardware"> <CustomerName>Hamid</CustomerName> <LineItems> <LineItem><Description>Pen</Description><Price>1.5</Price></LineItem> </LineItems> </Invoice> ') INSERT INTO xmlInvoice VALUES(' <Invoice InvoiceId="1005" dept="IT"> <CustomerName>Ali</CustomerName> <LineItems> <LineItem><Description>Book</Description><Price>3.2</Price></LineItem> </LineItems> </Invoice> ') INSERT INTO xmlInvoice VALUES(' <Invoice InvoiceId="1006" dept="hardware"> <CustomerName>Reza</CustomerName> <LineItems> <LineItem><Description>M.Board</Description><Price>19.5</Price></LineItem> </LineItems> </Invoice> ')
CREATE XML SCHEMA COLLECTION invoice_xsd AS ' <xs:schema attributeFormDefault="unqualified" elementFormDefault="qualified" xmlns:xs="http://www.w3.org/2001/XMLSchema"> <xs:element name="Invoice"> <xs:complexType> <xs:sequence> <xs:element name="CustomerName" type="xs:string" /> <xs:element name="LineItems"> <xs:complexType> <xs:sequence> <xs:element name="LineItem"> <xs:complexType> <xs:sequence> <xs:element name="Description" type="xs:string" /> <xs:element name="Price" type="xs:decimal" /> </xs:sequence> </xs:complexType> </xs:element> </xs:sequence> </xs:complexType> </xs:element> </xs:sequence> <xs:attribute name="InvoiceId" type="xs:unsignedShort" use="required" /> <xs:attribute name="dept" type="xs:string" use="required" /> </xs:complexType> </xs:element> </xs:schema>' Go CREATE TABLE xmlInvoice2 ( invoiceId INT IDENTITY PRIMARY KEY, invoice XML(document invoice_xsd) ) Go
در این جدول دوم، حالت پیش فرض content قبلی، به document تغییر کردهاست. با توجه به اینکه میدانیم اسناد ما چه فرمتی دارند و بیش از یک root element نخواهیم داشت، انتخاب document سبب خواهد شد تا Query Plan بهتری حاصل شود.
در ادامه برای مشاهدهی بهتر نتایج، کش Query Plan و اطلاعات آماری جدول xmlInvoice را حذف و به روز میکنیم:
UPDATE STATISTICS xmlInvoice DBCC FREEPROCCACHE
کوئریهایی را که در این قسمت بررسی خواهیم کرد، در ادامه ملاحظه میکنید. بار اول این کوئریها را بر روی xmlInvoice و بار دوم، بر روی نگارش دوم دارای اسکیمای آن اجرا خواهیم کرد:
-- query 1 SELECT * FROM xmlInvoice WHERE invoice.exist('/Invoice[@InvoiceId = "1003"]') = 1 -- query 2 SELECT * FROM xmlInvoice WHERE invoice.exist('/Invoice/@InvoiceId[. = "1003"]') = 1 -- query 3 SELECT * FROM xmlInvoice WHERE invoice.exist('/Invoice[1]/@InvoiceId[. = "1003"]') = 1 -- query 4 SELECT * FROM xmlInvoice WHERE invoice.exist('(/Invoice/@InvoiceId)[1][. = "1003"]') = 1 -- query 5 SELECT * FROM xmlInvoice WHERE invoice.exist('/Invoice[CustomerName = "Vahid"]') = 1 -- query 6 SELECT * FROM xmlInvoice WHERE invoice.exist('/Invoice/CustomerName [.= "Vahid"]') = 1 -- query 7 SELECT * FROM xmlInvoice WHERE invoice.exist('/Invoice/LineItems/LineItem[Description = "Trellis"]') = 1 -- query 8 SELECT * FROM xmlInvoice WHERE invoice.exist('/Invoice/LineItems/LineItem/Description [.= "Trellis"]') = 1 -- query 9 SELECT * FROM xmlInvoice WHERE invoice.exist(' for $x in /Invoice/@InvoiceId where $x = 1003 return $x ') = 1 -- query 10 SELECT * FROM xmlInvoice WHERE invoice.value('(/Invoice/@InvoiceId)[1]', 'VARCHAR(10)') = '1003' -- یکبار هم با جدول شماره 2 که اسکیما دارد تمام این موارد تکرار شود UPDATE STATISTICS xmlInvoice DBCC FREEPROCCACHE GO
کوئری 1
همانطور که عنوان شد، از منوی Query گزینهی Include actual execution plan را نیز انتخاب کنید (یا فشردن دکمههای Ctrl+M) تا پس از اجرای کوئری، بتوان Query Plan نهایی را نیز مشاهده کرد.
در کوئری 1، با استفاده از متد exist به دنبال رکوردهایی هستیم که دارای ویژگی InvoiceId مساوی 1003 هستند. پس از اجرای کوئری، تصویر Query Plan آن به شکل زیر خواهد بود:
برای خواندن این تصویر، از بالا به پایین و چپ به راست باید عمل شود. هزینهی انجام کوئری را نیز با نگه داشتن کرسر ماوس بر روی select نهایی سمت چپ تصویر میتوان مشاهده کرد. البته باید درنظر داشت که این اعداد از دیدگاه Query Processor مفهوم پیدا میکنند. پردازشگر کوئری، بر اساس اطلاعاتی که در اختیار دارد، سعی میکند بهترین روش پردازش کوئری دریافتی را پیدا کند. برای اندازه گیری کارآیی، باید اندازه گیری زمان اجرای کوئری، مستقلا انجام شود.
در این کوئری، مطابق تصویر اول، ابتدا قسمت SQL آن (چپ بالای تصویر) پردازش میشود و سپس قسمت XML آن. قسمت XQuery این عبارت در دو قسمت سمت چپ، پایین تصویر مشخص شدهاند. Table valued functionها جاهایی هستند که node table ابتدای بحث جاری در آنها ساخته میشوند. در اینجا دو مرحلهی تولید Table valued functionها مشاهده میشود. اگر به جمع درصدهای آنها دقت کنید، هزینهی این دو قسمت، 98 درصد کل Query plan است.
سؤال: چرا دو مرحلهی تولید Table valued functionها در اینجا قابل مشاهده است؟ یک مرحلهی آن مربوط است به انتخاب نود Invoice و مرحلهی دوم مربوط است به فیلتر داخل [] ذکر شد برای یافتن ویژگیهای مساوی 1003.
در اینجا و در کوئریهای بعدی، هر Query Plan ایی که تعداد مراحل تولید Table valued function کمتری داشته باشد، بهینهتر است.
کوئری 5
اگر کوئری پلن شماره 5 را بررسی کنیم، به 3 مرحله تولید Table valued functionها خواهیم رسید. یک XML Reader برای خارج از [] (اصطلاحا به آن predicate گفته میشود) و دو مورد برای داخل [] تشکیل شدهاست؛ یکی برای انتخاب نود متنی و دیگری برای تساوی.
کوئری 7
اگر کوئری پلن شماره 7 را بررسی کنیم، به 3 مرحله تولید Table valued functionها خواهیم رسید که بسیار شبیه است به مورد 5. بنابراین در اینجا عمق بررسی و سلسله مراتب اهمیتی ندارد.
کوئری 9
کوئری 9 دقیقا معادل است با کوئری 1 نوشته شده؛ با این تفاوت که از روش FLOWR استفاده کردهاست. نکتهی جالب آن، وجود تنها یک XML reader در Query plan آن است که باید آنرا بخاطر داشت.
کوئری 2
کوئری 3
کوئری 4
کوئری 6
کوئری 8
اگر به این 5 کوئری یاد شده دقت کنید، از یک دات به معنای self استفاده کردهاند (یعنی پردازش بیشتری را انجام نده و از همین نود جاری برای پردازش نهایی استفاده کن). با توجه به بکارگیری متد exist، معنای کوئریهای یک و دو، یکیاست. اما در کوئری شماره 2، تنها یک XML Reader در Query plan نهایی وجود دارد (همانند عبارت FLOWR کوئری شماره 9).
یک نکته: اگر میخواهید بدانید بین کوئریهای 1 و 2 کدامیک بهتر عمل میکنند، از بین تمام کوئریهای موجود، دو کوئری یاد شده را انتخاب کرده و سپس با فرض روش بودن نمایش Query plan، هر دو کوئری را با هم اجرا کنید.
در این حالت، کوئری پلنهای هر دو کوئری را با هم یکجا میتوان مشاهده کرد؛ به علاوهی هزینهی نسبی آنها را در کل عملیات صورت گرفته. در حالت استفاده از دات و وجود تنها یک XML Reader، این هزینه تنها 6 درصد است، در مقابل هزینهی 94 درصدی کوئری شماره یک.
بنابراین از دیدگاه پردازشگر کوئریهای SQL Server، کوئری شماره 2، بسیار بهتر است از کوئری شماره 1.
در کوئریهای 3 و 4، شماره نود مدنظر را دقیقا مشخص کردهایم. این مورد در حالت سوم تفاوت محسوسی را از لحاظ کارآیی ایجاد نمیکند و حتی کارآیی را به علت اضافه کردن یک XML Reader دیگر برای پردازش عدد نود وارد شده، کاهش میدهد. اما کوئری 4 که عدد اولین نود را خارج از پرانتز قرار دادهاست، تنها در کل یک XML Reader را به همراه خواهد داشت.
سؤال: بین کوئریهای 2، 3 و 4 کدامیک بهینهتر است؟
بله. اگر هر سه کوئری را با هم انتخاب کرده و اجرا کنیم، میتوان در قسمت کوئری پلنها، هزینهی هر کدام را نسبت به کل مشاهده کرد. در این حالت کوئری 4 بهتر است از کوئری 2 و تنها یک درصد هزینهی کل را تشکیل میدهد.
کوئری 10
کوئری 10 اندکی متفاوت است نسبت به کوئریهای دیگر. در اینجا بجای متد exist از متد value استفاده شدهاست. یعنی ابتدا صریحا مقدار ویژگی InvoiceId استخراج شده و با 1003 مقایسه میشود.
اگر کوئری پلن آنرا با کوئری 4 که بهترین کوئری سری exist است مقایسه کنیم، کوئری 10، هزینهی 70 درصدی کل عملیات را به خود اختصاص خواهد داد، در مقابل 30 درصد هزینهی کوئری 4. بنابراین در این موارد، استفاده از متد exist بسیار بهینهتر است از متد value.
استفاده از Schema collection و تاثیر آن بر کارآیی
تمام مراحلی را که در اینجا ملاحظه کردید، صرفا با تغییر نام xmlInvoice به xmlInvoice2، تکرار کنید. xmlInvoice2 دارای ساختاری مشخص است، به همراه ذکر صریح document حین تعریف ستون XML ایی آن.
تمام پاسخهایی را که دریافت خواهید کرد با حالت بدون Schema collection یکی است.
برای مقایسه بهتر، یکبار نیز سعی کنید کوئری 1 جدول xmlInvoice را با کوئری 1 جدول xmlInvoice2 با هم در طی یک اجرا مقایسه کنید، تا بهتر بتوان Query plan نسبی آنها را بررسی کرد.
پس از این بررسی و مقایسه، به این نتیجه خواهید رسید که تفاوت محسوسی در اینجا و بین این دو حالت، قابل ملاحظه نیست. در SQL Server از Schema collection بیشتر برای اعتبارسنجی ورودیها استفاده میشود تا بهبود کارآیی کوئریها.
بنابراین به صورت خلاصه
- متد exist را به value ترجیح دهید.
- اصطلاحا ordinal (همان مشخص کردن نود 1 در اینجا) را در آخر قرار دهید (نه در بین نودها).
- مراحل اجرایی را با معرفی دات (استفاده از نود جاری) تا حد ممکن کاهش دهید.
و ... کوئری 4 در این سری، بهترین کارآیی را ارائه میدهد.
روشهای مختلف تعریف متغیرها در TypeScript
تمام توسعه دهندههای JavaScript با واژهی کلیدی var آشنایی دارند؛ اما TypeScript واژههای کلیدی let و const را نیز اضافه کردهاست (که جزئی از ES 6 نیز میباشند). تفاوت مهم بین var و let، در میدان دید متغیرهای تعریف شدهی توسط آنها خلاصه میشود. پیشتر در سری مباحث بررسی ES 6، مطلب «متغیرها در ES 6» را نیز بررسی کردیم که در TypeScript نیز صادق میباشند؛ با این تفاوت که TypeScript را میتوان به ES 5 نیز کامپایل کرد و بدون مشکل با تمام مرورگرهای موجود، اجرا نمود.
- متغیرهایی که با var تعریف میشوند، به صورت سراسری در متدی که تعریف شدهاند، قابل دسترسی هستند؛ حتی اگر 5 سطح داخل ifهای تو در تو تعریف شده باشند. اما let و const تنها در block و قطعهای که معرفی شدهاند، معتبر بوده و خارج از آن تعریف نشدهاند. در اینجا یک block توسط {} معرفی میشود.
- متغیرهای از نوع var به دلیل مفهومی به نام hoisting توسط runtime جاوا اسکریپت، به بالاترین سطح متد منتقل میشوند. به همین دلیل عمق تعریف آنها، اثری در دسترسی به این متغیرها ندارد. اما hoisting در مورد let و const اعمال نمیشود.
- متغیرهای var را میتوان چندبار مجددا تعریف کرد (هرچند این روش توصیه نمیشود؛ اما مجاز است). یک چنین تعریف مجددی با متغیرهای از نوع let و const مجاز نیست.
برای توضیحات بیشتر به مثال ذیل دقت کنید:
در اینجا داخل قطعهی if تعریف شده، یک متغیر، از نوع var و متغیر دیگری از نوع let تعریف شدهاست. خارج از بدنهی این متد، متغیر foo هنوز قابل دسترسی است، اما متغیر bar خیر.
نوعهای پایهی TypeScript
نوعهای پایهی TypeScript شامل موارد ذیل هستند:
Boolean: برای ذخیره سازی true یا false.
Number: معرف مقادیر عددی اعشاری هستند.
String: مقادیر رشتهای را ذخیره میکند.
Array: روشهای متفاوتی برای تعریف آرایهها در TypeScript وجود دارند که در ادامه آنها را بیشتر بررسی خواهیم کرد.
Enum: نوعهای شمارشی؛ جهت دادن نامهایی بهتر به مجموعهی مشخصی از مقادیر.
Any: به این معنا که یک متغیر میتواند به هر نوع دلخواهی اشاره کند. هدف از وجود این نوع، امکان استفادهی از کتابخانههای فعلی جاوا اسکریپتی است که نوعی برای متغیرهای آنها تعریف نشدهاند و در اساس هر نوعی میتوانند باشند. برای مثال در جاوا اسکریپت مجاز است در سطر اول متغیری را تعریف کرد و در سطر دوم به آن یک رشته و در سطر سوم به آن یک عدد را انتساب داد.
Void: به معنای فقدان یک نوع است. برای مثال مشخص کردن اینکه متدی، خروجی ندارد.
یک نکته: قابلیت Template string در ES 6 نیز در TypeScript پشتیبانی میشود.
مفهوم Type Inference در TypeScript
در TypeScript الزاما نیازی نیست تا نوع متغیری را صریحا مشخص کرد. در اینجا اگر نوع متغیری را در ابتدای کار تعریف نکنید، نوع آن در اولین باری که مقدار دهی میشود، مشخص خواهد شد که به آن Type Inference نیز میگویند.
در مثال فوق، نوع متغیر myString در زمان تعریف آن مشخص نشدهاست؛ اما با یک رشته، مقدار دهی و آغاز شدهاست. بر این اساس، TypeScript نوع این متغیر را رشتهای در نظر میگیرد و در سطر بعدی، از انتساب یک مقدار عددی به آن جلوگیری خواهد کرد.
و یا در مثال ذیل، نوع خروجی متد ReturnNumber به صورت صریح مشخص نشدهاست:
اما با توجه به اینکه خروجی آن عددی است، نوع آن نیز عددی درنظر گرفته شده و از انتساب آن به یک متغیر رشتهای جلوگیری میشود.
تعریف صریح نوعها در TypeScript با استفاده از Type Annotations
برای لغو Type Inference و تعیین صریح نوعها در TypeScript میتوان به صورت زیر عمل کرد:
با قراردادن یک کولن پس از نام متغیر و سپس تعریف نوع دادهی مدنظر، میتوان نوعها را در TypeScript به صورت صریحی تعریف کرد. در مورد متدها نیز به همین صورت است. کولن پس از پایان بسته شدن پرانترها قرار میگیرد و سپس نوع بازگشتی متد مشخص میگردد.
نوعهای شمارشی در TypeScript
Enums در بسیاری از زبانهای برنامه نویسی متداول وجود دارند و هدف از آنها، دادن نامهایی بهتر و مشخص، به مجموعهای از مقادیر است:
در مثال فوق یک enum جهت تعریف گروههای کتابها تعریف شدهاست. در اینجا بجای استفاده از مقادیر نامفهوم 0 تا 2، نامهایی مشخص و بهتر، جهت معرفی آنها ارائه شدهاند. به این ترتیب میتوان در نهایت به کدهایی خواناتر رسید.
اگر میخواهید این مقادیر با اعداد دیگری شروع شوند (بجای صفر پیش فرض)، میتوان مقدار اولین نام را به صورت صریحی مشخص کرد:
و یا الزامی به استفاده از مقادیر افزایش یابندهای در اینجا نیست. در صورت نیاز میتوان مقدار هر المان را جداگانه تعریف کرد:
پس از اینکه نوع enum تعریف شده، استفاده از آن همانند سایر نوعهای پایهی TypeScript است:
در اینجا متغیر favoriteCategory از نوع enum گروه کتابها تعیین شدهاست؛ با مقدار اولیهی Category.Biography.
در این حالت اگر مقدار favoriteCategory را چاپ کنیم، خروجی عددی 5 نمایش داده میشود:
اگر نیاز به بازگشت مقدار رشتهای این آیتم است، میتوان از ایندکسهای یک enum استفاده کرد:
آرایهها در TypeScript
در حالت عمومی، آرایهها در TypeScript همانند جاوا اسکریپت تعریف میشوند؛ البته به همراه تعدادی استثناء. در TypeScript سه روش برای تعریف آرایهها وجود دارند:
الف) در مثال زیر آرایهای از رشتهها تعریف شدهاست. در اینجا نوع آرایه به همراه [] مشخص میشود:
ب) روش دوم تعریف آرایهها در TypeScript با استفاده از مفهوم Generics است که در بحثی جداگانه به آن خواهیم پرداخت. در اینجا از واژهی کلیدی Array به همراه مشخص سازی نوع آن در داخل <> استفاده شدهاست:
ج) اگر نیاز به آرایههایی شبیه به جاوا اسکریپت دارید که میتوانند حاوی انواع و اقسام المانهایی با نوعهای مختلف باشند، نوع آرایه را []any تعریف کنید:
نوع Tuples در TypeScript
Tuples در TypeScript نوع خاصی از آرایهها هستند که نوع مقادیر اعضای آنها به صورت صریح مشخص میشوند:
برای مثال در اینجا نوع متغیر myTuple به صورت آرایهای از دو عنصر عددی و رشتهای تعریف شدهاست.
اکنون برای دسترسی به مقادیر این المانها، همانند کار با آرایههای معمولی، از ایندکسهای آرایهی تعریف شده استفاده میشود:
یک نکته: میتوان به آرایهی تعریف شده، عناصر جدیدی را نیز افزود؛ با این شرط که نوع آنها، یکی از نوعهای مشخص شدهی در تعریف Tuple باشند:
مفهوم Type assertions در TypeScript
حتما با مفهوم cast و تبدیل نوعهای مختلف به یکدیگر، در زبانهای دیگر برنامه نویسی آشنا هستید. در TypeScript نیز این مفهوم تحت عنوان Type assertions پشتیبانی میشود و دو روش برای تعریف آن وجود دارد:
الف) تعریف cast توسط angle-bracket syntax که در آن نوع مدنظر داخل یک <> قرار میگیرد:
در اینجا نوع نامشخص any به string تبدیل شده و سپس امکان دسترسی به خاصیت طول آن که تحت کنترل کامپایلر است و قابل انتساب به یک مقدار عددی، میسر شدهاست.
ب) تعریف cast توسط as syntax به نحو ذیل:
هر دو حالت تعریف شده معادل هستند. فقط باید دقت داشت در حین کار با ReactJS توسط TypeScript، فقط حالت as syntax پشتیبانی میشود.
تمام توسعه دهندههای JavaScript با واژهی کلیدی var آشنایی دارند؛ اما TypeScript واژههای کلیدی let و const را نیز اضافه کردهاست (که جزئی از ES 6 نیز میباشند). تفاوت مهم بین var و let، در میدان دید متغیرهای تعریف شدهی توسط آنها خلاصه میشود. پیشتر در سری مباحث بررسی ES 6، مطلب «متغیرها در ES 6» را نیز بررسی کردیم که در TypeScript نیز صادق میباشند؛ با این تفاوت که TypeScript را میتوان به ES 5 نیز کامپایل کرد و بدون مشکل با تمام مرورگرهای موجود، اجرا نمود.
- متغیرهایی که با var تعریف میشوند، به صورت سراسری در متدی که تعریف شدهاند، قابل دسترسی هستند؛ حتی اگر 5 سطح داخل ifهای تو در تو تعریف شده باشند. اما let و const تنها در block و قطعهای که معرفی شدهاند، معتبر بوده و خارج از آن تعریف نشدهاند. در اینجا یک block توسط {} معرفی میشود.
- متغیرهای از نوع var به دلیل مفهومی به نام hoisting توسط runtime جاوا اسکریپت، به بالاترین سطح متد منتقل میشوند. به همین دلیل عمق تعریف آنها، اثری در دسترسی به این متغیرها ندارد. اما hoisting در مورد let و const اعمال نمیشود.
- متغیرهای var را میتوان چندبار مجددا تعریف کرد (هرچند این روش توصیه نمیشود؛ اما مجاز است). یک چنین تعریف مجددی با متغیرهای از نوع let و const مجاز نیست.
برای توضیحات بیشتر به مثال ذیل دقت کنید:
function ScopeTest() { if (true) { var foo = 'use anywhere'; let bar = 'use in this block'; } console.log(foo); // works! console.log(bar); // error! }
نوعهای پایهی TypeScript
نوعهای پایهی TypeScript شامل موارد ذیل هستند:
Boolean: برای ذخیره سازی true یا false.
let isDone: boolean = false;
let decimal: number = 6; let hex: number = 0xf00d; let binary: number = 0b1010; let octal: number = 0o744;
let name: string = "bob"; name = 'smith';
let list: number[] = [1, 2, 3];
enum Color {Red, Green, Blue}; let c: Color = Color.Green;
let notSure: any = 4; notSure = "maybe a string instead"; notSure = false; // okay, definitely a boolean
function warnUser(): void { alert("This is my warning message"); }
یک نکته: قابلیت Template string در ES 6 نیز در TypeScript پشتیبانی میشود.
مفهوم Type Inference در TypeScript
در TypeScript الزاما نیازی نیست تا نوع متغیری را صریحا مشخص کرد. در اینجا اگر نوع متغیری را در ابتدای کار تعریف نکنید، نوع آن در اولین باری که مقدار دهی میشود، مشخص خواهد شد که به آن Type Inference نیز میگویند.
let myString= 'this is a string'; myString= 42; // error!
و یا در مثال ذیل، نوع خروجی متد ReturnNumber به صورت صریح مشخص نشدهاست:
function ReturnNumber() { return 42; } let anotherString = 'this is also a string'; anotherString = ReturnNumber(); // error!
تعریف صریح نوعها در TypeScript با استفاده از Type Annotations
برای لغو Type Inference و تعیین صریح نوعها در TypeScript میتوان به صورت زیر عمل کرد:
let myString : string = 'this is a string'; myString = 42; // error! function ReturnNumber() : number { return 42; } let anotherString : string = 'this is also a string'; anotherString = ReturnNumber(); // error!
نوعهای شمارشی در TypeScript
Enums در بسیاری از زبانهای برنامه نویسی متداول وجود دارند و هدف از آنها، دادن نامهایی بهتر و مشخص، به مجموعهای از مقادیر است:
enum Category { Biography, Poetry, Fiction }; // 0, 1, 2
اگر میخواهید این مقادیر با اعداد دیگری شروع شوند (بجای صفر پیش فرض)، میتوان مقدار اولین نام را به صورت صریحی مشخص کرد:
enum Category { Biography = 1, Poetry, Fiction }; // 1, 2, 3
enum Category{ Biography = 5, Poetry = 8, Fiction = 9 }; // 5, 8, 9
let favoriteCategory: Category = Category.Biography;
در این حالت اگر مقدار favoriteCategory را چاپ کنیم، خروجی عددی 5 نمایش داده میشود:
console.log(favoriteCategory); // 5
let categoryString= Category[favoriteCategory]; // Biography
آرایهها در TypeScript
در حالت عمومی، آرایهها در TypeScript همانند جاوا اسکریپت تعریف میشوند؛ البته به همراه تعدادی استثناء. در TypeScript سه روش برای تعریف آرایهها وجود دارند:
الف) در مثال زیر آرایهای از رشتهها تعریف شدهاست. در اینجا نوع آرایه به همراه [] مشخص میشود:
let strArray1: string[] = ['here', 'are', 'strings'];
let strArray2: Array<string> = ['more', 'strings', 'here'];
let anyArray: any[] = [42, true, 'banana'];
نوع Tuples در TypeScript
Tuples در TypeScript نوع خاصی از آرایهها هستند که نوع مقادیر اعضای آنها به صورت صریح مشخص میشوند:
let myTuple: [number, string] = [25, 'truck'];
اکنون برای دسترسی به مقادیر این المانها، همانند کار با آرایههای معمولی، از ایندکسهای آرایهی تعریف شده استفاده میشود:
let firstElement= myTuple[0]; // 25 let secondElement= myTuple[1]; // truck
یک نکته: میتوان به آرایهی تعریف شده، عناصر جدیدی را نیز افزود؛ با این شرط که نوع آنها، یکی از نوعهای مشخص شدهی در تعریف Tuple باشند:
// other elements can have numbers or strings myTuple[2] = 100; myTuple[2] = 'this works!';
مفهوم Type assertions در TypeScript
حتما با مفهوم cast و تبدیل نوعهای مختلف به یکدیگر، در زبانهای دیگر برنامه نویسی آشنا هستید. در TypeScript نیز این مفهوم تحت عنوان Type assertions پشتیبانی میشود و دو روش برای تعریف آن وجود دارد:
الف) تعریف cast توسط angle-bracket syntax که در آن نوع مدنظر داخل یک <> قرار میگیرد:
let someValue: any = "this is a string"; let strLength: number = (<string>someValue).length;
ب) تعریف cast توسط as syntax به نحو ذیل:
let someValue: any = "this is a string"; let strLength: number = (someValue as string).length;