diff --git a/1-js/01-getting-started/1-intro/article.md b/1-js/01-getting-started/1-intro/article.md index c048430b3..512216467 100644 --- a/1-js/01-getting-started/1-intro/article.md +++ b/1-js/01-getting-started/1-intro/article.md @@ -29,7 +29,13 @@ من الجيد تذكر المصطلحات الواردة أعلاه لأنها مستخدمة في مقالات المطورين على الإنترنت. سنستخدمهم أيضًا. على سبيل المثال ، إذا كانت "الميزة إكس مدعومة بواسطة في8" ، فمن المحتمل أنها تعمل في كروم و أوبرا. +<<<<<<< HEAD ```smart header="كيف تعمل المحركات؟" +======= +- [V8](https://en.wikipedia.org/wiki/V8_(JavaScript_engine)) -- in Chrome and Opera. +- [SpiderMonkey](https://en.wikipedia.org/wiki/SpiderMonkey) -- in Firefox. +- ...There are other codenames like "Chakra" for IE, "JavaScriptCore", "Nitro" and "SquirrelFish" for Safari, etc. +>>>>>>> 4d01fc20d4d82358e61518a31efe80dec9bb2602 المحركات معقدة. ولكن الأساسيات بسيطه . diff --git a/1-js/01-getting-started/4-devtools/article.md b/1-js/01-getting-started/4-devtools/article.md index 3dded1566..5f657c4fa 100644 --- a/1-js/01-getting-started/4-devtools/article.md +++ b/1-js/01-getting-started/4-devtools/article.md @@ -8,7 +8,11 @@ يفضل معظم المطورون العمل على متصفِّحي Chrome أو FireFox لاحتوائهما على أفضل أدوات المطوّر. توفر المتصفِّحات الأخرى أيضًا مجموعة أدوات للمطوّر والتي من الممكن أن تحتوي على مزايا خاصة. لكن عادةً ما تحاول اللحاق بمتصفِّحي Chrome و FireFox الأفضل من هذه الناحية. يفضل المطوّرون بشكل عام العمل على متصفِّح واحد وينتقلون إلى متصفِّح آخر عندما تكون المشكلة التي يعملون عليها محدَّدة بهذا المتصفِّح. +<<<<<<< HEAD بناءً على ذلك، نجد أنَّ أدوات المطور مهمة للغاية لما تمتلكه من مزايا تساعدك أثناء مسيرتك في تطوير الويب عبر JavaScript. سنتعلم في البداية طريقة فتحها، واستخدامها لاستكشاف الأخطاء، وتشغيل تعليمات JavaScript ضمنها. +======= +Developer tools are potent; they have many features. To start, we'll learn how to open them, look at errors, and run JavaScript commands. +>>>>>>> 4d01fc20d4d82358e61518a31efe80dec9bb2602 أدوات المطور قوية, لديها العديد من الميزات. للبدء, سنتعلم كيفية فتحها ، والنظر في الأخطاء ، وتشغيل أوامر جافا سكريبت. diff --git a/1-js/02-first-steps/02-structure/article.md b/1-js/02-first-steps/02-structure/article.md index 6285c198d..c7d0c5d73 100644 --- a/1-js/02-first-steps/02-structure/article.md +++ b/1-js/02-first-steps/02-structure/article.md @@ -46,6 +46,7 @@ alert(3+ قريبًا خلال الشيفرات التي ستكتبها). إذا كنت ترغب في الاطلاع على مثال واقعي عن هذه الحالة، إليك الشيفرة البرمجية التالية: +<<<<<<< HEAD ``` [1, 2].foreach(alert) ``` @@ -55,10 +56,36 @@ alert(3+ ``` alert("There will be an error") [1 ,2].forEach(alert) +======= +The code outputs `6` because JavaScript does not insert semicolons here. It is intuitively obvious that if the line ends with a plus `"+"`, then it is an "incomplete expression", so a semicolon there would be incorrect. And in this case, that works as intended. + +**But there are situations where JavaScript "fails" to assume a semicolon where it is really needed.** + +Errors which occur in such cases are quite hard to find and fix. + +````smart header="An example of an error" +If you're curious to see a concrete example of such an error, check this code out: + +```js run +alert("Hello"); + +[1, 2].forEach(alert); +``` + +No need to think about the meaning of the brackets `[]` and `forEach` yet. We'll study them later. For now, just remember the result of running the code: it shows `Hello`, then `1`, then `2`. + +Now let's remove the semicolon after the `alert`: + +```js run no-beautify +alert("Hello") + +[1, 2].forEach(alert); +>>>>>>> 4d01fc20d4d82358e61518a31efe80dec9bb2602 ``` عند تنفيذ الشيفرة البرمجية آنذاك، سيتم إظهار التنبيه الأول فقط ثم سنحصل على خطأ. تعود الشيفرة البرمجية للعمل بشكل صحيح مرة أخرى عند إضافة الفاصلة المنقوطة بعد التنبيه الأول: +<<<<<<< HEAD ``` alert("All fine now"); [1 ,2].forEach(alert) @@ -72,6 +99,18 @@ alert("All fine now"); ``` alert("There will be an error")[1, 2].forEach(alert) +======= +The difference compared to the code above is only one character: the semicolon at the end of the first line is gone. + +If we run this code, only the first `Hello` shows (and there's an error, you may need to open the console to see it). There are no numbers any more. + +That's because JavaScript does not assume a semicolon before square brackets `[...]`. So, the code in the last example is treated as a single statement. + +Here's how the engine sees it: + +```js run no-beautify +alert("Hello")[1, 2].forEach(alert); +>>>>>>> 4d01fc20d4d82358e61518a31efe80dec9bb2602 ``` ولكنهما عبارتين برمجيتين منفصلتين وليستا عبارة واحدة، وبالتالي عملية الدمج في هذه الحالة خطأ. من الممكن أن تتكرر هذه الحالة ضمن شروط أخرى. @@ -87,7 +126,24 @@ alert("There will be an error")[1, 2].forEach(alert) المائلين على نفس السطر تعليقًا. ومن الممكن أن يشغل التعليق سطرًا كاملًا أو يأتي التعليق بعد العبارة البرمجية. إليك المثال التالي الذي يشرح ما سبق: +<<<<<<< HEAD ``` +======= +Looks weird, right? Such merging in this case is just wrong. We need to put a semicolon after `alert` for the code to work correctly. + +This can happen in other situations also. +```` + +We recommend putting semicolons between statements even if they are separated by newlines. This rule is widely adopted by the community. Let's note once again -- *it is possible* to leave out semicolons most of the time. But it's safer -- especially for a beginner -- to use them. + +## Comments [#code-comments] + +As time goes on, programs become more and more complex. It becomes necessary to add *comments* which describe what the code does and why. + +Comments can be put into any place of a script. They don't affect its execution because the engine simply ignores them. + +**One-line comments start with two forward slash characters `//`.** +>>>>>>> 4d01fc20d4d82358e61518a31efe80dec9bb2602 // يمتد هذا التعليق على كامل السطر فقط diff --git a/1-js/02-first-steps/04-variables/article.md b/1-js/02-first-steps/04-variables/article.md index 89d21f287..0d40df61d 100644 --- a/1-js/02-first-steps/04-variables/article.md +++ b/1-js/02-first-steps/04-variables/article.md @@ -26,7 +26,11 @@ let message; let message; *!* +<<<<<<< HEAD message = 'Hello'; // تخزين النص +======= +message = 'Hello'; // store the string 'Hello' in the variable named message +>>>>>>> 4d01fc20d4d82358e61518a31efe80dec9bb2602 */!* ``` diff --git a/1-js/02-first-steps/05-types/article.md b/1-js/02-first-steps/05-types/article.md index d555a8c24..14efde718 100644 --- a/1-js/02-first-steps/05-types/article.md +++ b/1-js/02-first-steps/05-types/article.md @@ -65,7 +65,7 @@ n = 12.345; سنرى المزيد من التعامل مع الأرقام خلال هذا الفصل . -## BigInt +## BigInt [#bigint-type] في جافا سكريبت، النوع "number" لا يمثل الأعداد الصحيحة أكبر من (253-1) (و هو `9007199254740991`)، أو أقل من -(-253-1) للأرقام السالبة. إنها قيود فنية ناتجة عن تمثيلهم الداخلي. diff --git a/1-js/02-first-steps/08-operators/article.md b/1-js/02-first-steps/08-operators/article.md index f3b7469c2..b23f2c3f4 100644 --- a/1-js/02-first-steps/08-operators/article.md +++ b/1-js/02-first-steps/08-operators/article.md @@ -56,18 +56,30 @@ alert( 8 % 3 ); // 2, a remainder of 8 divided by 3 ### الضرب الأسي ** +<<<<<<< HEAD عامل الضرب الأسي `a ** b` يقوم بضرب الرقم `a` في نفسه عدد `b` من المرات +======= +The exponentiation operator `a ** b` raises `a` to the power of `b`. + +In school maths, we write that as ab. +>>>>>>> 4d01fc20d4d82358e61518a31efe80dec9bb2602 على سبيل المثال: ```js run -alert( 2 ** 2 ); // 4 (2 multiplied by itself 2 times) -alert( 2 ** 3 ); // 8 (2 * 2 * 2, 3 times) -alert( 2 ** 4 ); // 16 (2 * 2 * 2 * 2, 4 times) +alert( 2 ** 2 ); // 2² = 4 +alert( 2 ** 3 ); // 2³ = 8 +alert( 2 ** 4 ); // 2⁴ = 16 ``` +<<<<<<< HEAD من الناحية الرياضياتية الضرب الأسي يستخدم أيضا مع الأرقام غير الصحيحة. على سبيل المثال الجذر التربيعي هو ضرب أسي بقيمة `1/2`: +======= +Just like in maths, the exponentiation operator is defined for non-integer numbers as well. + +For example, a square root is an exponentiation by ½: +>>>>>>> 4d01fc20d4d82358e61518a31efe80dec9bb2602 ```js run alert( 4 ** (1/2) ); // 2 (power of 1/2 is the same as a square root) diff --git a/1-js/02-first-steps/13-while-for/article.md b/1-js/02-first-steps/13-while-for/article.md index 3dd3a9db0..a40529b07 100644 --- a/1-js/02-first-steps/13-while-for/article.md +++ b/1-js/02-first-steps/13-while-for/article.md @@ -106,12 +106,21 @@ for (let i = 0; i < 3; i++) { لنشرح `for` جزء بجزء: +<<<<<<< HEAD | الجزء | | | | --------- | ---------- | ----------------------------------------------------------- | | begin | `i = 0` | ينفذ مرة واحدة فقط في البداية. | | condition | `i < 3` | يتم اختباره قبل كل عملية تكرار وإذا لم يتحقق يتوقف التكرار. | | body | `alert(i)` | تنفذ طالما الشرط محقق. | | step | `i++` | ينفذ بعد body في كل عملية تكرار. | +======= +| part | | | +|-------|----------|----------------------------------------------------------------------------| +| begin | `let i = 0` | Executes once upon entering the loop. | +| condition | `i < 3`| Checked before every loop iteration. If false, the loop stops. | +| body | `alert(i)`| Runs again and again while the condition is truthy. | +| step| `i++` | Executes after the body on each iteration. | +>>>>>>> 4d01fc20d4d82358e61518a31efe80dec9bb2602 الخوارزمية العامة للتكرار تعمل كالتالي: @@ -377,9 +386,24 @@ break label; // تنتقل إلى الحقل بالأسفل (لا تعمل) label: for (...) ``` +<<<<<<< HEAD إستخدام `continue` يكون ممكنًا فقط من داخل الحلقه. `break` ربما يمكن وضعه قبل الشيفرة ايضًا, as `label: { ... }`, لكنها لا تستخدم أبدًا بهذه الطريقة. وهي تعمل أيضًا من الداخل إلى الخارج فقط. +======= +A `break` directive must be inside a code block. Technically, any labelled code block will do, e.g.: +```js +label: { + // ... + break label; // works + // ... +} +``` + +...Although, 99.9% of the time `break` is used inside loops, as we've seen in the examples above. + +A `continue` is only possible from inside a loop. +>>>>>>> 4d01fc20d4d82358e61518a31efe80dec9bb2602 ```` ## ملخص diff --git a/1-js/02-first-steps/15-function-basics/2-rewrite-function-question-or/solution.md b/1-js/02-first-steps/15-function-basics/2-rewrite-function-question-or/solution.md index 191d03c97..77c56e774 100644 --- a/1-js/02-first-steps/15-function-basics/2-rewrite-function-question-or/solution.md +++ b/1-js/02-first-steps/15-function-basics/2-rewrite-function-question-or/solution.md @@ -14,4 +14,8 @@ function checkAge(age) { } ``` +<<<<<<< HEAD لاحظ أن الأقواس حول `age > 18` غير مطلوبة ولكن تم وضعها لزيادة القدرة على قراءة الكود. +======= +Note that the parentheses around `age > 18` are not required here. They exist for better readability. +>>>>>>> 4d01fc20d4d82358e61518a31efe80dec9bb2602 diff --git a/1-js/02-first-steps/15-function-basics/article.md b/1-js/02-first-steps/15-function-basics/article.md index 0c7b690e3..7318d508f 100644 --- a/1-js/02-first-steps/15-function-basics/article.md +++ b/1-js/02-first-steps/15-function-basics/article.md @@ -20,10 +20,14 @@ function showMessage() { } ``` +<<<<<<< HEAD كلمة `function` تكتب أولا ثم يكتب _اسم الدالة_ ثم قائمة _parameters_ بين القوسين (يفصل بينهم بفاصلة وهي فارغة في المثال السابق) وأخيرا الكود الذي ينفذ ويسمى "the function body" بين القوسين المعقوفين. +======= +The `function` keyword goes first, then goes the *name of the function*, then a list of *parameters* between the parentheses (comma-separated, empty in the example above, we'll see examples later) and finally the code of the function, also named "the function body", between curly braces. +>>>>>>> 4d01fc20d4d82358e61518a31efe80dec9bb2602 ```js -function name(parameters) { +function name(parameter1, parameter2, ... parameterN) { ...body... } ``` @@ -137,25 +141,30 @@ alert( userName ); // *!*John*/!*, لم يتغير, الدالة لن تصل ل ## Parameters +<<<<<<< HEAD يمكننا تمرير أي قيم إلى الدالة باستخدام parameters (أيضًا تسمى _function arguments_) . +======= +We can pass arbitrary data to functions using parameters. +>>>>>>> 4d01fc20d4d82358e61518a31efe80dec9bb2602 في هذا المثال الدالة لديها معاملين: `from` و `text`. ```js run -function showMessage(*!*from, text*/!*) { // arguments: from, text +function showMessage(*!*from, text*/!*) { // parameters: from, text alert(from + ': ' + text); } -*!* -showMessage('Ann', 'Hello!'); // Ann: Hello! (*) -showMessage('Ann', "What's up?"); // Ann: What's up? (**) -*/!* +*!*showMessage('Ann', 'Hello!');*/!* // Ann: Hello! (*) +*!*showMessage('Ann', "What's up?");*/!* // Ann: What's up? (**) ``` عند استدعاء الدالة في السطر `(*)` و `(**)` فإن القيم الممررة تنسخ إلى المتغيرات المحلية `from` و `text`. ثم تقوم الدالة باستخدامهم. +<<<<<<< HEAD هنا مثال آخر حيث لدينا المتغير `from` وقمنا بتمريره إلى الدالة. لاحظ أن الدالة قامت بتغيير قيمة `from` ولكن التغيير لا يؤثر في المتغير الممرر لأن الدالة تحصل على نسخة من القيمة: +======= +>>>>>>> 4d01fc20d4d82358e61518a31efe80dec9bb2602 ```js run function showMessage(from, text) { @@ -174,9 +183,27 @@ showMessage(from, "Hello"); // *Ann*: Hello alert( from ); // Ann ``` +<<<<<<< HEAD ## القيم الإفتراضية إذا لم يتم تمرير قيمة Parameter يأخذ القيمة `undefined`. +======= +When a value is passed as a function parameter, it's also called an *argument*. + +In other words, to put these terms straight: + +- A parameter is the variable listed inside the parentheses in the function declaration (it's a declaration time term) +- An argument is the value that is passed to the function when it is called (it's a call time term). + +We declare functions listing their parameters, then call them passing arguments. + +In the example above, one might say: "the function `showMessage` is declared with two parameters, then called with two arguments: `from` and `"Hello"`". + + +## Default values + +If a function is called, but an argument is not provided, then the corresponding value becomes `undefined`. +>>>>>>> 4d01fc20d4d82358e61518a31efe80dec9bb2602 على سبيل المثال الفنكشن السابقة `showMessage(from, text)` يمكن استدعائها وتمرير قيمة واحدة فقط: @@ -184,9 +211,15 @@ alert( from ); // Ann showMessage("Ann"); ``` +<<<<<<< HEAD هذا ليس خطأ ولكن سينتج `"Ann: undefined"`. لم يتم تمرير `text` لذلك يتم افتراض أن `text === undefined`. إذا أردت تخصيص قيمة إفتراضية ل `text` يمكن وضعها بعد `=`: +======= +That's not an error. Such a call would output `"*Ann*: undefined"`. As the value for `text` isn't passed, it becomes `undefined`. + +We can specify the so-called "default" (to use if omitted) value for a parameter in the function declaration, using `=`: +>>>>>>> 4d01fc20d4d82358e61518a31efe80dec9bb2602 ```js run function showMessage(from, *!*text = "no text given"*/!*) { @@ -210,19 +243,33 @@ function showMessage(from, text = anotherFunction()) { ```smart header="تنفيذ القيم الإفتراضية" في جافا سكريبت يتم تنفيذ القيم الإفتراضية في كل مرة يتم استدعاء الدالة دون تمرير قيمة. +<<<<<<< HEAD في المثال السابق سيتم تنفيذ `anotherFunction()` في كل مرة يتم استدعا `showMessage()` دون تمرير قيمة `text`. +======= +In the example above, `anotherFunction()` isn't called at all, if the `text` parameter is provided. + +On the other hand, it's independently called every time when `text` is missing. +>>>>>>> 4d01fc20d4d82358e61518a31efe80dec9bb2602 ``` ### بديل القيم الإفتراضية +<<<<<<< HEAD أحيانا نريدتحديد قيمة لإفتراضية ولكن ليس في تعريف الدالة بل في وقت لاحق أثناء التنفيذ. لمعرفة المتغير الذي لم يمرر قيمته يمكننا مقارنته مع `undefined`: +======= +Sometimes it makes sense to assign default values for parameters not in the function declaration, but at a later stage. + +We can check if the parameter is passed during the function execution, by comparing it with `undefined`: +>>>>>>> 4d01fc20d4d82358e61518a31efe80dec9bb2602 ```js run function showMessage(text) { + // ... + *!* - if (text === undefined) { + if (text === undefined) { // if the parameter is missing text = 'empty message'; } */!* @@ -236,19 +283,32 @@ showMessage(); // empty message ...أو نستخدم العامل `||`: ```js +<<<<<<< HEAD // إذا لم يتم تمرير قيمة text أو تم تمرير "" يجعل قيمته 'empty' +======= +>>>>>>> 4d01fc20d4d82358e61518a31efe80dec9bb2602 function showMessage(text) { + // if text is undefined or otherwise falsy, set it to 'empty' text = text || 'empty'; ... } ``` +<<<<<<< HEAD محركات جافا سكريبت الحديثة تدعم [nullish coalescing operator](info:nullish-coalescing-operator) `??`وهوأفضل في التعامل مع falsy values مثل `0`: ```js run // إذا لم يوجد قيمة "count" يعرض "unknown" function showCount(count) { alert(count ?? "unknown"); +======= +Modern JavaScript engines support the [nullish coalescing operator](info:nullish-coalescing-operator) `??`, it's better when most falsy values, such as `0`, should be considered "normal": + +```js run +function showCount(count) { + // if count is undefined or null, show "unknown" + alert(count ?? "unknown"); +>>>>>>> 4d01fc20d4d82358e61518a31efe80dec9bb2602 } showCount(0); // 0 @@ -413,7 +473,11 @@ checkPermission(..) // checks a permission, returns true/false على سبيل المثال مكتبة [jQuery](http://jquery.com) تعرف دالة اسمها `$`. ومكتبة [Lodash](http://lodash.com/) لديها دالة اسمها `_`. +<<<<<<< HEAD هذه مجرد استثناءات ففي العموم يجب أن يكون اسم الدالة معبرًا. +======= +These are exceptions. Generally function names should be concise and descriptive. +>>>>>>> 4d01fc20d4d82358e61518a31efe80dec9bb2602 ``` ## الدوال == تعليقات diff --git a/1-js/02-first-steps/17-arrow-functions-basics/1-rewrite-arrow/solution.md b/1-js/02-first-steps/17-arrow-functions-basics/1-rewrite-arrow/solution.md index 935ed23f8..516d76cec 100644 --- a/1-js/02-first-steps/17-arrow-functions-basics/1-rewrite-arrow/solution.md +++ b/1-js/02-first-steps/17-arrow-functions-basics/1-rewrite-arrow/solution.md @@ -1,6 +1,6 @@ ```js run function ask(question, yes, no) { - if (confirm(question)) yes() + if (confirm(question)) yes(); else no(); } diff --git a/1-js/02-first-steps/17-arrow-functions-basics/1-rewrite-arrow/task.md b/1-js/02-first-steps/17-arrow-functions-basics/1-rewrite-arrow/task.md index ef8576c11..2988577ab 100644 --- a/1-js/02-first-steps/17-arrow-functions-basics/1-rewrite-arrow/task.md +++ b/1-js/02-first-steps/17-arrow-functions-basics/1-rewrite-arrow/task.md @@ -4,8 +4,13 @@ ```js run function ask(question, yes, no) { +<<<<<<< HEAD if (confirm(question)) yes(); else no(); +======= + if (confirm(question)) yes(); + else no(); +>>>>>>> 4d01fc20d4d82358e61518a31efe80dec9bb2602 } ask( diff --git a/1-js/03-code-quality/01-debugging-chrome/article.md b/1-js/03-code-quality/01-debugging-chrome/article.md index 1f969765a..2cd1b519d 100644 --- a/1-js/03-code-quality/01-debugging-chrome/article.md +++ b/1-js/03-code-quality/01-debugging-chrome/article.md @@ -1,4 +1,8 @@ +<<<<<<< HEAD # تصحيح الأخطاء في كروم +======= +# Debugging in the browser +>>>>>>> 4d01fc20d4d82358e61518a31efe80dec9bb2602 قبل كتابة أي كود معقد , فلنتحدث قليلا عن تصحيح الأخطاء. diff --git a/1-js/03-code-quality/05-testing-mocha/article.md b/1-js/03-code-quality/05-testing-mocha/article.md index 7e0885492..56279a351 100644 --- a/1-js/03-code-quality/05-testing-mocha/article.md +++ b/1-js/03-code-quality/05-testing-mocha/article.md @@ -2,7 +2,11 @@ يُستخدَم الاختبار الآلي في الكثير من المهام، كما يستخدم بكثرة في المشاريع الحقيقية. +<<<<<<< HEAD ## لم نحتاج الاختبارات؟ +======= +## Why do we need tests? +>>>>>>> 4d01fc20d4d82358e61518a31efe80dec9bb2602 عند كتابة دالة، يمكننا تخيل ما يجب أن تقوم به: ما هي المعاملات التي تعطي نتائج معينة. يمكننا فحص الدالة أثناء التطوير من خلال تشغيلها وموازنة مخرجاتها مع ما هو متوقع. مثلا يمكننا القيام بذلك في الطرفية. diff --git a/1-js/03-code-quality/06-polyfills/article.md b/1-js/03-code-quality/06-polyfills/article.md index 9b86c4f36..38ff2bf66 100644 --- a/1-js/03-code-quality/06-polyfills/article.md +++ b/1-js/03-code-quality/06-polyfills/article.md @@ -21,7 +21,7 @@ Here, in this chapter, our purpose is to get the gist of how they work, and thei ## Transpilers -A [transpiler](https://en.wikipedia.org/wiki/Source-to-source_compiler) is a special piece of software that can parse ("read and understand") modern code, and rewrite it using older syntax constructs, so that the result would be the same. +A [transpiler](https://en.wikipedia.org/wiki/Source-to-source_compiler) is a special piece of software that translates source code to another source code. It can parse ("read and understand") modern code and rewrite it using older syntax constructs, so that it'll also work in outdated engines. E.g. JavaScript before year 2020 didn't have the "nullish coalescing operator" `??`. So, if a visitor uses an outdated browser, it may fail to understand the code like `height = height ?? 100`. @@ -47,7 +47,7 @@ Modern project build systems, such as [webpack](http://webpack.github.io/), prov New language features may include not only syntax constructs and operators, but also built-in functions. -For example, `Math.trunc(n)` is a function that "cuts off" the decimal part of a number, e.g `Math.trunc(1.23) = 1`. +For example, `Math.trunc(n)` is a function that "cuts off" the decimal part of a number, e.g `Math.trunc(1.23)` returns `1`. In some (very outdated) JavaScript engines, there's no `Math.trunc`, so such code will fail. diff --git a/1-js/04-object-basics/04-object-methods/article.md b/1-js/04-object-basics/04-object-methods/article.md index acbb2e8b8..f00aedd59 100644 --- a/1-js/04-object-basics/04-object-methods/article.md +++ b/1-js/04-object-basics/04-object-methods/article.md @@ -80,7 +80,7 @@ user = { // يبدو شكل الدالة المختصر أفضل، أليس كذلك؟ user = { *!* - sayHi() { // same as "sayHi: function()" + sayHi() { // same as "sayHi: function(){...}" */!* alert("Hello"); } diff --git a/1-js/04-object-basics/06-constructor-new/1-two-functions-one-object/task.md b/1-js/04-object-basics/06-constructor-new/1-two-functions-one-object/task.md index 8c1fea8eb..d80113acc 100644 --- a/1-js/04-object-basics/06-constructor-new/1-two-functions-one-object/task.md +++ b/1-js/04-object-basics/06-constructor-new/1-two-functions-one-object/task.md @@ -4,7 +4,7 @@ importance: 2 # Two functions – one object -Is it possible to create functions `A` and `B` such as `new A()==new B()`? +Is it possible to create functions `A` and `B` so that `new A() == new B()`? ```js no-beautify function A() { ... } diff --git a/1-js/04-object-basics/06-constructor-new/article.md b/1-js/04-object-basics/06-constructor-new/article.md index 6c0fa9039..2d9888ca8 100644 --- a/1-js/04-object-basics/06-constructor-new/article.md +++ b/1-js/04-object-basics/06-constructor-new/article.md @@ -55,6 +55,7 @@ isAdmin: false لاحظ أنَّه يمكن استخدام أي دالة لتكون دالة بانية تقنيًا. يعني أنه يمكن تنفيذ أي دالة مع `new`، وستُنَفَّذ باستخدام الخوارزمية أعلاه. استخدام الأحرف الكبيرة في البداية هو اتفاق شائع لتمييز الدالة البانية من غيرها وأنَّه يجب استدعاؤها مع `new`. +<<<<<<< HEAD ### `**new function() { … }‎**` إن كان لدينا العديد من الأسطر البرمجية، وجميعها عن إنشاء كائن واحد مُعَقَّد، فبإمكاننا تضمينها في دالة بانية، هكذا: @@ -70,6 +71,27 @@ this.isAdmin = false; ``` لا يمكن استدعاء المُنشِئ مجددًا، لأنه غير محفوظ في أي مكان، يُنشَأ ويُستدعى فقط. لذا فإن الخدعة تهدف إلى تضمين الشيفرة التي تُنشِئ كائنًا واحدًا، دون إعادة الاستخدام وتكرار العملية مستقبلًا. +======= +Let's note once again -- technically, any function (except arrow functions, as they don't have `this`) can be used as a constructor. It can be run with `new`, and it will execute the algorithm above. The "capital letter first" is a common agreement, to make it clear that a function is to be run with `new`. + +````smart header="new function() { ... }" +If we have many lines of code all about creation of a single complex object, we can wrap them in an immediately called constructor function, like this: + +```js +// create a function and immediately call it with new +let user = new function() { + this.name = "John"; + this.isAdmin = false; + + // ...other code for user creation + // maybe complex logic and statements + // local variables etc +}; +``` + +This constructor can't be called again, because it is not saved anywhere, just created and called. So this trick aims to encapsulate the code that constructs the single object, without future reuse. +```` +>>>>>>> 4d01fc20d4d82358e61518a31efe80dec9bb2602 ## وضع اختبار الباني: `new.target` 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 2ad596136..feee596f9 100644 --- a/1-js/04-object-basics/09-object-toprimitive/article.md +++ b/1-js/04-object-basics/09-object-toprimitive/article.md @@ -4,7 +4,28 @@ فى هذه الحالة، تتحول الكائنات إلى قيم فردية تلقائيًا، ثم يتم تنفيذ هذه العملية الحسابية. +<<<<<<< HEAD فى قسم (تحويل الأنواع) رأينا كيف يمكن تحويل النصوص (strings) والأرقام والقيَم المنطقيه (booleans) إلى قيم فردية. ولكننا تركنا مساحة فارغة من أجل الكائنات. والآن بعد أن عرفنا الكثير عن الدوال (methods) والرموز (symbols)، أصبح الآن ممكنًا أن نملأ هذه المساحه. +======= +JavaScript doesn't exactly allow to customize how operators work on objects. Unlike some other programming languages, such as Ruby or C++, we can't implement a special object method to handle an addition (or other operators). + +In case of such operations, objects are auto-converted to primitives, and then the operation is carried out over these primitives and results in a primitive value. + +That's an important limitation, as the result of `obj1 + obj2` can't be another object! + +E.g. we can't make objects representing vectors or matrices (or achievements or whatever), add them and expect a "summed" object as the result. Such architectural feats are automatically "off the board". + +So, because we can't do much here, there's no maths with objects in real projects. When it happens, it's usually because of a coding mistake. + +In this chapter we'll cover how an object converts to primitive and how to customize it. + +We have two purposes: + +1. It will allow us to understand what's going on in case of coding mistakes, when such an operation happened accidentally. +2. There are exceptions, where such operations are possible and look good. E.g. subtracting or comparing dates (`Date` objects). We'll come across them later. + +## Conversion rules +>>>>>>> 4d01fc20d4d82358e61518a31efe80dec9bb2602 1. كل الكائنات عند تحويلها إلى قيمه منطقيه (boolean) فإن قيمتها تساوى `true`. وبالتالى فإن التحويلات المتاحة هي التحويل إلى نص أو رقم. @@ -12,6 +33,7 @@ 3. وبالنسبه إلى التحويل إلى نص -- فإنه يحدث عادة عند طباعة الكائن باستخدام دالة التنبيه `alert(obj)` والدوال المشابهة. +<<<<<<< HEAD ## ToPrimitive يمكننا التحكم فى التحويل إلى نص أو رقم، باستخدام بعض دوال الكائنات. @@ -19,6 +41,13 @@ هناك ثلاث ملاحظات مختلفه على تحويل الأنواع ويطلق عليها "hints" وتم ذكرها فى [المصدر](https://tc39.github.io/ecma262/#sec-toprimitive): `"النص"` +======= +We can fine-tune string and numeric conversion, using special object methods. + +There are three variants of type conversion, that happen in various situations. + +They're called "hints", as described in the [specification](https://tc39.github.io/ecma262/#sec-toprimitive): +>>>>>>> 4d01fc20d4d82358e61518a31efe80dec9bb2602 : يحدث التحويل إلى نص عندما نقوم بعملية معينه على كائن تتوقع نصًا لا كائنًا مثل دالة التنبيه `alert`: @@ -87,12 +116,19 @@ if (user == 1) { ... }; ```js obj[Symbol.toPrimitive] = function(hint) { - // must return a primitive value + // here goes the code to convert this object to a primitive + // it must return a primitive value // hint = one of "string", "number", "default" }; ```` +<<<<<<< HEAD على سبيل المثال, يطبق هذه الطريقه الكائن `user`: +======= +If the method `Symbol.toPrimitive` exists, it's used for all hints, and no more methods are needed. + +For instance, here `user` object implements it: +>>>>>>> 4d01fc20d4d82358e61518a31efe80dec9bb2602 ```js run let user = { @@ -115,12 +151,21 @@ alert(user + 500); // hint: default -> 1500 ## toString/valueOf +<<<<<<< HEAD الدوال `toString` و `valueOf`موجوده من قديم الأزل. إنهم ليسو رموزًا ولكنهم دوال تستعمل مع النصوص. ويقومون بتوفير طريقة قديمه للقيام بالتحويل. إذا لم يكن هناك `Symbol.toPrimitive` فإن جافا سكريبت تقوم بالبحث عنهمو استخدامهم بالترتيب الآتى: - `toString -> valueOf` فى الطريقه النصيه. - `valueOf -> toString` غير ذلك. +======= +If there's no `Symbol.toPrimitive` then JavaScript tries to find methods `toString` and `valueOf`: + +- For the "string" hint: `toString`, and if it doesn't exist, then `valueOf` (so `toString` has the priority for string conversions). +- For other hints: `valueOf`, and if it doesn't exist, then `toString` (so `valueOf` has the priority for maths). + +Methods `toString` and `valueOf` come from ancient times. They are not symbols (symbols did not exist that long ago), but rather "regular" string-named methods. They provide an alternative "old-style" way to implement the conversion. +>>>>>>> 4d01fc20d4d82358e61518a31efe80dec9bb2602 هذه الدوال لابد أن تقوم بإرجاع قيمه فردية. فإذا قامت هاتان الدالتان بإرجاع كائن فسيتم تجاهله. @@ -140,9 +185,15 @@ alert(user.valueOf() === user); // true لذلك إذا حاولنا أن نستخدم الكائن كنص، كما فى حالة استخدام الداله النصيه `alert` سنرى بشكل افتراضي `[object object]`. +<<<<<<< HEAD الداله `valueOf` تم ذكرها هنا فقط لإكمال المعلومات ولتجنب أى التباس. فكما ترى فإن هذه الداله تقوم بإرجاع الكائن نفسه وبالتالى يتم تجاهله. لا تسأل لماذا فهذا لأسباب متأصله historical reasons. ولذلك يمكننا اعتبار أنها غير موجوده. هيا نقوم باستخدام هذه الدوال. +======= +The default `valueOf` is mentioned here only for the sake of completeness, to avoid any confusion. As you can see, it returns the object itself, and so is ignored. Don't ask me why, that's for historical reasons. So we can assume it doesn't exist. + +Let's implement these methods to customize the conversion. +>>>>>>> 4d01fc20d4d82358e61518a31efe80dec9bb2602 على سبيل المثال، فإن الكائن `user` هنا يقوم بنفس التصرف أعلاه عند استخدام خليط من `toString` و `valueOf` بدلًا من `Symbol.toPrimitive`: @@ -186,7 +237,11 @@ alert(user + 500); // toString -> John500 فى حالة غياب `Symbol.toPrimitive` و `valueOf` فإن `toString` ستقوم بالتعامل مع كل حالات التحويل إلى قيم فرديه. +<<<<<<< HEAD ## أنواع القيم المسترجعه +======= +### A conversion can return any primitive type +>>>>>>> 4d01fc20d4d82358e61518a31efe80dec9bb2602 هناك شئ مهم يجب أن تعرفه وهو أن كل طرق التحويل إلى قيم مفرده لا يجب بالضروره أن تقوم بإرجاع نفس نوع القيمه المفرده المحوَّله إليه. @@ -261,4 +316,10 @@ alert(obj + 2); // 22 ("2" + 2), conversion to primitive returned a string => co 3. غير ذلك إذا كانت الطريقة `"رقمًا"` أو `"الطريقة الإفتراضيه"` - استخدام `obj.valueOf()` أو `obj.toString()` فى حالة وجود أى منهم. +<<<<<<< HEAD ويكفى عمليًا استخدام `obj.toString()` لكل التحويلات والتى تقوم بإرجاع قيمة يمكن قرائتها من أجل الطباعة أو البحث عن الأخطاء. +======= +In practice, it's often enough to implement only `obj.toString()` as a "catch-all" method for string conversions that should return a "human-readable" representation of an object, for logging or debugging purposes. + +As for math operations, JavaScript doesn't provide a way to "override" them using methods, so real life projects rarely use them on objects. +>>>>>>> 4d01fc20d4d82358e61518a31efe80dec9bb2602 diff --git a/1-js/05-data-types/01-primitives-methods/article.md b/1-js/05-data-types/01-primitives-methods/article.md index 7c7671d2d..51a9a40c4 100644 --- a/1-js/05-data-types/01-primitives-methods/article.md +++ b/1-js/05-data-types/01-primitives-methods/article.md @@ -40,8 +40,13 @@ Objects "اثقل" من الأساليب البدائية. وهي تتطلب م هنا التناقض الذي واجه صانع جافا سكريبت: +<<<<<<< HEAD - هناك العديد من الأشياء التي يمكن أن يفعلها الشخص بالأسلوب البدائي مثل string أو number. سيكون من الرائع استخدامهم كا methods. - الأساليب البدائية يجب أن تكون سريعة وخفيفة بقدر الإمكان. +======= +- There are many things one would want to do with a primitive like a string or a number. It would be great to access them using methods. +- Primitives must be as fast and lightweight as possible. +>>>>>>> 4d01fc20d4d82358e61518a31efe80dec9bb2602 إن الحل يبدو غريبا بعض الشيء، ولكن ها هو: diff --git a/1-js/05-data-types/02-number/article.md b/1-js/05-data-types/02-number/article.md index 0198ff2ee..ea5c3b93c 100644 --- a/1-js/05-data-types/02-number/article.md +++ b/1-js/05-data-types/02-number/article.md @@ -5,7 +5,11 @@ 1. أعداد عادية تخزَّن بصيغة 64-بت [IEEE-754](https://en.wikipedia.org/wiki/IEEE_754-2008_revision)، تُعرف أيضًا ب "الأعداد العشرية مضاعفة الدقة" (double precision floating point numbers). هذا النوع هو ما سنستعلمه أغلب الوقت وسنسلط عليه الضوء في هذا الفصل. 2. أعداد صحيحة كبيرة (BigInt numbers) تمثِّل عددًا صحيحًا متغير الحجم، إذ قد نلجأ إليها أحيانًا لأن النوع السابق لا يمكن أن يتجاوز القيمة 2^53 أو أن تقل عن -2^53، وسنخصص لهذا النوع فصلًا خاصًا به نظرًا للحاجة إليه في حالات خاصة. +<<<<<<< HEAD حاليًا، لِنتوسع عن ما نعرفه عنها، وننتقل إلى الحديث عن النوع الأول، الأعداد العادية. +======= +2. BigInt numbers, to represent integers of arbitrary length. They are sometimes needed, because a regular number can't safely exceed 253 or be less than -253. As bigints are used in few special areas, we devote them a special chapter . +>>>>>>> 4d01fc20d4d82358e61518a31efe80dec9bb2602 ## طرق أخرى لكتابة عدد @@ -35,9 +39,15 @@ let billion = 1e9; // بليون، حرفيًا: 1 وجانبه 9 أصفار In other words, `e` multiplies the number by `1` with the given zeroes count. ```js +<<<<<<< HEAD 1e3 = 1 * 1000 // e3 means *1000 1.23e6 = 1.23 * 1000000 // e6 means *1000000 ```` +======= +1e3 === 1 * 1000; // e3 means *1000 +1.23e6 === 1.23 * 1000000; // e6 means *1000000 +``` +>>>>>>> 4d01fc20d4d82358e61518a31efe80dec9bb2602 لنكتب الآن شيئَا صغيرًا جدًا. مثلًا، جزء من المليون من الثانية: @@ -51,16 +61,29 @@ let ms = 0.000001; let ms = 1e-6; // ستة أصفار على يسار 1 ``` +<<<<<<< HEAD إن قمنا بعد الأصفار في `0.000001`، سنجد عددها 6. لذا يكون الرقم `1e-6`. +======= +If we count the zeroes in `0.000001`, there are 6 of them. So naturally it's `1e-6`. +>>>>>>> 4d01fc20d4d82358e61518a31efe80dec9bb2602 بمعنى آخر، وجود رقم سالب بعد `"e"` يعني القسمة على 1 متبوعًا بِعدد الأصفار المعطى: +<<<<<<< HEAD ``` // -3 بالقسمة على 1 متبوعًا ب 3 أصفار 1e-3 = 1 / 1000 (=0.001) // -6 بالقسمة على 1 متبوعًا ب 6 أصفار 1.23e-6 = 1.23 / 1000000 (=0.00000123) +======= +```js +// -3 divides by 1 with 3 zeroes +1e-3 === 1 / 1000; // 0.001 + +// -6 divides by 1 with 6 zeroes +1.23e-6 === 1.23 / 1000000; // 0.00000123 +>>>>>>> 4d01fc20d4d82358e61518a31efe80dec9bb2602 ``` ### الأعداد الست عشرية، والثنائية والثمانية @@ -102,8 +125,22 @@ alert( num.toString(2) ); // 11111111 - `base=2`: يستخدم بكثرة في تصحيح العمليات الدقيقة، يمكن أن يحوي الرقمين `0` أو `1`. - `base=36`: هو الحد الأعلى، يمكن أن يحوي الأرقام `0..9` أو الأحرُف `A..Z`. يمكن استخدام جميع الأحرف اللاتينية لتمثيل عدد. قد يبدو أمرًا ممتعًا لكن يكون مفيدًا في حال احتجنا لتحويل معرف عددي طويل إلى عدد أقصر، مثلًا، لتقصير رابط url. يمكن تمثيله بالنظام العددي ذي الأساس `36`: +<<<<<<< HEAD ``` alert( 123456..toString(36) ); // 2n9c +======= + ```js run + alert( 123456..toString(36) ); // 2n9c + ``` + +```warn header="Two dots to call a method" +Please note that two dots in `123456..toString(36)` is not a typo. If we want to call a method directly on a number, like `toString` in the example above, then we need to place two dots `..` after it. + +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 goes the method. + +Also could write `(123456).toString(36)`. + +>>>>>>> 4d01fc20d4d82358e61518a31efe80dec9bb2602 ``` ## Rounding @@ -342,7 +379,13 @@ let num = +prompt("Enter a number", ''); alert( isFinite(num) ); ``` +<<<<<<< HEAD يرجى ملاحظة أن الفراغ أو المسافة الواحدة تُعامل معاملة الصفر `0` في جميع التوابع العددية بما فيها `isFinite`. +======= +Please note that an empty or a space-only string is treated as `0` in all numeric functions including `isFinite`. + +```smart header="Compare with `Object.is`" +>>>>>>> 4d01fc20d4d82358e61518a31efe80dec9bb2602 ```smart header="المقارنة باستخدام `Object.is`" diff --git a/1-js/05-data-types/03-string/article.md b/1-js/05-data-types/03-string/article.md index e83e5aa67..96b472058 100644 --- a/1-js/05-data-types/03-string/article.md +++ b/1-js/05-data-types/03-string/article.md @@ -532,7 +532,12 @@ alert( str ); - تأتي الأحرف الصغيرة بعد الأحرف الكبيرة دائمًا لأن رموزها العددية دائمًا أكبر. - تكون بعض الأحرف مثل `Ö` بعيدة عن الأحرف الهجائية. هنا، قيمة الحرف هذا أكبر من أي حرف بين `a` و `z`. +<<<<<<< HEAD ### موازنات صحيحة +======= +- All lowercase letters go after uppercase letters because their codes are greater. +- Some letters like `Ö` stand apart from the main alphabet. Here, its code is greater than anything from `a` to `z`. +>>>>>>> 4d01fc20d4d82358e61518a31efe80dec9bb2602 ### Correct comparisons [#correct-comparisons] diff --git a/1-js/05-data-types/04-array/article.md b/1-js/05-data-types/04-array/article.md index 29a027ded..363d97378 100644 --- a/1-js/05-data-types/04-array/article.md +++ b/1-js/05-data-types/04-array/article.md @@ -371,9 +371,13 @@ alert( arr[0] ); // غير معرف! لا توجد عناصر. alert( arr.length ); // الطول 2 ``` +<<<<<<< HEAD في الكود أعلاه, `مصفوفه جديده(رقم)` تكون لديها كل العناصر `غير معرفه`. للتهرب من هذه المفاجآت ، نستخدم عادةً الأقواس المربعة ، إلا إذا كنا نعرف حقًا ما نقوم به. +======= +To avoid such surprises, we usually use square brackets, unless we really know what we're doing. +>>>>>>> 4d01fc20d4d82358e61518a31efe80dec9bb2602 ## مصفوفات متعدده الأبعاد diff --git a/1-js/05-data-types/05-array-methods/12-reduce-object/task.md b/1-js/05-data-types/05-array-methods/12-reduce-object/task.md index f89f17263..00c925630 100644 --- a/1-js/05-data-types/05-array-methods/12-reduce-object/task.md +++ b/1-js/05-data-types/05-array-methods/12-reduce-object/task.md @@ -4,7 +4,11 @@ importance: 4 # انشاء مفاتيح خاصة بكائنات المصفوفة +<<<<<<< HEAD دعنا نقول أننا نستقبل مصفوفة خاصة بالمستخدمين داخل form مكونة `{id:..., name:..., age... }` +======= +Let's say we received an array of users in the form `{id:..., name:..., age:... }`. +>>>>>>> 4d01fc20d4d82358e61518a31efe80dec9bb2602 اكتب دالة `groupById(arr)` لانشاء كائن منها يحتوى على `id` كمفتاح و عناصر المصفوفة كقيم diff --git a/1-js/05-data-types/08-weakmap-weakset/01-recipients-read/solution.md b/1-js/05-data-types/08-weakmap-weakset/01-recipients-read/solution.md index 6a4c20baf..e2147ccfa 100644 --- a/1-js/05-data-types/08-weakmap-weakset/01-recipients-read/solution.md +++ b/1-js/05-data-types/08-weakmap-weakset/01-recipients-read/solution.md @@ -25,7 +25,7 @@ messages.shift(); // now readMessages has 1 element (technically memory may be cleaned later) ``` -The `WeakSet` allows to store a set of messages and easily check for the existance of a message in it. +The `WeakSet` allows to store a set of messages and easily check for the existence of a message in it. It cleans up itself automatically. The tradeoff is that we can't iterate over it, can't get "all read messages" from it directly. But we can do it by iterating over all messages and filtering those that are in the set. diff --git a/1-js/05-data-types/08-weakmap-weakset/article.md b/1-js/05-data-types/08-weakmap-weakset/article.md index b9336405e..e41b34652 100644 --- a/1-js/05-data-types/08-weakmap-weakset/article.md +++ b/1-js/05-data-types/08-weakmap-weakset/article.md @@ -1,7 +1,11 @@  # النوع WeakMap والنوع WeakSet: الخرائط والأطقم ضعيفة الإشارة +<<<<<<< HEAD كما عرفنا من فصل «كنس المهملات»، فمُحرّك جافا سكريبت يخُزّن القيمة في الذاكرة طالما يمكن أن يصل لها شيء (أي يمكن استعمالها لاحقًا). هكذا: +======= +As we know from the chapter , JavaScript engine keeps a value in memory while it is "reachable" and can potentially be used. +>>>>>>> 4d01fc20d4d82358e61518a31efe80dec9bb2602 ``` let john = { name: "John" }; diff --git a/1-js/05-data-types/09-keys-values-entries/article.md b/1-js/05-data-types/09-keys-values-entries/article.md index ed4ad83e5..2ca1e68b5 100644 --- a/1-js/05-data-types/09-keys-values-entries/article.md +++ b/1-js/05-data-types/09-keys-values-entries/article.md @@ -68,7 +68,15 @@ for (let value of Object.values(user)) { 2. استعمل توابِع المصفوفات على تلك المصفوفة (مثلًا map). 3. استعمل Object.fromEntries(array)‎ على المصفوفة الناتج لتُحوّلها ثانيةً إلى كائن. +<<<<<<< HEAD إليك مثالًا لدينا كائنًا فيه تسعير البضائع، ونريد مضاعفتها (إذ ارتفع الدولار): +======= +1. Use `Object.entries(obj)` to get an array of key/value pairs from `obj`. +2. Use array methods on that array, e.g. `map`, to transform these key/value pairs. +3. Use `Object.fromEntries(array)` on the resulting array to turn it back into an object. + +For example, we have an object with prices, and would like to double them: +>>>>>>> 4d01fc20d4d82358e61518a31efe80dec9bb2602 ```js run let prices = { @@ -79,12 +87,22 @@ let prices = { *!* let doublePrices = Object.fromEntries( +<<<<<<< HEAD // ‫نحوّله إلى مصفوفة، ثمّ نستعمل الطقم، ثمّ يُعيد إلينا fromEntries الكائن المطلوب Object.entries(prices).map(([key, value]) => [key, value * 2]) +======= + // convert prices to array, map each key/value pair into another pair + // and then fromEntries gives back the object + Object.entries(prices).map(entry => [entry[0], entry[1] * 2]) +>>>>>>> 4d01fc20d4d82358e61518a31efe80dec9bb2602 ); */!* alert(doublePrices.meat); // 8 ``` +<<<<<<< HEAD ربّما تراه صعبًا أوّل وهلة، ولكن لا تقلق فسيصير أسهل أكثر متى ما بدأت استعمالها مرّة واثنتان وثلاث. يمكن أن نصنع سلسلة فعّالة من التعديلات بهذه الطريقة: +======= +It may look difficult at first sight, but becomes easy to understand after you use it once or twice. We can make powerful chains of transforms this way. +>>>>>>> 4d01fc20d4d82358e61518a31efe80dec9bb2602 diff --git a/1-js/05-data-types/11-date/article.md b/1-js/05-data-types/11-date/article.md index 8d5bcd01b..cde0e2e66 100644 --- a/1-js/05-data-types/11-date/article.md +++ b/1-js/05-data-types/11-date/article.md @@ -318,7 +318,12 @@ function bench(f) { let time1 = 0; let time2 = 0; +<<<<<<< HEAD // ‫نشغّل bench(upperSlice)‎ وbench(upperLoop)‎ عشر مرات مرّة بمرّة +======= +*!* +// run bench(diffSubtract) and bench(diffGetTime) each 10 times alternating +>>>>>>> 4d01fc20d4d82358e61518a31efe80dec9bb2602 for (let i = 0; i < 10; i++) { time1 += bench(diffSubtract); time2 += bench(diffGetTime); diff --git a/1-js/06-advanced-functions/03-closure/10-make-army/solution.md b/1-js/06-advanced-functions/03-closure/10-make-army/solution.md index b718b3668..eac68b391 100644 --- a/1-js/06-advanced-functions/03-closure/10-make-army/solution.md +++ b/1-js/06-advanced-functions/03-closure/10-make-army/solution.md @@ -44,6 +44,7 @@ Let's examine what exactly happens inside `makeArmy`, and the solution will beco ``` 3. The array is returned from the function. +<<<<<<< HEAD Then, later, the call to any member, e.g. `army[5]()` will get the element `army[5]` from the array (which is a function) and calls it. @@ -138,6 +139,102 @@ Let's examine what exactly happens inside `makeArmy`, and the solution will beco That's essentially the same, because `for` on each iteration generates a new lexical environment, with its own variable `i`. So `shooter` generated in every iteration references its own `i`, from that very iteration. ![](lexenv-makearmy-for-fixed.svg) +======= + + Then, later, the call to any member, e.g. `army[5]()` will get the element `army[5]` from the array (which is a function) and calls it. + + Now why do all such functions show the same value, `10`? + + That's because there's no local variable `i` inside `shooter` functions. When such a function is called, it takes `i` from its outer lexical environment. + + Then, what will be the value of `i`? + + If we look at the source: + + ```js + function makeArmy() { + ... + let i = 0; + while (i < 10) { + let shooter = function() { // shooter function + alert( i ); // should show its number + }; + shooters.push(shooter); // add function to the array + i++; + } + ... + } + ``` + + We can see that all `shooter` functions are created in the lexical environment of `makeArmy()` function. But when `army[5]()` is called, `makeArmy` has already finished its job, and the final value of `i` is `10` (`while` stops at `i=10`). + + As the result, all `shooter` functions get the same value from the outer lexical environment and that is, the last value, `i=10`. + + ![](lexenv-makearmy-empty.svg) + + As you can see above, on each iteration of a `while {...}` block, a new lexical environment is created. So, to fix this, we can copy the value of `i` into a variable within the `while {...}` block, like this: + + ```js run + function makeArmy() { + let shooters = []; + + let i = 0; + while (i < 10) { + *!* + let j = i; + */!* + let shooter = function() { // shooter function + alert( *!*j*/!* ); // should show its number + }; + shooters.push(shooter); + i++; + } + + return shooters; + } + + let army = makeArmy(); + + // Now the code works correctly + army[0](); // 0 + army[5](); // 5 + ``` + + Here `let j = i` declares an "iteration-local" variable `j` and copies `i` into it. Primitives are copied "by value", so we actually get an independent copy of `i`, belonging to the current loop iteration. + + The shooters work correctly, because the value of `i` now lives a little bit closer. Not in `makeArmy()` Lexical Environment, but in the Lexical Environment that corresponds to the current loop iteration: + + ![](lexenv-makearmy-while-fixed.svg) + + Such a problem could also be avoided if we used `for` in the beginning, like this: + + ```js run demo + function makeArmy() { + + let shooters = []; + + *!* + for(let i = 0; i < 10; i++) { + */!* + let shooter = function() { // shooter function + alert( i ); // should show its number + }; + shooters.push(shooter); + } + + return shooters; + } + + let army = makeArmy(); + + army[0](); // 0 + army[5](); // 5 + ``` + + That's essentially the same, because `for` on each iteration generates a new lexical environment, with its own variable `i`. So `shooter` generated in every iteration references its own `i`, from that very iteration. + + ![](lexenv-makearmy-for-fixed.svg) +>>>>>>> 4d01fc20d4d82358e61518a31efe80dec9bb2602 Now, as you've put so much effort into reading this, and the final recipe is so simple - just use `for`, you may wonder -- was it worth that? diff --git a/1-js/06-advanced-functions/03-closure/7-let-scope/solution.md b/1-js/06-advanced-functions/03-closure/7-let-scope/solution.md index 975c14d2e..db389310b 100644 --- a/1-js/06-advanced-functions/03-closure/7-let-scope/solution.md +++ b/1-js/06-advanced-functions/03-closure/7-let-scope/solution.md @@ -24,8 +24,14 @@ In this example we can observe the peculiar difference between a "non-existing" ```js function func() { *!* +<<<<<<< HEAD // المتغير المحلي X يعتبر معروف للمحرك من البداية, لكن **غير معرف بقيمة** تظل حتي let // لذلك هناك خطأ +======= + // the local variable x is known to the engine from the beginning of the function, + // but "uninitialized" (unusable) until let ("dead zone") + // hence the error +>>>>>>> 4d01fc20d4d82358e61518a31efe80dec9bb2602 */!* console.log(x); // ReferenceError: لا نستطيع الوصول لـ 'x' قبل إعطائها قيمة diff --git a/1-js/06-advanced-functions/03-closure/article.md b/1-js/06-advanced-functions/03-closure/article.md index 55cbb0902..958f6cfa7 100644 --- a/1-js/06-advanced-functions/03-closure/article.md +++ b/1-js/06-advanced-functions/03-closure/article.md @@ -158,7 +158,11 @@ alert(counter()); // 2 فهم هذه الأشياء يعد عظيماً للمعلومات الشاملة لجافا سكريبت ومفيد جداً في حالة السيناريوهات المعقدة. لذلك هيا نتعمق أكثر في أمور أكثر صعوبة وتحتاج إلي تركيز. +<<<<<<< HEAD ## البيئات المعجمية +======= +Understanding such things is great for the overall knowledge of JavaScript and beneficial for more complex scenarios. So let's go a bit in-depth. +>>>>>>> 4d01fc20d4d82358e61518a31efe80dec9bb2602 ```warn header="هنا يجب أن تكون شديد التركيز!" diff --git a/1-js/06-advanced-functions/04-var/article.md b/1-js/06-advanced-functions/04-var/article.md index 8b01fb9b0..0e6651079 100644 --- a/1-js/06-advanced-functions/04-var/article.md +++ b/1-js/06-advanced-functions/04-var/article.md @@ -3,6 +3,13 @@ ```smart header="هذه المقالة من أجل فهم النصوص القديمة" المعلومات داخل هذه المقالة تساعدنا فى فهم النصوص القديمة أكثر ولا تحتوي علي أي معلومات عن كيفية كتابة كود جديد +<<<<<<< HEAD +======= +```smart header="This article is for understanding old scripts" +The information in this article is useful for understanding old scripts. + +That's not how we write new code. +>>>>>>> 4d01fc20d4d82358e61518a31efe80dec9bb2602 ``` ذكرنا في أوائل الفصول حين تكلمنا عن [المتغيرات](info:variables), ذكرنا ثلاث طرائق للتصريح عنها: diff --git a/1-js/06-advanced-functions/06-function-object/5-sum-many-brackets/solution.md b/1-js/06-advanced-functions/06-function-object/5-sum-many-brackets/solution.md index e66eecae8..bbe06c9df 100644 --- a/1-js/06-advanced-functions/06-function-object/5-sum-many-brackets/solution.md +++ b/1-js/06-advanced-functions/06-function-object/5-sum-many-brackets/solution.md @@ -56,6 +56,10 @@ function f(b) { } ``` +<<<<<<< HEAD وستُستعمل `‎f‎` هذه في الاستدعاء التالي، وتُعيد نفسها ثانيةً مهما لزم. وبعدها حين نستعمل العدد أو السلسلة النصية، يُعيد التابِع `‎toString‎` المجموع `‎currentSum‎`. يمكن أيضًا أن نستعمل `‎Symbol.toPrimitive‎` أو `‎valueOf‎` لإجراء عملية التحويل. ترجمة -وبتصرف- للفصل [Function object, NFE](https://javascript.info/function-object) من كتاب [The JavaScript language](https://javascript.info/js) +======= +This `f` will be used in the next call, again return itself, as many times as needed. Then, when used as a number or a string -- the `toString` returns the `currentSum`. We could also use `Symbol.toPrimitive` or `valueOf` here for the conversion. +>>>>>>> 4d01fc20d4d82358e61518a31efe80dec9bb2602 diff --git a/1-js/06-advanced-functions/09-call-apply-decorators/04-throttle/solution.md b/1-js/06-advanced-functions/09-call-apply-decorators/04-throttle/solution.md index cf851f771..6950664be 100644 --- a/1-js/06-advanced-functions/09-call-apply-decorators/04-throttle/solution.md +++ b/1-js/06-advanced-functions/09-call-apply-decorators/04-throttle/solution.md @@ -12,11 +12,10 @@ function throttle(func, ms) { savedThis = this; return; } + isThrottled = true; func.apply(this, arguments); // (1) - isThrottled = true; - setTimeout(function() { isThrottled = false; // (3) if (savedArgs) { diff --git a/1-js/06-advanced-functions/09-call-apply-decorators/article.md b/1-js/06-advanced-functions/09-call-apply-decorators/article.md index 6bd5650fc..af4d9de29 100644 --- a/1-js/06-advanced-functions/09-call-apply-decorators/article.md +++ b/1-js/06-advanced-functions/09-call-apply-decorators/article.md @@ -288,19 +288,34 @@ func.apply(context, args) أي أنّ الاستدعاءين الآتين متساويين تقريبًا: +<<<<<<< HEAD ``` func.call(context, ...args); // نمرّر الكائن قائمةً بمُعامل التوزيع func.apply(context, args); // ‫نفس الفكرة باستعمال apply ``` ولكن هناك فرق بسيط واحد: +======= +```js +func.call(context, ...args); +func.apply(context, args); +``` + +They perform the same call of `func` with given context and arguments. + +There's only a subtle difference regarding `args`: +>>>>>>> 4d01fc20d4d82358e61518a31efe80dec9bb2602 - يُتيح لنا مُعامل التوزيع `‎...‎` تمرير _المُتعدَّد_ `‎args‎` قائمةً إلى `‎call‎`. - لا يقبل `‎apply‎` إلّا مُعامل `‎args‎` _شبيه بالمصفوفات_. +<<<<<<< HEAD أي أنّ هذين الاستدعاءين يُكمّلان بعضهما البعض. لو توقّعنا وصول مُتعدَّد فنستعمل `‎call‎`، ولو توقّعنا شبيهًا بالمصفوفات نستعمل `‎apply‎`. أمّا الكائنات المُتعدَّدة والشبيهة بالمصفوفات (مثل المصفوفات الحقيقية)، فيمكننا نظريًا استعمال أيّ من الاثنين، إلّا أنّ `‎apply‎` سيكون أسرع غالبًا إذ أنّ مُعظم محرّكات جافا سكريبت تحسّن أدائه داخليًا أكثر من `‎call‎`. +======= +...And for objects that are both iterable and array-like, such as a real array, we can use any of them, but `apply` will probably be faster, because most JavaScript engines internally optimize it better. +>>>>>>> 4d01fc20d4d82358e61518a31efe80dec9bb2602 يُدى تمرير كافة المُعاملات (مع السياق) من دالة إلى أخرى _بتمرير الاستدعاء_. diff --git a/1-js/07-object-properties/01-property-descriptors/article.md b/1-js/07-object-properties/01-property-descriptors/article.md index 57f71f3cc..7798be638 100644 --- a/1-js/07-object-properties/01-property-descriptors/article.md +++ b/1-js/07-object-properties/01-property-descriptors/article.md @@ -20,7 +20,11 @@ الطريقة [Object.getOwnPropertyDescriptor](mdn:js/Object/getOwnPropertyDescriptor) تسمح بالإستعلام _الكامل_ عن المعلومات الخاصة بأيّ خاصية. +<<<<<<< HEAD و صياغتها تكون كالآتي: +======= +The method [Object.getOwnPropertyDescriptor](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/getOwnPropertyDescriptor) allows to query the *full* information about a property. +>>>>>>> 4d01fc20d4d82358e61518a31efe80dec9bb2602 ```js let descriptor = Object.getOwnPropertyDescriptor(obj, propertyName); @@ -54,7 +58,11 @@ alert(JSON.stringify(descriptor, null, 2)); */ ``` +<<<<<<< HEAD لتغيير الرايات, يمكننا إستخدام [Object.defineProperty](mdn:js/Object/defineProperty). +======= +To change the flags, we can use [Object.defineProperty](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty). +>>>>>>> 4d01fc20d4d82358e61518a31efe80dec9bb2602 و صياغتها تكون كالآتي: @@ -194,7 +202,11 @@ alert(Object.keys(user)); // name راية عدم الضبط (`configurable:false`) احياناً يتم إعدادها مسبقاً في بعض الكائنات والخصائص المضمّنة في اللغة. +<<<<<<< HEAD الخاصية الغير قابلة للإحصاء لا يمكن حذفها. +======= +A non-configurable property can't be deleted, its attributes can't be modified. +>>>>>>> 4d01fc20d4d82358e61518a31efe80dec9bb2602 فمثلاً, `Math.PI` غير قابلة للتعديل, غير قابلة للإحصاء و غير قابلة لإعادة الضبط: @@ -215,11 +227,16 @@ alert(JSON.stringify(descriptor, null, 2)); لذا, لن يستطيع المبرمج تغيير قيمة `Math.PI` أو التعديل عليها. ```js run +<<<<<<< HEAD Math.PI = 3; // خطأ +======= +Math.PI = 3; // Error, because it has writable: false +>>>>>>> 4d01fc20d4d82358e61518a31efe80dec9bb2602 // delete Math.PI لن تعمل أيضًا ``` +<<<<<<< HEAD إن تفعيل خاصيّة منع قابلية إعادة الضبط هو قرار لا عودة فيه. فلا يمكننا تغيير الراية (إتاحة قابلية إعادة الضبط) باستعمال `defineProperty`. وللدقّة فهذا المنع يضع تقييدات أخرى على `defineProperty`: @@ -228,8 +245,20 @@ Math.PI = 3; // خطأ 2. منع تغيير راية قابلية الإحصاء `enumerable`. 3. منع تغيير راية قابلية التعديل `writable: false` الي `true` (و لكن العكس ممكن). 4. منع تغيير ضابط وجالب واصف الوصول `get/set` (ولكن يمكن إسناد قيم إليه). +======= +We also can't change `Math.PI` to be `writable` again: -**The idea of "configurable: false" is to prevent changes of property flags and its deletion, while allowing to change its value.** +```js run +// Error, because of configurable: false +Object.defineProperty(Math, "PI", { writable: true }); +``` + +There's absolutely nothing we can do with `Math.PI`. +>>>>>>> 4d01fc20d4d82358e61518a31efe80dec9bb2602 + +Making a property non-configurable is a one-way road. We cannot change it back with `defineProperty`. + +**Please note: `configurable: false` prevents changes of property flags and its deletion, while allowing to change its value.** Here `user.name` is non-configurable, but we can still change it (as it's writable): @@ -246,7 +275,7 @@ user.name = 'Pete'; // works fine delete user.name; // Error ``` -And here we make `user.name` a "forever sealed" constant: +And here we make `user.name` a "forever sealed" constant, just like the built-in `Math.PI`: ```js run let user = { @@ -265,9 +294,21 @@ delete user.name; Object.defineProperty(user, 'name', { value: 'Pete' }); ``` +<<<<<<< HEAD ## Object.defineProperties يوجد طريقة [Object.defineProperties(obj, descriptors)](mdn:js/Object/defineProperties) و التي تسمح بتعريف كثير من الخصائص مره واحده. +======= +```smart header="The only attribute change possible: writable true -> false" +There's a minor exception about changing flags. + +We can change `writable: true` to `false` for a non-configurable property, thus preventing its value modification (to add another layer of protection). Not the other way around though. +``` + +## Object.defineProperties + +There's a method [Object.defineProperties(obj, descriptors)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperties) that allows to define many properties at once. +>>>>>>> 4d01fc20d4d82358e61518a31efe80dec9bb2602 و صياغتها تكون كالآتي: @@ -293,7 +334,11 @@ Object.defineProperties(user, { ## Object.getOwnPropertyDescriptors +<<<<<<< HEAD لجلب كلّ واصفات الخصائص معًا, يمكننا إستعمال الطريقة [Object.getOwnPropertyDescriptors(obj)](mdn:js/Object/getOwnPropertyDescriptors). +======= +To get all property descriptors at once, we can use the method [Object.getOwnPropertyDescriptors(obj)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/getOwnPropertyDescriptors). +>>>>>>> 4d01fc20d4d82358e61518a31efe80dec9bb2602 بدمجه مع `Object.defineProperties` يمكن إستخدامها لنسخ الكائنات "ونحن على علمٍ براياتها": @@ -319,6 +364,7 @@ for (let key in user) { يوجد ايضاً تحدد الدخول الى الكائن _كله_ : +<<<<<<< HEAD [Object.preventExtensions(obj)](mdn:js/Object/preventExtensions) : يمنع إضافة خصائص جديدة إلى الكائن. @@ -327,9 +373,20 @@ for (let key in user) { [Object.freeze(obj)](mdn:js/Object/freeze) : يمنع إضافة الخصائص أو إزالتها أو تغييرها. يقوم بوضع `configurable: false, writable: false` لكل الخصائص الموجودة. +======= +[Object.preventExtensions(obj)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/preventExtensions) +: Forbids the addition of new properties to the object. + +[Object.seal(obj)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/seal) +: Forbids adding/removing of properties. Sets `configurable: false` for all existing properties. + +[Object.freeze(obj)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze) +: Forbids adding/removing/changing of properties. Sets `configurable: false, writable: false` for all existing properties. +>>>>>>> 4d01fc20d4d82358e61518a31efe80dec9bb2602 كما أنّ هناك توابِع أخرى تفحص تلك المزايا: +<<<<<<< HEAD [Object.isExtensible(obj)](mdn:js/Object/isExtensible) : يُعيد `false` لو كان ممنوعًا إضافة الخصائص, غير ذلك `true`. @@ -338,5 +395,15 @@ for (let key in user) { [Object.isFrozen(obj)](mdn:js/Object/isFrozen) : يُعيد `true` إذا كان إضافة/حذف/تعديل الخصائص ممنوعاً, و كل الخصائص الحالية `configurable: false, writable: false`. +======= +[Object.isExtensible(obj)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/isExtensible) +: Returns `false` if adding properties is forbidden, otherwise `true`. + +[Object.isSealed(obj)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/isSealed) +: Returns `true` if adding/removing properties is forbidden, and all existing properties have `configurable: false`. + +[Object.isFrozen(obj)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/isFrozen) +: Returns `true` if adding/removing/changing properties is forbidden, and all current properties are `configurable: false, writable: false`. +>>>>>>> 4d01fc20d4d82358e61518a31efe80dec9bb2602 أمّا على أرض الواقع، فنادرًا ما نستعمل تلك الطرق. diff --git a/1-js/08-prototypes/01-prototype-inheritance/article.md b/1-js/08-prototypes/01-prototype-inheritance/article.md index 0db392e7f..761c3efde 100644 --- a/1-js/08-prototypes/01-prototype-inheritance/article.md +++ b/1-js/08-prototypes/01-prototype-inheritance/article.md @@ -10,7 +10,7 @@ _الوراثة النموذجية_ (تدعى أيضًا الوراثة عبر لكائنات جافا سكريبت خاصية مخفية أخرى باسم `[[Prototype]]` (هذا اسمها في المواصفات القياسية للغة جافا سكريبت)، وهي إمّا أن تكون `null` أو أن تشير إلى كائن آخر. نسمّي هذا الكائن بِـ”prototype“ (نموذج أولي). -When we read a property from `object`, and it's missing, JavaScript automatically takes it from the prototype. In programming, such thing is called "prototypal inheritance". And soon we'll study many examples of such inheritance, as well as cooler language features built upon it. +When we read a property from `object`, and it's missing, JavaScript automatically takes it from the prototype. In programming, this is called "prototypal inheritance". And soon we'll study many examples of such inheritance, as well as cooler language features built upon it. إن كائن النموذج الأولي ”سحريٌ“ إن صحّ القول، فحين نريد قراءة خاصية من كائن `object` ولا يجدها محرّك جافا سكريبت، يأخذها تلقائيًا من كائن النموذج الأولي لذاك الكائن. يُسمّى هذا في علم البرمجة ”بالوراثة النموذجية“ (‏Prototypal inheritance)، وهناك العديد من المزايا الرائعة في اللغة وفي التقنيات البرمجية مبنية عليها. diff --git a/1-js/08-prototypes/02-function-prototype/4-new-object-same-constructor/solution.md b/1-js/08-prototypes/02-function-prototype/4-new-object-same-constructor/solution.md index 0073e252e..372d50dd6 100644 --- a/1-js/08-prototypes/02-function-prototype/4-new-object-same-constructor/solution.md +++ b/1-js/08-prototypes/02-function-prototype/4-new-object-same-constructor/solution.md @@ -38,7 +38,12 @@ Why `user2.name` is `undefined`? Here's how `new user.constructor('Pete')` works: 1. First, it looks for `constructor` in `user`. Nothing. -2. Then it follows the prototype chain. The prototype of `user` is `User.prototype`, and it also has nothing. -3. The value of `User.prototype` is a plain object `{}`, its prototype is `Object.prototype`. And there is `Object.prototype.constructor == Object`. So it is used. +2. Then it follows the prototype chain. The prototype of `user` is `User.prototype`, and it also has no `constructor` (because we "forgot" to set it right!). +3. Going further up the chain, `User.prototype` is a plain object, its prototype is the built-in `Object.prototype`. +4. Finally, for the built-in `Object.prototype`, there's a built-in `Object.prototype.constructor == Object`. So it is used. -At the end, we have `let user2 = new Object('Pete')`. The built-in `Object` constructor ignores arguments, it always creates an empty object, similar to `let user2 = {}`, that's what we have in `user2` after all. +Finally, at the end, we have `let user2 = new Object('Pete')`. + +Probably, that's not what we want. We'd like to create `new User`, not `new Object`. That's the outcome of the missing `constructor`. + +(Just in case you're curious, the `new Object(...)` call converts its argument to an object. That's a theoretical thing, in practice no one calls `new Object` with a value, and generally we don't use `new Object` to make objects at all). \ No newline at end of file diff --git a/1-js/09-classes/01-class/article.md b/1-js/09-classes/01-class/article.md index 19711656e..f9b897759 100644 --- a/1-js/09-classes/01-class/article.md +++ b/1-js/09-classes/01-class/article.md @@ -112,7 +112,7 @@ alert(typeof User); // function alert(User === User.prototype.constructor); // true // The methods are in User.prototype, e.g: -alert(User.prototype.sayHi); // alert(this.name); +alert(User.prototype.sayHi); // the code of the sayHi method // there are exactly two methods in the prototype alert(Object.getOwnPropertyNames(User.prototype)); // constructor, sayHi @@ -146,7 +146,11 @@ user.sayHi(); لا تزال هناك اختلافات مهمة. +<<<<<<< HEAD 1. أولاً ، يتم تصنيف دالة تم إنشاؤها بواسطة "class" بواسطة خاصية داخلية خاصة `[[FunctionKind]]:" classConstructor "`. لذلك فهي ليست تمامًا مثل إنشائها يدويًا. +======= +1. First, a function created by `class` is labelled by a special internal property `[[IsClassConstructor]]: true`. So it's not entirely the same as creating it manually. +>>>>>>> 4d01fc20d4d82358e61518a31efe80dec9bb2602 تقوم اللغة بالتحقق من هذه الخاصية في أماكن متنوعة. على سبيل المثال ، على عكس الوظيفة العادية ، يجب أن يتم استدعاؤها بـ `new`: diff --git a/1-js/09-classes/04-private-protected-properties-methods/article.md b/1-js/09-classes/04-private-protected-properties-methods/article.md index 223926857..ce0a74750 100644 --- a/1-js/09-classes/04-private-protected-properties-methods/article.md +++ b/1-js/09-classes/04-private-protected-properties-methods/article.md @@ -113,7 +113,7 @@ class CoffeeMachine { let coffeeMachine = new CoffeeMachine(100); // add water -coffeeMachine.waterAmount = -10; // Error: Negative water +coffeeMachine.waterAmount = -10; // _waterAmount will become 0, not -10 ``` Now the access is under control, so setting the water amount below zero becomes impossible. @@ -188,7 +188,11 @@ new CoffeeMachine().setWaterAmount(100); يجب أن يبدأ الأفراد بـ `#`. يمكن الوصول إليها فقط من داخل الفصل. +<<<<<<< HEAD على سبيل المثال ، إليك خاصية `# waterLimit` الخاصة والطريقة الخاصة لفحص المياه` # checkWater`: +======= +For instance, here's a private `#waterLimit` property and the water-checking private method `#fixWaterAmount`: +>>>>>>> 4d01fc20d4d82358e61518a31efe80dec9bb2602 ```js run class CoffeeMachine { diff --git a/1-js/10-error-handling/2-custom-errors/article.md b/1-js/10-error-handling/2-custom-errors/article.md index ead4734ea..cce7cde75 100644 --- a/1-js/10-error-handling/2-custom-errors/article.md +++ b/1-js/10-error-handling/2-custom-errors/article.md @@ -15,6 +15,7 @@ ``` let json = `{ "name": "John", "age": 30 }`; ``` +<<<<<<< HEAD سنستعمل في الشيفرة التابِع `JSON.parse`، وإن استلم كائن `json` معطوب رمى خطأ `SyntaxError`. ولكن، حتّى لو كان الكائن صحيحًا صياغيًا، فلا يعني هذا أنّ المستخدم صالحًا أيضًا، أم لا؟ لربّما لا يحتوي بعض البيانات مثل خاصيتي الاسم `json` والعمر `name` الضروريتين للمستخدمين. @@ -26,6 +27,19 @@ let json = `{ "name": "John", "age": 30 }`; لنعرف ما نحاول توسعته: ``` // شيفرة مبسّطة لصنف الخطأ ‫Error المضمّن في لغة جافا سكريبت نفسها +======= + +Internally, we'll use `JSON.parse`. If it receives malformed `json`, then it throws `SyntaxError`. But even if `json` is syntactically correct, that doesn't mean that it's a valid user, right? It may miss the necessary data. For instance, it may not have `name` and `age` properties that are essential for our users. + +Our function `readUser(json)` will not only read JSON, but check ("validate") the data. If there are no required fields, or the format is wrong, then that's an error. And that's not a `SyntaxError`, because the data is syntactically correct, but another kind of error. We'll call it `ValidationError` and create a class for it. An error of that kind should also carry the information about the offending field. + +Our `ValidationError` class should inherit from the `Error` class. + +The `Error` class is built-in, but here's its approximate code so we can understand what we're extending: + +```js +// The "pseudocode" for the built-in Error class defined by JavaScript itself +>>>>>>> 4d01fc20d4d82358e61518a31efe80dec9bb2602 class Error { constructor(message) { this.message = message; @@ -100,6 +114,7 @@ throw err; // خطأ لا نعرفه، علينا إعادة رميه (**) } else if (err.name == "SyntaxError") { // (*) // ... ``` +<<<<<<< HEAD ولكنّ استعمال `instanceof` أفضل بكثير إذ يحدث ونوسّع مستقبلًا الصنف `ValidationError` بأصناف فرعية منه مثل `PropertyRequiredError`، والفحص عبر `instanceof` سيظلّ يعمل للأصناف الموروثة منه، كما من المهمّ أن تُعيد كتلة `catch` رمي الأخطاء التي لا تفهمها، كما في السطر `(**)`. ليس على هذه الكتلة إلّا التعامل مع @@ -110,6 +125,18 @@ throw err; // خطأ لا نعرفه، علينا إعادة رميه (**) موجودة أو كان نسقها خطأ (مثل تقديم سلسلة نصية قيمةً للعمر `age`). لنصنع الصنف .... `PropertyRequiredError` ونستعمله فقط للخاصيات غير الموجودة، وسيحتوي على أيّة معلومات إضافية عن الخاصية الناقصة. ``` +======= + +The `instanceof` version is much better, because in the future we are going to extend `ValidationError`, make subtypes of it, like `PropertyRequiredError`. And `instanceof` check will continue to work for new inheriting classes. So that's future-proof. + +Also it's important that if `catch` meets an unknown error, then it rethrows it in the line `(**)`. The `catch` block only knows how to handle validation and syntax errors, other kinds (caused by a typo in the code or other unknown reasons) should fall through. + +## Further inheritance + +The `ValidationError` class is very generic. Many things may go wrong. The property may be absent or it may be in a wrong format (like a string value for `age` instead of a number). Let's make a more concrete class `PropertyRequiredError`, exactly for absent properties. It will carry additional information about the property that's missing. + +```js run +>>>>>>> 4d01fc20d4d82358e61518a31efe80dec9bb2602 class ValidationError extends Error { constructor(message) { diff --git a/1-js/11-async/01-callbacks/article.md b/1-js/11-async/01-callbacks/article.md index 37412d2fb..3d9e659b1 100644 --- a/1-js/11-async/01-callbacks/article.md +++ b/1-js/11-async/01-callbacks/article.md @@ -28,7 +28,11 @@ function loadScript(src) { } ``` +<<<<<<< HEAD يتم إلحاق المستند الجديد ، الذي تم إنشاؤه ديناميكيًا ، العنصر ` ``` +<<<<<<< HEAD ولو أردنا أن ننشئ متغير عام على مستوى النافذة يمكننا تعيينه صراحة للمتغيّر `window` ويمكننا الوصول إليه هكذا `window.user`. ولكن لابد من وجود سبب وجيهٍ لذلك. +======= +```smart +In the browser, we can make a variable window-level global by explicitly assigning it to a `window` property, e.g. `window.user = "John"`. + +Then all scripts will see it, both with `type="module"` and without it. + +That said, making such global variables is frowned upon. Please try to avoid them. +``` +>>>>>>> 4d01fc20d4d82358e61518a31efe80dec9bb2602 ### تقييم شيفرة الوحدة لمرة واحدة فقط +<<<<<<< HEAD لو استوردتَ نفس الوحدة في أكثر من مكان، فلا تُنفّذ شيفرتها إلّا مرة واحدة، وبعدها تُصدّر إلى من استوردها. ولهذا توابع مهمّ معرفتها. لنرى بعض الأمثلة. +======= +If the same module is imported into multiple other modules, its code is executed only once, upon the first import. Then its exports are given to all further importers. + +The one-time evaluation has important consequences, that we should be aware of. + +Let's see a couple of examples. +>>>>>>> 4d01fc20d4d82358e61518a31efe80dec9bb2602 أولًا، لو كان لشيفرة الوحدة التي ستُنفّذ أيّ تأثيرات (مثل عرض رسالة أو ما شابه)، فاستيرادها أكثر من مرّة سيشغّل ذلك التأثير مرة واحدة، وهي أول مرة فقط: @@ -134,9 +175,17 @@ import `./alert.js`; // ‫نُفّذت شيفرة الوحدة! import `./alert.js`; // (لا نرى شيئًا هنا) ``` +<<<<<<< HEAD في الواقع، فشيفرات الوحدات عالية المستوى في بنية البرمجية لا تُستعمل إلّا لتمهيد بنى البيانات الداخلية وإنشائها. ولو أردنا شيئًا نُعيد استعماله، نُصدّر الوحدة. الآن حان وقت مثال مستواه متقدّم أكثر. +======= +The second import shows nothing, because the module has already been evaluated. + +There's a rule: top-level module code should be used for initialization, creation of module-specific internal data structures. If we need to make something callable multiple times - we should export it as a function, like we did with `sayHi` above. + +Now, let's consider a deeper example. +>>>>>>> 4d01fc20d4d82358e61518a31efe80dec9bb2602 لنقل بأنّ هناك وحدة تُصدّر كائنًا: @@ -161,6 +210,7 @@ import {admin} from './admin.js'; alert(admin.name); // Pete *!* +<<<<<<< HEAD // ‫كِلا الملفين ‎1.js و ‎2.js سيستوردان نفس الكائن // ‫التغييرات الّتي ستحدثُ في الملف ‎1.js ستكون مرئية في الملف ‎2.js */!* @@ -171,45 +221,90 @@ alert(admin.name); // Pete يتيح لنا هذا السلوك ”ضبط“ الوحدة عند أوّل استيراد لها، فنضبط خاصياتها المرة الأولى، ومتى ما استوُردت مرة أخرى تكون جاهزة. فمثلًا قد تقدّم لنا وحدة `admin.js` بعض المزايا ولكن تطلب أن تأتي امتيازات الإدارة من خارج كائن `admin` إلى داخله: +======= +// Both 1.js and 2.js reference the same admin object +// Changes made in 1.js are visible in 2.js +*/!* +``` + +As you can see, when `1.js` changes the `name` property in the imported `admin`, then `2.js` can see the new `admin.name`. + +That's exactly because the module is executed only once. Exports are generated, and then they are shared between importers, so if something changes the `admin` object, other modules will see that. + +**Such behavior is actually very convenient, because it allows us to *configure* modules.** + +In other words, a module can provide a generic functionality that needs a setup. E.g. authentication needs credentials. Then it can export a configuration object expecting the outer code to assign to it. + +Here's the classical pattern: +1. A module exports some means of configuration, e.g. a configuration object. +2. On the first import we initialize it, write to its properties. The top-level application script may do that. +3. Further imports use the module. + +For instance, the `admin.js` module may provide certain functionality (e.g. authentication), but expect the credentials to come into the `config` object from outside: +>>>>>>> 4d01fc20d4d82358e61518a31efe80dec9bb2602 ``` // 📁 admin.js -export let admin = { }; +export let config = { }; export function sayHi() { - alert(`Ready to serve, ${admin.name}!`); + alert(`Ready to serve, ${config.user}!`); } ``` +<<<<<<< HEAD نضبط في `init.js` (أوّل نص برمجي لتطبيقنا) المتغير `admin.name`. بعدها سيراه كلّ من أراد بما في ذلك الاستدعاءات من داخل وحدة `admin.js` نفسها: +======= +Here, `admin.js` exports the `config` object (initially empty, but may have default properties too). + +Then in `init.js`, the first script of our app, we import `config` from it and set `config.user`: +>>>>>>> 4d01fc20d4d82358e61518a31efe80dec9bb2602 ``` // 📁 init.js -import {admin} from './admin.js'; -admin.name = "Pete"; +import {config} from './admin.js'; +config.user = "Pete"; ``` +<<<<<<< HEAD ويمكن لوحدة أخرى استعمال `admin.name`: ``` // 📁 other.js import {admin, sayHi} from './admin.js'; +======= +...Now the module `admin.js` is configured. -alert(admin.name); // *!*Pete*/!* +Further importers can call it, and it correctly shows the current user: +>>>>>>> 4d01fc20d4d82358e61518a31efe80dec9bb2602 + +```js +// 📁 another.js +import {sayHi} from './admin.js'; sayHi(); // Ready to serve, *!*Pete*/!*! ``` + ### import.meta يحتوي الكائن `import.meta` على معلومات الوحدة الحالية. +<<<<<<< HEAD ويعتمد محتواها على البيئة الحالية، ففي المتصفّحات يحتوي على عنوان النص البرمجي أو عنوان صفحة الوِب الحالية لو كان داخل HTML: +======= +Its content depends on the environment. In the browser, it contains the URL of the script, or a current webpage URL if inside HTML: +>>>>>>> 4d01fc20d4d82358e61518a31efe80dec9bb2602 ``` html run height=0 ``` diff --git a/1-js/99-js-misc/04-reference-type/article.md b/1-js/99-js-misc/04-reference-type/article.md index c291abc05..e139b12b8 100644 --- a/1-js/99-js-misc/04-reference-type/article.md +++ b/1-js/99-js-misc/04-reference-type/article.md @@ -3,7 +3,14 @@ ```warn header="خصائص متقدمه فى اللغه" هذه المقالة تقوم بتغطية موضوع متقدم, لفهم بعض الحالات بشكل أفضل. +<<<<<<< HEAD إنها ليست مهمة. يعيش العديد من المطورين ذوي الخبرة بشكل جيد دون معرفة ذلك. تابع القراءة إذا كنت تريد معرفة كيفية عمل الأشياء خلف الكواليس. +======= +```warn header="In-depth language feature" +This article covers an advanced topic, to understand certain edge-cases better. + +It's not important. Many experienced developers live fine without knowing it. Read on if you want to know how things work under the hood. +>>>>>>> 4d01fc20d4d82358e61518a31efe80dec9bb2602 ``` قد تفقد استدعاء تابع تم تقييمه بشكل ديناميكي `this`. diff --git a/2-ui/1-document/03-dom-navigation/article.md b/2-ui/1-document/03-dom-navigation/article.md index f7123d70d..b5f03098c 100644 --- a/2-ui/1-document/03-dom-navigation/article.md +++ b/2-ui/1-document/03-dom-navigation/article.md @@ -214,7 +214,7 @@ alert( document.body.previousSibling ); // HTMLHeadElement ## Element-only navigation -Navigation properties listed above refer to *all* nodes. For instance, in `childNodes` we can see both text nodes, element nodes, and even comment nodes if there exist. +Navigation properties listed above refer to *all* nodes. For instance, in `childNodes` we can see both text nodes, element nodes, and even comment nodes if they exist. But for many tasks we don't want text or comment nodes. We want to manipulate element nodes that represent tags and form the structure of the page. diff --git a/2-ui/1-document/07-modifying-document/5-why-aaa/solution.md b/2-ui/1-document/07-modifying-document/5-why-aaa/solution.md index 6b85168b9..3d1f6698f 100644 --- a/2-ui/1-document/07-modifying-document/5-why-aaa/solution.md +++ b/2-ui/1-document/07-modifying-document/5-why-aaa/solution.md @@ -1,9 +1,9 @@ The HTML in the task is incorrect. That's the reason of the odd thing. -The browser has to fix it automatically. But there may be no text inside the ``: according to the spec only table-specific tags are allowed. So the browser adds `"aaa"` *before* the `
`. +The browser has to fix it automatically. But there may be no text inside the `
`: according to the spec only table-specific tags are allowed. So the browser shows `"aaa"` *before* the `
`. Now it's obvious that when we remove the table, it remains. -The question can be easily answered by exploring the DOM using the browser tools. It shows `"aaa"` before the `
`. +The question can be easily answered by exploring the DOM using the browser tools. You'll see `"aaa"` before the `
`. The HTML standard specifies in detail how to process bad HTML, and such behavior of the browser is correct. diff --git a/2-ui/1-document/10-size-and-scroll-window/article.md b/2-ui/1-document/10-size-and-scroll-window/article.md index d0985f3ad..276c596b5 100644 --- a/2-ui/1-document/10-size-and-scroll-window/article.md +++ b/2-ui/1-document/10-size-and-scroll-window/article.md @@ -77,7 +77,17 @@ alert('Current scroll from the left: ' + window.pageXOffset); هذه الخصائص للقراءة فقط. +<<<<<<< HEAD ## التمرير: التمرير إلى التمرير التمرير التمرير العرضي [# window-تمرير] +======= +```smart header="Also available as `window` properties `scrollX` and `scrollY`" +For historical reasons, both properties exist, but they are the same: +- `window.pageXOffset` is an alias of `window.scrollX`. +- `window.pageYOffset` is an alias of `window.scrollY`. +``` + +## Scrolling: scrollTo, scrollBy, scrollIntoView [#window-scroll] +>>>>>>> 4d01fc20d4d82358e61518a31efe80dec9bb2602 ```warn To scroll the page with JavaScript, its DOM must be fully built. diff --git a/2-ui/3-event-details/4-mouse-drag-and-drop/article.md b/2-ui/3-event-details/4-mouse-drag-and-drop/article.md index 6cb1152c1..49ab88be2 100644 --- a/2-ui/3-event-details/4-mouse-drag-and-drop/article.md +++ b/2-ui/3-event-details/4-mouse-drag-and-drop/article.md @@ -124,7 +124,7 @@ Let's update our algorithm: ```js // onmousemove - // ball has position:absoute + // ball has position:absolute ball.style.left = event.pageX - *!*shiftX*/!* + 'px'; ball.style.top = event.pageY - *!*shiftY*/!* + 'px'; ``` 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 3e751a4af..4b58f3ffc 100644 --- a/2-ui/3-event-details/6-pointer-events/article.md +++ b/2-ui/3-event-details/6-pointer-events/article.md @@ -9,16 +9,16 @@ Let's make a small overview, so that you understand the general picture and the - Long ago, in the past, there were only mouse events. Then touch devices became widespread, phones and tablets in particular. For the existing scripts to work, they generated (and still generate) mouse events. For instance, tapping a touchscreen generates `mousedown`. So touch devices worked well with web pages. - + But touch devices have more capabilities than a mouse. For example, it's possible to touch multiple points at once ("multi-touch"). Although, mouse events don't have necessary properties to handle such multi-touches. - So touch events were introduced, such as `touchstart`, `touchend`, `touchmove`, that have touch-specific properties (we don't cover them in detail here, because pointer events are even better). - Still, it wasn't enough, as there are many other devices, such as pens, that have their own features. Also, writing code that listens for both touch and mouse events was cumbersome. + Still, it wasn't enough, as there are many other devices, such as pens, that have their own features. Also, writing code that listens for both touch and mouse events was cumbersome. - To solve these issues, the new standard Pointer Events was introduced. It provides a single set of events for all kinds of pointing devices. -As of now, [Pointer Events Level 2](https://www.w3.org/TR/pointerevents2/) specification is supported in all major browsers, while the newer [Pointer Events Level 3](https://w3c.github.io/pointerevents/) is in the works and is mostly compartible with Pointer Events level 2. +As of now, [Pointer Events Level 2](https://www.w3.org/TR/pointerevents2/) specification is supported in all major browsers, while the newer [Pointer Events Level 3](https://w3c.github.io/pointerevents/) is in the works and is mostly compatible with Pointer Events level 2. Unless you develop for old browsers, such as Internet Explorer 10, or for Safari 12 or below, there's no point in using mouse or touch events any more -- we can switch to pointer events. @@ -43,12 +43,12 @@ Pointer events are named similarly to mouse events: | `gotpointercapture` | - | | `lostpointercapture` | - | -As we can see, for every `mouse`, there's a `pointer` that plays a similar role. Also there are 3 additional pointer events that don't have a corresponding `mouse...` counterpart, we'll explain them soon. +As we can see, for every `mouse`, there's a `pointer` that plays a similar role. Also there are 3 additional pointer events that don't have a corresponding `mouse...` counterpart, we'll explain them soon. ```smart header="Replacing `mouse` with `pointer` in our code" We can replace `mouse` events with `pointer` in our code and expect things to continue working fine with mouse. -The support for touch devices will also "magically" improve. Although, we may need to add `touch-action: none` in some places in CSS. We'll cover it below in the section about `pointercancel`. +The support for touch devices will also "magically" improve. Although, we may need to add `touch-action: none` in some places in CSS. We'll cover it below in the section about `pointercancel`. ``` ## Pointer event properties @@ -56,16 +56,16 @@ The support for touch devices will also "magically" improve. Although, we may ne Pointer events have the same properties as mouse events, such as `clientX/Y`, `target`, etc., plus some others: - `pointerId` - the unique identifier of the pointer causing the event. - + Browser-generated. Allows us to handle multiple pointers, such as a touchscreen with stylus and multi-touch (examples will follow). -- `pointerType` - the pointing device type. Must be a string, one of: "mouse", "pen" or "touch". +- `pointerType` - the pointing device type. Must be a string, one of: "mouse", "pen" or "touch". We can use this property to react differently on various pointer types. - `isPrimary` - is `true` for the primary pointer (the first finger in multi-touch). Some pointer devices measure contact area and pressure, e.g. for a finger on the touchscreen, there are additional properties for that: -- `width` - the width of the area where the pointer (e.g. a finger) touches the device. Where unsupported, e.g. for a mouse, it's always `1`. +- `width` - the width of the area where the pointer (e.g. a finger) touches the device. Where unsupported, e.g. for a mouse, it's always `1`. - `height` - the height of the area where the pointer touches the device. Where unsupported, it's always `1`. - `pressure` - the pressure of the pointer tip, in range from 0 to 1. For devices that don't support pressure must be either `0.5` (pressed) or `0`. - `tangentialPressure` - the normalized tangential pressure. @@ -102,11 +102,11 @@ Please note: you must be using a touchscreen device, such as a phone or a tablet ## Event: pointercancel -The `pointercancel` event fires when there's an ongoing pointer interaction, and then something happens that causes it to be aborted, so that no more pointer events are generated. +The `pointercancel` event fires when there's an ongoing pointer interaction, and then something happens that causes it to be aborted, so that no more pointer events are generated. -Such causes are: +Such causes are: - The pointer device hardware was physically disabled. -- The device orientation changed (tablet rotated). +- The device orientation changed (tablet rotated). - The browser decided to handle the interaction on its own, considering it a mouse gesture or zoom-and-pan action or something else. We'll demonstrate `pointercancel` on a practical example to see how it affects us. @@ -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 loggin of pointer events (only `up/down`, `move` and `cancel`) in the `textarea`: [iframe src="ball" height=240 edit] ``` @@ -141,7 +141,7 @@ We need to do two things: - We can do this by setting `ball.ondragstart = () => false`, just as described in the article . - That works well for mouse events. 2. For touch devices, there are other touch-related browser actions (besides drag'n'drop). To avoid problems with them too: - - Prevent them by setting `#ball { touch-action: none }` in CSS. + - Prevent them by setting `#ball { touch-action: none }` in CSS. - Then our code will start working on touch devices. After we do that, the events will work as intended, the browser won't hijack the process and doesn't emit `pointercancel`. @@ -163,7 +163,7 @@ Pointer capturing is a special feature of pointer events. The idea is very simple, but may seem quite odd at first, as nothing like that exists for any other event type. The main method is: -- `elem.setPointerCapture(pointerId)` - binds events with the given `pointerId` to `elem`. After the call all pointer events with the same `pointerId` will have `elem` as the target (as if happened on `elem`), no matter where in document they really happened. +- `elem.setPointerCapture(pointerId)` -- binds events with the given `pointerId` to `elem`. After the call all pointer events with the same `pointerId` will have `elem` as the target (as if happened on `elem`), no matter where in document they really happened. In other words, `elem.setPointerCapture(pointerId)` retargets all subsequent events with the given `pointerId` to `elem`. @@ -172,29 +172,43 @@ The binding is removed: - automatically when `elem` is removed from the document, - when `elem.releasePointerCapture(pointerId)` is called. +Now what is it good for? It's time to see a real-life example. + **Pointer capturing can be used to simplify drag'n'drop kind of interactions.** -As an example, let's recall how one can implement a custom slider, described in the . +Let's recall how one can implement a custom slider, described in the . + +We can make a `slider` element to represent the strip and the "runner" (`thumb`) inside it: + +```html +
+
+
+``` + +With styles, it looks like this: + +[iframe src="slider-html" height=40 edit] -We make a slider element with the strip and the "runner" (`thumb`) inside it. +

-Then it works like this: +And here's the working logic, as it was described, after replacing mouse events with similar pointer events: -1. The user presses on the slider `thumb` - `pointerdown` triggers. -2. Then they move the pointer - `pointermove` triggers, and we move the `thumb` along. - - ...As the pointer moves, it may leave the slider `thumb`: go above or below it. The `thumb` should move strictly horizontally, remaining aligned with the pointer. +1. The user presses on the slider `thumb` -- `pointerdown` triggers. +2. Then they move the pointer -- `pointermove` triggers, and our code moves the `thumb` element along. + - ...As the pointer moves, it may leave the slider `thumb` element, go above or below it. The `thumb` should move strictly horizontally, remaining aligned with the pointer. -So, to track all pointer movements, including when it goes above/below the `thumb`, we had to assign `pointermove` event handler on the whole `document`. +In the mouse event based solution, to track all pointer movements, including when it goes above/below the `thumb`, we had to assign `mousemove` event handler on the whole `document`. -That solution looks a bit "dirty". One of the problems is that pointer movements around the document may cause side effects, trigger other event handlers, totally not related to the slider. +That's not a cleanest solution, though. One of the problems is that when a user moves the pointer around the document, it may trigger event handlers (such as `mouseover`) on some other elements, invoke totally unrelated UI functionality, and we don't want that. -Pointer capturing provides a means to bind `pointermove` to `thumb` and avoid any such problems: +This is the place where `setPointerCapture` comes into play. - We can call `thumb.setPointerCapture(event.pointerId)` in `pointerdown` handler, -- Then future pointer events until `pointerup/cancel` will be retargeted to `thumb`. +- Then future pointer events until `pointerup/cancel` will be retargeted to `thumb`. - When `pointerup` happens (dragging complete), the binding is removed automatically, we don't need to care about it. -So, even if the user moves the pointer around the whole document, events handlers will be called on `thumb`. Besides, coordinate properties of the event objects, such as `clientX/clientY` will still be correct - the capturing only affects `target/currentTarget`. +So, even if the user moves the pointer around the whole document, events handlers will be called on `thumb`. Nevertheless, coordinate properties of the event objects, such as `clientX/clientY` will still be correct - the capturing only affects `target/currentTarget`. Here's the essential code: @@ -202,15 +216,23 @@ Here's the essential code: thumb.onpointerdown = function(event) { // retarget all pointer events (until pointerup) to thumb thumb.setPointerCapture(event.pointerId); -}; -thumb.onpointermove = function(event) { - // moving the slider: listen on the thumb, as all pointer events are retargeted to it - let newLeft = event.clientX - slider.getBoundingClientRect().left; - thumb.style.left = newLeft + 'px'; + // start tracking pointer moves + thumb.onpointermove = function(event) { + // moving the slider: listen on the thumb, as all pointer events are retargeted to it + let newLeft = event.clientX - slider.getBoundingClientRect().left; + thumb.style.left = newLeft + 'px'; + }; + + // on pointer up finish tracking pointer moves + thumb.onpointerup = function(event) { + thumb.onpointermove = null; + thumb.onpointerup = null; + // ...also process the "drag end" if needed + }; }; -// note: no need to call thumb.releasePointerCapture, +// note: no need to call thumb.releasePointerCapture, // it happens on pointerup automatically ``` @@ -218,15 +240,27 @@ thumb.onpointermove = function(event) { The full demo: [iframe src="slider" height=100 edit] + +

+ +In the demo, there's also an additional element with `onmouseover` handler showing the current date. + +Please note: while you're dragging the thumb, you may hover over this element, and its handler *does not* trigger. + +So the dragging is now free of side effects, thanks to `setPointerCapture`. ``` + + At the end, pointer capturing gives us two benefits: 1. The code becomes cleaner as we don't need to add/remove handlers on the whole `document` any more. The binding is released automatically. -2. If there are any `pointermove` handlers in the document, they won't be accidentally triggered by the pointer while the user is dragging the slider. +2. If there are other pointer event handlers in the document, they won't be accidentally triggered by the pointer while the user is dragging the slider. ### Pointer capturing events -There are two associated pointer events: +There's one more thing to mention here, for the sake of completeness. + +There are two events associated with pointer capturing: - `gotpointercapture` fires when an element uses `setPointerCapture` to enable capturing. - `lostpointercapture` fires when the capture is released: either explicitly with `releasePointerCapture` call, or automatically on `pointerup`/`pointercancel`. diff --git a/2-ui/3-event-details/6-pointer-events/slider-html.view/index.html b/2-ui/3-event-details/6-pointer-events/slider-html.view/index.html new file mode 100644 index 000000000..781016f52 --- /dev/null +++ b/2-ui/3-event-details/6-pointer-events/slider-html.view/index.html @@ -0,0 +1,6 @@ + + + +
+
+
diff --git a/2-ui/3-event-details/6-pointer-events/slider-html.view/style.css b/2-ui/3-event-details/6-pointer-events/slider-html.view/style.css new file mode 100644 index 000000000..9b3d3b82d --- /dev/null +++ b/2-ui/3-event-details/6-pointer-events/slider-html.view/style.css @@ -0,0 +1,19 @@ +.slider { + border-radius: 5px; + background: #E0E0E0; + background: linear-gradient(left top, #E0E0E0, #EEEEEE); + width: 310px; + height: 15px; + margin: 5px; +} + +.thumb { + width: 10px; + height: 25px; + border-radius: 3px; + position: relative; + left: 10px; + top: -5px; + background: blue; + cursor: pointer; +} diff --git a/2-ui/3-event-details/6-pointer-events/slider.view/index.html b/2-ui/3-event-details/6-pointer-events/slider.view/index.html index 2c2a69ec7..b29e646a1 100644 --- a/2-ui/3-event-details/6-pointer-events/slider.view/index.html +++ b/2-ui/3-event-details/6-pointer-events/slider.view/index.html @@ -5,22 +5,33 @@
+

Mouse over here to see the date

+ diff --git a/2-ui/3-event-details/6-pointer-events/slider.view/style.css b/2-ui/3-event-details/6-pointer-events/slider.view/style.css index 9b3d3b82d..a84cd5e7e 100644 --- a/2-ui/3-event-details/6-pointer-events/slider.view/style.css +++ b/2-ui/3-event-details/6-pointer-events/slider.view/style.css @@ -8,6 +8,7 @@ } .thumb { + touch-action: none; width: 10px; height: 25px; border-radius: 3px; diff --git a/2-ui/3-event-details/7-keyboard-events/article.md b/2-ui/3-event-details/7-keyboard-events/article.md index 54bde42b4..51c1618f6 100644 --- a/2-ui/3-event-details/7-keyboard-events/article.md +++ b/2-ui/3-event-details/7-keyboard-events/article.md @@ -139,22 +139,25 @@ For instance, the `` below expects a phone number, so it does not accept ```html autorun height=60 run ``` -Please note that special keys, such as `key:Backspace`, `key:Left`, `key:Right`, `key:Ctrl+V`, do not work in the input. That's a side-effect of the strict filter `checkPhoneKey`. +The `onkeydown` handler here uses `checkPhoneKey` to check for the key pressed. If it's valid (from `0..9` or one of `+-()`), then it returns `true`, otherwise `false`. -Let's relax it a little bit: +As we know, the `false` value returned from the event handler, assigned using a DOM property or an attribute, such as above, prevents the default action, so nothing appears in the `` for keys that don't pass the test. (The `true` value returned doesn't affect anything, only returning `false` matters) +Please note that special keys, such as `key:Backspace`, `key:Left`, `key:Right`, do not work in the input. That's a side-effect of the strict filter `checkPhoneKey`. These keys make it return `false`. + +Let's relax the filter a little bit by allowing arrow keys `key:Left`, `key:Right` and `key:Delete`, `key:Backspace`: ```html autorun height=60 run @@ -162,7 +165,9 @@ function checkPhoneKey(key) { Now arrows and deletion works well. -...But we still can enter anything by using a mouse and right-click + Paste. So the filter is not 100% reliable. We can just let it be like that, because most of time it works. Or an alternative approach would be to track the `input` event -- it triggers after any modification. There we can check the new value and highlight/modify it when it's invalid. +Even though we have the key filter, one still can enter anything using a mouse and right-click + Paste. Mobile devices provide other means to enter values. So the filter is not 100% reliable. + +The alternative approach would be to track the `oninput` event -- it triggers *after* any modification. There we can check the new `input.value` and modify it/highlight the `` when it's invalid. Or we can use both event handlers together. ## Legacy diff --git a/2-ui/4-forms-controls/2-focus-blur/4-edit-td-click/task.md b/2-ui/4-forms-controls/2-focus-blur/4-edit-td-click/task.md index 2cccea020..378bd1f54 100644 --- a/2-ui/4-forms-controls/2-focus-blur/4-edit-td-click/task.md +++ b/2-ui/4-forms-controls/2-focus-blur/4-edit-td-click/task.md @@ -6,7 +6,7 @@ importance: 5 Make table cells editable on click. -- On click -- the cell should became "editable" (textarea appears inside), we can change HTML. There should be no resize, all geometry should remain the same. +- On click -- the cell should become "editable" (textarea appears inside), we can change HTML. There should be no resize, all geometry should remain the same. - Buttons OK and CANCEL appear below the cell to finish/cancel the editing. - Only one cell may be editable at a moment. While a `
` is in "edit mode", clicks on other cells are ignored. - The table may have many cells. Use event delegation. diff --git a/2-ui/4-forms-controls/2-focus-blur/5-keyboard-mouse/task.md b/2-ui/4-forms-controls/2-focus-blur/5-keyboard-mouse/task.md index fc48c21ff..644d814d9 100644 --- a/2-ui/4-forms-controls/2-focus-blur/5-keyboard-mouse/task.md +++ b/2-ui/4-forms-controls/2-focus-blur/5-keyboard-mouse/task.md @@ -9,4 +9,5 @@ Focus on the mouse. Then use arrow keys to move it: [demo src="solution"] P.S. Don't put event handlers anywhere except the `#mouse` element. + P.P.S. Don't modify HTML/CSS, the approach should be generic and work with any element. diff --git a/2-ui/4-forms-controls/2-focus-blur/article.md b/2-ui/4-forms-controls/2-focus-blur/article.md index d4348d25b..b866a5e2b 100644 --- a/2-ui/4-forms-controls/2-focus-blur/article.md +++ b/2-ui/4-forms-controls/2-focus-blur/article.md @@ -104,7 +104,7 @@ The best recipe is to be careful when using these events. If we want to track us ``` ## Allow focusing on any element: tabindex -By default many elements do not support focusing. +By default, many elements do not support focusing. The list varies a bit between browsers, but one thing is always correct: `focus/blur` support is guaranteed for elements that a visitor can interact with: ` + ## Selection properties -Similar to a range, a selection has a start, called "anchor", and the end, called "focus". +As said, a selection may in theory contain multiple ranges. We can get these range objects using the method: + +- `getRangeAt(i)` -- get i-th range, starting from `0`. In all browsers except Firefox, only `0` is used. + +Also, there exist properties that often provide better convenience. + +Similar to a range, a selection object has a start, called "anchor", and the end, called "focus". The main selection properties are: @@ -294,36 +352,39 @@ The main selection properties are: - `isCollapsed` -- `true` if selection selects nothing (empty range), or doesn't exist. - `rangeCount` -- count of ranges in the selection, maximum `1` in all browsers except Firefox. -````smart header="Usually, the selection end `focusNode` is after its start `anchorNode`, but it's not always the case" -There are many ways to select the content, depending on the user agent: mouse, hotkeys, taps on a mobile etc. +```smart header="Selection end/start vs Range" + +There's an important differences of a selection anchor/focus compared with a `Range` start/end. + +As we know, `Range` objects always have their start before the end. -Some of them, such as a mouse, allow the same selection can be created in two directions: "left-to-right" and "right-to-left". +For selections, that's not always the case. -If the start (anchor) of the selection goes in the document before the end (focus), this selection is said to have "forward" direction. +Selecting something with a mouse can be done in both directions: either "left-to-right" or "right-to-left". + +In other words, when the mouse button is pressed, and then it moves forward in the document, then its end (focus) will be after its start (anchor). E.g. if the user starts selecting with mouse and goes from "Example" to "italic": ![](selection-direction-forward.svg) -Otherwise, if they go from the end of "italic" to "Example", the selection is directed "backward", its focus will be before the anchor: +...But the same selection could be done backwards: starting from "italic" to "Example" (backward direction), then its end (focus) will be before the start (anchor): ![](selection-direction-backward.svg) - -That's different from `Range` objects that are always directed forward: the range start can't be after its end. -```` +``` ## Selection events There are events on to keep track of selection: -- `elem.onselectstart` -- when a selection starts on `elem`, e.g. the user starts moving mouse with pressed button. - - Preventing the default action makes the selection not start. -- `document.onselectionchange` -- whenever a selection changes. - - Please note: this handler can be set only on `document`. +- `elem.onselectstart` -- when a selection *starts* specifically on element `elem` (or inside it). For instance, when the user presses the mouse button on it and starts to move the pointer. + - Preventing the default action cancels the selection start. So starting a selection from this element becomes impossible, but the element is still selectable. The visitor just needs to start the selection from elsewhere. +- `document.onselectionchange` -- whenever a selection changes or starts. + - Please note: this handler can be set only on `document`, it tracks all selections in it. ### Selection tracking demo -Here's a small demo that shows selection boundaries dynamically as it changes: +Here's a small demo. It tracks the current selection on the `document` and shows its boundaries: ```html run height=80

Select me: italic and bold

@@ -331,21 +392,25 @@ Here's a small demo that shows selection boundaries dynamically as it changes: From – To ``` -### Selection getting demo +### Selection copying demo + +There are two approaches to copying the selected content: -To get the whole selection: -- As text: just call `document.getSelection().toString()`. -- As DOM nodes: get the underlying ranges and call their `cloneContents()` method (only first range if we don't support Firefox multiselection). +1. We can use `document.getSelection().toString()` to get it as text. +2. Otherwise, to copy the full DOM, e.g. if we need to keep formatting, we can get the underlying ranges with `getRangesAt(...)`. A `Range` object, in turn, has `cloneContents()` method that clones its content and returns as `DocumentFragment` object, that we can insert elsewhere. -And here's the demo of getting the selection both as text and as DOM nodes: +Here's the demo of copying the selected content both as text and as DOM nodes: ```html run height=100

Select me: italic and bold

@@ -373,15 +438,15 @@ As text: ## Selection methods -Selection methods to add/remove ranges: +We can work with the selection by addding/removing ranges: -- `getRangeAt(i)` -- get i-th range, starting from `0`. In all browsers except firefox, only `0` is used. +- `getRangeAt(i)` -- get i-th range, starting from `0`. In all browsers except Firefox, only `0` is used. - `addRange(range)` -- add `range` to selection. All browsers except Firefox ignore the call, if the selection already has an associated range. - `removeRange(range)` -- remove `range` from the selection. - `removeAllRanges()` -- remove all ranges. - `empty()` -- alias to `removeAllRanges`. -Also, there are convenience methods to manipulate the selection range directly, without `Range`: +There are also convenience methods to manipulate the selection range directly, without intermediate `Range` calls: - `collapse(node, offset)` -- replace selected range with a new one that starts and ends at the given `node`, at position `offset`. - `setPosition(node, offset)` -- alias to `collapse`. @@ -393,7 +458,7 @@ Also, there are convenience methods to manipulate the selection range directly, - `deleteFromDocument()` -- remove selected content from the document. - `containsNode(node, allowPartialContainment = false)` -- checks whether the selection contains `node` (partially if the second argument is `true`) -So, for many tasks we can call `Selection` methods, no need to access the underlying `Range` object. +For most tasks these methods are just fine, there's no need to access the underlying `Range` object. For example, selecting the whole contents of the paragraph `

`: @@ -420,10 +485,10 @@ The same thing using ranges: ``` -```smart header="To select, remove the existing selection first" -If the selection already exists, empty it first with `removeAllRanges()`. And then add ranges. Otherwise, all browsers except Firefox ignore new ranges. +```smart header="To select something, remove the existing selection first" +If a document selection already exists, empty it first with `removeAllRanges()`. And then add ranges. Otherwise, all browsers except Firefox ignore new ranges. -The exception is some selection methods, that replace the existing selection, like `setBaseAndExtent`. +The exception is some selection methods, that replace the existing selection, such as `setBaseAndExtent`. ``` ## Selection in form controls diff --git a/2-ui/99-ui-misc/02-selection-range/range-hello-1.svg b/2-ui/99-ui-misc/02-selection-range/range-hello-1.svg new file mode 100644 index 000000000..90054cef1 --- /dev/null +++ b/2-ui/99-ui-misc/02-selection-range/range-hello-1.svg @@ -0,0 +1 @@ +<p>Hello</p>p.firstChild \ No newline at end of file diff --git a/4-binary/03-blob/article.md b/4-binary/03-blob/article.md index a44308f6f..5031afa32 100644 --- a/4-binary/03-blob/article.md +++ b/4-binary/03-blob/article.md @@ -74,7 +74,7 @@ link.href = URL.createObjectURL(blob); We can also create a link dynamically in JavaScript and simulate a click by `link.click()`, then download starts automatically. -Here's the similar code that causes user to download the dynamicallly created `Blob`, without any HTML: +Here's the similar code that causes user to download the dynamically created `Blob`, without any HTML: ```js run let link = document.createElement('a'); @@ -186,7 +186,7 @@ let context = canvas.getContext('2d'); context.drawImage(img, 0, 0); // we can context.rotate(), and do many other things on canvas -// toBlob is async opereation, callback is called when done +// toBlob is async operation, callback is called when done canvas.toBlob(function(blob) { // blob ready, download it let link = document.createElement('a'); @@ -235,7 +235,7 @@ That makes Blobs convenient for upload/download operations, that are so common i Methods that perform web-requests, such as [XMLHttpRequest](info:xmlhttprequest), [fetch](info:fetch) and so on, can work with `Blob` natively, as well as with other binary types. -We can easily convert betweeen `Blob` and low-level binary data types: +We can easily convert between `Blob` and low-level binary data types: - We can make a Blob from a typed array using `new Blob(...)` constructor. - We can get back `ArrayBuffer` from a Blob using `FileReader`, and then create a view over it for low-level binary processing. diff --git a/5-network/02-formdata/article.md b/5-network/02-formdata/article.md index a3d924528..a73d554b1 100644 --- a/5-network/02-formdata/article.md +++ b/5-network/02-formdata/article.md @@ -168,7 +168,7 @@ The server reads form data and the file, as if it were a regular form submission [FormData](https://xhr.spec.whatwg.org/#interface-formdata) objects are used to capture HTML form and submit it using `fetch` or another network method. -We can either create `new FormData(form)` from an HTML form, or create a object without a form at all, and then append fields with methods: +We can either create `new FormData(form)` from an HTML form, or create an object without a form at all, and then append fields with methods: - `formData.append(name, value)` - `formData.append(name, blob, fileName)` diff --git a/5-network/03-fetch-progress/article.md b/5-network/03-fetch-progress/article.md index f5a64da54..76b05d514 100644 --- a/5-network/03-fetch-progress/article.md +++ b/5-network/03-fetch-progress/article.md @@ -5,11 +5,11 @@ The `fetch` method allows to track *download* progress. Please note: there's currently no way for `fetch` to track *upload* progress. For that purpose, please use [XMLHttpRequest](info:xmlhttprequest), we'll cover it later. -To track download progress, we can use `response.body` property. It's `ReadableStream` -- a special object that provides body chunk-by-chunk, as it comes. Readable streams are described in the [Streams API](https://streams.spec.whatwg.org/#rs-class) specification. +To track download progress, we can use `response.body` property. It's a `ReadableStream` -- a special object that provides body chunk-by-chunk, as it comes. Readable streams are described in the [Streams API](https://streams.spec.whatwg.org/#rs-class) specification. Unlike `response.text()`, `response.json()` and other methods, `response.body` gives full control over the reading process, and we can count how much is consumed at any moment. -Here's the sketch of code that reads the reponse from `response.body`: +Here's the sketch of code that reads the response from `response.body`: ```js // instead of response.json() and other methods diff --git a/5-network/06-fetch-api/article.md b/5-network/06-fetch-api/article.md index e57689c75..e7c3fbe61 100644 --- a/5-network/06-fetch-api/article.md +++ b/5-network/06-fetch-api/article.md @@ -147,7 +147,7 @@ This option may be useful when the URL for `fetch` comes from a 3rd-party, and w The `credentials` option specifies whether `fetch` should send cookies and HTTP-Authorization headers with the request. - **`"same-origin"`** -- the default, don't send for cross-origin requests, -- **`"include"`** -- always send, requires `Accept-Control-Allow-Credentials` from cross-origin server in order for JavaScript to access the response, that was covered in the chapter , +- **`"include"`** -- always send, requires `Access-Control-Allow-Credentials` from cross-origin server in order for JavaScript to access the response, that was covered in the chapter , - **`"omit"`** -- never send, even for same-origin requests. ## cache @@ -171,7 +171,7 @@ The `redirect` option allows to change that: - **`"follow"`** -- the default, follow HTTP-redirects, - **`"error"`** -- error in case of HTTP-redirect, -- **`"manual"`** -- allows to process HTTP-redirects manually. In case of redirect, we'll get a special response object, with `response.type="opaqueredirect"` and and zeroed/empty status and most other properies. +- **`"manual"`** -- allows to process HTTP-redirects manually. In case of redirect, we'll get a special response object, with `response.type="opaqueredirect"` and zeroed/empty status and most other properies. ## integrity diff --git a/5-network/11-websocket/article.md b/5-network/11-websocket/article.md index aab6d31be..8193f8419 100644 --- a/5-network/11-websocket/article.md +++ b/5-network/11-websocket/article.md @@ -88,7 +88,7 @@ Sec-WebSocket-Key: Iv8io/9s+lYFgZWcXczP8Q== Sec-WebSocket-Version: 13 ``` -- `Origin` -- the origin of the client page, e.g. `https://javascript.info`. WebSocket objects are cross-origin by nature. There are no special headers or other limitations. Old servers are unable to handle WebSocket anyway, so there are no compabitility issues. But `Origin` header is important, as it allows the server to decide whether or not to talk WebSocket with this website. +- `Origin` -- the origin of the client page, e.g. `https://javascript.info`. WebSocket objects are cross-origin by nature. There are no special headers or other limitations. Old servers are unable to handle WebSocket anyway, so there are no compatibility issues. But `Origin` header is important, as it allows the server to decide whether or not to talk WebSocket with this website. - `Connection: Upgrade` -- signals that the client would like to change the protocol. - `Upgrade: websocket` -- the requested protocol is "websocket". - `Sec-WebSocket-Key` -- a random browser-generated key for security. diff --git a/6-data-storage/01-cookie/article.md b/6-data-storage/01-cookie/article.md index 9fc5f6955..33161cd97 100644 --- a/6-data-storage/01-cookie/article.md +++ b/6-data-storage/01-cookie/article.md @@ -39,7 +39,7 @@ We leave it as an exercise for the reader. Also, at the end of the chapter you'l ## Writing to document.cookie -We can write to `document.cookie`. But it's not a data property, it's an accessor (getter/setter). An assignment to it is treated specially. +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.** @@ -247,8 +247,11 @@ But anything more complicated, like a network request from another site or a for If that's fine for you, then adding `samesite=lax` will probably not break the user experience and add protection. -Overall, `samesite` is a great option, but it has an important drawback: -- `samesite` is ignored (not supported) by old browsers, year 2017 or so. +Overall, `samesite` is a great option. + +There's a drawback: + +- `samesite` is ignored (not supported) by very old browsers, year 2017 or so. **So if we solely rely on `samesite` to provide protection, then old browsers will be vulnerable.** diff --git a/6-data-storage/01-cookie/cookie.js b/6-data-storage/01-cookie/cookie.js index cd3e17c02..653ea5b7f 100644 --- a/6-data-storage/01-cookie/cookie.js +++ b/6-data-storage/01-cookie/cookie.js @@ -1,6 +1,6 @@ function getCookie(name) { let matches = document.cookie.match(new RegExp( - "(?:^|; )" + name.replace(/([\.$?*|{}\(\)\[\]\\\/\+^])/g, '\\$1') + "=([^;]*)" + "(?:^|; )" + name.replace(/([.$?*|{}()[\]\\/+^])/g, '\\$1') + "=([^;]*)" )); return matches ? decodeURIComponent(matches[1]) : undefined; } diff --git a/6-data-storage/02-localstorage/article.md b/6-data-storage/02-localstorage/article.md index e2e2751db..3b4df0a6a 100644 --- a/6-data-storage/02-localstorage/article.md +++ b/6-data-storage/02-localstorage/article.md @@ -127,17 +127,17 @@ Please note that both key and value must be strings. If were any other type, like a number, or an object, it gets converted to string automatically: ```js run -sessionStorage.user = {name: "John"}; -alert(sessionStorage.user); // [object Object] +localStorage.user = {name: "John"}; +alert(localStorage.user); // [object Object] ``` We can use `JSON` to store objects though: ```js run -sessionStorage.user = JSON.stringify({name: "John"}); +localStorage.user = JSON.stringify({name: "John"}); // sometime later -let user = JSON.parse( sessionStorage.user ); +let user = JSON.parse( localStorage.user ); alert( user.name ); // John ``` @@ -202,7 +202,7 @@ If both windows are listening for `window.onstorage`, then each one will react o ```js run // triggers on updates made to the same storage from other documents -window.onstorage = event => { // same as window.addEventListener('storage', event => { +window.onstorage = event => { // can also use window.addEventListener('storage', event => { if (event.key != 'now') return; alert(event.key + ':' + event.newValue + " at " + event.url); }; diff --git a/6-data-storage/03-indexeddb/article.md b/6-data-storage/03-indexeddb/article.md index a4fb78c1e..546e24907 100644 --- a/6-data-storage/03-indexeddb/article.md +++ b/6-data-storage/03-indexeddb/article.md @@ -5,7 +5,7 @@ libs: # IndexedDB -IndexedDB is a database that is built into browser, much more powerful than `localStorage`. +IndexedDB is a database that is built into a browser, much more powerful than `localStorage`. - Stores almost any kind of values by keys, multiple key types. - Supports transactions for reliability. @@ -50,7 +50,7 @@ Unlike server-side databases, IndexedDB is client-side, the data is stored in th If the local database version is less than specified in `open`, then a special event `upgradeneeded` is triggered, and we can compare versions and upgrade data structures as needed. -The `upgradeneeded` event also triggers when the database doesn't yet exist (technically, it's version is `0`), so we can perform the initialization. +The `upgradeneeded` event also triggers when the database doesn't yet exist (technically, its version is `0`), so we can perform the initialization. Let's say we published the first version of our app. @@ -95,7 +95,7 @@ openRequest.onupgradeneeded = function(event) { }; ``` -Please note: as our current version is `2`, `onupgradeneeded` handler has a code branch for version `0`, suitable for users that are accessing for the first time and have no database, and also for version `1`, for upgrades. +Please note: as our current version is `2`, the `onupgradeneeded` handler has a code branch for version `0`, suitable for users that are accessing for the first time and have no database, and also for version `1`, for upgrades. And then, only if `onupgradeneeded` handler finishes without errors, `openRequest.onsuccess` triggers, and the database is considered successfully opened. @@ -106,7 +106,7 @@ let deleteRequest = indexedDB.deleteDatabase(name) // deleteRequest.onsuccess/onerror tracks the result ``` -```warn header="We can't open an older version of the database" +```warn header="We can't open a database using an older open call version" If the current user database has a higher version than in the `open` call, e.g. the existing DB version is `3`, and we try to `open(...2)`, then that's an error, `openRequest.onerror` triggers. That's rare, but such a thing may happen when a visitor loads outdated JavaScript code, e.g. from a proxy cache. So the code is old, but his database is new. @@ -156,7 +156,7 @@ openRequest.onsuccess = function() { openRequest.onblocked = function() { // this event shouldn't trigger if we handle onversionchange correctly - // it means that there's another open connection to same database + // it means that there's another open connection to the same database // and it wasn't closed after db.onversionchange triggered for it }; */!* @@ -171,7 +171,7 @@ We can handle things more gracefully in `db.onversionchange`, prompt the visitor Or, an alternative approach would be to not close the database in `db.onversionchange`, but instead use the `onblocked` handler (in the new tab) to alert the visitor, tell him that the newer version can't be loaded until they close other tabs. -These update collisions happen rarely, but we should at least have some handling for them, at least `onblocked` handler, to prevent our script from dying silently. +These update collisions happen rarely, but we should at least have some handling for them, at least an `onblocked` handler, to prevent our script from dying silently. ## Object store @@ -189,7 +189,7 @@ An example of an object that can't be stored: an object with circular references **There must be a unique `key` for every value in the store.** -A key must be one of the these types - number, date, string, binary, or array. It's a unique identifier, so we can search/remove/update values by the key. +A key must be one of these types - number, date, string, binary, or array. It's a unique identifier, so we can search/remove/update values by the key. ![](indexeddb-structure.svg) @@ -253,7 +253,7 @@ db.deleteObjectStore('books') The term "transaction" is generic, used in many kinds of databases. -A transaction is a group operations, that should either all succeed or all fail. +A transaction is a group of operations, that should either all succeed or all fail. For instance, when a person buys something, we need to: 1. Subtract the money from their account. @@ -347,9 +347,9 @@ Usually, we can assume that a transaction commits when all its requests are comp So, in the example above no special call is needed to finish the transaction. -Transactions auto-commit principle has an important side effect. We can't insert an async operation like `fetch`, `setTimeout` in the middle of transaction. IndexedDB will not keep the transaction waiting till these are done. +Transactions auto-commit principle has an important side effect. We can't insert an async operation like `fetch`, `setTimeout` in the middle of a transaction. IndexedDB will not keep the transaction waiting till these are done. -In the code below, `request2` in line `(*)` fails, because the transaction is already committed, and can't make any request in it: +In the code below, `request2` in the line `(*)` fails, because the transaction is already committed, and can't make any request in it: ```js let request1 = books.add(book); @@ -370,7 +370,7 @@ That's because `fetch` is an asynchronous operation, a macrotask. Transactions a Authors of IndexedDB spec believe that transactions should be short-lived. Mostly for performance reasons. -Notably, `readwrite` transactions "lock" the stores for writing. So if one part of application initiated `readwrite` on `books` object store, then another part that wants to do the same has to wait: the new transaction "hangs" till the first one is done. That can lead to strange delays if transactions take a long time. +Notably, `readwrite` transactions "lock" the stores for writing. So if one part of the application initiated `readwrite` on `books` object store, then another part that wants to do the same has to wait: the new transaction "hangs" till the first one is done. That can lead to strange delays if transactions take a long time. So, what to do? @@ -792,7 +792,7 @@ await inventory.add({ id: 'js', price: 10, created: new Date() }); // Error The next `inventory.add` after `fetch` `(*)` fails with an "inactive transaction" error, because the transaction is already committed and closed at that time. -The workaround is same as when working with native IndexedDB: either make a new transaction or just split things apart. +The workaround is the same as when working with native IndexedDB: either make a new transaction or just split things apart. 1. Prepare the data and fetch all that's needed first. 2. Then save in the database. diff --git a/7-animation/2-css-animations/article.md b/7-animation/2-css-animations/article.md index 97c75da2b..fd1703cdb 100644 --- a/7-animation/2-css-animations/article.md +++ b/7-animation/2-css-animations/article.md @@ -86,7 +86,7 @@ In `transition-duration` we can specify how long the animation should take. The In `transition-delay` we can specify the delay *before* the animation. For instance, if `transition-delay` is `1s` and `transition-duration` is `2s`, then the animation starts 1 second after the property change and the total duration will be 2 seconds. -Negative values are also possible. Then the animation is shown immediately, but the starting point of the animation will be after given value (time). For example, if `transition-delay` is `-1s` and `transition-duration` is `2s`, then animation starts from the halfway point and total duration will be 1 second. +Negative values are also possible. Then the animation is shown immediately, but the starting point of the animation will be after given value (time). For example, if `transition-delay` is `-1s` and `transition-duration` is `2s`, then animation starts from the halfway point and total duration will be 1 second. Here the animation shifts numbers from `0` to `9` using CSS `translate` property: @@ -168,7 +168,7 @@ The CSS `transition` is based on that curve: .train { left: 0; transition: left 5s cubic-bezier(0, 0, 1, 1); - /* JavaScript sets left to 450px */ + /* click on a train sets left to 450px, thus triggering the animation */ } ``` @@ -191,7 +191,7 @@ CSS: .train { left: 0; transition: left 5s cubic-bezier(0, .5, .5, 1); - /* JavaScript sets left to 450px */ + /* click on a train sets left to 450px, thus triggering the animation */ } ``` @@ -215,7 +215,7 @@ So we could use `ease-out` for our slowing down train: .train { left: 0; transition: left 5s ease-out; - /* transition: left 5s cubic-bezier(0, .5, .5, 1); */ + /* same as transition: left 5s cubic-bezier(0, .5, .5, 1); */ } ``` @@ -230,7 +230,7 @@ In the example below the animation code is: .train { left: 100px; transition: left 5s cubic-bezier(.5, -1, .5, 2); - /* JavaScript sets left to 400px */ + /* click on a train sets left to 450px */ } ``` @@ -258,7 +258,7 @@ But how do we make a Bezier curve for a specific task? There are many tools. For ### Steps -The timing function `steps(number of steps[, start/end])` allows splitting an animation into steps. +The timing function `steps(number of steps[, start/end])` allows splitting an transition into multiple steps. Let's see that in an example with digits. @@ -299,9 +299,9 @@ The process is progressing like this: The alternative value `end` would mean that the change should be applied not in the beginning, but at the end of each second. -So the process would go like this: +So the process for `steps(9, end)` would go like this: -- `0s` -- `0` +- `0s` -- `0` (during the first second nothing changes) - `1s` -- `-10%` (first change at the end of the 1st second) - `2s` -- `-20%` - ... @@ -407,9 +407,90 @@ There are many articles about `@keyframes` and a [detailed specification](https: You probably won't need `@keyframes` often, unless everything is in constant motion on your sites. +## Performance + +Most CSS properties can be animated, because most of them are numeric values. For instance, `width`, `color`, `font-size` are all numbers. When you animate them, the browser gradually changes these numbers frame by frame, creating a smooth effect. + +However, not all animations will look as smooth as you'd like, because different CSS properties cost differently to change. + +In more technical details, when there's a style change, the browser goes through 3 steps to render the new look: + +1. **Layout**: re-compute the geometry and position of each element, then +2. **Paint**: re-compute how everything should look like at their places, including background, colors, +3. **Composite**: render the final results into pixels on screen, apply CSS transforms if they exist. + +During a CSS animation, this process repeats every frame. However, CSS properties that never affect geometry or position, such as `color`, may skip the Layout step. If a `color` changes, the browser doesn't calculate any new geometry, it goes to Paint -> Composite. And there are few properties that directly go to Composite. You can find a longer list of CSS properties and which stages they trigger at . + +The calculations may take time, especially on pages with many elements and a complex layout. And the delays are actually visible on most devices, leading to "jittery", less fluid animations. + +Animations of properties that skip the Layout step are faster. It's even better if Paint is skipped too. + +The `transform` property is a great choice, because: +- CSS transforms affect the target element box as a whole (rotate, flip, stretch, shift it). +- CSS transforms never affect neighbour elements. + +...So browsers apply `transform` "on top" of existing Layout and Paint calculations, in the Composite stage. + +In other words, the browser calculates the Layout (sizes, positions), paints it with colors, backgrounds, etc at the Paint stage, and then applies `transform` to element boxes that need it. + +Changes (animations) of the `transform` property never trigger Layout and Paint steps. More than that, the browser leverages the graphics accelerator (a special chip on the CPU or graphics card) for CSS transforms, thus making them very efficient. + +Luckily, the `transform` property is very powerful. By using `transform` on an element, you could rotate and flip it, stretch and shrink it, move it around, and [much more](https://developer.mozilla.org/docs/Web/CSS/transform#syntax). So instead of `left/margin-left` properties we can use `transform: translateX(…)`, use `transform: scale` for increasing element size, etc. + +The `opacity` property also never triggers Layout (also skips Paint in Mozilla Gecko). We can use it for show/hide or fade-in/fade-out effects. + +Paring `transform` with `opacity` can usually solve most of our needs, providing fluid, good-looking animations. + +For example, here clicking on the `#boat` element adds the class with `transform: translateX(300)` and `opacity: 0`, thus making it move `300px` to the right and disappear: + +```html run height=260 autorun no-beautify + + + + +``` + +Here's a more complex example, with `@keyframes`: + +```html run height=80 autorun no-beautify +

click me to start / stop

+ +``` + ## Summary -CSS animations allow smoothly (or not) animated changes of one or multiple CSS properties. +CSS animations allow smoothly (or step-by-step) animated changes of one or multiple CSS properties. They are good for most animation tasks. We're also able to use JavaScript for animations, the next chapter is devoted to that. @@ -422,6 +503,8 @@ Limitations of CSS animations compared to JavaScript animations: - Not just property changes. We can create new elements in JavaScript as part of the animation. ``` +In early examples in this chapter, we animate `font-size`, `left`, `width`, `height`, etc. In real life projects, we should use `transform: scale()` and `transform: translate()` for better performance. + The majority of animations can be implemented using CSS as described in this chapter. And the `transitionend` event allows JavaScript to be run after the animation, so it integrates fine with the code. But in the next chapter we'll do some JavaScript animations to cover more complex cases. diff --git a/8-web-components/1-webcomponents-intro/article.md b/8-web-components/1-webcomponents-intro/article.md index 3279cb133..c3522dea9 100644 --- a/8-web-components/1-webcomponents-intro/article.md +++ b/8-web-components/1-webcomponents-intro/article.md @@ -26,9 +26,9 @@ The International Space Station: ...And this thing flies, keeps humans alive in space! -How such complex devices are created? +How are such complex devices created? -Which principles we could borrow to make our development same-level reliable and scalable? Or, at least, close to it. +Which principles could we borrow to make our development same-level reliable and scalable? Or, at least, close to it? ## Component architecture diff --git a/8-web-components/2-custom-elements/article.md b/8-web-components/2-custom-elements/article.md index 13af80b8c..a84ed1192 100644 --- a/8-web-components/2-custom-elements/article.md +++ b/8-web-components/2-custom-elements/article.md @@ -365,7 +365,7 @@ Our new button extends the built-in one. So it keeps the same styles and standar ## References - HTML Living Standard: . -- Compatiblity: . +- Compatiblity: . ## Summary @@ -397,4 +397,4 @@ Custom elements can be of two types: /*