diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 000000000..490051876 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1 @@ +github: iliakan diff --git a/1-js/01-getting-started/4-devtools/article.md b/1-js/01-getting-started/4-devtools/article.md index d4bb0147e..961d53cd2 100644 --- a/1-js/01-getting-started/4-devtools/article.md +++ b/1-js/01-getting-started/4-devtools/article.md @@ -20,7 +20,7 @@ پنل توسعه‌دهندگان به صورت پیش‌فرض روی تب Console باز می‌شود و شما چیزی شبیه به این را باید ببینید : -![chrome](chrome.png) +![chrome](chrome.webp) ظاهر دقیق این پنل بر اساس نسخه Chrome تغییر می‌کند ولی در نهایت چیزی شبیه به این خواهد بود. @@ -42,7 +42,11 @@ این مرورگر (فقط در سیستم عامل Mac و نه در Windows و Linux) در اینجا مقداری متفاوت است و ابتدا باید ویژگی Developer menu را فعال کنیم. +<<<<<<< HEAD به Preferences رفته و به تب Advanced بروید و تیک مربوط به Developer menu را در پایین فعال کنید. +======= +Open Settings and go to the "Advanced" pane. There's a checkbox at the bottom: +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b ![safari](safari.png) diff --git a/1-js/01-getting-started/4-devtools/chrome.png b/1-js/01-getting-started/4-devtools/chrome.png deleted file mode 100644 index 4cb3ea2f4..000000000 Binary files a/1-js/01-getting-started/4-devtools/chrome.png and /dev/null differ diff --git a/1-js/01-getting-started/4-devtools/chrome.webp b/1-js/01-getting-started/4-devtools/chrome.webp new file mode 100644 index 000000000..bdf067079 Binary files /dev/null and b/1-js/01-getting-started/4-devtools/chrome.webp differ diff --git a/1-js/01-getting-started/4-devtools/chrome@2.webp b/1-js/01-getting-started/4-devtools/chrome@2.webp new file mode 100644 index 000000000..2aeca5898 Binary files /dev/null and b/1-js/01-getting-started/4-devtools/chrome@2.webp differ diff --git a/1-js/01-getting-started/4-devtools/chrome@2x.png b/1-js/01-getting-started/4-devtools/chrome@2x.png deleted file mode 100644 index b87404a8f..000000000 Binary files a/1-js/01-getting-started/4-devtools/chrome@2x.png and /dev/null differ diff --git a/1-js/01-getting-started/4-devtools/safari.png b/1-js/01-getting-started/4-devtools/safari.png index 64c7a3f6c..4538827eb 100644 Binary files a/1-js/01-getting-started/4-devtools/safari.png and b/1-js/01-getting-started/4-devtools/safari.png differ diff --git a/1-js/01-getting-started/4-devtools/safari@2x.png b/1-js/01-getting-started/4-devtools/safari@2x.png index 27def4d09..1561b2bd9 100644 Binary files a/1-js/01-getting-started/4-devtools/safari@2x.png and b/1-js/01-getting-started/4-devtools/safari@2x.png differ diff --git a/1-js/02-first-steps/04-variables/article.md b/1-js/02-first-steps/04-variables/article.md index 143f2b247..0b5cafad3 100644 --- a/1-js/02-first-steps/04-variables/article.md +++ b/1-js/02-first-steps/04-variables/article.md @@ -88,16 +88,26 @@ let user = 'John' *!*var*/!* message = 'سلام'; ``` +<<<<<<< HEAD کلیدواژه `var` تقریبا* با* `let` یکسان است. آن هم یک متغیر را تعریف می‌کند، ولی روش کار آن قدیمی است. تفاوت‌های کوچکی بین این دو وجود دارد که در حال حاضر به آن نمی‌پردازیم. در مبحث با جزییات به آن خواهیم پرداخت. +======= +The `var` keyword is *almost* the same as `let`. It also declares a variable but in a slightly different, "old-school" way. + +There are subtle differences between `let` and `var`, but they do not matter to us yet. We'll cover them in detail in the chapter . +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b ```` ## یک مقایسه‌ی واقعی برای درک مفهوم متغیر، می‌توانیم آن را یک جعبه برای نگهداری اطلاعات تصور کنیم، که یک نام منحصربه‌فرد روی آن چسبانده‌ایم. +<<<<<<< HEAD برای نمونه، تصور کنید متغیر "`message`" در جعبه‌ای با برچسب `message` و با مقدار "`سلام!`" داخل آن وجود دارد: +======= +For instance, the variable `message` can be imagined as a box labelled `"message"` with the value `"Hello!"` in it: +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b ![](variable.svg) @@ -196,15 +206,24 @@ let my-name; // خط‌های پیوند '-' در نام‌ها ممنوع هس متغیرهای apple و Apple دو متغیر متفاوت هستند. ``` +<<<<<<< HEAD ````smart header="کاراکترهای غیر انگلیسی مجاز هستند ولی توصیه نمی‌شوند" امکان استفاده از هر زبانی، شامل حروف cyrillic، لوگوگرام‌های چینی و دیگر زبان‌ها وجود دارد، مثلا اینگونه:: +======= +````smart header="Non-Latin letters are allowed, but not recommended" +It is possible to use any language, including Cyrillic letters, Chinese logograms and so on, like this: +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b ```js let имя = '...'; let 我 = '...'; ``` +<<<<<<< HEAD از نظر فنی، این‌ها درست کار می‌کنند و مجاز هستند، ولی بر اساس یک قائده بین‌المللی برای نام متغیرها از زبان انگلیسی استفاده می‌شود. حتی اگر اسکریپتی کوچک می‌نویسیم، ممکن است تا مدت طولانی‌ای مورد استفاده و توسعه قرار بگیرد. اشخاصی از سایر کشورها ممکن است نیاز باشد روزی آن اسکریپت را بخوانند. +======= +Technically, there is no error here. Such names are allowed, but there is an international convention to use English in variable names. Even if we're writing a small script, it may have a long life ahead. People from other countries may need to read it sometime. +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b ```` ````warn header="نام‌های رِزِرو شده" @@ -261,10 +280,18 @@ const myBirthday = '18.04.1982'; myBirthday = '01.01.2001'; // !ارور، نمی‌توان ثابت را دوباره مقداردهی کرد ``` +<<<<<<< HEAD وقتی برنامه‎‌نویس اطمینان دارد که متغیری هیچگاه تغییر نمی‌کند می‌تواند آن را به عنوان const تعریف کند تا برای دیگران نیز این موضوع واضح باشد. +======= +When a programmer is sure that a variable will never change, they can declare it with `const` to guarantee and communicate that fact to everyone. +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b +<<<<<<< HEAD ### ثابت‌های با حروف بزرگ +======= +There is a widespread practice to use constants as aliases for difficult-to-remember values that are known before execution. +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b به صورت یک عُرفِ همه‌گیر، از ثابت‌های با حروف بزرگ به عنوان نام مستعار برای مقادیری که به خاطر سپردن آنها دشوار است، استفاده می‌شود. این دسته از ثابت‌ها اصطلاحا prior to execution (پیش از اجرای برنامه) مقدارشان مشخص است. @@ -291,7 +318,11 @@ alert(color); // #FF7F00 چه زمانی باید از حروف بزرگ و چه زمانی باید از حروف معمولی برای نام‌گذاری یک constant استفاده کنیم؟ بیایید قضیه را روشن کنیم. +<<<<<<< HEAD ثابت بودن یک متغیر صرفا بدین معناست که مقدار آن تغییر نخواهد کرد. یک دسته از ثابت‌ها پیش از اجرای برنامه مقدارشان مشخص خواهد بود (مثل هگزادسیمال برای رنگ قرمز) و دسته دیگر در حین اجرای (Run Time) برنامه مقدارشان مشخص می‌شود، اما پس از مقدار دهی اولیه مقدارشان تغییر نمی‌کند. +======= +Being a "constant" just means that a variable's value never changes. But some constants are known before execution (like a hexadecimal value for red) and some constants are *calculated* in run-time, during the execution, but do not change after their initial assignment. +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b برای نمونه: @@ -299,7 +330,11 @@ alert(color); // #FF7F00 const pageLoadTime = /* زمان بارگیری برای یک صفحه وب */; ``` +<<<<<<< HEAD مقدار `pageLoadTime` پیش از اجرای برنامه مشخص نبوده و به همین دلیل به صورت عادی نوشته شده است. اما همچنان یک ثابت است چراکه زمان اجرای برنامه بعد از مقداردهی دیگر تغییر نخواهد کرد. +======= +The value of `pageLoadTime` is not known before the page load, so it's named normally. But it's still a constant because it doesn't change after the assignment. +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b به تعبیری دیگر، ثابت‌های با حروف بزرگ فقط به عنوان نام مستعار برای مقدارهای «hard-coded» استفاده می‌شوند. @@ -309,18 +344,31 @@ const pageLoadTime = /* زمان بارگیری برای یک صفحه وب */; نام یک متغیر باید معنی واضح و روشنی داشته باشد که داده درون خود را توصیف می‌کند. +<<<<<<< HEAD انتخاب نام برای متغیرها یکی از کارهای مهم و پیچیده در برنامه‌نویسی است. یک نگاه سریع به نام متغیرها می‌تواند تفاوت یک برنامه‌نویس تازه‌کار و با تجربه را نشان دهد. در پروژه‌های واقعی، بجای از صفر نوشتن برنامه‌ها، بیشتر زمان صرف اصلاح و توسعه کدهای موجود می‌شود. وقتی پس از مدتی به کدهای قبلی باز می‌گردیم، بدست آوردن اطلاعات از آن دسته کدهایی که نام‌گذاری‌های خوبی دارند بسیار راحت‌تر است (منظور متغیرهایی است که نام‌های خوبی دارند). +======= +Variable naming is one of the most important and complex skills in programming. A glance at variable names can reveal which code was written by a beginner versus an experienced developer. + +In a real project, most of the time is spent modifying and extending an existing code base rather than writing something completely separate from scratch. When we return to some code after doing something else for a while, it's much easier to find information that is well-labelled. Or, in other words, when the variables have good names. +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b لطفا پیش از انتخاب نام برای یک متغیر، خوب به آن فکر کنید. ثمره‌ی آن را خواهید دید. بعضی از دستورالعمل‌های مفید: +<<<<<<< HEAD - از نام‌هایی که برای انسان قابل فهم است استفاده کنید مانند `userName` یا `shoppingCart`. - از نام‌های مخفف یا کوتاه استفاده نکنید مانند `a`، `b`، `c`، مگر آنکه واقعا بدانید چه می‌کنید. - نام‌های کاملا واضح و مختصر انتخاب کنید. نمونه‌هایی از نام‌های بد `data` و `value` هستند. این نام‌ها هیچ‌چیز را توضیح نمی‌دهند. استفاده از این دست نام‌ها فقط زمانی قابل قبول است که محتوای کدی که می‌نویسید به طور استثنا مشخص کند که data یا value به چه چیزی اشاره می‌کند. - با ذهن خود و تیم کاریتان توافق نظر داشته باشید. اگر به مخاطبان وب‌سایت‌تان user می‌گویید، متغیرهای مرتبط را بجای `currentVisitor` یا `newManInTown` باید `currentUser` یا `newUser` بنامید. +======= +- Use human-readable names like `userName` or `shoppingCart`. +- Stay away from abbreviations or short names like `a`, `b`, and `c`, unless you know what you're doing. +- Make names maximally descriptive and concise. Examples of bad names are `data` and `value`. Such names say nothing. It's only okay to use them if the context of the code makes it exceptionally obvious which data or value the variable is referencing. +- Agree on terms within your team and in your mind. If a site visitor is called a "user" then we should name related variables `currentUser` or `newUser` instead of `currentVisitor` or `newManInTown`. +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b ```smart header="ایجاد یا استفاده مجدد?" و آخرین نکته: برخی برنامه‌نویسان تنبل بجای تعریف متغیرهای جدید، از متغیرهای موجود دوباره استفاده می‌کنند. diff --git a/1-js/02-first-steps/05-types/article.md b/1-js/02-first-steps/05-types/article.md index 46ea20152..8e88b8c3e 100644 --- a/1-js/02-first-steps/05-types/article.md +++ b/1-js/02-first-steps/05-types/article.md @@ -94,6 +94,7 @@ const bigInt = 1234567890123456789012345678901234567890n; به دلیل اینکه اعداد `BigInt` به ندرت نیاز می‌شوند، ما آنها را اینجا پوشش نمی‌دهیم، اما یک فصل جدا به آنها اختصاص دادیم. زمانی که به چنین اعداد بزرگی نیاز داشتید آن را بخوانید. +<<<<<<< HEAD ```smart header="مشکلات سازگاری" در حال حاضر، `BigInt` در Firefox/Chrome/Edge/Safari پشتیبانی می‌شوند، اما در IE خیر. @@ -101,6 +102,8 @@ const bigInt = 1234567890123456789012345678901234567890n; شما می‌توانید [جدول سازگاری BigInt در *MDN*](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt#Browser_compatibility) را برای اینکه بدانید کدام نسخه از مرورگرها پشتیبانی می‌شوند، بررسی کنید. +======= +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b ## String در جاوااسکریپت یک رشته (string) باید توسط کوتِیشِن‌ها احاطه شود. diff --git a/1-js/02-first-steps/16-function-expressions/article.md b/1-js/02-first-steps/16-function-expressions/article.md index 994f59549..c4e4a3387 100644 --- a/1-js/02-first-steps/16-function-expressions/article.md +++ b/1-js/02-first-steps/16-function-expressions/article.md @@ -82,7 +82,7 @@ let sayHi = function() { // (1) ایجاد alert( "سلام" ); }; -let func = sayHi; +let func = sayHi; //(2) // ... ``` diff --git a/1-js/03-code-quality/06-polyfills/article.md b/1-js/03-code-quality/06-polyfills/article.md index d4f4d5de6..eeceffb3e 100644 --- a/1-js/03-code-quality/06-polyfills/article.md +++ b/1-js/03-code-quality/06-polyfills/article.md @@ -7,7 +7,11 @@ پس کاملا طبیعی است که یک موتور فقط بخشی از یک استاندارد را پیاده‌سازی کند. +<<<<<<< HEAD یک صفحه‌ی خوب برای این که ببینید در حال حاضر چه چیزهایی پشتیبانی می‌شود اینجاست (خیلی بزرگ است، ما چیزهای زیادی برای مطالعه داریم). +======= +A good page to see the current state of support for language features is (it's big, we have a lot to study yet). +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b به عنوان توسعه‌دهنده، ما همیشه دوست داریم از ویژگی‌ها و امکانات جدید استفاده کنیم. هر چه جدیدتر، بهتر! @@ -71,10 +75,14 @@ if (!Math.trunc) { // اگر چنین تابعی وجود ندارد جاوااسکریپت یک زبان به‌شدت داینامیک است. اسکریپت‌ها می‌توانند هر تابعی را تغییر دهند یا اضافه کنند. حتی تابع‌های built-in. +<<<<<<< HEAD دو کتابخانه جالب پلیفیل‌ها: - [core js](https://github.com/zloirock/core-js) که از چیزهای زیادی پشتیبانی می‌کند و اجازه می‌دهد فقط فیچرهای مورد نیاز خود را اضافه کنید. - [polyfill.io](http://polyfill.io) سرویسی که یک اسکریپت با پلیفیل‌ها ارائه می‌دهد. بسته به فیچرها و مرورگر کاربر. +======= +One interesting polyfill library is [core-js](https://github.com/zloirock/core-js), which supports a wide range of features and allows you to include only the ones you need. +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b ## خلاصه @@ -84,9 +92,15 @@ if (!Math.trunc) { // اگر چنین تابعی وجود ندارد برای مثال، بعدها که با جاوااسکریپت آشنایی بیشتری پیدا کنید، می‌توانید یک سیستم build کد با [webpack](https://webpack.js.org/) و پلاگین [babel-loader](https://github.com/babel/babel-loader) راه‌اندازی کنید. +<<<<<<< HEAD منابع خوبی که نشان می‌دهند فیچرهای مختلف در چه حالتی از پشتیبانی قرار دارند: - - برای جاوااسکریپت. - - برای تابع‌های مربوط به مرورگر. +======= +Good resources that show the current state of support for various features: +- - for pure JavaScript. +- - for browser-related functions. +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b پانوشت گوگل کروم معمولا نسبت به فیچرهای زبان به‌روزترین است. اگر دموی یک آموزش کار نکرد، آن را امتحان کنید. البته بیشتر دموهای آموزش با هر مرورگر مدرنی کار می‌کنند. diff --git a/1-js/04-object-basics/04-object-methods/8-chain-calls/task.md b/1-js/04-object-basics/04-object-methods/8-chain-calls/task.md index a19760285..c3f0dccdc 100644 --- a/1-js/04-object-basics/04-object-methods/8-chain-calls/task.md +++ b/1-js/04-object-basics/04-object-methods/8-chain-calls/task.md @@ -4,7 +4,11 @@ importance: 2 # زنجیره‌ای +<<<<<<< HEAD یک شیء `ladder` وجود دارد که بالا و پایین رفتن را ممکن می‌کند: +======= +There's a `ladder` object that allows you to go up and down: +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b ```js let ladder = { @@ -21,7 +25,11 @@ let ladder = { }; ``` +<<<<<<< HEAD حال اگر ما نیاز داشته باشیم که برای چند بار متوالی صدا بزنیم، می‌توانیم اینگونه این کار را انجام دهیم: +======= +Now, if we need to make several calls in sequence, we can do it like this: +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b ```js ladder.up(); @@ -32,10 +40,18 @@ ladder.down(); ladder.showStep(); // 0 ``` +<<<<<<< HEAD کد `up`، `down` و `showStep` را تغییر دهید تا صدازدن‌ها را زنجیره‌ای کنید، مثل این: +======= +Modify the code of `up`, `down`, and `showStep` to make the calls chainable, like this: +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b ```js ladder.up().up().down().showStep().down().showStep(); // اول 1 را نشان می‌دهد سپس 0 را ``` +<<<<<<< HEAD چنین روشی در بین کتابخانه‌های جاوااسکریپت به طور گسترده استفاده می‌شود. +======= +Such an approach is widely used across JavaScript libraries. +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b diff --git a/1-js/04-object-basics/09-object-toprimitive/article.md b/1-js/04-object-basics/09-object-toprimitive/article.md index 086f1c0c7..bec4f12bf 100644 --- a/1-js/04-object-basics/09-object-toprimitive/article.md +++ b/1-js/04-object-basics/09-object-toprimitive/article.md @@ -253,7 +253,11 @@ let obj = { } }; +<<<<<<< HEAD alert(obj + 2); // تبدیل به مقدار اصلی یک رشته برگرداند => ادغام، (2 + "2") 22 +======= +alert(obj + 2); // "22" ("2" + 2), conversion to primitive returned a string => concatenation +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b ``` ## خلاصه diff --git a/1-js/05-data-types/02-number/article.md b/1-js/05-data-types/02-number/article.md index 74f44f097..5d49d6321 100644 --- a/1-js/05-data-types/02-number/article.md +++ b/1-js/05-data-types/02-number/article.md @@ -4,7 +4,11 @@ 1. اعداد معمولی در جاوااسکریپت با فرمت 64 بیتی [IEEE-754](https://en.wikipedia.org/wiki/IEEE_754-2008_revision) ذخیره می‌شوند، همچنین با "اعداد اعشاری با دقت یک صدم" هم شناخته می‌شوند. ما اکثر اوقات از این اعداد استفاده می‌کنیم، و درباره آنها در این فصل صحبت خواهیم کرد. +<<<<<<< HEAD 2. اعداد BigInt، برای نمایش اعدادی با طول دلخواه استفاده می‌شوند. آنها بعضی اوقات مورد نیاز هستند، چون اعداد معمولی نمی‌توانند از 253 بیشتر یا از -253 کمتر باشند. چون bigintها در چند حوزه خاص استفاده می‌شوند، ما به آنها یک فصل خاص اختصاص می‌دهیم. +======= +2. BigInt numbers represent integers of arbitrary length. They are sometimes needed because a regular integer number can't safely exceed (253-1) or be less than -(253-1), as we mentioned earlier in the chapter . As bigints are used in a few special areas, we devote them to a special chapter . +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b پس اینجا درباره اعداد معمولی صحبت می‌کنیم. بیایید دانش‌مان درباره آنها را گسترش دهیم. @@ -41,7 +45,11 @@ alert( 7.3e9 ); // 7.3 billions (same as 7300000000 or 7_300_000_000) 1.23e6 === 1.23 * 1000000; // e6 یعنی *1000000 ``` +<<<<<<< HEAD حالا بگذارید مقداری خیلی کوچک بنویسیم. مثلا؛ یک میکروثانیه، (یک میلیونیوم ثانیه): +======= +Now let's write something very small. Say, 1 microsecond (one-millionth of a second): +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b ```js let mсs = 0.000001; @@ -103,13 +111,23 @@ alert( num.toString(16) ); // ff alert( num.toString(2) ); // 11111111 ``` +<<<<<<< HEAD پایه میتواند از بین `۲` تا `۳۶` تغییر کند. در حالت عادی، `۱۰` است. +======= +The `base` can vary from `2` to `36`. By default, it's `10`. +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b حالات معمول استفاده بدین شکلند: +<<<<<<< HEAD - **base=16** برای رنگ‌های هگزایی، کدگذاری حروف و غیره، ارقام میتوانند به صورت `0..9` یا `A..F` باشند. - **base=2** برای دیباگ کردن عملوند‌های بیتی هستند، ارقام میتوانند `0` یا `1` باشند. - **base=36** بزرگترین است، ارقام میتوانند `0..9` یا `A..Z` باشند. کل الفبای لاتین برای نمایش یک عدد مورد استفاده قرار میگیرد. یک حالت خنده دار، اما مفید برای `۳۶` وقتی است که ما نیاز داریم تا یک نشانگر بزرگ عددی را به چیز کوچکتری تبدیل کنیم، به عنوان مثال برای . ساختن لینک‌ های کوتاه شده (short url). میتوان به سادگی اعداد را در پایه‌ی `۳۶` نمایش داد: +======= +- **base=16** is used for hex colors, character encodings etc, digits can be `0..9` or `A..F`. +- **base=2** is mostly for debugging bitwise operations, digits can be `0` or `1`. +- **base=36** is the maximum, digits can be `0..9` or `A..Z`. The whole Latin alphabet is used to represent a number. A funny, but useful case for `36` is when we need to turn a long numeric identifier into something shorter, for example, to make a short url. Can simply represent it in the numeral system with base `36`: +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b ```js run alert( 123456..toString(36) ); // 2n9c @@ -118,7 +136,13 @@ alert( num.toString(2) ); // 11111111 ```warn header="دو نقطه برای صدا زدن یک تابع" توجه داشته باشید که دو نقطه در `123456..toString(36)`، غلط املایی نیست. اگر میخواهیم یک تابعی را مستقیما روی عدد صدا بزنیم مثل `toString` در مثال بالا، آنگاه ما نیاز داریم تا بعد از آن دوتا نقطه بگذاریم. +<<<<<<< HEAD اگر ما یک نقطه بگذاریم: `123456.toString(36)`، آنگاه خطایی به وجود می‌آید، چراکه قواعد نوشتاری جاوااسکریپت بعد از یک نقطه، آن را قسمت اعشاری آن در نظر میگیرد. و اگر یک نقطه بیشتر بگذاریم، جاوااسکریپت فرض میکند قسمت اعشاری خالی‌ست و سپس تابع فراخوانده میشود. +======= +If we placed a single dot: `123456.toString(36)`, then there would be an error, because JavaScript syntax implies the decimal part after the first dot. And if we place one more dot, then JavaScript knows that the decimal part is empty and now uses the method. + +Also could write `(123456).toString(36)`. +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b همچنین میتوانیم بنویسیم `(123456).toString(36)`: ``` @@ -136,7 +160,11 @@ alert( num.toString(2) ); // 11111111 : رند بالا: `۳.۱` میشود `۴`, و `-۱.۱` میشود `-۱`. `Math.round` +<<<<<<< HEAD : رند کردن به نزدیک ترین عدد صحیح: `۳.۱` میشود `۳`, `۳.۶` میشود `۴` و `-۱.۱` میشود `-۱`. +======= +: Rounds to the nearest integer: `3.1` becomes `3`, `3.6` becomes `4`. In the middle cases `3.5` rounds up to `4`, and `-3.5` rounds up to `-3`. +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b `Math.trunc` (توسط اینترنت اکسپلورر ساپورت نمیشود) : حذف کردن قسمت اعشاری بدون رند کردن: `۳.۱` میشود `۳`, `-۱.۱` میشود `-۱`. @@ -145,10 +173,19 @@ alert( num.toString(2) ); // 11111111 | | `Math.floor` | `Math.ceil` | `Math.round` | `Math.trunc` | |---|---------|--------|---------|---------| +<<<<<<< HEAD |`۳.۱`| `۳` | `۴` | `۳` | `۳` | |`۳.۶`| `۳` | `۴` | `۴` | `۳` | |`-۱.۱`| `-۲` | `-۱` | `-۱` | `-۱` | |`-۱.۶`| `-۲` | `-۱` | `-۲` | `-۱` | +======= +|`3.1`| `3` | `4` | `3` | `3` | +|`3.5`| `3` | `4` | `4` | `3` | +|`3.6`| `3` | `4` | `4` | `3` | +|`-1.1`| `-2` | `-1` | `-1` | `-1` | +|`-1.5`| `-2` | `-1` | `-1` | `-1` | +|`-1.6`| `-2` | `-1` | `-2` | `-1` | +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b این توابع تمام حالات کار با قسمت اعشاری یک عدد را پوشش میدهند. اما چطور میتوانیم عدد را تا رقم `n-ام` بعد از اعشار رند کنیم؟ @@ -187,7 +224,11 @@ alert( num.toString(2) ); // 11111111 alert( num.toFixed(5) ); // "12.34000", صفر اضافه شده تا دقیقا ۵ رقم شود ``` +<<<<<<< HEAD ما می‌توانیم آن را با کمک عملگر جمع یگانه یا با فراخوانی `Number()` تبدیل کنیم؛ برای مثال بنویسیم `+num.toFixed(5)`. +======= + We can convert it to a number using the unary plus or a `Number()` call, e.g. write `+num.toFixed(5)`. +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b ## محاسبات تقریبی @@ -221,7 +262,17 @@ alert( 0.1 + 0.2 ); // 0.30000000000000004 یک عدد در حافظه به شکل دودویی آن ذخیره میشود، مجموعه‌ای از صفرها و یک‌ها. اما کسرهایی مثل `0.1`، `0.2` که در سیستم اعداد اعشاری ساده به نظر میرسند در اصل کسرهای بی‌پایانی در شکل دودویی خود هستند. +<<<<<<< HEAD مقدار `0.1` چیست؟ مقدار یک که بر ده تقسیم شده، `1/10` یعنی یک دهم. در سیستم اعداد اعشاری چنین اعدادی به سادگی قابل نمایش هستند. آن را با یک سوم `1/3` مقایسه کنید. به کسری بی‌پایان تبدیل می‌شود `0.33333(3)`. +======= +```js run +alert(0.1.toString(2)); // 0.0001100110011001100110011001100110011001100110011001101 +alert(0.2.toString(2)); // 0.001100110011001100110011001100110011001100110011001101 +alert((0.1 + 0.2).toString(2)); // 0.0100110011001100110011001100110011001100110011001101 +``` + +What is `0.1`? It is one divided by ten `1/10`, one-tenth. In the decimal numeral system, such numbers are easily representable. Compare it to one-third: `1/3`. It becomes an endless fraction `0.33333(3)`. +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b بنابراین، تقسیم های از توان ده، قطعا در سیستم اعشاری کار میکند اما تقسیم‌های بر ۳ اینطور نیست. به علت مشابه، در سیستم اعداد دودویی، تقسیم توان‌های ۲ هم قطعا کار میکند اما ۱/۱۰، کسر دودویی بی‌پایانی میشود. @@ -241,7 +292,11 @@ alert( 0.1.toFixed(20) ); // 0.10000000000000000555 ```smart header="نه تنها جاوااسکریپت" این مشکل در دیگر زبان‌های برنامه‌نویسی بسیاری وجود دارد. +<<<<<<< HEAD PHP، Java، C، Perl، Ruby دقیقا نتیجه‌ی مشابه را میدهند چراکه بر پایه‌ی فرمت عددی یکسانی بنا شده‌‌اند. +======= +PHP, Java, C, Perl, and Ruby give exactly the same result, because they are based on the same numeric format. +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b ``` آیا ما میتوانیم راهی برای حل این مسأله پیدا کنیم؟ طبعا، قابل اطمینان ترین راه حل این است که نتیجه را با کمک متد [toFixed(n)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/toFixed) رند کنیم: @@ -265,7 +320,11 @@ alert( (0.1 * 10 + 0.2 * 10) / 10 ); // 0.3 alert( (0.28 * 100 + 0.14 * 100) / 100); // 0.4200000000000001 ``` +<<<<<<< HEAD پس روش ضرب/تقسیم ارور را کاهش می‌دهد، اما آن را به طور کامل ازبین نمی‌برد. +======= +So, the multiply/divide approach reduces the error, but doesn't remove it totally. +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b گاهی اوقات می‌توانیم به طور کلی از کسرها فرار کنیم. برای مثال اگر ما با یک فروشگاه سر و کار داریم، می‌توانیم قیمت‌ها را به جای دلار به صورت سنت (cent) ذخیره کنیم. اما اگر یک تخفیف 30 درصدی را اعمال کنیم چه؟ در عمل، فرار کردن از کسرها به ندرت ممکن است. زمانی که نیاز شد فقط آنها را رند کنید تا "دم‌شان" را ببرید. @@ -287,7 +346,11 @@ alert( 9999999999999999 ); // shows 10000000000000000 این به این علت است که علامت با یک بیت مشخص میشود بنابراین هر عدد میتواند مثبت یا منفی باشد، حتی صفر. +<<<<<<< HEAD در اکثر حالات، تفاوت غیرقابل توجه است، چراکه عملگرها برای داشتن رفتار یکسان وفق داده شده‌ند. +======= +In most cases, the distinction is unnoticeable, because operators are suited to treat them as the same. +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b ``` ## آزمون‌ها: isFinite و isNaN @@ -336,7 +399,11 @@ alert( isFinite(num) ); ````smart header="`Number.isNaN` و `Number.isFinite`" متدهای [Number.isNaN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/isNaN) و [Number.isFinite](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/isFinite) نسخه‌های «سخت‌گیرتر» از تابع‌های `isNaN` و `isFinite` هستند. آن‌ها به طور خودکار آرگومان خود را به عدد تبدیل نمی‌کنند بلکه در عوض بررسی می‌کنند که آیا آرگومان به نوع `number` تعلق دارد یا خیر. +<<<<<<< HEAD - `Number.isNaN(value)` اگر آرگومان به نوع `number` تعلق داشته باشد و برابر با `NaN` باشد مقدار `true` را برمی‌گرداند. در غیر این صورت `false` برگردانده می‌شود. +======= +- `Number.isNaN(value)` returns `true` if the argument belongs to the `number` type and it is `NaN`. In any other case, it returns `false`. +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b ```js run alert( Number.isNaN(NaN) ); // true @@ -347,7 +414,11 @@ alert( isFinite(num) ); alert( isNaN("str") ); // true را دریافت می‌کند پس NaN را به یک عدد تبدیل می‌کند و از این تبدیل "str" رشته isNaN چون ``` +<<<<<<< HEAD - `Number.isFinite(value)` اگر آرگومان به نوع `number` تعلق داشته باشد و `NaN/Infinity/-Infinity` نباشد مقدار `true` برگردانده می‌شود. در غیر این صورت `false` را برمی‌گرداند. +======= +- `Number.isFinite(value)` returns `true` if the argument belongs to the `number` type and it is not `NaN/Infinity/-Infinity`. In any other case, it returns `false`. +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b ```js run alert( Number.isFinite(123) ); // true @@ -365,8 +436,13 @@ alert( isFinite(num) ); ```smart header="مقایسه کنید با `Object.is`" یک متد درون‌ساخت خاص به نام [Object.is](mdn:js/Object/is) وجود دارد که مقادیر را مثل `===` مقایسه میکند، اما برای دو حالت مرزی قابل اعتمادتر است: +<<<<<<< HEAD ۱. با `NaN` کار میکند: `Object.is(NaN, NaN) === true`، که چیز خوبیست. ۲. مقادیر `0` و `-0` متفاوت هستند: `Object.is(0, -0) === false`، به ندرت اهمیت دارد، اما این مقادیر در اصل متفاوتند. +======= +1. It works with `NaN`: `Object.is(NaN, NaN) === true`, that's a good thing. +2. Values `0` and `-0` are different: `Object.is(0, -0) === false`, technically that's correct because internally the number has a sign bit that may be different even if all other bits are zeroes. +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b در تمام حالات دیگر، `Object.is(a, b)` با `a === b` برابراست. @@ -384,7 +460,11 @@ alert( +"100px" ); // NaN تنها استثنا، کاراکتر خالی در شروع یا انتهای رشته حرفی هستند که آنها در تابع در نظر گرفته نمی‌شوند. +<<<<<<< HEAD اما در دنیای واقعی، ما مقادیر در واحدهای مختلفی داریم، مثل `"100px"` یا `"12pt"` در CSS. همینطور در بسیاری از کشورها، نماد پولی آنها بعد از مقدار عددی ظاهر میشود. مثل `"19€"`، که میخواهیم آن مقدار را از قسمت حرفی جدا کنیم. +======= +But in real life, we often have values in units, like `"100px"` or `"12pt"` in CSS. Also in many countries, the currency symbol goes after the amount, so we have `"19€"` and would like to extract a numeric value out of that. +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b به همین علت است که `parseInt` و `parseFloat` استفاده می‌شوند. @@ -478,4 +558,8 @@ alert( parseInt('2n9c', 36) ); // 123456 توابع ریاضی بیشتر: +<<<<<<< HEAD - [Math](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Math) شئ را وقتی به آنها نیاز دارید ببینید. کتابخانه‌ی بسیار کوچکیست اما توابع پایه‌ای را پوشش می‌دهد. +======= +- See the [Math](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Math) object when you need them. The library is very small but can cover basic needs. +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b diff --git a/1-js/05-data-types/03-string/3-truncate/task.md b/1-js/05-data-types/03-string/3-truncate/task.md index 9df35c91f..fd60545a2 100644 --- a/1-js/05-data-types/03-string/3-truncate/task.md +++ b/1-js/05-data-types/03-string/3-truncate/task.md @@ -11,7 +11,7 @@ importance: 5 برای مثال: ```js -truncate("What I'd like to tell on this topic is:", 20) = "What I'd like to te…" +truncate("What I'd like to tell on this topic is:", 20) == "What I'd like to te…" -truncate("Hi everyone!", 20) = "Hi everyone!" +truncate("Hi everyone!", 20) == "Hi everyone!" ``` diff --git a/1-js/05-data-types/04-array/article.md b/1-js/05-data-types/04-array/article.md index 04ec5d0b9..2516aa94b 100644 --- a/1-js/05-data-types/04-array/article.md +++ b/1-js/05-data-types/04-array/article.md @@ -426,7 +426,11 @@ let matrix = [ [7, 8, 9] ]; +<<<<<<< HEAD alert( matrix[1][1] ); // 5 ،المان مرکزی +======= +alert( matrix[0][1] ); // 2, the second value of the first inner array +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b ``` ## متد toString diff --git a/1-js/05-data-types/05-array-methods/article.md b/1-js/05-data-types/05-array-methods/article.md index 380eef8e7..c80b0bc52 100644 --- a/1-js/05-data-types/05-array-methods/article.md +++ b/1-js/05-data-types/05-array-methods/article.md @@ -1,6 +1,10 @@ # متدهای آرایه +<<<<<<< HEAD آرایه‌ها متدهای زیادی را فراهم می‌کنند. برای ساده‌سازی، در این فصل متدها به چند گروه تقسیم شده‌اند. +======= +Arrays provide a lot of methods. To make things easier, in this chapter, they are split into groups. +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b ## اضافه/حذف کردن عضوها @@ -32,11 +36,19 @@ alert( arr.length ); // 3 المان حذف شد، اما آرایه هنوز هم 3 عضو دارد که می‌توانیم آن را با `arr.length == 3` ببینیم. +<<<<<<< HEAD این چیز طبیعی است چون `delete obj.key` یک مقدار را با استفاده از `key` حذف می‌کند. به طور کلی کارش همین است. برای شیءها مناسب است. اما برای آرایه‌ها ما معمولا می‌خواهیم که بقیه المان‌ها پخش شوند و فضای آزاد شده را اشغال کنند. توقع داریم که الان آرایه‌ای کوتاه‌تر داشته باشیم. +======= +That's natural, because `delete obj.key` removes a value by the `key`. It's all it does. Fine for objects. But for arrays we usually want the rest of the elements to shift and occupy the freed place. We expect to have a shorter array now. +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b بنابراین متدهای خاص باید استفاده شوند. +<<<<<<< HEAD متد [arr.splice](mdn:js/Array/splice) یک شمشیر ارتشی سوئیسی برای آرایه‌ها است. می‌تواند هر کاری کند: اضافه کند، حذف کند و المان‌ها را جایگزین کند. +======= +The [arr.splice](mdn:js/Array/splice) method is a Swiss army knife for arrays. It can do everything: insert, remove and replace elements. +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b سینتکس آن اینگونه است: @@ -62,7 +74,11 @@ alert( arr ); // ["I", "JavaScript"] راحت است، نه؟ از ایندکس `1` به تعداد `1` المان حذف کرد. +<<<<<<< HEAD در مثال بعد ما 3 المان را حذف و آنها را با دو المان جایگزین می‌کنیم: +======= +In the next example, we remove 3 elements and replace them with the other two: +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b ```js run let arr = [*!*"I", "study", "JavaScript",*/!* "right", "now"]; @@ -84,7 +100,11 @@ let removed = arr.splice(0, 2); alert( removed ); // "I", "study" <-- array of removed elements ``` +<<<<<<< HEAD متد `splice` همچنین قادر به اضافه کردن المان بدون هیچ حذفیاتی است. برای این کار باید `deleteCount` را `0` بگذاریم: +======= +The `splice` method is also able to insert the elements without any removals. For that, we need to set `deleteCount` to `0`: +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b ```js run let arr = ["I", "study", "JavaScript"]; @@ -114,7 +134,11 @@ alert( arr ); // 1,2,3,4,5 ### متد slice +<<<<<<< HEAD متد [arr.slice](mdn:js/Array/slice) از متد `arr.splice` که از لحاظ ظاهری شبیه به آن است بسیار ساده‌تر است. +======= +The method [arr.slice](mdn:js/Array/slice) is much simpler than the similar-looking `arr.splice`. +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b سینتکس اینگونه است: @@ -124,7 +148,11 @@ arr.slice([start], [end]) این متد یک آرایه جدید که تمام المان‌ها را از ایندکس `start` تا `end` (شامل خود `end` نمی‌شود) کپی می‌کند، برمی‌گرداند. `start` و `end` هر دو می‌توانند منفی باشند، که در این صورت موقعیت از انتهای آرایه حساب می‌شود. +<<<<<<< HEAD این متد شبیه متد رشته `str.slice` است، اما به جای زیر رشته، زیر آرایه ایجاد می‌کند. +======= +It's similar to a string method `str.slice`, but instead of substrings, it makes subarrays. +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b برای مثال: @@ -206,7 +234,11 @@ alert( arr.concat(arrayLike) ); // 1,2,something,else سینتکس اینگونه است: ```js arr.forEach(function(item, index, array) { +<<<<<<< HEAD // ... با المان کاری انجام دهید +======= + // ... do something with an item +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b }); ``` @@ -239,7 +271,11 @@ arr.forEach(function(item, index, array) { - `arr.indexOf(item, from)` -- با شروع از ایندکس `from` به دنبال `item` می‌گردد و ایندکسی که المان در آن پیدا شد را برمی‌گرداند، در غیر این صورت `1-`. - `arr.includes(item, from)` -- با شروع از ایندکس `from` به دنبال `item` می‌گردد، اگر پیدا کند `true` را برمی‌گرداند. +<<<<<<< HEAD معمولا این متدها تنها با یک آرگومان استفاده می‌شوند: المانی (`item`) که جستجو برای آن انجام می‌شود. به طور پیش‌فرض، جستجو از ابتدا انجام می‌شود. +======= +Usually, these methods are used with only one argument: the `item` to search. By default, the search is from the beginning. +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b برای مثال: @@ -255,7 +291,11 @@ alert( arr.includes(1) ); // true توجه داشته باشید که متدها از مقایسه `===` استفاده می‌کنند. پس اگر ما به دنبال `false` باشیم، متد دقیقا `false` را پیدا می‌کند و نه صفر را. +<<<<<<< HEAD اگر ما بخواهیم بررسی کنیم که `item` درون آرایه وجود دارد یا نه و به دنبال ایندکس دقیق نیستیم، پس `arr.includes` ترجیح داده می‌شود. +======= +If we want to check if `item` exists in the array and don't need the index, then `arr.includes` is preferred. +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b متد [arr.lastIndexOf](mdn:js/Array/lastIndexOf) مانند `indexOf` است اما از راست به چپ جستجو می‌کند. @@ -273,11 +313,20 @@ const arr = [NaN]; alert( arr.indexOf(NaN) ); // -1 (اشتباه است، باید 0 باشد) alert( arr.includes(NaN) );// true (درست است) ``` +<<<<<<< HEAD به این دلیل که `includes` بسیار بعدتر به جاوااسکریپت اضافه شد و از درون از الگوریتم‌های مقایسه بروزتری استفاده می‌کند. +======= +That's because `includes` was added to JavaScript much later and uses the more up-to-date comparison algorithm internally. +```` +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b ### متدهای find و findIndex/findLastIndex +<<<<<<< HEAD تصور کنید که یک آرایه‌ای از شیءها داریم. چگونه باید یک شیء با شرطی مشخص را پیدا کنیم؟ +======= +Imagine we have an array of objects. How do we find an object with a specific condition? +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b اینجاست که متد [arr.find(fn)](mdn:js/Array/find) بدرد می‌خورد. @@ -295,7 +344,11 @@ let result = arr.find(function(item, index, array) { - `index` ایندکس آن است. - `array` خود آرایه است. +<<<<<<< HEAD اگر `true` برگرداند، جستجو متوقف می‎شود، `item` برگردانده می‌شود. اگر چیزی پیدا نشود، `undefined` برگردانده می‌شود. +======= +If it returns `true`, the search is stopped, the `item` is returned. If nothing is found, `undefined` is returned. +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b برای مثال، ما یک آرایه‌ای از کاربران داریم، که هر کدام دارای `id` و `name` هستند. بیایید کاربری که `id == 1` داشته باشد را پیدا کنیم: @@ -311,11 +364,19 @@ let user = users.find(item => item.id == 1); alert(user.name); // John ``` +<<<<<<< HEAD در واقعیت، آرایه‌هایی از شیءها چیز متداولی است، پس متد `find` بسیار مفید است. +======= +In real life, arrays of objects are a common thing, so the `find` method is very useful. +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b توجه داشته باشید که در مثال بالا ما تابع `item => item.id == 1` را همراه با یک آرگومان برای `find` در نظر گرفتیم. این چیز معمولی است، بقیه آرگومان‌های این تابع به ندرت استفاده می‌شوند. +<<<<<<< HEAD متد [arr.findIndex](mdn:js/Array/findIndex) سینتکس یکسانی دارد اما به جای خود المان ایندکسی که المان در آن پیدا شد را برمی‌گرداند. اگر چیزی پیدا نشد مقدار `1-` برگردانده می‌شود. +======= +The [arr.findIndex](mdn:js/Array/findIndex) method has the same syntax but returns the index where the element was found instead of the element itself. The value of `-1` is returned if nothing is found. +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b متد [arr.findLastIndex](mdn:js/Array/findLastIndex) مانند `findIndex` است اما مانند `lastIndexOf` از راست به چپ جستجو می‌کند. @@ -447,11 +508,19 @@ alert(arr); // *!*1, 2, 15*/!* حالا همانطور که انتظار می‌رفت کار می‌کند. +<<<<<<< HEAD بیایید کمی عقب بمانیم و ببینیم چه چیزی در حال اتفاق افتادن است. `arr` می‌تواند آرایه‌ای از هر چیزی باشد نه؟ ممکن است شامل اعداد یا رشته‌ها یا شیءها یا هرچیز دیگری باشد. ما دسته‌ای از *چیزها* داریم. برای مرتب کردن آن، ما به یک *تابع مرتب‌کننده* که می‌داند چگونه المان‌های دسته را مقایسه کند، نیاز داریم. ترتیب رشته‌ای پیش‌فرض است. +======= +Let's step aside and think about what's happening. The `arr` can be an array of anything, right? It may contain numbers or strings or objects or whatever. We have a set of *some items*. To sort it, we need an *ordering function* that knows how to compare its elements. The default is a string order. +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b متد `arr.sort(fn)` یک الگوریتم مرتب‌سازی کلی را پیاده‌سازی می‌کند. ما نیازی نداریم که بدانیم درون آن چه اتفاقی می‌افتد (اکثر اوقات از یک [مرتب‌سازی سریع](https://fa.wikipedia.org/wiki/مرتب%E2%80%8Cسازی_سریع) یا [Timsort](https://fa.wikipedia.org/wiki/مرتب%E2%80%8Cسازی_تیم) بهینه‌شده استفاده می‌شود). این متد آرایه را طی می‌کند، المان‌های آن را با استفاده از تابع فراهم شده مقایسه می‌کند و آنها را مرتب می‌کند، تمام آن چیزی که ما نیاز داریم این است که یک `fn` فراهم کنیم که مقایسه را انجام دهد. +<<<<<<< HEAD راستی، اگر ما بخواهیم بدانیم که کدام المان‌ها مقایسه می‌شوند -- چیزی ما را از alert کردن آنها متوقف نمی‌کند: +======= +By the way, if we ever want to know which elements are compared -- nothing prevents us from alerting them: +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b ```js run [1, -2, 15, 2, 0, 8].sort(function(a, b) { @@ -523,7 +592,11 @@ alert( arr ); // 5,4,3,2,1 متد [str.split(delim)](mdn:js/String/split) دقیقا همین کار را انجام می‌دهد. این متد رشته را با استفاده از جداکننده‌ی داده شده `delim` به یک آرایه تقسیم می‌کند. +<<<<<<< HEAD در مثال بالا، ما توسط یک کاما که بعد آن space می‌آید رشته را جدا می‌کنیم: +======= +In the example below, we split by a comma followed by a space: +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b ```js run let names = 'Bilbo, Gandalf, Nazgul'; @@ -590,9 +663,15 @@ let value = arr.reduce(function(accumulator, item, index, array) { - `index` -- موقعیت آن است. - `array` -- آرایه است. +<<<<<<< HEAD همانطور که تابع اعمال می‌شود، نتیجه فراخوانی قبلی به عنوان آرگومان اول به فراخوانی بعدی منتقل می‌شود. بنابراین، اولین آرگومان اساسا همان حافظه‌ای است که نتیجه ترکیب شده تمام فراخوانی‌های قبلی را ذخیره کرده است. و در پایان تبدیل به نتیجه `reduce` می‌شود. +======= +As the function is applied, the result of the previous function call is passed to the next one as the first argument. + +So, the first argument is essentially the accumulator that stores the combined result of all previous executions. And at the end, it becomes the result of `reduce`. +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b بنظر پیچیده می‌آید؟ @@ -661,7 +740,11 @@ arr.reduce((sum, current) => sum + current); بنابراین توصیه می‌شود همیشه مقدار اولیه را تعیین کنید. +<<<<<<< HEAD متد [arr.reduceRight](mdn:js/Array/reduceRight) کار یکسان را انجام می‌هد، اما از راست به چپ. +======= +The method [arr.reduceRight](mdn:js/Array/reduceRight) does the same but goes from right to left. +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b ##متد Array.isArray @@ -687,7 +770,11 @@ alert(Array.isArray([])); // true تقریبا تمام متدهای آرایه که تابعی را صدا می‌زنند -- مانند `find`، `filter`، `map`، همچنین یک استثنا از `sort`، پارامتر اختیاری اضافی `thisArg` را قبول می‌کنند. +<<<<<<< HEAD این پارامتر به دلیل اینکه به ندرت استفاده می‌شود، در قسمت‌های بالایی گفته نشد. اما برای کامل بودن ما باید آن را پوشش دهیم. +======= +That parameter is not explained in the sections above, because it's rarely used. But for completeness, we have to cover it. +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b سینتکس کامل این متدها در زیر آمده است: @@ -746,11 +833,19 @@ alert(soldiers[1].age); // 23 - `slice(start, end)` -- با ساختن یک آرایه جدید، المان‌ها را از ایندکس `start` تا `end` (شامل نمی‌شود) در آن کپی می‌کند. - `concat(...items)` -- یک آرایه جدید را برمی‌گرداند: تمام عضوهای آرایه کنونی را کپی می‌کند و `items` را به آن اضافه می‌کند. اگر هر کدام از `items` آرایه باشد، سپس المان‌های آن اضافه می‌شوند. +<<<<<<< HEAD - برای جستجو در بین المان‌ها: - `indexOf/lastIndexOf(item, pos)` -- با شروع از موقعیت `pos` به دنبال `item` می‌گردد، ایندکس آن را برمی‌گرداند و در صورتی که پیدا نشود `1-` را برمی‌گرداند. - `includes(value)` -- اگر آرایه دارای `value` باشد، مقدار `true` را برمی‌گرداند در غیر این صورت `false`. - `find/filter(func)` -- المان‌ها را از طریق تابع فیلتر می‌کند، اولین/تمام مقدارهایی که سبب می‌شوند تابع `true` برگرداند را برمی‌گرداند. - `findIndex` مانند `find` است اما به جای مقدار ایندکس را برمی‌گرداند. +======= +- To search among elements: + - `indexOf/lastIndexOf(item, pos)` -- look for `item` starting from position `pos`, and return the index or `-1` if not found. + - `includes(value)` -- returns `true` if the array has `value`, otherwise `false`. + - `find/filter(func)` -- filter elements through the function, return first/all values that make it return `true`. + - `findIndex` is like `find`, but returns the index instead of a value. +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b - برای حلقه زدن در یک آرایه: - `forEach(func)` -- برای تمام المان‌ها تابع `func` را صدا می‌زند، چیزی را برنمی‌گرداند. @@ -792,7 +887,11 @@ alert(soldiers[1].age); // 23 برای دیدن لیست کامل، از [راهنما](mdn:js/Array) استفاده کنید. +<<<<<<< HEAD با اولین نگاه ممکن است به نظر برسد که متدهای بسیار زیادی وجود دارد و به حافظه سپردن آنها مشکل است. اما در واقع بسیار آسان‌تر است. +======= +At first sight, it may seem that there are so many methods, quite difficult to remember. But actually, that's much easier. +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b برای داشتن شناخت از آنها به برگه تقلب نگاه بیاندازید. سپس تکلیف‌های این فصل را برای تمرین انجام دهید تا نسبت به متدهای آرایه تجربه بدست بیاورید. diff --git a/1-js/05-data-types/06-iterable/article.md b/1-js/05-data-types/06-iterable/article.md index 68d469009..66d0fede4 100644 --- a/1-js/05-data-types/06-iterable/article.md +++ b/1-js/05-data-types/06-iterable/article.md @@ -174,7 +174,11 @@ while (true) { برای مثال، رشته‌ها هم حلقه‌پذیر هستند (`for..of` روی آنها کار می‌کند) و هم شبه آرایه هستند (آنها ایندکس عددی و `length` دارند). +<<<<<<< HEAD اما یک حلقه‌پذیر ممکن است شبه آرایه نباشد. برعکس آن هم ممکن است یعنی یک شبه آرایه ممکن است حلقه‌پذیر نباشد. +======= +But an iterable may not be array-like. And vice versa an array-like may not be iterable. +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b برای مثال، در مثال بالا `range` حلقه‌پذیر است اما شبه آرایه نیست، چون ویژگی‌های ایندکسی و `length` ندارد. diff --git a/1-js/05-data-types/10-destructuring-assignment/article.md b/1-js/05-data-types/10-destructuring-assignment/article.md index a6d89d5ea..6179dc3b0 100644 --- a/1-js/05-data-types/10-destructuring-assignment/article.md +++ b/1-js/05-data-types/10-destructuring-assignment/article.md @@ -5,18 +5,30 @@ - شیءها به ما این امکان را می‌دهند تا چیزی بسازیم که المان‌های داده را به واسطه کلید ذخیره کند. - آرایه‌ها به ما امکان جمع‌آوری المان‌های داده را در لیستی مرتب می‌دهند. +<<<<<<< HEAD اگرچه، زمانی که ما آن‌ها را به تابع می‌دهیم، ممکن است که نیازی به کل یک شیء/آرایه نباشد. شاید تنها قطعه‌های تکی نیاز باشد. *مقداردهیِ تجزیه‌کننده‌ی ساختار (Destructuring assignment)* یک سینتکس خاص است که به ما امکان می‌دهد تا آرایه‌ها یا شیءها را درون چند متغیر «پخش کنیم» چون بعضی اوقات این موضوع کار را راحت‌تر می‌کند. +======= +However, when we pass these to a function, we may not need all of it. The function might only require certain elements or properties. +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b تخریب ساختار همچنین با تابع‌های پیچیده که تعداد زیادی پارامتر، مقدارهای پیش‌فرض و... دارند هم به خوبی کار می‌کند. به زودی آن را خواهیم دید. +<<<<<<< HEAD ## تجزیه ساختار آرایه +======= +Destructuring also works well with complex functions that have a lot of parameters, default values, and so on. Soon we'll see that. +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b کد پایین یک مثال از چگونگی تبدیل یک آرایه به چند متغیر است: ```js +<<<<<<< HEAD // ما یک آرایه شامل نام و نام خانوادگی داریم +======= +// we have an array with a name and surname +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b let arr = ["John", "Smith"] *!* @@ -40,10 +52,17 @@ alert(firstName); // John alert(surname); // Smith ``` +<<<<<<< HEAD همانطور که می‌بینید، سینتکس ساده است. البته چند چیز ویژه در جزییات خود دارد. بیایید برای فهمیدن بهتر آن، مثال‌های بیشتری ببینیم. ````smart header="عبارت «تجزیه‌کننده‌ی ساختار» به معنی «مخرب» نیست." این سینتکس «مقداردهی تجزیه‌کننده‌ی ساختار» نامیده می‌شود چون با کپی کردن المان‌ها در چند متغیر «ساختار را تغییر می‌دهد». اما خود آرایه تغییر نمی‌کند. +======= +As you can see, the syntax is simple. There are several peculiar details though. Let's see more examples to understand it better. + +````smart header="\"Destructuring\" does not mean \"destructive\"." +It's called "destructuring assignment," because it "destructurizes" by copying items into variables. However, the array itself is not modified. +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b فقط یک راه کوتاه‌تر برای نوشتن است: ```js @@ -65,7 +84,11 @@ let [firstName, , title] = ["Julius", "Caesar", "Consul", "of the Roman Republic alert( title ); // Consul ``` +<<<<<<< HEAD در کد بالا، از المان دوم آرایه گذشتیم، المان سوم به `title` تخصیص داده شد و بقیه المان‌های آرایه هم نادیده گرفته شدند (به دلیل اینکه متغیری برای ذخیره آنها وجود ندارد). +======= +In the code above, the second element of the array is skipped, the third one is assigned to `title`, and the rest of the array items are also skipped (as there are no variables for them). +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b ```` ````smart header="با هر حلقه‌پذیری در سمت راست کار می‌کند" @@ -94,10 +117,17 @@ alert(user.surname); // Smith ```` +<<<<<<< HEAD ````smart header="حلقه زدن با .entries()" در فصل قبل ما متد [Object.entries(obj)](mdn:js/Object/entries) را دیدیم. می‌توانیم آن را با تجزیه‌کننده‌ی ساختار برای حلقه زدن در کلیدها و مقدارهای یک شیء استفاده کنیم: +======= +````smart header="Looping with .entries()" +In the previous chapter, we saw the [Object.entries(obj)](mdn:js/Object/entries) method. + +We can use it with destructuring to loop over the keys-and-values of an object: +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b ```js run let user = { @@ -105,7 +135,11 @@ let user = { age: 30 }; +<<<<<<< HEAD // حلقه زدن در کلیدها و مقدارها +======= +// loop over the keys-and-values +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b *!* for (let [key, value] of Object.entries(user)) { */!* @@ -169,7 +203,11 @@ alert(name2); // Caesar let [name1, name2, *!*...rest*/!*] = ["Julius", "Caesar", *!*"Consul", "of the Roman Republic"*/!*]; *!* +<<<<<<< HEAD // آرایه‌ای از المان‌ها است که از المان سوم شروع می‌شود rest +======= +// rest is an array of items, starting from the 3rd one +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b alert(rest[0]); // Consul alert(rest[1]); // of the Roman Republic alert(rest.length); // 2 @@ -187,7 +225,11 @@ let [name1, name2, *!*...titles*/!*] = ["Julius", "Caesar", "Consul", "of the Ro ### مقدارهای پیش‌فرض +<<<<<<< HEAD اگر آرایه از لیست متغیرهای سمت چپ کوتاه‌تر باشد، هیچ اروری ایجاد نمی‌شود. مقدارهای ناموجود undefined در نظر گرفته می‌شوند: +======= +If the array is shorter than the list of variables on the left, there will be no errors. Absent values are considered undefined: +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b ```js run *!* @@ -418,7 +460,11 @@ alert( title ); // Menu ## تجزیه ساختار تودرتو +<<<<<<< HEAD اگر یک شیء یا آرایه، شیء و آرایه‌های تودرتو دیگری را شامل شود، ما می‌توانیم از الگوری پیچیده‌تری در سمت چپ برای استخراج قسمت‌های عمیق‌تر استفاده کنیم. +======= +If an object or an array contains other nested objects and arrays, we can use more complex left-side patterns to extract deeper portions. +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b در کد زیر `options` یک شیء دیگری درون ویژگی `size` و یک آرایه درون ویژگی `items` دارد. الگوی سمت چپ مقداردهی ساختار یکسانی برای استخراج مقدار از آنها را دارد: @@ -449,7 +495,11 @@ alert(item1); // Cake alert(item2); // Donut ``` +<<<<<<< HEAD تمام ویژگی‌های شیء `options` به جز `extra` که در سمت چپ وجود ندارد، به متغیرهای متناظر خود تخصیص داده شده‌اند: +======= +All properties of `options` object except `extra` which is absent in the left part, are assigned to corresponding variables: +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b ![](destructuring-complex.svg) @@ -459,9 +509,15 @@ alert(item2); // Donut ## پارامترهای هوشمند تابع +<<<<<<< HEAD بعضی اوقات پیش می‌آید که یک تابع پارامترهای زیادی داشته باشد که اکثر آنها الزامی نیستند. خصوصا برای رابط کاربری این اتفاق می‌افتد. یک تابع را تصور کنید که یک منو ایجاد می‌کند. این منو ممکن است دارای طول(width)، ارتفاع(height)، عنوان(title)، لیستی از کالاها و غیره باشد. این یک راه برای نوشتن چنین تابعی است: +======= +There are times when a function has many parameters, most of which are optional. That's especially true for user interfaces. Imagine a function that creates a menu. It may have a width, a height, a title, an item list and so on. + +Here's a bad way to write such a function: +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b ```js function showMenu(title = "Untitled", width = 200, height = 100, items = []) { @@ -469,7 +525,11 @@ function showMenu(title = "Untitled", width = 200, height = 100, items = []) { } ``` +<<<<<<< HEAD در واقعیت، مشکل این است که چگونه ترتیب آرگومان‌ها را به یاد بسپاریم. معمولا محیط‌های کدنویسی (IDE) سعی می‌کنند به ما کمک کنند، مخصوصا اگر کد، مستند خوبی داشته باشد اما باز هم... مشکل دیگری که وجود دارد این است که چگونه یک تابع را زمانی که اکثر پارامترها به صورت پیش‌فرض مشکلی ندارند فراخوانی کنیم. +======= +In real-life, the problem is how to remember the order of arguments. Usually, IDEs try to help us, especially if the code is well-documented, but still... Another problem is how to call a function when most parameters are ok by default. +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b مثلا اینگونه؟ @@ -534,7 +594,11 @@ function({ }) ``` +<<<<<<< HEAD سپس به صورت پیش‌فرض، برای یک شیء شامل پارامترها، یک متغیر `varName` (اسم متغیر) برای ویژگی `incomingProperty` (ویژگی ورودی)، همراه با `defaultValue` وجود خواهد داشت. +======= +Then, for an object of parameters, there will be a variable `varName` for the property `incomingProperty`, with `defaultValue` by default. +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b لطفا در نظر داشته باشید که چنین تجزیه ساختاری فرض می‌کند که `showMenu()` یک آرگومان دارد. اگر ما تمام مقدارهای پیش‌فرض را بخواهیم، پس باید یک شیء خالی مشخص کنیم: @@ -561,7 +625,7 @@ showMenu(); // Menu 100 200 - مقداردهی تجزیه‌کننده‌ی ساختار به ما اجازه می‌دهد تا بلافاصله یک شیء یا آرایه را روی بسیاری از متغیرها ترسیم کنیم. - سینتکس کامل شیء: ```js - let {prop : varName = default, ...rest} = object + let {prop : varName = defaultValue, ...rest} = object ``` این به این معنی است که ویژگی `prop` باید درون متغیر `varName` برود و اگر چنین ویژگی‌ای وجود نداشت، سپس مقدار `default` باید استفاده شود. @@ -571,9 +635,13 @@ showMenu(); // Menu 100 200 - سینتکس کامل آرایه: ```js - let [item1 = default, item2, ...rest] = array + let [item1 = defaultValue, item2, ...rest] = array ``` +<<<<<<< HEAD المان اول درون `item1` می‎رود؛ المان دوم درون `item2` می‌شود، تمام المان‌های باقی مانده آرایه `rest` را تشکیل می‌دهند. +======= + The first item goes to `item1`; the second goes into `item2`, and all the rest makes the array `rest`. +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b - استخراج داده از آرایه/شیءهای تودرتو هم ممکن است، برای اینکار سمت چپ باید ساختار یکسانی با سمت راست داشته باشد. \ No newline at end of file diff --git a/1-js/05-data-types/12-json/article.md b/1-js/05-data-types/12-json/article.md index 6e6a52bbf..44daf7de1 100644 --- a/1-js/05-data-types/12-json/article.md +++ b/1-js/05-data-types/12-json/article.md @@ -405,7 +405,7 @@ alert( JSON.stringify(meetup) ); سینتکس آن: ```js -let value = JSON.parse(str, [reviver]); +let value = JSON.parse(str[, reviver]); ``` پارامتر str diff --git a/1-js/06-advanced-functions/06-function-object/article.md b/1-js/06-advanced-functions/06-function-object/article.md index f5cd3b0cc..baf0b4bdd 100644 --- a/1-js/06-advanced-functions/06-function-object/article.md +++ b/1-js/06-advanced-functions/06-function-object/article.md @@ -325,7 +325,11 @@ welcome(); // (فراخوانی تودرتو کار می‌کند) Guest ،سل حالا کار می‌کند چون اسم `"func"` یک تابع محلی است. این اسم از بیرون دریافت نمی‌شود (و آنجا هم قابل رویت نیست). مشخصات زبان تضمین می‌کند که این اسم همیشه به تابع کنونی رجوع می‌کند. +<<<<<<< HEAD کد بیرونی هنوز هم متغیر `sayHi` یا `welcome` خود را دارد. و `func` یک «اسم درونی تابع» است، جوری که تابع می‌توانند از درون خودش را فراخوانی کند. +======= +The outer code still has its variable `sayHi` or `welcome`. And `func` is an "internal function name", the way for the function to call itself reliably. +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b ```smart header="چنین چیزی برای Function Declaration وجود ندارد" خصوصیت «اسم درونی» که اینجا توضیح داده شد فقط برای Function Expessionها قابل استفاده است نه برای Function Declarationها. برای Function Declarationها، سینتکسی برای اضاف کردن اسم «درونی» وجود ندارد. diff --git a/1-js/06-advanced-functions/10-bind/5-question-use-bind/solution.md b/1-js/06-advanced-functions/10-bind/5-question-use-bind/solution.md index b607c4cfe..785b57b54 100644 --- a/1-js/06-advanced-functions/10-bind/5-question-use-bind/solution.md +++ b/1-js/06-advanced-functions/10-bind/5-question-use-bind/solution.md @@ -1,5 +1,9 @@ +<<<<<<< HEAD به دلیل اینکه `ask` تابع‌های `loginOk/loginFail` را بدون شیء دریافت می‌کند ارور ایجاد می‌شود. +======= +The error occurs because `askPassword` gets functions `loginOk/loginFail` without the object. +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b زمانی که این تابع آن‌ها را فرا می‌خواند، به طور طبیعی آن‌ها `this=undefined` را فرض می‌کنند. diff --git a/1-js/06-advanced-functions/10-bind/article.md b/1-js/06-advanced-functions/10-bind/article.md index cca4fcfb8..adb45e44f 100644 --- a/1-js/06-advanced-functions/10-bind/article.md +++ b/1-js/06-advanced-functions/10-bind/article.md @@ -125,7 +125,11 @@ funcUser(); // John */!* ``` +<<<<<<< HEAD اینجا `func.bind(user)` به عنوان «یک نوع پیوند زده شده» از `func` با `this=user` شناخته می‌شود. +======= +Here `func.bind(user)` is a "bound variant" of `func`, with fixed `this=user`. +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b تمام آرگومان‌ها «بدون تغییر» به تابع اصلی `func` منتقل می‌شوند، برای مثال: diff --git a/1-js/08-prototypes/04-prototype-methods/article.md b/1-js/08-prototypes/04-prototype-methods/article.md index 248b88320..fd424aa89 100644 --- a/1-js/08-prototypes/04-prototype-methods/article.md +++ b/1-js/08-prototypes/04-prototype-methods/article.md @@ -14,7 +14,11 @@ اگرچه یک متد خاص برای این کار هم وجود دارد: +<<<<<<< HEAD - [Object.create(proto, [descriptors])](mdn:js/Object/create) -- یک شیء خالی با تنظیم `proto` داده شده به عنوان `[[Prototype]]` و توصیف‌کننده‌های ویژگی اختیاری ایجاد می‌کند. +======= +- [Object.create(proto[, descriptors])](mdn:js/Object/create) -- creates an empty object with given `proto` as `[[Prototype]]` and optional property descriptors. +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b برای مثال: @@ -111,7 +115,11 @@ alert(obj[key]); // [object Object], not "یک مقدار"! در اینجا، اگر کاربر `__proto__` را تایپ کند، انتساب در خط 4 نادیده گرفته می‌شود! +<<<<<<< HEAD این می‌تواند قطعا برای یک غیر توسعه‌دهنده شوکه کننده باشد اما برای ما بسیار قابل فهم است. ویژگی `__proto__` خاص است: باید یک شیء یا `null` باشد. یک رشته نمی‌تواند به یک پروتوتایپ تبدیل شود. به همین دلیل است که انتساب یک رشته به `__proto__` نادیده گرفته می‌شود. +======= +That could surely be surprising for a non-developer, but pretty understandable for us. The `__proto__` property is special: it must be either an object or `null`. A string can not become a prototype. That's why assigning a string to `__proto__` is ignored. +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b اما ما *قصد* اجرای چنین رفتاری را نداشتیم، درست است؟ ما می‌خواهیم جفت‌های کلید/مقدار را ذخیره کنیم، و کلید با نام `"__proto__"` به درستی ذخیره نشده است. پس این یک اشکال است! @@ -195,8 +203,13 @@ alert(Object.keys(chineseDictionary)); // hello,bye - برای ایجاد یک شیء با پروتوتایپ تعیین شده، از این‌ها استفاده کنید: +<<<<<<< HEAD - سینتکس لیترال: `{ __proto__: ...}`، اجازه می‌دهد که چند ویژگی تعیین کنیم - یا [Object.create(proto, [descriptors])](mdn:js/Object/create)، اجازه می‌دهد که توصیف‌کننده‌های ویژگی را تعیین کنیم. +======= + - literal syntax: `{ __proto__: ... }`, allows to specify multiple properties + - or [Object.create(proto[, descriptors])](mdn:js/Object/create), allows to specify property descriptors. +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b - متدهای مدرن برای دریافت/تنظیم پروتوتایپ این‌ها هستند: diff --git a/1-js/10-error-handling/1-try-catch/article.md b/1-js/10-error-handling/1-try-catch/article.md index 429e0eb31..fabda3374 100644 --- a/1-js/10-error-handling/1-try-catch/article.md +++ b/1-js/10-error-handling/1-try-catch/article.md @@ -632,7 +632,11 @@ window.onerror = function(message, url, line, col, error) { معمولا نقش کنترل‌کننده گلوبال `window.onerror` این نیست که اجرای اسکریپت را ترمیم کند -- این موضوع در صورتی که ارور برنامه‌نویسی وجود داشته باشد احتمالا غیر ممکن است اما فرستادن پیام ارور به توسعه‌دهندگان ممکن است. +<<<<<<< HEAD همچنین سرویس‌های وب وجود دارند که رخدادنگاری ارور را برای چنین مواردی فراهم می‌کنند مانند یا . +======= +There are also web-services that provide error-logging for such cases, like or . +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b آن‌ها اینگونه کار می‌کنند: diff --git a/1-js/11-async/02-promise-basics/article.md b/1-js/11-async/02-promise-basics/article.md index 9ce6efc9f..11b9d0ddd 100644 --- a/1-js/11-async/02-promise-basics/article.md +++ b/1-js/11-async/02-promise-basics/article.md @@ -46,7 +46,7 @@ let promise = new Promise(function(resolve, reject) { در اینجا یک نمونه از سازنده Promise و یک تابع اجرا‌کننده ساده با «کد تولید‌کننده» داریم که زمانبر است (از طریق `setTimeout`): -```js run +```js let promise = new Promise(function(resolve, reject) { // ساخته می‌شود به طور خودکار اجرا می‌شود Promise این تابع زمانی که @@ -223,7 +223,11 @@ promise.catch(alert); // .را بعد از 1 ثانیه نشان می‌دهد " به عنوان مثال، نشانگرهای بارگیری(loading indicators) خود را متوقف می‌کنیم، اتصال‌هایی که دیگر نیاز نیستند یا ببندیم و غیره. +<<<<<<< HEAD به عنوان یک پایان‌دهنده مهمانی به آن فکر کنید. مهم نیست که مهمانی خوب یا بد بود یا چند دوست در آن حضور داشتند، ما هنوز نیاز داریم (یا حداقل باید) که بعد از مهمانی تمیزکاری انجام دهیم. +======= +Think of it as a party finisher. Irresepective of whether a party was good or bad, how many friends were in it, we still need (or at least should) do a cleanup after it. +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b کد ما ممکن است اینگونه بنظر برسد: diff --git a/1-js/11-async/08-async-await/04-promise-all-failure/solution.md b/1-js/11-async/08-async-await/04-promise-all-failure/solution.md new file mode 100644 index 000000000..9fda8e000 --- /dev/null +++ b/1-js/11-async/08-async-await/04-promise-all-failure/solution.md @@ -0,0 +1,113 @@ + +The root of the problem is that `Promise.all` immediately rejects when one of its promises rejects, but it do nothing to cancel the other promises. + +In our case, the second query fails, so `Promise.all` rejects, and the `try...catch` block catches this error.Meanwhile, other promises are *not affected* - they independently continue their execution. In our case, the third query throws an error of its own after a bit of time. And that error is never caught, we can see it in the console. + +The problem is especially dangerous in server-side environments, such as Node.js, when an uncaught error may cause the process to crash. + +How to fix it? + +An ideal solution would be to cancel all unfinished queries when one of them fails. This way we avoid any potential errors. + +However, the bad news is that service calls (such as `database.query`) are often implemented by a 3rd-party library which doesn't support cancellation. Then there's no way to cancel a call. + +As an alternative, we can write our own wrapper function around `Promise.all` which adds a custom `then/catch` handler to each promise to track them: results are gathered and, if an error occurs, all subsequent promises are ignored. + +```js +function customPromiseAll(promises) { + return new Promise((resolve, reject) => { + const results = []; + let resultsCount = 0; + let hasError = false; // we'll set it to true upon first error + + promises.forEach((promise, index) => { + promise + .then(result => { + if (hasError) return; // ignore the promise if already errored + results[index] = result; + resultsCount++; + if (resultsCount === promises.length) { + resolve(results); // when all results are ready - successs + } + }) + .catch(error => { + if (hasError) return; // ignore the promise if already errored + hasError = true; // wops, error! + reject(error); // fail with rejection + }); + }); + }); +} +``` + +This approach has an issue of its own - it's often undesirable to `disconnect()` when queries are still in the process. + +It may be important that all queries complete, especially if some of them make important updates. + +So we should wait until all promises are settled before going further with the execution and eventually disconnecting. + +Here's another implementation. It behaves similar to `Promise.all` - also resolves with the first error, but waits until all promises are settled. + +```js +function customPromiseAllWait(promises) { + return new Promise((resolve, reject) => { + const results = new Array(promises.length); + let settledCount = 0; + let firstError = null; + + promises.forEach((promise, index) => { + Promise.resolve(promise) + .then(result => { + results[index] = result; + }) + .catch(error => { + if (firstError === null) { + firstError = error; + } + }) + .finally(() => { + settledCount++; + if (settledCount === promises.length) { + if (firstError !== null) { + reject(firstError); + } else { + resolve(results); + } + } + }); + }); + }); +} +``` + +Now `await customPromiseAllWait(...)` will stall the execution until all queries are processed. + +This is a more reliable approach, as it guarantees a predictable execution flow. + +Lastly, if we'd like to process all errors, we can use either use `Promise.allSettled` or write a wrapper around it to gathers all errors in a single [AggregateError](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/AggregateError) object and rejects with it. + +```js +// wait for all promises to settle +// return results if no errors +// throw AggregateError with all errors if any +function allOrAggregateError(promises) { + return Promise.allSettled(promises).then(results => { + const errors = []; + const values = []; + + results.forEach((res, i) => { + if (res.status === 'fulfilled') { + values[i] = res.value; + } else { + errors.push(res.reason); + } + }); + + if (errors.length > 0) { + throw new AggregateError(errors, 'One or more promises failed'); + } + + return values; + }); +} +``` diff --git a/1-js/11-async/08-async-await/04-promise-all-failure/task.md b/1-js/11-async/08-async-await/04-promise-all-failure/task.md new file mode 100644 index 000000000..74571c43e --- /dev/null +++ b/1-js/11-async/08-async-await/04-promise-all-failure/task.md @@ -0,0 +1,79 @@ + +# Dangerous Promise.all + +`Promise.all` is a great way to parallelize multiple operations. It's especially useful when we need to make parallel requests to multiple services. + +However, there's a hidden danger. We'll see an example in this task and explore how to avoid it. + +Let's say we have a connection to a remote service, such as a database. + +There're two functions: `connect()` and `disconnect()`. + +When connected, we can send requests using `database.query(...)` - an async function which usually returns the result but also may throw an error. + +Here's a simple implementation: + +```js +let database; + +function connect() { + database = { + async query(isOk) { + if (!isOk) throw new Error('Query failed'); + } + }; +} + +function disconnect() { + database = null; +} + +// intended usage: +// connect() +// ... +// database.query(true) to emulate a successful call +// database.query(false) to emulate a failed call +// ... +// disconnect() +``` + +Now here's the problem. + +We wrote the code to connect and send 3 queries in parallel (all of them take different time, e.g. 100, 200 and 300ms), then disconnect: + +```js +// Helper function to call async function `fn` after `ms` milliseconds +function delay(fn, ms) { + return new Promise((resolve, reject) => { + setTimeout(() => fn().then(resolve, reject), ms); + }); +} + +async function run() { + connect(); + + try { + await Promise.all([ + // these 3 parallel jobs take different time: 100, 200 and 300 ms + // we use the `delay` helper to achieve this effect +*!* + delay(() => database.query(true), 100), + delay(() => database.query(false), 200), + delay(() => database.query(false), 300) +*/!* + ]); + } catch(error) { + console.log('Error handled (or was it?)'); + } + + disconnect(); +} + +run(); +``` + +Two of these queries happen to be unsuccessful, but we're smart enough to wrap the `Promise.all` call into a `try..catch` block. + +However, this doesn't help! This script actually leads to an uncaught error in console! + +Why? How to avoid it? \ No newline at end of file diff --git a/1-js/13-modules/02-import-export/article.md b/1-js/13-modules/02-import-export/article.md index 1a1ad451c..b1f1f7943 100644 --- a/1-js/13-modules/02-import-export/article.md +++ b/1-js/13-modules/02-import-export/article.md @@ -96,10 +96,17 @@ say.sayBye('John'); 1. فهرست کردن صریح مواردی که باید وارد شوند، نام‌های کوتاه‌تری را نشان می‌دهد: `sayHi()‎` به جای `say.sayHi()‎`. 2. لیست واضح از import مرور بهتری از ساختار کد ایجاد می‌کند: چه چیزی استفاده شده و کجا. این پشتیبانی و بازبینی کد را آسان تر می‌کند. +<<<<<<< HEAD ```smart header="از import کردن زیاد نترسید." ابزارهای مدرن بیلد مانند [webpack](https://webpack.js.org/) و مانند آن، ماژول ها را با هم باندل (ترکیب و فشرده سازی) می‌کنند و بهینه می‌کنند تا سرعت بارگذاری را افزایش دهند. آنها همچنین import های استفاده نشده را حذف می‌کنند. به عنوان مثال، اگر `import * as library` از یک کتابخانه کد بزرگ import کنیم و سپس تنها از چند تابع آن استفاده کنیم، موارد استفاده نشده [درون بسته بهینه شده نخواهد بود](https://github.com/webpack/webpack/tree/main/examples/harmony-unused#examplejs). +======= +```smart header="Don't be afraid to import too much" +Modern build tools, such as [webpack](https://webpack.js.org/) and others, bundle modules together and optimize them to speedup loading. They also remove unused imports. + +For instance, if you `import * as library` from a huge code library, and then use only few methods, then unused ones [will not be included](https://github.com/webpack/webpack/tree/main/examples/harmony-unused#examplejs) into the optimized bundle. +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b ``` ## Import "as"‎ diff --git a/1-js/99-js-misc/07-weakref-finalizationregistry/article.md b/1-js/99-js-misc/07-weakref-finalizationregistry/article.md new file mode 100644 index 000000000..777bf703c --- /dev/null +++ b/1-js/99-js-misc/07-weakref-finalizationregistry/article.md @@ -0,0 +1,483 @@ + +# WeakRef and FinalizationRegistry + +```warn header="\"Hidden\" features of the language" +This article covers a very narrowly focused topic, that most developers extremely rarely encounter in practice (and may not even be aware of its existence). + +We recommend skipping this chapter if you have just started learning JavaScript. +``` + +Recalling the basic concept of the *reachability principle* from the chapter, +we can note that the JavaScript engine is guaranteed to keep values in memory that are accessible or in use. + +For example: + + +```js +// the user variable holds a strong reference to the object +let user = { name: "John" }; + +// let's overwrite the value of the user variable +user = null; + +// the reference is lost and the object will be deleted from memory + +``` + +Or a similar, but slightly more complicated code with two strong references: + +```js +// the user variable holds a strong reference to the object +let user = { name: "John" }; + +// copied the strong reference to the object into the admin variable +*!* +let admin = user; +*/!* + +// let's overwrite the value of the user variable +user = null; + +// the object is still reachable through the admin variable +``` +The object `{ name: "John" }` would only be deleted from memory if there were no strong references to it (if we also overwrote the value of the `admin` variable). + +In JavaScript, there is a concept called `WeakRef`, which behaves slightly differently in this case. + + +````smart header="Terms: \"Strong reference\", \"Weak reference\"" +**Strong reference** - is a reference to an object or value, that prevents them from being deleted by the garbage collector. Thereby, keeping the object or value in memory, to which it points. + +This means, that the object or value remains in memory and is not collected by the garbage collector as long, as there are active strong references to it. + +In JavaScript, ordinary references to objects are strong references. For example: + +```js +// the user variable holds a strong reference to this object +let user = { name: "John" }; +``` +**Weak reference** - is a reference to an object or value, that does *not* prevent them from being deleted by the garbage collector. +An object or value can be deleted by the garbage collector if, the only remaining references to them are weak references. +```` + +## WeakRef + + +````warn header="Note of caution" +Before we dive into it, it is worth noting that the correct use of the structures discussed in this article requires very careful thought, and they are best avoided if possible. +```` + +`WeakRef` - is an object, that contains a weak reference to another object, called `target` or `referent`. + +The peculiarity of `WeakRef` is that it does not prevent the garbage collector from deleting its referent-object. In other words, a `WeakRef` object does not keep the `referent` object alive. + +Now let's take the `user` variable as the "referent" and create a weak reference from it to the `admin` variable. +To create a weak reference, you need to use the `WeakRef` constructor, passing in the target object (the object you want a weak reference to). + +In our case — this is the `user` variable: + + +```js +// the user variable holds a strong reference to the object +let user = { name: "John" }; + +// the admin variable holds a weak reference to the object +*!* +let admin = new WeakRef(user); +*/!* + +``` + +The diagram below depicts two types of references: a strong reference using the `user` variable and a weak reference using the `admin` variable: + +![](weakref-finalizationregistry-01.svg) + +Then, at some point, we stop using the `user` variable - it gets overwritten, goes out of scope, etc., while keeping the `WeakRef` instance in the `admin` variable: + +```js +// let's overwrite the value of the user variable +user = null; +``` + +A weak reference to an object is not enough to keep it "alive". When the only remaining references to a referent-object are weak references, the garbage collector is free to destroy this object and use its memory for something else. + +However, until the object is actually destroyed, the weak reference may return it, even if there are no more strong references to this object. +That is, our object becomes a kind of "[Schrödinger's cat](https://en.wikipedia.org/wiki/Schr%C3%B6dinger%27s_cat)" – we cannot know for sure whether it's "alive" or "dead": + +![](weakref-finalizationregistry-02.svg) + +At this point, to get the object from the `WeakRef` instance, we will use its `deref()` method. + +The `deref()` method returns the referent-object that the `WeakRef` points to, if the object is still in memory. If the object has been deleted by the garbage collector, then the `deref()` method will return `undefined`: + + +```js +let ref = admin.deref(); + +if (ref) { + // the object is still accessible: we can perform any manipulations with it +} else { + // the object has been collected by the garbage collector +} +``` + +## WeakRef use cases + +`WeakRef` is typically used to create caches or [associative arrays](https://en.wikipedia.org/wiki/Associative_array) that store resource-intensive objects. +This allows one to avoid preventing these objects from being collected by the garbage collector solely based on their presence in the cache or associative array. + +One of the primary examples - is a situation when we have numerous binary image objects (for instance, represented as `ArrayBuffer` or `Blob`), and we want to associate a name or path with each image. +Existing data structures are not quite suitable for these purposes: + +- Using `Map` to create associations between names and images, or vice versa, will keep the image objects in memory since they are present in the `Map` as keys or values. +- `WeakMap` is ineligible for this goal either: because the objects represented as `WeakMap` keys use weak references, and are not protected from deletion by the garbage collector. + +But, in this situation, we need a data structure that would use weak references in its values. + +For this purpose, we can use a `Map` collection, whose values are `WeakRef` instances referring to the large objects we need. +Consequently, we will not keep these large and unnecessary objects in memory longer than they should be. + +Otherwise, this is a way to get the image object from the cache if it is still reachable. +If it has been garbage collected, we will re-generate or re-download it again. + +This way, less memory is used in some situations. + +## Example №1: using WeakRef for caching + +Below is a code snippet that demonstrates the technique of using `WeakRef`. + +In short, we use a `Map` with string keys and `WeakRef` objects as their values. +If the `WeakRef` object has not been collected by the garbage collector, we get it from the cache. +Otherwise, we re-download it again and put it in the cache for further possible reuse: + +```js +function fetchImg() { + // abstract function for downloading images... +} + +function weakRefCache(fetchImg) { // (1) + const imgCache = new Map(); // (2) + + return (imgName) => { // (3) + const cachedImg = imgCache.get(imgName); // (4) + + if (cachedImg?.deref()) { // (5) + return cachedImg?.deref(); + } + + const newImg = fetchImg(imgName); // (6) + imgCache.set(imgName, new WeakRef(newImg)); // (7) + + return newImg; + }; +} + +const getCachedImg = weakRefCache(fetchImg); +``` + +Let's delve into the details of what happened here: +1. `weakRefCache` - is a higher-order function that takes another function, `fetchImg`, as an argument. In this example, we can neglect a detailed description of the `fetchImg` function, since it can be any logic for downloading images. +2. `imgCache` - is a cache of images, that stores cached results of the `fetchImg` function, in the form of string keys (image name) and `WeakRef` objects as their values. +3. Return an anonymous function that takes the image name as an argument. This argument will be used as a key for the cached image. +4. Trying to get the cached result from the cache, using the provided key (image name). +5. If the cache contains a value for the specified key, and the `WeakRef` object has not been deleted by the garbage collector, return the cached result. +6. If there is no entry in the cache with the requested key, or `deref()` method returns `undefined` (meaning that the `WeakRef` object has been garbage collected), the `fetchImg` function downloads the image again. +7. Put the downloaded image into the cache as a `WeakRef` object. + +Now we have a `Map` collection, where the keys - are image names as strings, and values - are `WeakRef` objects containing the images themselves. + +This technique helps to avoid allocating a large amount of memory for resource-intensive objects, that nobody uses anymore. +It also saves memory and time in case of reusing cached objects. + +Here is a visual representation of what this code looks like: + +![](weakref-finalizationregistry-03.svg) + +But, this implementation has its drawbacks: over time, `Map` will be filled with strings as keys, that point to a `WeakRef`, whose referent-object has already been garbage collected: + +![](weakref-finalizationregistry-04.svg) + +One way to handle this problem - is to periodically scavenge the cache and clear out "dead" entries. +Another way - is to use finalizers, which we will explore next. + + +## Example №2: Using WeakRef to track DOM objects + +Another use case for `WeakRef` - is tracking DOM objects. + +Let's imagine a scenario where some third-party code or library interacts with elements on our page as long as they exist in the DOM. +For example, it could be an external utility for monitoring and notifying about the system's state (commonly so-called "logger" – a program that sends informational messages called "logs"). + +Interactive example: + +[codetabs height=420 src="weakref-dom"] + +When the "Start sending messages" button is clicked, in the so-called "logs display window" (an element with the `.window__body` class), messages (logs) start to appear. + +But, as soon as this element is deleted from the DOM, the logger should stop sending messages. +To reproduce the removal of this element, just click the "Close" button in the top right corner. + +In order not to complicate our work, and not to notify third-party code every time our DOM-element is available, and when it is not, it will be enough to create a weak reference to it using `WeakRef`. + +Once the element is removed from the DOM, the logger will notice it and stop sending messages. + +Now let's take a closer look at the source code (*tab `index.js`*): + +1. Get the DOM-element of the "Start sending messages" button. +2. Get the DOM-element of the "Close" button. +3. Get the DOM-element of the logs display window using the `new WeakRef()` constructor. This way, the `windowElementRef` variable holds a weak reference to the DOM-element. +4. Add an event listener on the "Start sending messages" button, responsible for starting the logger when clicked. +5. Add an event listener on the "Close" button, responsible for closing the logs display window when clicked. +6. Use `setInterval` to start displaying a new message every second. +7. If the DOM-element of the logs display window is still accessible and kept in memory, create and send a new message. +8. If the `deref()` method returns `undefined`, it means that the DOM-element has been deleted from memory. In this case, the logger stops displaying messages and clears the timer. +9. `alert`, which will be called, after the DOM-element of the logs display window is deleted from memory (i.e. after clicking the "Close" button). **Note, that deletion from memory may not happen immediately, as it depends only on the internal mechanisms of the garbage collector.** + + We cannot control this process directly from the code. However, despite this, we still have the option to force garbage collection from the browser. + + In Google Chrome, for example, to do this, you need to open the developer tools (`key:Ctrl` + `key:Shift` + `key:J` on Windows/Linux or `key:Option` + `key:⌘` + `key:J` on macOS), go to the "Performance" tab, and click on the bin icon button – "Collect garbage": + + ![](google-chrome-developer-tools.png) + +
+ This functionality is supported in most modern browsers. After the actions are taken, the alert will trigger immediately. + +## FinalizationRegistry + +Now it is time to talk about finalizers. Before we move on, let's clarify the terminology: + +**Cleanup callback (finalizer)** - is a function that is executed, when an object, registered in the `FinalizationRegistry`, is deleted from memory by the garbage collector. + +Its purpose - is to provide the ability to perform additional operations, related to the object, after it has been finally deleted from memory. + +**Registry** (or `FinalizationRegistry`) - is a special object in JavaScript that manages the registration and unregistration of objects and their cleanup callbacks. + +This mechanism allows registering an object to track and associate a cleanup callback with it. +Essentially it is a structure that stores information about registered objects and their cleanup callbacks, and then automatically invokes those callbacks when the objects are deleted from memory. + +To create an instance of the `FinalizationRegistry`, it needs to call its constructor, which takes a single argument - the cleanup callback (finalizer). + +Syntax: + +```js +function cleanupCallback(heldValue) { + // cleanup callback code +} + +const registry = new FinalizationRegistry(cleanupCallback); +``` + +Here: + +- `cleanupCallback` - a cleanup callback that will be automatically called when a registered object is deleted from memory. +- `heldValue` - the value that is passed as an argument to the cleanup callback. If `heldValue` is an object, the registry keeps a strong reference to it. +- `registry` - an instance of `FinalizationRegistry`. + +`FinalizationRegistry` methods: + +- `register(target, heldValue [, unregisterToken])` - used to register objects in the registry. + + `target` - the object being registered for tracking. If the `target` is garbage collected, the cleanup callback will be called with `heldValue` as its argument. + + Optional `unregisterToken` – an unregistration token. It can be passed to unregister an object before the garbage collector deletes it. Typically, the `target` object is used as `unregisterToken`, which is the standard practice. +- `unregister(unregisterToken)` - the `unregister` method is used to unregister an object from the registry. It takes one argument - `unregisterToken` (the unregister token that was obtained when registering the object). + +Now let's move on to a simple example. Let's use the already-known `user` object and create an instance of `FinalizationRegistry`: + +```js +let user = { name: "John" }; + +const registry = new FinalizationRegistry((heldValue) => { + console.log(`${heldValue} has been collected by the garbage collector.`); +}); +``` + +Then, we will register the object, that requires a cleanup callback by calling the `register` method: + +```js +registry.register(user, user.name); +``` + +The registry does not keep a strong reference to the object being registered, as this would defeat its purpose. If the registry kept a strong reference, then the object would never be garbage collected. + +If the object is deleted by the garbage collector, our cleanup callback may be called at some point in the future, with the `heldValue` passed to it: + +```js +// When the user object is deleted by the garbage collector, the following message will be printed in the console: +"John has been collected by the garbage collector." +``` + +There are also situations where, even in implementations that use a cleanup callback, there is a chance that it will not be called. + +For example: +- When the program fully terminates its operation (for example, when closing a tab in a browser). +- When the `FinalizationRegistry` instance itself is no longer reachable to JavaScript code. + If the object that creates the `FinalizationRegistry` instance goes out of scope or is deleted, the cleanup callbacks registered in that registry might also not be invoked. + +## Caching with FinalizationRegistry + +Returning to our *weak* cache example, we can notice the following: +- Even though the values wrapped in the `WeakRef` have been collected by the garbage collector, there is still an issue of "memory leakage" in the form of the remaining keys, whose values have been collected by the garbage collector. + +Here is an improved caching example using `FinalizationRegistry`: + +```js +function fetchImg() { + // abstract function for downloading images... +} + +function weakRefCache(fetchImg) { + const imgCache = new Map(); + + *!* + const registry = new FinalizationRegistry((imgName) => { // (1) + const cachedImg = imgCache.get(imgName); + if (cachedImg && !cachedImg.deref()) imgCache.delete(imgName); + }); + */!* + + return (imgName) => { + const cachedImg = imgCache.get(imgName); + + if (cachedImg?.deref()) { + return cachedImg?.deref(); + } + + const newImg = fetchImg(imgName); + imgCache.set(imgName, new WeakRef(newImg)); + *!* + registry.register(newImg, imgName); // (2) + */!* + + return newImg; + }; +} + +const getCachedImg = weakRefCache(fetchImg); +``` + +1. To manage the cleanup of "dead" cache entries, when the associated `WeakRef` objects are collected by the garbage collector, we create a `FinalizationRegistry` cleanup registry. + + The important point here is, that in the cleanup callback, it should be checked, if the entry was deleted by the garbage collector and not re-added, in order not to delete a "live" entry. +2. Once the new value (image) is downloaded and put into the cache, we register it in the finalizer registry to track the `WeakRef` object. + +This implementation contains only actual or "live" key/value pairs. +In this case, each `WeakRef` object is registered in the `FinalizationRegistry`. +And after the objects are cleaned up by the garbage collector, the cleanup callback will delete all `undefined` values. + +Here is a visual representation of the updated code: + +![](weakref-finalizationregistry-05.svg) + +A key aspect of the updated implementation is that finalizers allow parallel processes to be created between the "main" program and cleanup callbacks. +In the context of JavaScript, the "main" program - is our JavaScript-code, that runs and executes in our application or web page. + +Hence, from the moment an object is marked for deletion by the garbage collector, and to the actual execution of the cleanup callback, there may be a certain time gap. +It is important to understand that during this time gap, the main program can make any changes to the object or even bring it back to memory. + +That's why, in the cleanup callback, we must check to see if an entry has been added back to the cache by the main program to avoid deleting "live" entries. +Similarly, when searching for a key in the cache, there is a chance that the value has been deleted by the garbage collector, but the cleanup callback has not been executed yet. + +Such situations require special attention if you are working with `FinalizationRegistry`. + +## Using WeakRef and FinalizationRegistry in practice + +Moving from theory to practice, imagine a real-life scenario, where a user synchronizes their photos on a mobile device with some cloud service +(such as [iCloud](https://en.wikipedia.org/wiki/ICloud) or [Google Photos](https://en.wikipedia.org/wiki/Google_Photos)), +and wants to view them from other devices. In addition to the basic functionality of viewing photos, such services offer a lot of additional features, for example: + +- Photo editing and video effects. +- Creating "memories" and albums. +- Video montage from a series of photos. +- ...and much more. + +Here, as an example, we will use a fairly primitive implementation of such a service. +The main point - is to show a possible scenario of using `WeakRef` and `FinalizationRegistry` together in real life. + +Here is what it looks like: + +![](weakref-finalizationregistry-demo-01.png) + +
+On the left side, there is a cloud library of photos (they are displayed as thumbnails). +We can select the images we need and create a collage, by clicking the "Create collage" button on the right side of the page. +Then, the resulting collage can be downloaded as an image. +

+ +To increase page loading speed, it would be reasonable to download and display photo thumbnails in *compressed* quality. +But, to create a collage from selected photos, download and use them in *full-size* quality. + +Below, we can see, that the intrinsic size of the thumbnails is 240x240 pixels. +The size was chosen on purpose to increase loading speed. +Moreover, we do not need full-size photos in preview mode. + +![](weakref-finalizationregistry-demo-02.png) + +
+Let's assume, that we need to create a collage of 4 photos: we select them, and then click the "Create collage" button. +At this stage, the already known to us weakRefCache function checks whether the required image is in the cache. +If not, it downloads it from the cloud and puts it in the cache for further use. +This happens for each selected image: +

+ +![](weakref-finalizationregistry-demo-03.gif) + +
+ +Paying attention to the output in the console, you can see, which of the photos were downloaded from the cloud - this is indicated by FETCHED_IMAGE. +Since this is the first attempt to create a collage, this means, that at this stage the "weak cache" was still empty, and all the photos were downloaded from the cloud and put in it. + +But, along with the process of downloading images, there is also a process of memory cleanup by the garbage collector. +This means, that the object stored in the cache, which we refer to, using a weak reference, is deleted by the garbage collector. +And our finalizer executes successfully, thereby deleting the key, by which the image was stored in the cache. +CLEANED_IMAGE notifies us about it: + +![](weakref-finalizationregistry-demo-04.jpg) + +
+Next, we realize that we do not like the resulting collage, and decide to change one of the images and create a new one. +To do this, just deselect the unnecessary image, select another one, and click the "Create collage" button again: +

+ +![](weakref-finalizationregistry-demo-05.gif) + +
+But this time not all images were downloaded from the network, and one of them was taken from the weak cache: the CACHED_IMAGE message tells us about it. +This means that at the time of collage creation, the garbage collector had not yet deleted our image, and we boldly took it from the cache, +thereby reducing the number of network requests and speeding up the overall time of the collage creation process: +

+ +![](weakref-finalizationregistry-demo-06.jpg) + +
+Let's "play around" a little more, by replacing one of the images again and creating a new collage: +

+ +![](weakref-finalizationregistry-demo-07.gif) + +
+This time the result is even more impressive. Of the 4 images selected, 3 of them were taken from the weak cache, and only one had to be downloaded from the network. +The reduction in network load was about 75%. Impressive, isn't it? +

+ +![](weakref-finalizationregistry-demo-08.jpg) + +
+ +Of course, it is important to remember, that such behavior is not guaranteed, and depends on the specific implementation and operation of the garbage collector. + +Based on this, a completely logical question immediately arises: why do not we use an ordinary cache, where we can manage its entities ourselves, instead of relying on the garbage collector? +That's right, in the vast majority of cases there is no need to use `WeakRef` and `FinalizationRegistry`. + +Here, we simply demonstrated an alternative implementation of similar functionality, using a non-trivial approach with interesting language features. +Still, we cannot rely on this example, if we need a constant and predictable result. + +You can [open this example in the sandbox](sandbox:weakref-finalizationregistry). + +## Summary + +`WeakRef` - designed to create weak references to objects, allowing them to be deleted from memory by the garbage collector if there are no longer strong references to them. +This is beneficial for addressing excessive memory usage and optimizing the utilization of system resources in applications. + +`FinalizationRegistry` - is a tool for registering callbacks, that are executed when objects that are no longer strongly referenced, are destroyed. +This allows releasing resources associated with the object or performing other necessary operations before deleting the object from memory. \ No newline at end of file diff --git a/1-js/99-js-misc/07-weakref-finalizationregistry/google-chrome-developer-tools.png b/1-js/99-js-misc/07-weakref-finalizationregistry/google-chrome-developer-tools.png new file mode 100644 index 000000000..021637342 Binary files /dev/null and b/1-js/99-js-misc/07-weakref-finalizationregistry/google-chrome-developer-tools.png differ diff --git a/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-dom.view/index.css b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-dom.view/index.css new file mode 100644 index 000000000..f6df812d0 --- /dev/null +++ b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-dom.view/index.css @@ -0,0 +1,49 @@ +.app { + display: flex; + flex-direction: column; + gap: 16px; +} + +.start-messages { + width: fit-content; +} + +.window { + width: 100%; + border: 2px solid #464154; + overflow: hidden; +} + +.window__header { + position: sticky; + padding: 8px; + display: flex; + justify-content: space-between; + align-items: center; + background-color: #736e7e; +} + +.window__title { + margin: 0; + font-size: 24px; + font-weight: 700; + color: white; + letter-spacing: 1px; +} + +.window__button { + padding: 4px; + background: #4f495c; + outline: none; + border: 2px solid #464154; + color: white; + font-size: 16px; + cursor: pointer; +} + +.window__body { + height: 250px; + padding: 16px; + overflow: scroll; + background-color: #736e7e33; +} \ No newline at end of file diff --git a/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-dom.view/index.html b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-dom.view/index.html new file mode 100644 index 000000000..7f93af4c7 --- /dev/null +++ b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-dom.view/index.html @@ -0,0 +1,28 @@ + + + + + + + WeakRef DOM Logger + + + + +
+ +
+
+

Messages:

+ +
+
+ No messages. +
+
+
+ + + + + diff --git a/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-dom.view/index.js b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-dom.view/index.js new file mode 100644 index 000000000..ea55b4478 --- /dev/null +++ b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-dom.view/index.js @@ -0,0 +1,24 @@ +const startMessagesBtn = document.querySelector('.start-messages'); // (1) +const closeWindowBtn = document.querySelector('.window__button'); // (2) +const windowElementRef = new WeakRef(document.querySelector(".window__body")); // (3) + +startMessagesBtn.addEventListener('click', () => { // (4) + startMessages(windowElementRef); + startMessagesBtn.disabled = true; +}); + +closeWindowBtn.addEventListener('click', () => document.querySelector(".window__body").remove()); // (5) + + +const startMessages = (element) => { + const timerId = setInterval(() => { // (6) + if (element.deref()) { // (7) + const payload = document.createElement("p"); + payload.textContent = `Message: System status OK: ${new Date().toLocaleTimeString()}`; + element.deref().append(payload); + } else { // (8) + alert("The element has been deleted."); // (9) + clearInterval(timerId); + } + }, 1000); +}; \ No newline at end of file diff --git a/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-01.svg b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-01.svg new file mode 100644 index 000000000..2a507dbcd --- /dev/null +++ b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-01.svg @@ -0,0 +1,32 @@ + + + + + + + + user + + name: "John" + Object + + <global> + + + + + + + + + + + + + + + + admin + + + \ No newline at end of file diff --git a/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-02.svg b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-02.svg new file mode 100644 index 000000000..6cc199a12 --- /dev/null +++ b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-02.svg @@ -0,0 +1,33 @@ + + + + + + + + + + <global> + + + name: "John" + Object + + + + + + + + + + + + admin + + + + + + + \ No newline at end of file diff --git a/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-03.svg b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-03.svg new file mode 100644 index 000000000..949a14f9f --- /dev/null +++ b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-03.svg @@ -0,0 +1,75 @@ + + + + + + + + + + + + + + + key + value + image-01.jpg + image-02.jpg + image-03.jpg + + + + + + + + + + + + + + WeakRef object + + + + + + + + + + + + + + + + WeakRef object + + + + + + + + + + + + + + + + + + + WeakRef object + + + + + + + \ No newline at end of file diff --git a/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-04.svg b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-04.svg new file mode 100644 index 000000000..1177d6580 --- /dev/null +++ b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-04.svg @@ -0,0 +1,77 @@ + + + + + + + name: "John" + Object + + admin + + + + + + + + + key + value + image-01.jpg + image-02.jpg + image-03.jpg + + + + + + + + + + + + + + WeakRef object + + + + + + + + + + + + + + + + WeakRef object + + + + + undefined + undefined + + + + + + + + + + + + + + + WeakRef object + + + \ No newline at end of file diff --git a/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-05.svg b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-05.svg new file mode 100644 index 000000000..e738f8e7e --- /dev/null +++ b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-05.svg @@ -0,0 +1,103 @@ + + + + + + + + + + + + + + + + image-02.jpg + image-03.jpg + + key + value + image-01.jpg + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + WeakRef object + + + + + + + + + + + + + + + + WeakRef object + + + + + undefined + undefined + Deleted by FinalizationRegistry cleanup callback + + + + + + + + + + + + + + + WeakRef object + + + + \ No newline at end of file diff --git a/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-demo-01.png b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-demo-01.png new file mode 100644 index 000000000..fc33a023a Binary files /dev/null and b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-demo-01.png differ diff --git a/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-demo-02.png b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-demo-02.png new file mode 100644 index 000000000..7d8bb01e8 Binary files /dev/null and b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-demo-02.png differ diff --git a/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-demo-03.gif b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-demo-03.gif new file mode 100644 index 000000000..b81966dda Binary files /dev/null and b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-demo-03.gif differ diff --git a/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-demo-04.jpg b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-demo-04.jpg new file mode 100644 index 000000000..ba60f1e86 Binary files /dev/null and b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-demo-04.jpg differ diff --git a/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-demo-05.gif b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-demo-05.gif new file mode 100644 index 000000000..d34bda4d7 Binary files /dev/null and b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-demo-05.gif differ diff --git a/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-demo-06.jpg b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-demo-06.jpg new file mode 100644 index 000000000..b2655540f Binary files /dev/null and b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-demo-06.jpg differ diff --git a/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-demo-07.gif b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-demo-07.gif new file mode 100644 index 000000000..51f874518 Binary files /dev/null and b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-demo-07.gif differ diff --git a/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-demo-08.jpg b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-demo-08.jpg new file mode 100644 index 000000000..5f98aec14 Binary files /dev/null and b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-demo-08.jpg differ diff --git a/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry.view/index.css b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry.view/index.css new file mode 100644 index 000000000..e6c9e3960 --- /dev/null +++ b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry.view/index.css @@ -0,0 +1,285 @@ +:root { + --mineralGreen: 60, 98, 85; + --viridianGreen: 97, 135, 110; + --swampGreen: 166, 187, 141; + --fallGreen: 234, 231, 177; + --brinkPink: #FA7070; + --silverChalice: 178, 178, 178; + --white: 255, 255, 255; + --black: 0, 0, 0; + + --topBarHeight: 64px; + --itemPadding: 32px; + --containerGap: 8px; +} + +@keyframes zoom-in { + 0% { + transform: scale(1, 1); + } + + 100% { + transform: scale(1.30, 1.30); + } +} + +body, html { + margin: 0; + padding: 0; +} + +.app { + min-height: 100vh; + background-color: rgba(var(--viridianGreen), 0.5); +} + +.header { + height: var(--topBarHeight); + padding: 0 24px; + display: flex; + justify-content: space-between; + align-items: center; + background-color: rgba(var(--mineralGreen), 1); +} + +.header-text { + color: white; +} + +.container { + display: flex; + gap: 24px; + padding: var(--itemPadding); +} + +.item { + width: 50%; +} + +.item--scrollable { + overflow-y: scroll; + height: calc(100vh - var(--topBarHeight) - (var(--itemPadding) * 2)); +} + +.thumbnails-container { + display: flex; + flex-wrap: wrap; + gap: 8px; + justify-content: center; + align-items: center; +} + +.thumbnail-item { + width: calc(25% - var(--containerGap)); + cursor: pointer; + position: relative; +} + +.thumbnail-item:hover { + z-index: 1; + animation: zoom-in 0.1s forwards; +} + +.thumbnail-item--selected { + outline: 3px solid rgba(var(--fallGreen), 1); + outline-offset: -3px; +} + +.badge { + width: 16px; + height: 16px; + display: flex; + justify-content: center; + align-items: center; + padding: 4px; + position: absolute; + right: 8px; + bottom: 8px; + border-radius: 50%; + border: 2px solid rgba(var(--fallGreen), 1); + background-color: rgba(var(--swampGreen), 1); +} + +.check { + display: inline-block; + transform: rotate(45deg); + border-bottom: 2px solid white; + border-right: 2px solid white; + width: 6px; + height: 12px; +} + +.img { + width: 100%; + height: 100%; + object-fit: cover; +} + +.actions { + display: flex; + flex-wrap: wrap; + justify-content: center; + align-content: center; + padding: 0 0 16px 0; + gap: 8px; +} + +.select { + padding: 16px; + cursor: pointer; + font-weight: 700; + color: rgba(var(--black), 1); + border: 2px solid rgba(var(--swampGreen), 0.5); + background-color: rgba(var(--swampGreen), 1); +} + +.select:disabled { + cursor: not-allowed; + background-color: rgba(var(--silverChalice), 1); + color: rgba(var(--black), 0.5); + border: 2px solid rgba(var(--black), 0.25); +} + +.btn { + outline: none; + padding: 16px; + cursor: pointer; + font-weight: 700; + color: rgba(var(--black), 1); + border: 2px solid rgba(var(--black), 0.5); +} + +.btn--primary { + background-color: rgba(var(--mineralGreen), 1); +} + +.btn--primary:hover:not([disabled]) { + background-color: rgba(var(--mineralGreen), 0.85); +} + +.btn--secondary { + background-color: rgba(var(--viridianGreen), 0.5); +} + +.btn--secondary:hover:not([disabled]) { + background-color: rgba(var(--swampGreen), 0.25); +} + +.btn--success { + background-color: rgba(var(--fallGreen), 1); +} + +.btn--success:hover:not([disabled]) { + background-color: rgba(var(--fallGreen), 0.85); +} + +.btn:disabled { + cursor: not-allowed; + background-color: rgba(var(--silverChalice), 1); + color: rgba(var(--black), 0.5); + border: 2px solid rgba(var(--black), 0.25); +} + +.previewContainer { + margin-bottom: 16px; + display: flex; + width: 100%; + height: 40vh; + overflow: scroll; + border: 3px solid rgba(var(--black), 1); +} + +.previewContainer--disabled { + background-color: rgba(var(--black), 0.1); + cursor: not-allowed; +} + +.canvas { + margin: auto; + display: none; +} + +.canvas--ready { + display: block; +} + +.spinnerContainer { + display: flex; + gap: 8px; + flex-direction: column; + align-content: center; + align-items: center; + margin: auto; +} + +.spinnerContainer--hidden { + display: none; +} + +.spinnerText { + margin: 0; + color: rgba(var(--mineralGreen), 1); +} + +.spinner { + display: inline-block; + width: 50px; + height: 50px; + margin: auto; + border: 3px solid rgba(var(--mineralGreen), 0.3); + border-radius: 50%; + border-top-color: rgba(var(--mineralGreen), 0.9); + animation: spin 1s ease-in-out infinite; +} + +@keyframes spin { + to { + transform: rotate(360deg); + } +} + +.loggerContainer { + display: flex; + flex-direction: column; + gap: 8px; + padding: 0 8px 8px 8px; + width: 100%; + min-height: 30vh; + max-height: 30vh; + overflow: scroll; + border-left: 3px solid rgba(var(--black), 0.25); +} + +.logger-title { + display: flex; + align-items: center; + padding: 8px; + position: sticky; + height: 40px; + min-height: 40px; + top: 0; + left: 0; + background-color: rgba(var(--viridianGreen), 1); + font-size: 24px; + font-weight: 700; + margin: 0; +} + +.logger-item { + font-size: 14px; + padding: 8px; + border: 2px solid #5a5a5a; + color: white; +} + +.logger--primary { + background-color: #13315a; +} + +.logger--success { + background-color: #385a4e; +} + +.logger--error { + background-color: #5a1a24; +} \ No newline at end of file diff --git a/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry.view/index.html b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry.view/index.html new file mode 100644 index 000000000..7ce52f927 --- /dev/null +++ b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry.view/index.html @@ -0,0 +1,49 @@ + + + + + + + Photo Library Collage + + + + +
+
+

+ Photo Library Collage +

+
+
+
+ +
+
+
+
+
+ + + + +
+
+
+
+

+
+ +
+
+

Logger:

+
+
+
+
+
+ + + + + diff --git a/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry.view/index.js b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry.view/index.js new file mode 100644 index 000000000..983b34d9a --- /dev/null +++ b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry.view/index.js @@ -0,0 +1,228 @@ +import { + createImageFile, + loadImage, + weakRefCache, + LAYOUTS, + images, + THUMBNAIL_PARAMS, + stateObj, +} from "./utils.js"; + +export const state = new Proxy(stateObj, { + set(target, property, value) { + const previousValue = target[property]; + + target[property] = value; + + if (previousValue !== value) { + handleStateChange(target); + } + + return true; + }, +}); + +// Elements. +const thumbnailsContainerEl = document.querySelector(".thumbnails-container"); +const selectEl = document.querySelector(".select"); +const previewContainerEl = document.querySelector(".previewContainer"); +const canvasEl = document.querySelector(".canvas"); +const createCollageBtn = document.querySelector(".btn-create-collage"); +const startOverBtn = document.querySelector(".btn-start-over"); +const downloadBtn = document.querySelector(".btn-download"); +const spinnerContainerEl = document.querySelector(".spinnerContainer"); +const spinnerTextEl = document.querySelector(".spinnerText"); +const loggerContainerEl = document.querySelector(".loggerContainer"); + +// Renders. +// Render thumbnails previews. +images.forEach((img) => { + const thumbnail = document.createElement("div"); + thumbnail.classList.add("thumbnail-item"); + + thumbnail.innerHTML = ` + + `; + + thumbnail.addEventListener("click", (e) => handleSelection(e, img)); + + thumbnailsContainerEl.appendChild(thumbnail); +}); +// Render layouts select. +LAYOUTS.forEach((layout) => { + const option = document.createElement("option"); + option.value = JSON.stringify(layout); + option.innerHTML = layout.name; + selectEl.appendChild(option); +}); + +const handleStateChange = (state) => { + if (state.loading) { + selectEl.disabled = true; + createCollageBtn.disabled = true; + startOverBtn.disabled = true; + downloadBtn.disabled = true; + previewContainerEl.classList.add("previewContainer--disabled"); + spinnerContainerEl.classList.remove("spinnerContainer--hidden"); + spinnerTextEl.innerText = "Loading..."; + canvasEl.classList.remove("canvas--ready"); + } else if (!state.loading) { + selectEl.disabled = false; + createCollageBtn.disabled = false; + startOverBtn.disabled = false; + downloadBtn.disabled = false; + previewContainerEl.classList.remove("previewContainer--disabled"); + spinnerContainerEl.classList.add("spinnerContainer--hidden"); + canvasEl.classList.add("canvas--ready"); + } + + if (!state.selectedImages.size) { + createCollageBtn.disabled = true; + document.querySelectorAll(".badge").forEach((item) => item.remove()); + } else if (state.selectedImages.size && !state.loading) { + createCollageBtn.disabled = false; + } + + if (!state.collageRendered) { + downloadBtn.disabled = true; + } else if (state.collageRendered) { + downloadBtn.disabled = false; + } +}; +handleStateChange(state); + +const handleSelection = (e, imgName) => { + const imgEl = e.currentTarget; + + imgEl.classList.toggle("thumbnail-item--selected"); + + if (state.selectedImages.has(imgName)) { + state.selectedImages.delete(imgName); + state.selectedImages = new Set(state.selectedImages); + imgEl.querySelector(".badge")?.remove(); + } else { + state.selectedImages = new Set(state.selectedImages.add(imgName)); + + const badge = document.createElement("div"); + badge.classList.add("badge"); + badge.innerHTML = ` +
+ `; + imgEl.prepend(badge); + } +}; + +// Make a wrapper function. +let getCachedImage; +(async () => { + getCachedImage = await weakRefCache(loadImage); +})(); + +const calculateGridRows = (blobsLength) => + Math.ceil(blobsLength / state.currentLayout.columns); + +const drawCollage = (images) => { + state.drawing = true; + + let context = canvasEl.getContext("2d"); + + /** + * Calculate canvas dimensions based on the current layout. + * */ + context.canvas.width = + state.currentLayout.itemWidth * state.currentLayout.columns; + context.canvas.height = + calculateGridRows(images.length) * state.currentLayout.itemHeight; + + let currentRow = 0; + let currentCanvasDx = 0; + let currentCanvasDy = 0; + + for (let i = 0; i < images.length; i++) { + /** + * Get current row of the collage. + * */ + if (i % state.currentLayout.columns === 0) { + currentRow += 1; + currentCanvasDx = 0; + + if (currentRow > 1) { + currentCanvasDy += state.currentLayout.itemHeight; + } + } + + context.drawImage( + images[i], + 0, + 0, + images[i].width, + images[i].height, + currentCanvasDx, + currentCanvasDy, + state.currentLayout.itemWidth, + state.currentLayout.itemHeight, + ); + + currentCanvasDx += state.currentLayout.itemWidth; + } + + state.drawing = false; + state.collageRendered = true; +}; + +const createCollage = async () => { + state.loading = true; + + const images = []; + + for (const image of state.selectedImages.values()) { + const blobImage = await getCachedImage(image.img); + + const url = URL.createObjectURL(blobImage); + const img = await createImageFile(url); + + images.push(img); + URL.revokeObjectURL(url); + } + + state.loading = false; + + drawCollage(images); +}; + +/** + * Clear all settled data to start over. + * */ +const startOver = () => { + state.selectedImages = new Set(); + state.collageRendered = false; + const context = canvasEl.getContext("2d"); + context.clearRect(0, 0, canvasEl.width, canvasEl.height); + + document + .querySelectorAll(".thumbnail-item--selected") + .forEach((item) => item.classList.remove("thumbnail-item--selected")); + + loggerContainerEl.innerHTML = '

Logger:

'; +}; + +const downloadCollage = () => { + const date = new Date(); + const fileName = `Collage-${date.getDay()}-${date.getMonth()}-${date.getFullYear()}.png`; + const img = canvasEl.toDataURL("image/png"); + const link = document.createElement("a"); + link.download = fileName; + link.href = img; + link.click(); + link.remove(); +}; + +const changeLayout = ({ target }) => { + state.currentLayout = JSON.parse(target.value); +}; + +// Listeners. +selectEl.addEventListener("change", changeLayout); +createCollageBtn.addEventListener("click", createCollage); +startOverBtn.addEventListener("click", startOver); +downloadBtn.addEventListener("click", downloadCollage); diff --git a/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry.view/utils.js b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry.view/utils.js new file mode 100644 index 000000000..f0140c116 --- /dev/null +++ b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry.view/utils.js @@ -0,0 +1,321 @@ +const loggerContainerEl = document.querySelector(".loggerContainer"); + +export const images = [ + { + img: "https://images.unsplash.com/photo-1471357674240-e1a485acb3e1", + }, + { + img: "https://images.unsplash.com/photo-1589118949245-7d38baf380d6", + }, + { + img: "https://images.unsplash.com/photo-1527631746610-bca00a040d60", + }, + { + img: "https://images.unsplash.com/photo-1500835556837-99ac94a94552", + }, + { + img: "https://images.unsplash.com/photo-1503220317375-aaad61436b1b", + }, + { + img: "https://images.unsplash.com/photo-1501785888041-af3ef285b470", + }, + { + img: "https://images.unsplash.com/photo-1528543606781-2f6e6857f318", + }, + { + img: "https://images.unsplash.com/photo-1523906834658-6e24ef2386f9", + }, + { + img: "https://images.unsplash.com/photo-1539635278303-d4002c07eae3", + }, + { + img: "https://images.unsplash.com/photo-1533105079780-92b9be482077", + }, + { + img: "https://images.unsplash.com/photo-1516483638261-f4dbaf036963", + }, + { + img: "https://images.unsplash.com/photo-1502791451862-7bd8c1df43a7", + }, + { + img: "https://plus.unsplash.com/premium_photo-1663047367140-91adf819d007", + }, + { + img: "https://images.unsplash.com/photo-1506197603052-3cc9c3a201bd", + }, + { + img: "https://images.unsplash.com/photo-1517760444937-f6397edcbbcd", + }, + { + img: "https://images.unsplash.com/photo-1518684079-3c830dcef090", + }, + { + img: "https://images.unsplash.com/photo-1505832018823-50331d70d237", + }, + { + img: "https://images.unsplash.com/photo-1524850011238-e3d235c7d4c9", + }, + { + img: "https://plus.unsplash.com/premium_photo-1661277758451-b5053309eea1", + }, + { + img: "https://images.unsplash.com/photo-1541410965313-d53b3c16ef17", + }, + { + img: "https://images.unsplash.com/photo-1528702748617-c64d49f918af", + }, + { + img: "https://images.unsplash.com/photo-1502003148287-a82ef80a6abc", + }, + { + img: "https://plus.unsplash.com/premium_photo-1661281272544-5204ea3a481a", + }, + { + img: "https://images.unsplash.com/photo-1503457574462-bd27054394c1", + }, + { + img: "https://images.unsplash.com/photo-1499363536502-87642509e31b", + }, + { + img: "https://images.unsplash.com/photo-1551918120-9739cb430c6d", + }, + { + img: "https://plus.unsplash.com/premium_photo-1661382219642-43e54f7e81d7", + }, + { + img: "https://images.unsplash.com/photo-1497262693247-aa258f96c4f5", + }, + { + img: "https://images.unsplash.com/photo-1525254134158-4fd5fdd45793", + }, + { + img: "https://plus.unsplash.com/premium_photo-1661274025419-4c54107d5c48", + }, + { + img: "https://images.unsplash.com/photo-1553697388-94e804e2f0f6", + }, + { + img: "https://images.unsplash.com/photo-1574260031597-bcd9eb192b4f", + }, + { + img: "https://images.unsplash.com/photo-1536323760109-ca8c07450053", + }, + { + img: "https://images.unsplash.com/photo-1527824404775-dce343118ebc", + }, + { + img: "https://images.unsplash.com/photo-1612278675615-7b093b07772d", + }, + { + img: "https://images.unsplash.com/photo-1522010675502-c7b3888985f6", + }, + { + img: "https://images.unsplash.com/photo-1501555088652-021faa106b9b", + }, + { + img: "https://plus.unsplash.com/premium_photo-1669223469435-27e091439169", + }, + { + img: "https://images.unsplash.com/photo-1506012787146-f92b2d7d6d96", + }, + { + img: "https://images.unsplash.com/photo-1511739001486-6bfe10ce785f", + }, + { + img: "https://images.unsplash.com/photo-1553342385-111fd6bc6ab3", + }, + { + img: "https://images.unsplash.com/photo-1516546453174-5e1098a4b4af", + }, + { + img: "https://images.unsplash.com/photo-1527142879-95b61a0b8226", + }, + { + img: "https://images.unsplash.com/photo-1520466809213-7b9a56adcd45", + }, + { + img: "https://images.unsplash.com/photo-1516939884455-1445c8652f83", + }, + { + img: "https://images.unsplash.com/photo-1545389336-cf090694435e", + }, + { + img: "https://plus.unsplash.com/premium_photo-1669223469455-b7b734c838f4", + }, + { + img: "https://images.unsplash.com/photo-1454391304352-2bf4678b1a7a", + }, + { + img: "https://images.unsplash.com/photo-1433838552652-f9a46b332c40", + }, + { + img: "https://images.unsplash.com/photo-1506125840744-167167210587", + }, + { + img: "https://images.unsplash.com/photo-1522199873717-bc67b1a5e32b", + }, + { + img: "https://images.unsplash.com/photo-1495904786722-d2b5a19a8535", + }, + { + img: "https://images.unsplash.com/photo-1614094082869-cd4e4b2905c7", + }, + { + img: "https://images.unsplash.com/photo-1474755032398-4b0ed3b2ae5c", + }, + { + img: "https://images.unsplash.com/photo-1501554728187-ce583db33af7", + }, + { + img: "https://images.unsplash.com/photo-1515859005217-8a1f08870f59", + }, + { + img: "https://images.unsplash.com/photo-1531141445733-14c2eb7d4c1f", + }, + { + img: "https://images.unsplash.com/photo-1500259783852-0ca9ce8a64dc", + }, + { + img: "https://images.unsplash.com/photo-1510662145379-13537db782dc", + }, + { + img: "https://images.unsplash.com/photo-1573790387438-4da905039392", + }, + { + img: "https://images.unsplash.com/photo-1512757776214-26d36777b513", + }, + { + img: "https://images.unsplash.com/photo-1518855706573-84de4022b69b", + }, + { + img: "https://images.unsplash.com/photo-1500049242364-5f500807cdd7", + }, + { + img: "https://images.unsplash.com/photo-1528759335187-3b683174c86a", + }, +]; +export const THUMBNAIL_PARAMS = "w=240&h=240&fit=crop&auto=format"; + +// Console styles. +export const CONSOLE_BASE_STYLES = [ + "font-size: 12px", + "padding: 4px", + "border: 2px solid #5a5a5a", + "color: white", +].join(";"); +export const CONSOLE_PRIMARY = [ + CONSOLE_BASE_STYLES, + "background-color: #13315a", +].join(";"); +export const CONSOLE_SUCCESS = [ + CONSOLE_BASE_STYLES, + "background-color: #385a4e", +].join(";"); +export const CONSOLE_ERROR = [ + CONSOLE_BASE_STYLES, + "background-color: #5a1a24", +].join(";"); + +// Layouts. +export const LAYOUT_4_COLUMNS = { + name: "Layout 4 columns", + columns: 4, + itemWidth: 240, + itemHeight: 240, +}; +export const LAYOUT_8_COLUMNS = { + name: "Layout 8 columns", + columns: 8, + itemWidth: 240, + itemHeight: 240, +}; +export const LAYOUTS = [LAYOUT_4_COLUMNS, LAYOUT_8_COLUMNS]; + +export const createImageFile = async (src) => + new Promise((resolve, reject) => { + const img = new Image(); + img.src = src; + img.onload = () => resolve(img); + img.onerror = () => reject(new Error("Failed to construct image.")); + }); + +export const loadImage = async (url) => { + try { + const response = await fetch(url); + if (!response.ok) { + throw new Error(String(response.status)); + } + + return await response.blob(); + } catch (e) { + console.log(`%cFETCHED_FAILED: ${e}`, CONSOLE_ERROR); + } +}; + +export const weakRefCache = (fetchImg) => { + const imgCache = new Map(); + const registry = new FinalizationRegistry(({ imgName, size, type }) => { + const cachedImg = imgCache.get(imgName); + if (cachedImg && !cachedImg.deref()) { + imgCache.delete(imgName); + console.log( + `%cCLEANED_IMAGE: Url: ${imgName}, Size: ${size}, Type: ${type}`, + CONSOLE_ERROR, + ); + + const logEl = document.createElement("div"); + logEl.classList.add("logger-item", "logger--error"); + logEl.innerHTML = `CLEANED_IMAGE: Url: ${imgName}, Size: ${size}, Type: ${type}`; + loggerContainerEl.appendChild(logEl); + loggerContainerEl.scrollTop = loggerContainerEl.scrollHeight; + } + }); + + return async (imgName) => { + const cachedImg = imgCache.get(imgName); + + if (cachedImg?.deref() !== undefined) { + console.log( + `%cCACHED_IMAGE: Url: ${imgName}, Size: ${cachedImg.size}, Type: ${cachedImg.type}`, + CONSOLE_SUCCESS, + ); + + const logEl = document.createElement("div"); + logEl.classList.add("logger-item", "logger--success"); + logEl.innerHTML = `CACHED_IMAGE: Url: ${imgName}, Size: ${cachedImg.size}, Type: ${cachedImg.type}`; + loggerContainerEl.appendChild(logEl); + loggerContainerEl.scrollTop = loggerContainerEl.scrollHeight; + + return cachedImg?.deref(); + } + + const newImg = await fetchImg(imgName); + console.log( + `%cFETCHED_IMAGE: Url: ${imgName}, Size: ${newImg.size}, Type: ${newImg.type}`, + CONSOLE_PRIMARY, + ); + + const logEl = document.createElement("div"); + logEl.classList.add("logger-item", "logger--primary"); + logEl.innerHTML = `FETCHED_IMAGE: Url: ${imgName}, Size: ${newImg.size}, Type: ${newImg.type}`; + loggerContainerEl.appendChild(logEl); + loggerContainerEl.scrollTop = loggerContainerEl.scrollHeight; + + imgCache.set(imgName, new WeakRef(newImg)); + registry.register(newImg, { + imgName, + size: newImg.size, + type: newImg.type, + }); + + return newImg; + }; +}; + +export const stateObj = { + loading: false, + drawing: true, + collageRendered: false, + currentLayout: LAYOUTS[0], + selectedImages: new Set(), +}; diff --git a/2-ui/3-event-details/6-pointer-events/article.md b/2-ui/3-event-details/6-pointer-events/article.md index de4d8e632..ecc144712 100644 --- a/2-ui/3-event-details/6-pointer-events/article.md +++ b/2-ui/3-event-details/6-pointer-events/article.md @@ -126,7 +126,7 @@ Here is the flow of user actions and the corresponding events: So the issue is that the browser "hijacks" the interaction: `pointercancel` fires in the beginning of the "drag-and-drop" process, and no more `pointermove` events are generated. ```online -Here's the drag'n'drop demo with loggin of pointer events (only `up/down`, `move` and `cancel`) in the `textarea`: +Here's the drag'n'drop demo with logging of pointer events (only `up/down`, `move` and `cancel`) in the `textarea`: [iframe src="ball" height=240 edit] ``` diff --git a/2-ui/4-forms-controls/1-form-elements/article.md b/2-ui/4-forms-controls/1-form-elements/article.md index 48c7ad1ca..08dffe5a3 100644 --- a/2-ui/4-forms-controls/1-form-elements/article.md +++ b/2-ui/4-forms-controls/1-form-elements/article.md @@ -245,7 +245,11 @@ option = new Option(text, value, defaultSelected, selected); - `defaultSelected` -- ایجاد می‌شود `selected` HTML-attribute باشد `true` اگر - `selected` -- انتخاب می‌شود option باشد `true` اگر +<<<<<<< HEAD .انتخاب شده است یا نه option مشخص می‌کند که آیا `selected` در حالی که (آن را بگیریم `option.getAttribute('selected')` که ما می‌توانیم با) می‌کند set را HTML-attribute مقدار `defaultSelected` در این است که `selected` و `defaultSelected` تفاوت بین +======= +The difference between `defaultSelected` and `selected` is that `defaultSelected` sets the HTML-attribute (that we can get using `option.getAttribute('selected')`), while `selected` sets whether the option is selected or not. +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b (`false` یا به سادگی حذف شوند، مقدار پیش‌فرض هر دو) .باشند `false` یا `true` در عمل باید معمولا _هر دو_ مقدار diff --git a/2-ui/4-forms-controls/3-events-change-input/article.md b/2-ui/4-forms-controls/3-events-change-input/article.md index 21298b94c..98f8a073a 100644 --- a/2-ui/4-forms-controls/3-events-change-input/article.md +++ b/2-ui/4-forms-controls/3-events-change-input/article.md @@ -97,7 +97,11 @@ در تمام مرورگرها به جز Firefox ممنوع است که با `dispatchEvent` یک سری "custom" clipboard event ایجاد کنیم. و حتی اگر بخاهیم چنین eventهایی را ارسال کنیم، مضخصات به وضوح بیان می‌کنند که چنین "syntetic" eventهایی نباید به clipboard دسترسی داشته باشند. +<<<<<<< HEAD حتی اگر کسی تصمیم بگیرد که `event.clipboardData` را در یک event handler ذخیره کند و بعدا به آن دسترسی داشته باشد -- کار نخواهد کرد. +======= +It's forbidden to generate "custom" clipboard events with `dispatchEvent` in all browsers except Firefox. And even if we manage to dispatch such event, the specification clearly states that such "synthetic" events must not provide access to the clipboard. +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b برای تکرار، [event.clipboardData](https://www.w3.org/TR/clipboard-apis/#clipboardevent-clipboarddata) تنها در زمینه‌ی user-initiated event handler کار می‌کند. diff --git a/2-ui/99-ui-misc/02-selection-range/article.md b/2-ui/99-ui-misc/02-selection-range/article.md index 93ba656a2..878c720f7 100644 --- a/2-ui/99-ui-misc/02-selection-range/article.md +++ b/2-ui/99-ui-misc/02-selection-range/article.md @@ -356,7 +356,11 @@ Click buttons to run methods on the selection, "resetExample" to reset it. ```smart header="انتخاب پایان/شروع در مقابل محدوده" +<<<<<<< HEAD یک تفاوت بسیار مهم بین selection anchor/focus و `Range` start/end +======= +There's an important difference between a selection anchor/focus compared with a `Range` start/end. +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b همانطور که می دانیم، اشیاء `Range` همیشه شروع خود را قبل از پایان دارند. diff --git a/2-ui/99-ui-misc/03-event-loop/article.md b/2-ui/99-ui-misc/03-event-loop/article.md index 48232753d..ce9372cbf 100644 --- a/2-ui/99-ui-misc/03-event-loop/article.md +++ b/2-ui/99-ui-misc/03-event-loop/article.md @@ -17,7 +17,11 @@ - آنها را با قدیمی ترین کار شروع کنید. 2. بخوابید تا زمانی که یک کار ظاهر شود، سپس به 1 بروید. +<<<<<<< HEAD ین یک رسمی سازی برای چیزی است که هنگام مرور یک صفحه می بینیم. موتور جاوا اسکریپت در اکثر مواقع هیچ کاری انجام نمی دهد، فقط در صورتی اجرا می شود که یک اسکریپت/هندلر/رویداد فعال شود. +======= +That's a formalization of what we see when browsing a page. The JavaScript engine does nothing most of the time, it only runs if a script/handler/event activates. +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b نمونه هایی از وظایف: @@ -30,6 +34,7 @@ ممکن است زمانی اتفاق بیفتد که یک کار در حالی که موتور مشغول است بیاید، سپس در نوبت قرار گیرد. +<<<<<<< HEAD وظایف یک صف تشکیل می دهند که اصطلاحاً به آن "macrotask queue" (v8 term) می گویند: ![](eventLoop.svg) @@ -37,12 +42,29 @@ به عنوان مثال، در حالی که موتور مشغول اجرای یک `script` است، یک کاربر ممکن است ماوس خود را حرکت دهد و باعث `mousemove` شود، و `setTimeout` ممکن است به دلیل وجود داشته باشد و غیره، این وظایف یک صف تشکیل می دهند، همانطور که در تصویر بالا نشان داده شده است. وظایف از صف بر اساس "first come – first served" پردازش می شود. وقتی مرورگر موتور با `script` تمام شد، رویداد `mousemove` و سپس `setTimeout`و غیره را کنترل می‌کند. +======= +It may happen that a task comes while the engine is busy, then it's enqueued. + +The tasks form a queue, the so-called "macrotask queue" ([v8](https://v8.dev/) term): + +![](eventLoop.svg) + +For instance, while the engine is busy executing a `script`, a user may move their mouse causing `mousemove`, and `setTimeout` may be due and so on, these tasks form a queue, as illustrated in the picture above. + +Tasks from the queue are processed on a "first come – first served" basis. When the engine browser is done with the `script`, it handles `mousemove` event, then `setTimeout` handler, and so on. +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b تا اینجا، کاملا ساده، درست است؟ +<<<<<<< HEAD دو جزئیات دیگر: 1. رندر هرگز اتفاق نمی افتد در حالی که موتور یک کار را اجرا می کند. مهم نیست که کار زمان زیادی ببرد. تغییرات در DOM فقط پس از تکمیل کار انجام می شود. 2. اگر یک کار بیش از حد طولانی شود، مرورگر نمی تواند کارهای دیگری مانند پردازش رویدادهای کاربر را انجام دهد. بنابراین پس از مدتی، هشداری مانند "Page Unresponsive" را مطرح می‌کند که نشان می‌دهد کار با کل صفحه از بین می‌رود. این زمانی اتفاق می افتد که محاسبات پیچیده زیادی وجود داشته باشد یا یک خطای برنامه نویسی منجر به یک حلقه بی نهایت شود. +======= +Two more details: +1. Rendering never happens while the engine executes a task. It doesn't matter if the task takes a long time. Changes to the DOM are painted only after the task is complete. +2. If a task takes too long, the browser can't do other tasks, such as processing user events. So after some time, it raises an alert like "Page Unresponsive", suggesting killing the task with the whole page. That happens when there are a lot of complex calculations or a programming error leading to an infinite loop. +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b این نظریه بود. حال بیایید ببینیم چگونه می توانیم این دانش را به کار ببریم. @@ -54,7 +76,11 @@ در حالی که موتور مشغول برجسته‌سازی نحو است، نمی‌تواند سایر کارهای مربوط به DOM، پردازش رویدادهای کاربر و غیره را انجام دهد. حتی ممکن است باعث شود مرورگر برای مدتی "hiccup" یا حتی "hang" کند، که غیرقابل قبول است. +<<<<<<< HEAD ما می توانیم با تقسیم کار بزرگ به قطعات از مشکلات جلوگیری کنیم. ابتدا 100 خط را برجسته کنید، سپس `setTimeout` (با تاخیر صفر) را برای 100 خط بعدی برنامه ریزی کنید، و به همین ترتیب. +======= +We can avoid problems by splitting the big task into pieces. Highlight the first 100 lines, then schedule `setTimeout` (with zero-delay) for the next 100 lines, and so on. +>>>>>>> 540d753e90789205fc6e75c502f68382c87dea9b برای نشان دادن این رویکرد، به‌خاطر سادگی، به‌جای برجسته‌سازی متن، تابعی را انتخاب می‌کنیم که از `1` تا `1000000000` محاسبه می‌شود. diff --git a/6-data-storage/01-cookie/article.md b/6-data-storage/01-cookie/article.md index 01c0e1fee..1b9e93414 100644 --- a/6-data-storage/01-cookie/article.md +++ b/6-data-storage/01-cookie/article.md @@ -2,17 +2,17 @@ Cookies are small strings of data that are stored directly in the browser. They are a part of the HTTP protocol, defined by the [RFC 6265](https://tools.ietf.org/html/rfc6265) specification. -Cookies are usually set by a web-server using the response `Set-Cookie` HTTP-header. Then, the browser automatically adds them to (almost) every request to the same domain using the `Cookie` HTTP-header. +Cookies are usually set by a web server using the response `Set-Cookie` HTTP header. Then, the browser automatically adds them to (almost) every request to the same domain using the `Cookie` HTTP header. One of the most widespread use cases is authentication: -1. Upon sign in, the server uses the `Set-Cookie` HTTP-header in the response to set a cookie with a unique "session identifier". -2. Next time when the request is sent to the same domain, the browser sends the cookie over the net using the `Cookie` HTTP-header. +1. Upon sign-in, the server uses the `Set-Cookie` HTTP header in the response to set a cookie with a unique "session identifier". +2. Next time the request is sent to the same domain, the browser sends the cookie over the net using the `Cookie` HTTP header. 3. So the server knows who made the request. We can also access cookies from the browser, using `document.cookie` property. -There are many tricky things about cookies and their options. In this chapter we'll cover them in detail. +There are many tricky things about cookies and their attributes. In this chapter, we'll cover them in detail. ## Reading from document.cookie @@ -31,17 +31,17 @@ alert( document.cookie ); // cookie1=value1; cookie2=value2;... ``` -The value of `document.cookie` consists of `name=value` pairs, delimited by `; `. Each one is a separate cookie. +The value of `document.cookie` consists of `name=value` pairs, delimited by `; `. Each one is a separate cookie. -To find a particular cookie, we can split `document.cookie` by `; `, and then find the right name. We can use either a regular expression or array functions to do that. +To find a particular cookie, we can split `document.cookie` by `; `, and then find the right name. We can use either a regular expression or array functions to do that. -We leave it as an exercise for the reader. Also, at the end of the chapter you'll find helper functions to manipulate cookies. +We leave it as an exercise for the reader. Also, at the end of the chapter, you'll find helper functions to manipulate cookies. ## Writing to document.cookie We can write to `document.cookie`. But it's not a data property, it's an [accessor (getter/setter)](info:property-accessors). An assignment to it is treated specially. -**A write operation to `document.cookie` updates only cookies mentioned in it, but doesn't touch other cookies.** +**A write operation to `document.cookie` updates only the cookie mentioned in it and doesn't touch other cookies.** For instance, this call sets a cookie with the name `user` and value `John`: @@ -50,12 +50,12 @@ document.cookie = "user=John"; // update only cookie named 'user' alert(document.cookie); // show all cookies ``` -If you run it, then probably you'll see multiple cookies. That's because the `document.cookie=` operation does not overwrite all cookies. It only sets the mentioned cookie `user`. +If you run it, you will likely see multiple cookies. That's because the `document.cookie=` operation does not overwrite all cookies. It only sets the mentioned cookie `user`. Technically, name and value can have any characters. To keep the valid formatting, they should be escaped using a built-in `encodeURIComponent` function: ```js run -// special characters (spaces), need encoding +// special characters (spaces) need encoding let name = "my name"; let value = "John Smith" @@ -67,29 +67,20 @@ alert(document.cookie); // ...; my%20name=John%20Smith ```warn header="Limitations" -There are few limitations: +There are a few limitations: +- You can only set/update a single cookie at a time using `document.cookie`. - The `name=value` pair, after `encodeURIComponent`, should not exceed 4KB. So we can't store anything huge in a cookie. - The total number of cookies per domain is limited to around 20+, the exact limit depends on the browser. ``` -Cookies have several options, many of them are important and should be set. +Cookies have several attributes, many of which are important and should be set. -The options are listed after `key=value`, delimited by `;`, like this: +The attributes are listed after `key=value`, delimited by `;`, like this: ```js run document.cookie = "user=John; path=/; expires=Tue, 19 Jan 2038 03:14:07 GMT" ``` -## path - -- **`path=/mypath`** - -The url path prefix must be absolute. It makes the cookie accessible for pages under that path. By default, it's the current path. - -If a cookie is set with `path=/admin`, it's visible at pages `/admin` and `/admin/something`, but not at `/home` or `/adminpage`. - -Usually, we should set `path` to the root: `path=/` to make the cookie accessible from all website pages. - ## domain - **`domain=site.com`** @@ -102,7 +93,7 @@ It's a safety restriction, to allow us to store sensitive data in cookies that s By default, a cookie is accessible only at the domain that set it. -Please note, by default a cookie is also not shared to a subdomain as well, such as `forum.site.com`. +Please note, by default, a cookie is not shared with a subdomain, such as `forum.site.com`. ```js // if we set a cookie at site.com website... @@ -114,7 +105,7 @@ alert(document.cookie); // no user ...But this can be changed. If we'd like to allow subdomains like `forum.site.com` to get a cookie set at `site.com`, that's possible. -For that to happen, when setting a cookie at `site.com`, we should explicitly set the `domain` option to the root domain: `domain=site.com`. Then all subdomains will see such cookie. +For that to happen, when setting a cookie at `site.com`, we should explicitly set the `domain` attribute to the root domain: `domain=site.com`. Then all subdomains will see such a cookie. For example: @@ -129,19 +120,31 @@ document.cookie = "user=John; *!*domain=site.com*/!*" alert(document.cookie); // has cookie user=John ``` -For historical reasons, `domain=.site.com` (with a dot before `site.com`) also works the same way, allowing access to the cookie from subdomains. That's an old notation and should be used if we need to support very old browsers. +```warn header="Legacy syntax" +Historically, `domain=.site.com` (with a dot before `site.com`) used to work the same way, allowing access to the cookie from subdomains. Leading dots in domain names are now ignored, but some browsers may decline to set the cookie containing such dots. +``` + +To summarize, the `domain` attribute allows to make a cookie accessible at subdomains. + +## path + +- **`path=/mypath`** + +The URL path prefix must be absolute. It makes the cookie accessible for pages under that path. By default, it's the current path. + +If a cookie is set with `path=/admin`, it's visible on pages `/admin` and `/admin/something`, but not at `/home`, `/home/admin` or `/`. -To summarize, the `domain` option allows to make a cookie accessible at subdomains. +Usually, we should set `path` to the root: `path=/` to make the cookie accessible from all website pages. If this attribute is not set the default is calculated using [this method](https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies#path_default_value). ## expires, max-age -By default, if a cookie doesn't have one of these options, it disappears when the browser is closed. Such cookies are called "session cookies" +By default, if a cookie doesn't have one of these attributes, it disappears when the browser/tab is closed. Such cookies are called "session cookies" -To let cookies survive a browser close, we can set either the `expires` or `max-age` option. +To let cookies survive a browser close, we can set either the `expires` or `max-age` attribute. `max-Age` has precedence if both are set. - **`expires=Tue, 19 Jan 2038 03:14:07 GMT`** -The cookie expiration date defines the time, when the browser will automatically delete it. +The cookie expiration date defines the time when the browser will automatically delete it (according to the browser's time zone). The date must be exactly in this format, in the GMT timezone. We can use `date.toUTCString` to get it. For instance, we can set the cookie to expire in 1 day: @@ -178,7 +181,7 @@ The cookie should be transferred only over HTTPS. That is, cookies are domain-based, they do not distinguish between the protocols. -With this option, if a cookie is set by `https://site.com`, then it doesn't appear when the same site is accessed by HTTP, as `http://site.com`. So if a cookie has sensitive content that should never be sent over unencrypted HTTP, the `secure` flag is the right thing. +With this attribute, if a cookie is set by `https://site.com`, then it doesn't appear when the same site is accessed by HTTP, as `http://site.com`. So if a cookie has sensitive content that should never be sent over unencrypted HTTP, the `secure` flag is the right thing. ```js // assuming we're on https:// now @@ -188,49 +191,49 @@ document.cookie = "user=John; secure"; ## samesite -That's another security attribute `samesite`. It's designed to protect from so-called XSRF (cross-site request forgery) attacks. +This is another security attribute `samesite`. It's designed to protect from so-called XSRF (cross-site request forgery) attacks. To understand how it works and when it's useful, let's take a look at XSRF attacks. ### XSRF attack -Imagine, you are logged into the site `bank.com`. That is: you have an authentication cookie from that site. Your browser sends it to `bank.com` with every request, so that it recognizes you and performs all sensitive financial operations. +Imagine, you are logged into the site `bank.com`. That is: you have an authentication cookie from that site. Your browser sends it to `bank.com` with every request so that it recognizes you and performs all sensitive financial operations. Now, while browsing the web in another window, you accidentally come to another site `evil.com`. That site has JavaScript code that submits a form `
` to `bank.com` with fields that initiate a transaction to the hacker's account. -The browser sends cookies every time you visit the site `bank.com`, even if the form was submitted from `evil.com`. So the bank recognizes you and actually performs the payment. +The browser sends cookies every time you visit the site `bank.com`, even if the form was submitted from `evil.com`. So the bank recognizes you and performs the payment. ![](cookie-xsrf.svg) -That's a so-called "Cross-Site Request Forgery" (in short, XSRF) attack. +This is a so-called "Cross-Site Request Forgery" (in short, XSRF) attack. -Real banks are protected from it of course. All forms generated by `bank.com` have a special field, a so-called "XSRF protection token", that an evil page can't generate or extract from a remote page. It can submit a form there, but can't get the data back. The site `bank.com` checks for such token in every form it receives. +Real banks are protected from it of course. All forms generated by `bank.com` have a special field, a so-called "XSRF protection token", that an evil page can't generate or extract from a remote page. It can submit a form there, but can't get the data back. The site `bank.com` checks for such a token in every form it receives. Such a protection takes time to implement though. We need to ensure that every form has the required token field, and we must also check all requests. -### Enter cookie samesite option +### Use cookie samesite attribute -The cookie `samesite` option provides another way to protect from such attacks, that (in theory) should not require "xsrf protection tokens". +The cookie `samesite` attribute provides another way to protect from such attacks, that (in theory) should not require "xsrf protection tokens". It has two possible values: -- **`samesite=strict` (same as `samesite` without value)** +- **`samesite=strict`** A cookie with `samesite=strict` is never sent if the user comes from outside the same site. -In other words, whether a user follows a link from their mail or submits a form from `evil.com`, or does any operation that originates from another domain, the cookie is not sent. +In other words, whether a user follows a link from their email, submits a form from `evil.com`, or does any operation that originates from another domain, the cookie is not sent. -If authentication cookies have the `samesite` option, then a XSRF attack has no chances to succeed, because a submission from `evil.com` comes without cookies. So `bank.com` will not recognize the user and will not proceed with the payment. +If authentication cookies have the `samesite=strict` attribute, then an XSRF attack has no chance of succeeding, because a submission from `evil.com` comes without cookies. So `bank.com` will not recognize the user and will not proceed with the payment. -The protection is quite reliable. Only operations that come from `bank.com` will send the `samesite` cookie, e.g. a form submission from another page at `bank.com`. +The protection is quite reliable. Only operations that come from `bank.com` will send the `samesite=strict` cookie, e.g. a form submission from another page at `bank.com`. Although, there's a small inconvenience. -When a user follows a legitimate link to `bank.com`, like from their own notes, they'll be surprised that `bank.com` does not recognize them. Indeed, `samesite=strict` cookies are not sent in that case. +When a user follows a legitimate link to `bank.com`, like from their notes, they'll be surprised that `bank.com` does not recognize them. Indeed, `samesite=strict` cookies are not sent in that case. -We could work around that by using two cookies: one for "general recognition", only for the purposes of saying: "Hello, John", and the other one for data-changing operations with `samesite=strict`. Then, a person coming from outside of the site will see a welcome, but payments must be initiated from the bank's website, for the second cookie to be sent. +We could work around that by using two cookies: one for "general recognition", only to say: "Hello, John", and the other one for data-changing operations with `samesite=strict`. Then, a person coming from outside of the site will see a welcome, but payments must be initiated from the bank's website, for the second cookie to be sent. -- **`samesite=lax`** +- **`samesite=lax` (same as `samesite` without value)** A more relaxed approach that also protects from XSRF and doesn't break the user experience. @@ -239,40 +242,40 @@ Lax mode, just like `strict`, forbids the browser to send cookies when coming fr A `samesite=lax` cookie is sent if both of these conditions are true: 1. The HTTP method is "safe" (e.g. GET, but not POST). - The full list of safe HTTP methods is in the [RFC7231 specification](https://tools.ietf.org/html/rfc7231). Basically, these are the methods that should be used for reading, but not writing the data. They must not perform any data-changing operations. Following a link is always GET, the safe method. + The full list of safe HTTP methods is in the [RFC7231 specification](https://tools.ietf.org/html/rfc7231#section-4.2.1). These are the methods that should be used for reading, but not writing the data. They must not perform any data-changing operations. Following a link is always GET, the safe method. 2. The operation performs a top-level navigation (changes URL in the browser address bar). - That's usually true, but if the navigation is performed in an `