diff --git a/1-js/05-data-types/07-map-set/article.md b/1-js/05-data-types/07-map-set/article.md index e08c84084..6bb9c82f7 100644 --- a/1-js/05-data-types/07-map-set/article.md +++ b/1-js/05-data-types/07-map-set/article.md @@ -1,156 +1,142 @@ +# النوع Map (الخرائط) والنوع Set (الأطقم) -# Map and Set -Now we've learned about the following complex data structures: +تعلّمنا حتّى الآن بنى البيانات المعقّدة هذه: -- Objects for storing keyed collections. -- Arrays for storing ordered collections. +- الكائنات `Object`: لتخزين التجميعات ذات المفاتيح. +- المصفوفات `Array`: لتخزين التجميعات المرتّبة. -But that's not enough for real life. That's why `Map` and `Set` also exist. +ولكن في الحياة الواقعية، هذا لا يكفي. ولهذا تقدّم لك اللغة نوعيين آخرين: الخارطة `Map` والطقم `Set`. -## Map +## الخارطة `Map` -[Map](mdn:js/Map) is a collection of keyed data items, just like an `Object`. But the main difference is that `Map` allows keys of any type. -Methods and properties are: +تُعدّ [الخارطة](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map) تجميعة ذات مفاتيح من عناصر البيانات، تمامًا مثل الكائنات `Object`، مع فرق بسيط، هو أنّ الخارطة `Map` تتيح استعمال المفاتيح مهمًا كان نوعها. -- `new Map()` -- creates the map. -- `map.set(key, value)` -- stores the value by the key. -- `map.get(key)` -- returns the value by the key, `undefined` if `key` doesn't exist in map. -- `map.has(key)` -- returns `true` if the `key` exists, `false` otherwise. -- `map.delete(key)` -- removes the value by the key. -- `map.clear()` -- removes everything from the map. -- `map.size` -- returns the current element count. +هذه توابِعها وخاصياتها: -For instance: +- `new Map()‎` -- يُنشِئ خارطة. +- `map.set(key, value)‎` -- يضبط القيمة حسب مفتاحها. +- `map.get(key)‎` -- يجلب القيمة حسب مفتاحها، و`undefined` لو لم يوجد `key` في الخارطة. +- `map.has(key)‎` -- يُعيد `true` لو كان `key` موجودًا، وإلا فَـ `false`. +- `map.delete(key)‎` -- يُزيل القيمة حسب مفتاحها. +- `map.clear()‎` -- يُزيل كل شيء من الخارطة. +- `map.size` -- يُعيد عدد العناصر الحالي. -```js run +إليك المثال الآتي: + +``` let map = new Map(); -map.set('1', 'str1'); // a string key -map.set(1, 'num1'); // a numeric key -map.set(true, 'bool1'); // a boolean key +map.set('1', 'str1'); // المفتاح سلسلة نصية +map.set(1, 'num1'); // المفتاح عدد +map.set(true, 'bool1'); // المفتاح قيمة منطقية -// remember the regular Object? it would convert keys to string -// Map keeps the type, so these two are different: +// أتذكر كيف أنّ الكائن العادي يُحوّل المفاتيح لأي سلاسل نصية؟ +// ‫الخارطة هنا تحترم النوع، وهذان السطران مختلفان: alert( map.get(1) ); // 'num1' alert( map.get('1') ); // 'str1' alert( map.size ); // 3 ``` -As we can see, unlike objects, keys are not converted to strings. Any type of key is possible. - -```smart header="`map[key]` isn't the right way to use a `Map`" -Although `map[key]` also works, e.g. we can set `map[key] = 2`, this is treating `map` as a plain JavaScript object, so it implies all corresponding limitations (no object keys and so on). - -So we should use `map` methods: `set`, `get` and so on. -``` +كما ترى، فالمفاتيح لا تُحوّل إلى سلاسل نصية (على العكس من الكائنات). يمكنك أن تضع أيّ نوع من المفاتيح تريد. -**Map can also use objects as keys.** +**يمكن أن تستعمل الخارطة الكائناتَ نفسها مفاتيح.** -For instance: +مثال: -```js run +``` let john = { name: "John" }; -// for every user, let's store their visits count +// لنخزّن عدد زيارات كل زائر لنا let visitsCountMap = new Map(); -// john is the key for the map +// ‫كائن john هو مفتاح الخارطة visitsCountMap.set(john, 123); alert( visitsCountMap.get(john) ); // 123 ``` -Using objects as keys is one of most notable and important `Map` features. For string keys, `Object` can be fine, but not for object keys. +يُعدّ استعمال الكائنات على أنّها مفاتيح أحدُ أهمّ صفات `Map`. لو أردت المفاتيح سلاسل نصية، فالكائنات `Object` تكفيك وزيادة، لكن لو أردت المفاتيح كائنات، فسيخونك `Object` للأسف. لنرى: -Let's try: - -```js run +``` let john = { name: "John" }; -let visitsCountObj = {}; // try to use an object +let visitsCountObj = {}; // نحاول استعمال كائن -visitsCountObj[john] = 123; // try to use john object as the key +visitsCountObj[john] = 123; // ‫ونحاول استعمال كائن john مفتاحًا فيه -*!* -// That's what got written! +// ‫وهذا ما وجدناه مكتوبًا! alert( visitsCountObj["[object Object]"] ); // 123 -*/!* ``` +المتغيّر `visitsCountObj` من نوع ”كائن“، ولهذا يحوّل كلّ المفاتيح (مثل `john`) إلى سلاسل نصية. وبهذا قدّم لنا المفتاح بالسلسلة النصية `"[object Object]"`. ليس ما نريد قطعًا. -As `visitsCountObj` is an object, it converts all keys, such as `john` to strings, so we've got the string key `"[object Object]"`. Definitely not what we want. +**كيف تُوازن الخارطة `Map` المفاتيح** -```smart header="How `Map` compares keys" -To test keys for equivalence, `Map` uses the algorithm [SameValueZero](https://tc39.github.io/ecma262/#sec-samevaluezero). It is roughly the same as strict equality `===`, but the difference is that `NaN` is considered equal to `NaN`. So `NaN` can be used as the key as well. +تستعمل `Map` الخوارزمية [SameValueZero](https://tc39.github.io/ecma262/#sec-samevaluezero) لتختبر تساوي المفتاح مع الآخر. تتشابه هذه الخوارزمية تقريبًا مع المساواة الصارمة `===` بفارق أنّ `NaN` تساوي `NaN` في نظرها. يعني ذلك بأنك تستطيع استعمال `NaN` كمفتاح هو الآخر. -This algorithm can't be changed or customized. -``` +لا يمكن تغيير هذه الخوارزمية ولا تخصيصها. -````smart header="Chaining" -Every `map.set` call returns the map itself, so we can "chain" the calls: +**سِلسلة الاستدعاءات** +كلّما نادينا `map.set` أعاد لنا التابِع الخارطة نفسها، وبهذا يمكن أن نستدعي التابع على ناتج الاستدعاء السابق: -```js +``` map.set('1', 'str1') .set(1, 'num1') .set(true, 'bool1'); ``` -```` - - -## Iteration over Map -For looping over a `map`, there are 3 methods: +## المرور على خارطة +هناك ثلاث طرائق للمرور على عناصر `Map` وتطبيق عملية عليها: -- `map.keys()` -- returns an iterable for keys, -- `map.values()` -- returns an iterable for values, -- `map.entries()` -- returns an iterable for entries `[key, value]`, it's used by default in `for..of`. +- `map.keys()‎` -- يُعيد مُتعدَّدًا للمفاتيح، +- `map.values()‎` -- يُعيد مُتعدَّدًا للقيم، +- `map.entries()‎` -- يُعيد مُتعدَّدًا للمدخلات `[key, value]`، وهي التي تستعملها `for..of` مبدئيًا. -For instance: +مثال: -```js run +``` let recipeMap = new Map([ ['cucumber', 500], ['tomatoes', 350], ['onion', 50] ]); -// iterate over keys (vegetables) +// نمرّ على المفاتيح (الخضراوات)‏ for (let vegetable of recipeMap.keys()) { alert(vegetable); // cucumber, tomatoes, onion } -// iterate over values (amounts) +// نمرّ على قيم المفاتيح (عدد الخضراوات)‏ for (let amount of recipeMap.values()) { alert(amount); // 500, 350, 50 } -// iterate over [key, value] entries -for (let entry of recipeMap) { // the same as of recipeMap.entries() - alert(entry); // cucumber,500 (and so on) +// ‫نمرّ على مدخلات [key, value] +for (let entry of recipeMap) { // ‫مثل recipeMap.entries()‎ + alert(entry); // ‫cucumber,500 (وهكذا) } ``` -```smart header="The insertion order is used" -The iteration goes in the same order as the values were inserted. `Map` preserves this order, unlike a regular `Object`. -``` +**ترتيب الإدخال هو المستعمل** +يسير التعداد بنفس الترتيب الذي أُدخلت به الكائنات، فالخارطة تحفظ هذا الترتيب على العكس من الكائنات `Object`. -Besides that, `Map` has a built-in `forEach` method, similar to `Array`: +علاوةً على ذلك، فتملك الخارطة `Map` التابِع المضمّن فيها `forEach`، كما المصفوفات `Array`: -```js -// runs the function for each (key, value) pair +``` +// ‫تُنفّذ الدالة على كلّ زوج (key, value) recipeMap.forEach( (value, key, map) => { - alert(`${key}: ${value}`); // cucumber: 500 etc + alert(`${key}: ${value}`); // ‫cucumber: 500 إلخ إلخ }); ``` -## Object.entries: Map from Object +## Object.entries: صنع خارطة من كائن -When a `Map` is created, we can pass an array (or another iterable) with key/value pairs for initialization, like this: +متى ما أُنشأت خارطة `Map` نستطيع تمرير مصفوفة (أو مُتعدَّدًا آخرًا) لها أزواج ”مفاتيح/قيم“ لتهيئتها، هكذا تمامًا: -```js run -// array of [key, value] pairs +``` +// ‫مصفوفة من أزواج [key, value] let map = new Map([ ['1', 'str1'], [1, 'num1'], @@ -160,33 +146,28 @@ let map = new Map([ alert( map.get('1') ); // str1 ``` -If we have a plain object, and we'd like to create a `Map` from it, then we can use built-in method [Object.entries(obj)](mdn:js/Object/entries) that returns an array of key/value pairs for an object exactly in that format. +لو كان أمامنا كائنًا عاديًا ونريد صناعة `Map` منه، فيمكننا استعمال التابِع المضمّن في اللغة [Object.entries(obj)](https://wiki.hsoub.com/JavaScript/Object/entries) إذ يُعيد مصفوفة مكوّنة من أزواج ”مفاتيح/قيم“ للكائن، بنفس الصيغة التي يطلبها ذاك التابِع. -So we can create a map from an object like this: +ولهذا يمكن أن نصنع خارطة من كائن بهذه الطريقة: -```js run +``` let obj = { name: "John", age: 30 }; -*!* let map = new Map(Object.entries(obj)); -*/!* alert( map.get('name') ); // John ``` -Here, `Object.entries` returns the array of key/value pairs: `[ ["name","John"], ["age", 30] ]`. That's what `Map` needs. - - -## Object.fromEntries: Object from Map +نرى هنا التابِع `Object.entries` يُعيد مصفوفة بأزواج ”مفاتيح/قيم“: `[ ["name","John"], ["age", 30] ]`، وهذا ما تحتاجه الخارطة. -We've just seen how to create `Map` from a plain object with `Object.entries(obj)`. +## Object.fromEntries: صنع كائن من خارطة -There's `Object.fromEntries` method that does the reverse: given an array of `[key, value]` pairs, it creates an object from them: +رأينا كيف نصنع خارطة `Map` من كائنٍ عاديّ باستعمال `Object.entries(obj)‎`. على العكس منه فالتابع `Object.fromEntries` يأخذ خارطة فيها أزواج `[key, value]` ويصنع كائنًا منها: -```js run +``` let prices = Object.fromEntries([ ['banana', 1], ['orange', 2], @@ -198,133 +179,301 @@ let prices = Object.fromEntries([ alert(prices.orange); // 2 ``` -We can use `Object.fromEntries` to get a plain object from `Map`. - -E.g. we store the data in a `Map`, but we need to pass it to a 3rd-party code that expects a plain object. +يمكننا استعمال `Object.fromEntries` لنصنع كائنًا عاديًا من `Map`. يُفيدنا هذا مثلًا في تخزين البيانات في خارطة، بينما نريد تمريرها إلى شيفرة من طرف ثالثة تريد كائنًا عاديًا لا خارطة. -Here we go: +هذه الشيفرة المنشودة: -```js run +``` let map = new Map(); map.set('banana', 1); map.set('orange', 2); map.set('meat', 4); -*!* -let obj = Object.fromEntries(map.entries()); // make a plain object (*) -*/!* +let obj = Object.fromEntries(map.entries()); // ن‫صنع كائنًا عاديًا (*) -// done! +//و‫هكذا انتهينا! // obj = { banana: 1, orange: 2, meat: 4 } alert(obj.orange); // 2 ``` -A call to `map.entries()` returns an iterable of key/value pairs, exactly in the right format for `Object.fromEntries`. +متى ما استدعينا `map.entries()‎` أعادت مصفوفة مؤلّفة من أزواج ”مفاتيح/قيم“ بنفس التنسيق الذي يطلبه `Object.fromEntries` تمامًا، لحسن الحظ. -We could also make line `(*)` shorter: -```js -let obj = Object.fromEntries(map); // omit .entries() +يمكننا تقصير السطر المعلّم `(*)` ذاك: ``` +let obj = Object.fromEntries(map); // ب‫دون ‎.entries()‎ +``` + +النتيجة نفسها إذ أنّ التابِع `Object.fromEntries` يتوقّع كائنًا مُتعدَّدًا وسيطًا له، وليس مصفوفة بالضرورة. كما والتعداد القياسي للخارطة يتوقّع ذات أزواج ”مفاتيح/قيم“ التي يتوقّعها `map.entries()‎`، وهكذا نجد في يدنا كائنًا عاديًا له نفس ”مفاتيح/قيم“ الخارطة `map`. -That's the same, because `Object.fromEntries` expects an iterable object as the argument. Not necessarily an array. And the standard iteration for `map` returns same key/value pairs as `map.entries()`. So we get a plain object with same key/values as the `map`. +## الطقم `Set` -## Set -A `Set` is a special type collection - "set of values" (without keys), where each value may occur only once. +الأطقم (`Set`) هي نوع خاصّ من التجميعات ليس له مفاتيح ولا يمكن أن يحوي أكثر من قيمة متطابقة. يمكن عدّها كأطقم المجوهرات والأسنان، حيث لا تتكرّر أي قطعة مرتين. -Its main methods are: +إليك توابِعه الرئيسة: -- `new Set(iterable)` -- creates the set, and if an `iterable` object is provided (usually an array), copies values from it into the set. -- `set.add(value)` -- adds a value, returns the set itself. -- `set.delete(value)` -- removes the value, returns `true` if `value` existed at the moment of the call, otherwise `false`. -- `set.has(value)` -- returns `true` if the value exists in the set, otherwise `false`. -- `set.clear()` -- removes everything from the set. -- `set.size` -- is the elements count. +- `new Set(iterable)‎` -- يصنع الطقم. في حال مرّرت كائن `iterable` (وهو عادةً مصفوفة)، فينسخ بياناته إلى الطقم. +- `set.add(value)‎` -- يُضيف قيمة إلى الطقم ويُعيده ذاته. +- `set.delete(value)‎` -- يُزيل القيمة ويُعيد `true` لو كانت القيمة `value` موجودة عند استدعاء التابِع، وإلّا يُعيد `false`. +- `set.has(value)‎` -- يُعيد `true` لو كانت القيمة موجودة في الطقم، وإلّا يُعيد `false`. +- `set.clear()‎` -- يُزيل كلّ شيء من الطقم. +- `set.size` -- خاصية عدد العناصر في الطقم. -The main feature is that repeated calls of `set.add(value)` with the same value don't do anything. That's the reason why each value appears in a `Set` only once. +الميزة الأهمّ للأطقم هي أنّك لو استدعيت `set.add(value)‎` أكثر من مرّة وبنفس القيمة، فكأنّك استدعيتهُ مرّة واحدة. لهذا تظهر كل قيمة في الطقم مرّة واحدة لا غير. -For example, we have visitors coming, and we'd like to remember everyone. But repeated visits should not lead to duplicates. A visitor must be "counted" only once. +عُدّ مثلًا أنّ زوّارًا قادمين إلى وليمة ونريد تذكّر كلّ واحد لإعداد ما يكفي من طعام... ولكن يجب ألّا نسجّل الزوّار مرتين، فالزائر ”واحد“ ونعدّه مرّة واحدة فقط. -`Set` is just the right thing for that: +الطقم هنا هو الخيار الأمثل: -```js run +``` let set = new Set(); let john = { name: "John" }; let pete = { name: "Pete" }; let mary = { name: "Mary" }; -// visits, some users come multiple times +// زارنا الناس، وهناك من زارنا أكثر من مرة set.add(john); set.add(pete); set.add(mary); set.add(john); set.add(mary); -// set keeps only unique values +// لا يحفظ الطقم إلا القيم الفريدة alert( set.size ); // 3 for (let user of set) { - alert(user.name); // John (then Pete and Mary) + alert(user.name); // ‫John (ثمّ Pete وMary) } ``` -The alternative to `Set` could be an array of users, and the code to check for duplicates on every insertion using [arr.find](mdn:js/Array/find). But the performance would be much worse, because this method walks through the whole array checking every element. `Set` is much better optimized internally for uniqueness checks. +يمكن عوض الأطقم استعمال مصفوفة من المستخدمين، مع نصّ يتحقّق من البيانات عند إدخالها لألّا تحدث تكرارات (باستعمال [arr.find](https://wiki.hsoub.com/JavaScript/Array/find)). هذا ممكن نعم، لكن الأداء سيكون أشنع بكثير فتابِع البحث `arr.find` يمرّ على _كامل المصفوفة_ فيفحص كلّ عنصر فيها. الطقم `Set` أفضل بمراحل فأداؤه في فحص تفرّد العناصر مُحسَّن داخل بنية اللغة. -## Iteration over Set +## المرور على طقم -We can loop over a set either with `for..of` or using `forEach`: +يمكن لنا المرور على عناصر الطقم باستعمال حلقة `for..of` أو تابِع `forEach`: -```js run +``` let set = new Set(["oranges", "apples", "bananas"]); for (let value of set) alert(value); -// the same with forEach: +// ‫نفس الأمر مع forEach: set.forEach((value, valueAgain, set) => { alert(value); }); ``` -Note the funny thing. The callback function passed in `forEach` has 3 arguments: a `value`, then *the same value* `valueAgain`, and then the target object. Indeed, the same value appears in the arguments twice. +ولكن لاحظ هنا طرافة التابِع: لدالة ردّ النداء المُمرّرة إلى `forEach` ثلاث وُسطاء: قيمة `value`، و*ذات القيمة الأولى* `valueAgain`، والكائن الهدف. لاحظتَ كيف تكرّرت ذات القيمة في الوُسطاء مرّتين؟ + +يعزو هذا إلى توافق `Set` مع `Map` إذ لدالة ردّ التابع المُمرّرة إلى `forEach` الخارطة ثلاث وُسطاء أيضًا. معك حق، أمرها غريب، ولكنّها تفيد فتُسهّل حياتنا لو أردنا استبدال الخارطة بالطقم في حالات حرجة، كما العكس أيضًا. + +كما تدعم الأطقم نفس التوابِع التي تدعمها الخارطة للتعامل مع المُتعدَّدات: + +- `set.keys()‎` -- يُعيد كائنًا مُتعدَّدًا من القيم، +- `set.values()‎` -- تمامًا مثل `set.keys()‎` (موجود للتوافق مع `Map`)، +- `set.entries()‎` -- يُعيد كائنًا مُتعدَّدًا من المُدخلات `[value, value]` (موجود للتوافق مع `Map`). + +## ملخص + + +الخارطة `Map` هي تجميعة ذات مفاتيح. + +توابعها وخاصياتها: + +- `new Map([iterable])‎` -- يصنع خريطة ويضع فيها أزواج `[key,value]` داخل المُتعدَّد `iteratable` الاختياري (يمكن أن يكون مثلًا مصفوفة). +- `map.set(key, value)‎` -- يخزّن القيمة حسب مفتاحها. +- `map.get(key)‎` -- يُعيد القيمة حسب مفتاحها، ويُعيد `undefined` لو لم يكن المفتاح `key` في الخارطة. +- `map.has(key)‎` -- يُعيد `true` لو كان المفتاح `key` موجودًا، وإلا يُعيد `false`. +- `map.delete(key)‎` -- يُزيل القيمة حسب مفتاحها. +- `map.clear()‎` -- يُزيل كل ما في الخارطة. +- `map.size` -- يُعيد عدد العناصر في الخارطة الآن. + +اختلافاتها مع الكائنات العادية (`Object`): + +- تدعم أنواع المفاتيح المختلفة، كما والكائنات نفسها أيضًا. +- فيها توابِع أخرى تفيدنا، كما وخاصية `size`. + +الطقم `Set` هو تجميعة من القيم الفريدة. + +توابعه وخاصياته: + +- `new Set([iterable])‎` -- يصنع طقمًا ويضع فيه أزواج `[key, value]` داخل المُتعدَّد الاختياري (يمكن أن يكون مثلًا مصفوفة). +- `set.add(value)‎` -- يُضيف القيمة `value` (ولو كانت موجودة لا يفعل شيء) ثمّ يُعيد الطقم نفسه. +- `set.delete(value)‎` -- يُزيل القيمة ويُعيد `true` لو كانت موجودة عند استدعاء التابِع، وإلا يُعيد `false`. +- `set.has(value)‎` -- يُعيد `true` لو كانت القيمة في الطقم، وإلا يُعيد `false`. +- `set.clear()‎` -- يُزيل كل ما في الطقم. +- `set.size` -- عدد عناصر الطقم. + +يسري ترتيب المرور على عناصر `Map` و`Set` بترتيب إدخالها فيهما _دومًا_، ولهذا لا يمكن أن نقول بأنّها تجميعات غير مرتّبة، بل أنّا لا نقدر على إعادة ترتيب عناصرها أو الحصول عليها بفهرسها فيها. + +## تمارين +### ترشيح العناصر الفريدة في مصفوفة +_الأهمية: 5_ + +عُدّ أنّ `arr` مصفوفة. أنشِئ دالة `unique(arr)‎` تُعيد مصفوفة مؤلّفة من العناصر الفريدة في `arr`. + +مثال: + +``` +function unique(arr) { + /* هنا تكتب شيفرتك*/ +} + +let values = ["Hare", "Krishna", "Hare", "Krishna", + "Krishna", "Krishna", "Hare", "Hare", ":-O" +]; + +alert( unique(values) ); // Hare, Krishna, :-O +``` + +لاحظ أنّ السلاسل النصية استُعملت هنا، ولكن يمكن أن تكون القيم بأيّ نوع آخر. + +غُشّ من هذه: استعمل `Set` لتخزين القيم الفريدة. + +#### الحل +``` +function unique(arr) { + return Array.from(new Set(arr)); +} +``` + +### ترشيح الألفاظ المقلوبة +_الأهمية: 4_ + +تُسمّى الكلمات التي لها ذات الأحرف ولكن بترتيب مختلف [ألفاظًا مقلوبة](https://ar.wikipedia.org/wiki/%D9%84%D9%81%D8%B8_%D9%85%D9%82%D9%84%D9%88%D8%A8)، مثل هذه: + +``` +nap - pan +ear - are - era +cheaters - hectares - teachers +``` + +أو العربية: +``` +ملّ - لمّ +مسكين - سيكمن +كاتب - اكتب - كتاب +``` -That's for compatibility with `Map` where the callback passed `forEach` has three arguments. Looks a bit strange, for sure. But may help to replace `Map` with `Set` in certain cases with ease, and vice versa. +اكتب دالة `aclean(arr)‎` تُعيد مصفوفةً بدون هذه الألفاظ المقلوبة. هكذا: -The same methods `Map` has for iterators are also supported: +``` +let arr = ["nap", "teachers", "cheaters", "PAN", "ear", "era", "hectares"]; -- `set.keys()` -- returns an iterable object for values, -- `set.values()` -- same as `set.keys()`, for compatibility with `Map`, -- `set.entries()` -- returns an iterable object for entries `[value, value]`, exists for compatibility with `Map`. +alert( aclean(arr) ); // "nap,teachers,ear" أو "PAN,cheaters,era" +``` -## Summary +يجب أن يكون ناتج كلّ مجموعة ألفاظ كلمة واحدة فقط، ولا يهمّنا أيّ واحدة. + +#### الحل +لو أردنا البحث عن كل الألفاظ المقلوبة، سنقسم كلّ كلمة إلى حروفها ونرتّبها. متى ما رتّبناها حسب الأحرف، فستكون الألفاظ كلها متطابقة. هكذا: + +``` +nap, pan -> anp +ear, era, are -> aer +cheaters, hectares, teachers -> aceehrst +... +``` + +سنستعمل كلّ قيمة مختلفة (ولكن متطابقة بترتيب أحرفها) لتكون مفاتيح خريطة فنخزّن لفظًا واحدًا لكل مفتاح فقط: + +``` +function aclean(arr) { + let map = new Map(); -`Map` -- is a collection of keyed values. + for (let word of arr) { -Methods and properties: + // نقسم الكلمة بأحرفها، ونرّتب الأحرف ونجمعها ثانيةً + let sorted = word.toLowerCase().split('').sort().join(''); // (*) + map.set(sorted, word); + } + + return Array.from(map.values()); +} + +let arr = ["nap", "teachers", "cheaters", "PAN", "ear", "era", "hectares"]; + +alert( aclean(arr) ); +``` + +نُنفّذ الترتيب حسب الأحرف بسلسلة استدعاءات كما في السطر `(*)`. سنقسمها على أكثر من سطر ليسهل فهمها: + +``` +let sorted = arr[i] // PAN + .toLowerCase() // pan + .split('') // ['p','a','n'] + .sort() // ['a','n','p'] + .join(''); // anp +``` + +هكذا يكون لدى الكلمتين المختلفتين `'PAN'` و`'nap'` ذات الشكل حين تُرتّب أحرفها: `'anp'`. + +في السطر اللاحق نُضيف الكلمة إلى الخارطة. + +``` +map.set(sorted, word); +``` -- `new Map([iterable])` -- creates the map, with optional `iterable` (e.g. array) of `[key,value]` pairs for initialization. -- `map.set(key, value)` -- stores the value by the key. -- `map.get(key)` -- returns the value by the key, `undefined` if `key` doesn't exist in map. -- `map.has(key)` -- returns `true` if the `key` exists, `false` otherwise. -- `map.delete(key)` -- removes the value by the key. -- `map.clear()` -- removes everything from the map. -- `map.size` -- returns the current element count. +لو قابلنا بينما نمرّ على الكلمات كلمةً لها نفس الشكل حين تُرتّب أحرفها، فستعوّض القيمة السابقة التي لها نفس المفتاح في الخارطة. هكذا لن تزيد الكلمات لكلّ شكل على واحد، دومًا. -The differences from a regular `Object`: +وفي النهاية يأخذ `Array.from(map.values())‎` متُعدَّدا يمرّ على قيم الخارطة (لا نريد مفاتيحها في ناتج الدالة) فيُعيد المصفوفة نفسها. -- Any keys, objects can be keys. -- Additional convenient methods, the `size` property. +يمكننا (في هذه المسألة) استعمال كائن عادي بدل الخارطة، إذ أنّ المفاتيح سلاسل نصية. هكذا سيبدو الحلّ لو اتبعنا هذا النهج: + +``` +function aclean(arr) { + let obj = {}; + + for (let i = 0; i < arr.length; i++) { + let sorted = arr[i].toLowerCase().split("").sort().join(""); + obj[sorted] = arr[i]; + } + + return Object.values(obj); +} + +let arr = ["nap", "teachers", "cheaters", "PAN", "ear", "era", "hectares"]; + +alert( aclean(arr) ); +``` + +### مفاتيح مُكرَّرة +_الأهمية: 5_ + +نريد تسجيل المصفوفة الناتجة من `map.keys()‎` في متغيّر وثمّ استدعاء توابِع تخصّ المصفوفات عليها مثل `‎.push`. ولكنّ ذلك لم ينفع: + +``` +let map = new Map(); + +map.set("name", "John"); + +let keys = map.keys(); + +// ‫خطأ: keys.push ليست دالة +keys.push("more"); +``` + +لماذا؟ وكيف يمكننا إصلاح الشيفرة ليعمل `keys.push`؟ + +#### الحل + +لأنّ التابِع `map.keys()‎` يُعيد مُتعدَّدًا لا مصفوفة. يمكننا تحويله إلى مصفوفة باستعمال `Array.from`: + +``` +let map = new Map(); + +map.set("name", "John"); + +let keys = Array.from(map.keys()); + +keys.push("more"); + +alert(keys); // name, more +``` -`Set` -- is a collection of unique values. -Methods and properties: +ترجمة -وبتصرف- للفصل [Map and Set](https://javascript.info/map-set) من كتاب [The JavaScript language](https://javascript.info/js) -- `new Set([iterable])` -- creates the set, with optional `iterable` (e.g. array) of values for initialization. -- `set.add(value)` -- adds a value (does nothing if `value` exists), returns the set itself. -- `set.delete(value)` -- removes the value, returns `true` if `value` existed at the moment of the call, otherwise `false`. -- `set.has(value)` -- returns `true` if the value exists in the set, otherwise `false`. -- `set.clear()` -- removes everything from the set. -- `set.size` -- is the elements count. -Iteration over `Map` and `Set` is always in the insertion order, so we can't say that these collections are unordered, but we can't reorder elements or directly get an element by its number. 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 bcc5e5e6f..70295380d 100644 --- a/1-js/05-data-types/08-weakmap-weakset/article.md +++ b/1-js/05-data-types/08-weakmap-weakset/article.md @@ -1,184 +1,161 @@ -# WeakMap and WeakSet + +# النوع WeakMap والنوع WeakSet: الخرائط والأطقم ضعيفة الإشارة -As we know from the chapter , JavaScript engine stores a value in memory while it is reachable (and can potentially be used). +كما عرفنا من فصل «كنس المهملات»، فمُحرّك جافاسكربت يخُزّن القيمة في الذاكرة طالما يمكن أن يصل لها شيء (أي يمكن استعمالها لاحقًا). هكذا: -For instance: -```js +``` let john = { name: "John" }; -// the object can be accessed, john is the reference to it +// ‫يمكننا الوصول إلى الكائن، فـ john هو الإشارة إليه -// overwrite the reference +// عوّض تلك الإِشارة john = null; -*!* -// the object will be removed from memory -*/!* +// سيُزال الكائن من الذاكرة ``` -Usually, properties of an object or elements of an array or another data structure are considered reachable and kept in memory while that data structure is in memory. +عادةً ما تكون خاصيات الكائن أو عناصر المصفوفة أو أية بنية بيانات أخرى - عادةً ما تُعدّ "مُتاحة لباقي الشيفرة" ويُبقيها المحرّك في الذاكرة طالما بنية البيانات نفسها في الذاكرة. -For instance, if we put an object into an array, then while the array is alive, the object will be alive as well, even if there are no other references to it. +لنفترض أنّا وضعنا كائنًا في مصفوفة، طالما المصفوفة موجودة ومُشار إليها، فسيكون الكائن موجودًا هو الآخر حتّى لو لم يكن هناك ما يُشير إليه. مثلما في هذه الشيفرة: -Like this: - -```js +``` let john = { name: "John" }; let array = [ john ]; -john = null; // overwrite the reference +john = null; // عوّض الإشارة -*!* -// john is stored inside the array, so it won't be garbage-collected -// we can get it as array[0] -*/!* +// ‫الكائن john مخزّن داخل مصفوفة ولن يُكنس باعتباره مهملات +// ‫إذ يمكننا أخذه بهذه: array[0]‎ ``` -Similar to that, if we use an object as the key in a regular `Map`, then while the `Map` exists, that object exists as well. It occupies memory and may not be garbage collected. +وبنفس المفهوم، لو استعملنا كائنًا ليكون مفتاحًا في خارطة `Map` عادية، فسيبقى هذا الكائن موجدًا طالما الخارطة تلك موجودة، ويشغل الذاكرة مانعًا عملية كنس المهملات من تحريرها. إليك هذا المثال: -For instance: - -```js +``` let john = { name: "John" }; let map = new Map(); map.set(john, "..."); -john = null; // overwrite the reference +john = null; // عوّض الإشارة -*!* -// john is stored inside the map, -// we can get it by using map.keys() -*/!* +// ‫الكائن john مخزّن داخل خارطة +// ‫ويمكننا أخذه بهذه: map.keys()‎ ``` -`WeakMap` is fundamentally different in this aspect. It doesn't prevent garbage-collection of key objects. - -Let's see what it means on examples. +على العكس فالخارطة ضعيفة الإشارة `WeakMap` مختلفة جذريًا عن هذا، فلا تمنع كنس مهملات أيٍّ من مفاتيحها الكائنات. لنأخذ بعض الأمثلة لتُدرك القصد هنا. ## WeakMap -The first difference from `Map` is that `WeakMap` keys must be objects, not primitive values: +أولى اختلافات الخارطة ضعيفة الإشارة `WeakMap` عن تلك العادية `Map` هي أنّها تُلزم مفاتيحها بأن تكون كائنات لا أنواع أولية: -```js run +``` let weakMap = new WeakMap(); let obj = {}; -weakMap.set(obj, "ok"); // works fine (object key) +weakMap.set(obj, "ok"); // لا مشاكل (المفتاح كائن) -*!* -// can't use a string as the key -weakMap.set("test", "Whoops"); // Error, because "test" is not an object -*/!* +// لا يمكن استعمال السلسلة النصية مفتاحًا +weakMap.set("test", "Whoops"); // ‫خطأ، لأنّ ”test“ ليس كائنًا ``` -Now, if we use an object as the key in it, and there are no other references to that object -- it will be removed from memory (and from the map) automatically. +بعد ذلك لو استعملنا أحد الكائنات ليكون مفتاحًا فيها، ولم يكن هناك ما يُشير إلى هذا الكائن، فسيُزال الكائن من الذاكرة (والخارطة) تلقائيا. -```js +``` let john = { name: "John" }; let weakMap = new WeakMap(); weakMap.set(john, "..."); -john = null; // overwrite the reference +john = null; // عوّض الإشارة -// john is removed from memory! +// ‫أُزيل الكائن john من الذاكرة! ``` -Compare it with the regular `Map` example above. Now if `john` only exists as the key of `WeakMap` -- it will be automatically deleted from the map (and memory). - -`WeakMap` does not support iteration and methods `keys()`, `values()`, `entries()`, so there's no way to get all keys or values from it. - -`WeakMap` has only the following methods: +وازِن هذه الشيفرة بشيفرة الخارطة `Map` أعلاه. الآن حتى لو لم يكن `john` موجودًا إلا مفتاحًا لِـ `WeakMap`، فسيُحذف تلقائيًا من الخارطة (ومن الذاكرة). -- `weakMap.get(key)` -- `weakMap.set(key, value)` -- `weakMap.delete(key)` -- `weakMap.has(key)` +لا تدعم الخارطة ضعيفة الإشارة `WeakMap` التكرار (iteration) ولا التوابِع `keys()‎` أو `values()‎` أو `entries()‎`، ولهذا لا نقدر على أخذ كلّ المفاتيح أو القيم التي فيها. بل أنّ للخارطة `WeakMap` التوابِع الآتية: -Why such a limitation? That's for technical reasons. If an object has lost all other references (like `john` in the code above), then it is to be garbage-collected automatically. But technically it's not exactly specified *when the cleanup happens*. +- `weakMap.get(key)‎` +- `weakMap.set(key, value)‎` +- `weakMap.delete(key)‎` +- `weakMap.has(key)‎` -The JavaScript engine decides that. It may choose to perform the memory cleanup immediately or to wait and do the cleaning later when more deletions happen. So, technically the current element count of a `WeakMap` is not known. The engine may have cleaned it up or not, or did it partially. For that reason, methods that access all keys/values are not supported. +تفكّر بسبب وجود هذا التقييد؟ الجواب هو: أسباب تقنية. عُدّ الكائن الآن قد فقد كلّ إشارة له (مثلما حصل مع الكائن `john` في الشيفرة أعلاه)، بهذا ستُكنس مهملاته تلقائيًا، ولكن... *وقت حدوث هذا الكنس* غير موضّح تقنيًا. الواقع أنّ محرّك جافاسكربت يُحدّد ذلك: هو يُحدّد متى يمسح الذاكرة، الآن حالًا أو بعد قليل حتّى تحدث عمليات حذف أخرى. لذا فعدد العناصر الحالي داخل `WeakMap` غير معلوم تقنيًا، ربما يكون المحرّك حذفها كلها أو لم يحذفها، أو حذف بعضها، لا نعلم. لهذا السبب لا تدعم اللغة التوابِع التي تحاول الوصول إلى كلّ القيم والعناصر. -Now where do we need such data structure? +الآن بعدما عرفناها، في أيّ حالات نستعمل هذه البنية من البيانات؟ -## Use case: additional data +## استعمالاتها: بيانات إضافية -The main area of application for `WeakMap` is an *additional data storage*. +المجال الرئيسي لتطبيقات `WeakMap` هي *تخزين البيانات الإضافية*. -If we're working with an object that "belongs" to another code, maybe even a third-party library, and would like to store some data associated with it, that should only exist while the object is alive - then `WeakMap` is exactly what's needed. +لو كنّا نتعامل مع كائن ”ينتمي“ إلى شيفرة أخرى (وحتّى مكتبة من طرف ثالث) وأردنا تخزين بيانات معيّنة لترتبط بها، وهذه البيانات لا تكون موجودة إلا لو كان الكائن موجودًا، فَـ `WeakMap` هي ما نريد تمامًا: نضع البيانات في خارطة بإشارة ضعيفة `WeakMap` (مستعملين الكائن مفتاحًا لها). متى ما كُنس الكائن باعتباره مهملات، ستختفي تلك البيانات معه أيضًا. -We put the data to a `WeakMap`, using the object as the key, and when the object is garbage collected, that data will automatically disappear as well. - -```js +``` weakMap.set(john, "secret documents"); -// if john dies, secret documents will be destroyed automatically +// ‫إن مات john فستُدمّر تلك المستندات فائقة السرية تلقائيًا ``` -Let's look at an example. - -For instance, we have code that keeps a visit count for users. The information is stored in a map: a user object is the key and the visit count is the value. When a user leaves (its object gets garbage collected), we don't want to store their visit count anymore. +لنرى مثالًا يوضّح الصورة. عُدّ بأنّ لدينا شيفرة تسجّل عدد زيارات المستخدمين - تسجّلها في خارطة، حيث كائن المستخدم هو مفتاحها وعدد زياراته هي القيمة. لا نريد أن نُسجّل عدد زياراته فيما لو غادر المستخدم (أي أنّ عملية كنس المهملات كنست ذاك الكائن). -Here's an example of a counting function with `Map`: +إليك مثالًا آخر عن دالة عَدّ باستعمال `Map`: -```js +``` // 📁 visitsCount.js -let visitsCountMap = new Map(); // map: user => visits count +let visitsCountMap = new Map(); // خارطة: المستخدم => عدد زياراته -// increase the visits count +// تزيد عدد الزيارات function countUser(user) { let count = visitsCountMap.get(user) || 0; visitsCountMap.set(user, count + 1); } ``` -And here's another part of the code, maybe another file using it: +وهذا الجزء الثاني من الشيفرة (يمكن أن يستعمل هذا الملف ذاك): -```js +``` // 📁 main.js let john = { name: "John" }; -countUser(john); // count his visits +countUser(john); // عُدّ الزوّار +countUser(john); -// later john leaves us +// ‫بعدها يغادر john الحفلة john = null; ``` -Now `john` object should be garbage collected, but remains in memory, as it's a key in `visitsCountMap`. +هكذا ”يُفترض“ أن يُكنس الكائن `john` باعتباره مهملات، لكنّه سيبقى في الذاكرة إذ تستعمله الخارطة `visitsCountMap` مفتاحًا فيها. -We need to clean `visitsCountMap` when we remove users, otherwise it will grow in memory indefinitely. Such cleaning can become a tedious task in complex architectures. +علينا مسح `visitsCountMap` حين نُزيل المستخدمين وإلا فسيزيد حجمها في الذاكرة إلى آباد الآبدين. لو كانت بنية البرمجية معقّدة، فستكون عملية المسح هذه مرهقة جدًا وغير عملية. لهذا يمكننا تجنّب التعب واستعمال `WeakMap` بدل العادية: -We can avoid it by switching to `WeakMap` instead: - -```js +``` // 📁 visitsCount.js -let visitsCountMap = new WeakMap(); // weakmap: user => visits count +let visitsCountMap = new WeakMap(); // خارطة بإشارة ضعيفة: المستخدم => عدد زياراته -// increase the visits count +// تزيد عدد الزيارات function countUser(user) { let count = visitsCountMap.get(user) || 0; visitsCountMap.set(user, count + 1); } ``` -Now we don't have to clean `visitsCountMap`. After `john` object becomes unreachable by all means except as a key of `WeakMap`, it gets removed from memory, along with the information by that key from `WeakMap`. +هكذا لا نمسح `visitsCountMap` يدويًا بل نترك للمحرّك القرار: لو لم يكن هناك ما يُشير إلى الكائن `john` عدا مفتاح `WeakMap`، سيحرّره من الذاكرة مع المعلومات التي في ذلك المفتاح داخل الخارطة ضعيفة الإشارة `WeakMap`. -## Use case: caching +## استعمالاتها: الخبيئة -Another common example is caching: when a function result should be remembered ("cached"), so that future calls on the same object reuse it. +يكثُر أيضًا استعمال الخرائط للخبيئة، أي حين علينا تذكّر ناتج الدالة (تخبئته ”cached“) كي يستعمل أيّ استدعاء لاحِق على هذا العنصر تلك الخبيئة. -We can use `Map` to store results, like this: +يمكن أن نستعمل الخارطة `Map` لتخزين النتائج: -```js run +``` // 📁 cache.js let cache = new Map(); -// calculate and remember the result +// نحسب النتيجة ونتذكرها function process(obj) { if (!cache.has(obj)) { - let result = /* calculations of the result for */ obj; + let result = /* حسابات الكائن هذا */ obj; cache.set(obj, result); } @@ -186,38 +163,36 @@ function process(obj) { return cache.get(obj); } -*!* -// Now we use process() in another file: -*/!* +// الآن نستعمل ‫process()‎ في ملف آخر: // 📁 main.js -let obj = {/* let's say we have an object */}; +let obj = {/* فلنفترض وجود هذا الكائن*/}; -let result1 = process(obj); // calculated +let result1 = process(obj); // حسبنا القيمة -// ...later, from another place of the code... -let result2 = process(obj); // remembered result taken from cache +// ‫...بعدها، في مكان آخر من الشيفرة... +let result2 = process(obj); // تُأخذ النتيجة تلك من الخبيئة -// ...later, when the object is not needed any more: +// ‫...بعدها، لو لم نرد الكائن بعد الآن: obj = null; -alert(cache.size); // 1 (Ouch! The object is still in cache, taking memory!) +alert(cache.size); // 1 (لاا! ما زال الكائن في الخبيئة ويستهلك الذاكرة) ``` -For multiple calls of `process(obj)` with the same object, it only calculates the result the first time, and then just takes it from `cache`. The downside is that we need to clean `cache` when the object is not needed any more. +لو استدعينا `process(obj)‎` أكثر من مرّة بتمرير نفس الكائن، فستحسب الشيفرة النتيجة أوّل مرة فقط، وفي المرات القادمة تأخذها من الكائن `cache`. مشكلة هذه الطريقة هي ضرورة مسح `cache` متى ما انتفت حاجتنا من الكائن. -If we replace `Map` with `WeakMap`, then this problem disappears: the cached result will be removed from memory automatically after the object gets garbage collected. +لكن، لو استبدلنا `Map` وعوّضناها بِـ `WeakMap` فستختفي المشكلة تمامًا، وتُزال النتيجة المُخبّأة من الذاكرة _تلقائيًا_ متى ما كُنس الكائن على أنّه مهملات. -```js run +``` // 📁 cache.js *!* let cache = new WeakMap(); */!* -// calculate and remember the result +// نحسب النتيجة ونتذكرها function process(obj) { if (!cache.has(obj)) { - let result = /* calculate the result for */ obj; + let result = /* حسابات الكائن هذا */ obj; cache.set(obj, result); } @@ -226,63 +201,164 @@ function process(obj) { } // 📁 main.js -let obj = {/* some object */}; +let obj = {/* كائن من الكائنات */}; let result1 = process(obj); let result2 = process(obj); -// ...later, when the object is not needed any more: +// ‫...بعدها، لو لم نرد الكائن بعد الآن: obj = null; -// Can't get cache.size, as it's a WeakMap, -// but it's 0 or soon be 0 -// When obj gets garbage collected, cached data will be removed as well ``` +هنا، ‫لا يمكن أن نعرف cache.size إذ أنها خارطة بإشارة ضعيفة، ولكن الحجم صفر، أو سيكون صفر قريبًا؛ فما أن تبدأ عملية كنس المهملات على الكائن، ستُزال البيانات المُخبّأة هي الأخرى. ## WeakSet -`WeakSet` behaves similarly: +حتّى الأطقم ضعيفة الإشارة `WeakSet` تسلك ذات السلوك: -- It is analogous to `Set`, but we may only add objects to `WeakSet` (not primitives). -- An object exists in the set while it is reachable from somewhere else. -- Like `Set`, it supports `add`, `has` and `delete`, but not `size`, `keys()` and no iterations. +- تشبه الأطقم العادية `Set` ولكن لا يمكننا إلّا إضافة الكائنات إلى `WeakSet` (وليس الأنواع الأولية). +- يبقى الكائن موجودًا في الطقم طالما هناك ما يصل إليه. +- ويدعم -كما تدعم `Set`- التوابِع `add` و`has` و`delete`، ولكن لا تدعم `size` أو `keys()‎` أو التعداد. -Being "weak", it also serves as an additional storage. But not for an arbitrary data, but rather for "yes/no" facts. A membership in `WeakSet` may mean something about the object. +هي الأخرى تخدمنا نحن المطورون في تخزين البيانات الإضافية (إذ أنّ الإشارة إليها ”ضعيفة“)، ولكنها ليست لأيّ بيانات كانت، بل فقط التي تُعطي إجابة ”نعم/لا“. لو كان الكائن موجودًا داخل طقم بإشارة ضعيفة، فلا بدّ أنّه موجود لداعٍ. -For instance, we can add users to `WeakSet` to keep track of those who visited our site: +يمكننا مثلًا إضافة المستخدمين إلى طقم بإشارة ضعيفة `WeakSet` لنسجّل من زار موقعنا: -```js run +``` let visitedSet = new WeakSet(); let john = { name: "John" }; let pete = { name: "Pete" }; let mary = { name: "Mary" }; -visitedSet.add(john); // John visited us -visitedSet.add(pete); // Then Pete -visitedSet.add(john); // John again +visitedSet.add(john); // زارنا ‫John +visitedSet.add(pete); // وبعده ‫Pete +visitedSet.add(john); // وعاد ‫John -// visitedSet has 2 users now +// ت‫حتوي visitedSet الآن على مستخدمين اثنين -// check if John visited? +// ه‫ل زارنا John؟ alert(visitedSet.has(john)); // true -// check if Mary visited? +// ‫هل زارتنا Mary؟ alert(visitedSet.has(mary)); // false john = null; -// visitedSet will be cleaned automatically +// ستُنظّف ‫visitedSet تلقائيًا ``` -The most notable limitation of `WeakMap` and `WeakSet` is the absence of iterations, and inability to get all current content. That may appear inconvenient, but does not prevent `WeakMap/WeakSet` from doing their main job -- be an "additional" storage of data for objects which are stored/managed at another place. +التقييد الأهم في هذه الأنواع `WeakSet` و`WeakMap` هي عدم موجود المُكرَّرات واستحالة أخذ محتواها كله. لربّما ترى ذلك غباءً، إلّا أنّه لا يمنع هذه الأنواع من إجراء مهامها التي صُنعت لها: مخزن "إضافي" من البيانات للكائنات المخزّنة (أو المُدارة) في مكان آخر. + +## خلاصة + +الخارطة ضعيفة الإشارة هي تجميعة تشبه الخرائط العادية، ولا تتيح إلا استعمال الكائنات مفاتيحٍ فيها، كما وتُزيلها هي والقيمة المرتبطة بها ما إن تنعدم الإشارة إليها. -## Summary +الطقم ضعيفة الإشارة هي تجميعة تشبه الأطقم العادية، ولا تخزّن إلا الكائنات فيها، كما وتُزيلها ما إن تنعدم الإشارة إليها. -`WeakMap` is `Map`-like collection that allows only objects as keys and removes them together with associated value once they become inaccessible by other means. +كِلا النوعان لا يدعمان التوابِع والخاصيات التي تُشير إلى كل المفاتيح فيهما، أو حتى عددها. المسموح فقط هو العمليات على العناصر فيها عنصرًا بعنصر. -`WeakSet` is `Set`-like collection that stores only objects and removes them once they become inaccessible by other means. +يُستعمل هذان النوعان `WeakMap` و`WeakSet` على أنّهما بنى بيانات ”ثانوية“ إلى جانب تلك ”الأساسية“ لتخزين العناصر. لو أُزيل الكائن من التخزين الأساسي، ولم يوجد له أي إشارة إلا مفتاحًا في `WeakMap` أو عنصرًا في `WeakSet`، مسحهُ المحرّك تلقائيًا. + +## تمارين +### تخزين رايات ”غير مقروءة“ +_الأهمية: 5_ + +لديك مصفوفة من الرسائل: + +``` +let messages = [ + {text: "Hello", from: "John"}, + {text: "How goes?", from: "John"}, + {text: "See you soon", from: "Alice"} +]; +``` + +ويمكن للشيفرة عندك الوصول إليها، إلّا أنّ شيفرة أحدهم تُدير تلك الرسائل، فتُضيف رسائل جديدة وتُزيل قديمة، ولا تعرف متى يحدث هذا بالضبط. + +السؤال هو: أيّ بنية من بنى البيانات تستعمل لتخزّن هذه المعلومة لكلّ رسالة: ”هل قُرأت؟“. يجب أن تكون البنية التي اخترتها مناسبة لتردّ على سؤال ”هل قُرأت؟“ لكلّ كائن رسالة. + +ملاحظة: حين تُزال رسالة من مصفوفة `messages`، يجب أن تختفي من بنية البيانات لديك هي الأخرى. + +ملاحظة أخرى: يجب ألّا نُعدّل كائنات الرسائل ولا نُضيف خاصيات من عندنا إليها؛ فيمكن أن يؤدّي هذا إلى عواقب وخيمة إذ لسنا من نديرها بل أحد آخر. + +#### الحل +لنجرّب تخزين الرسائل المقروءة في طقم بإشارة ضعيفة `WeakSet`: + +``` +let messages = [ + {text: "Hello", from: "John"}, + {text: "How goes?", from: "John"}, + {text: "See you soon", from: "Alice"} +]; + +let readMessages = new WeakSet(); + +// قرأ المستخدم رسالتين اثنتين +readMessages.add(messages[0]); +readMessages.add(messages[1]); +// في ‫readMessages الآن عنصرين + +// ‫...هيًا نُعيد قراءة أول رسالة! +readMessages.add(messages[0]); +// ما زالت في ‫readMessages عنصرين فريدين + +// ‫الجواب: هل قُرئتmessage [0]‎؟ +alert("Read message 0: " + readMessages.has(messages[0])); // نعم ‫true + +messages.shift(); +// الآن في ‫readMessages عنصر واحد (تقنيًا فستُنظّف الذاكرة فيما بعد) +``` + +يتيح لنا الطقم ضعيفة الإشارة تخزينَ مجموعة من الرسائل والتأكّد من وجود كلّ منها بسهولة تامة. كما وأنّها تمسح نفسها بنفسها. للأسف بهذا نُضحّي بميزة التكرار، فلا يمكن أن نجلب ”كلّ الرسائل المقروءة“ منها مباشرةً، ولكن... يمكننا المرور على عناصر كل الرسائل في `messages` وترشيح تلك التي في الطقم لدينا. + +يمكن أن يكون الحل الآخر هو إضافة خاصية مثل `message.isRead=true` إلى الرسالة بعد قراءتها. ولكننّا لسنا من نُدير هذه الكائنات بل أحد آخر، ولهذا لا يُوصى بذلك بصفة عامة. ولكن، يمكننا استعمال خاصيّة رمزية فنتجنّب أي مشكلة أو تعارض. + +هكذا: +``` +// الخاصية الرمزية معروفة في الشيفرة لدينا، فقط +let isRead = Symbol("isRead"); +messages[0][isRead] = true; +``` + +"لربما" الآن لن تعرف شيفرة الطرف الثالث بخاصيتنا الجديدة. + +صحيح أن الرموز تتيح لنا تقليل احتمال حدوث المشاكل، إلّا أنّ استعمال `WeakSet` أفضل بعين بنية البرمجية. + +### تخزين تواريخ القراءة +_الأهمية: 5_ + +لديك مصفوفة من الرسائل تشبه تلك في التمرين السابق، والفكرة هنا متشابهة قليلًا. + +``` +let messages = [ + {text: "Hello", from: "John"}, + {text: "How goes?", from: "John"}, + {text: "See you soon", from: "Alice"} +]; +``` + +السؤال: أيّ بنية بيانات تستعمل لتخزين هذه المعلومة: " متى قُرئت هذه الرسالة؟". + +كان عليك (في التمرين السابق) تخزين معلومة "نعم/لا" فقط، أمّا الآن فعليك تخزين التاريخ، ويجب أن يبقى في الذاكرة إلى أن تُكنس الرسالة على أنّها مهملات. + +ملاحظة: تُخزّن التواريخ كائنات بصنف `Date` المضمّن في اللغة، وسنتكلم عنه لاحقًا. + +#### الحل + +يمكن أن نستعمل الخارطة ضعيفة الإشارة لتخزين التاريخ: +``` +let messages = [ + {text: "Hello", from: "John"}, + {text: "How goes?", from: "John"}, + {text: "See you soon", from: "Alice"} +]; + +let readMap = new WeakMap(); + +readMap.set(messages[0], new Date(2017, 1, 1)); +// سنرى أمر كائن التاريخ لاحقًا +``` -Both of them do not support methods and properties that refer to all keys or their count. Only individual operations are allowed. +ترجمة -وبتصرف- للفصل [WeakMap and WeakSet](https://javascript.info/weakmap-weakset ) من كتاب [The JavaScript language](https://javascript.info/js) -`WeakMap` and `WeakSet` are used as "secondary" data structures in addition to the "main" object storage. Once the object is removed from the main storage, if it is only found as the key of `WeakMap` or in a `WeakSet`, it will be cleaned up automatically. diff --git a/1-js/05-data-types/11-date/6-get-seconds-today/solution.md b/1-js/05-data-types/11-date/6-get-seconds-today/solution.md index 8f8e52b68..a483afe93 100644 --- a/1-js/05-data-types/11-date/6-get-seconds-today/solution.md +++ b/1-js/05-data-types/11-date/6-get-seconds-today/solution.md @@ -23,6 +23,4 @@ function getSecondsToday() { let d = new Date(); return d.getHours() * 3600 + d.getMinutes() * 60 + d.getSeconds(); } - -alert( getSecondsToday() ); ``` diff --git a/1-js/05-data-types/11-date/article.md b/1-js/05-data-types/11-date/article.md index a2de63ae4..ca5a4c37c 100644 --- a/1-js/05-data-types/11-date/article.md +++ b/1-js/05-data-types/11-date/article.md @@ -1,296 +1,290 @@ -# Date and time + +# النوع Date: التاريخ والوقت -Let's meet a new built-in object: [Date](mdn:js/Date). It stores the date, time and provides methods for date/time management. -For instance, we can use it to store creation/modification times, to measure time, or just to print out the current date. -## Creation +حان وقت الحديث عن كائن آخر مضمّن في اللغة: التاريخ [`Date`](https://wiki.hsoub.com/JavaScript/Date). يخزّن هذا الكائن التاريخ والوقت ويقدّم توابِع تُدير أختام التاريخ والوقت. يمكننا مثلًا استعماله لتخزين أوقات الإنشاء/التعديل أو حساب الوقت أو طباعة التاريخ الحالي في الطرفية. -To create a new `Date` object call `new Date()` with one of the following arguments: +## الإنشاء + +استدعِ `new Date()‎` بتمرير واحدًا من الوُسطاء الآتية فتصنع كائن `Date` جديد: `new Date()` -: Without arguments -- create a `Date` object for the current date and time: +: بلا وُسطاء: يُنشئ كائن `Date` بالتاريخ والوقت الحاليين - ```js run +``` let now = new Date(); - alert( now ); // shows current date/time - ``` + alert( now ); // نعرض التاريخ والوقت الحاليين +``` -`new Date(milliseconds)` -: Create a `Date` object with the time equal to number of milliseconds (1/1000 of a second) passed after the Jan 1st of 1970 UTC+0. +إليك كيفية إنشاء كائن `Date`: +*`new Date(milliseconds)‎`* +يُنشئ كائن `Date` إذ تساوي قيمته عدد المليثوان الممرّرة (المليثانية هي 1/1000 من الثاني) حسابًا من بعد الأول من يناير عام ١٩٧٠ بتوقيت UTC+0. - ```js run - // 0 means 01.01.1970 UTC+0 +``` + // UTC+0 + // 01.01.1970 نعني بـ 0 التاريخ let Jan01_1970 = new Date(0); alert( Jan01_1970 ); - // now add 24 hours, get 02.01.1970 UTC+0 + // نضيف الآن 24 ساعة لنحصل على 02.01.1970 let Jan02_1970 = new Date(24 * 3600 * 1000); alert( Jan02_1970 ); - ``` +``` - An integer number representing the number of milliseconds that has passed since the beginning of 1970 is called a *timestamp*. - It's a lightweight numeric representation of a date. We can always create a date from a timestamp using `new Date(timestamp)` and convert the existing `Date` object to a timestamp using the `date.getTime()` method (see below). +يُسمّى العدد الصحيح الذي يمثّل عدد المليثوان التي مرّت من بداية عام 1970 *بالختم الزمني* (بصمة وقت). وهو يمثّل التاريخ بنحوٍ عددي خفيف (lightweight). يمكننا إنشاء التواريخ من الأختام الزمنية باستعمال `new Date(timestamp)‎` وتحويل كائن التاريخ `Date` الموجود إلى ختم زمني باستعمال التابِع `date.getTime()‎` (طالع أسفله). - Dates before 01.01.1970 have negative timestamps, e.g.: - ```js run - // 31 Dec 1969 +والتواريخ قبل الأول من يناير 1970 أختامها سالبة: +``` + // ‫31 ديسمبر 1969 let Dec31_1969 = new Date(-24 * 3600 * 1000); alert( Dec31_1969 ); - ``` +``` -`new Date(datestring)` -: If there is a single argument, and it's a string, then it is parsed automatically. The algorithm is the same as `Date.parse` uses, we'll cover it later. +*`new Date(datestring)‎`* +لو كان هناك وسيط واحد وكان سلسلة نصيّة، فسيحلّله المحرّك تلقائيًا. الخوازرمية هنا هي ذات التي يستعملها `Date.parse`. لا تقلق، سنتكلم عنه لاحقًا. - ```js run +``` let date = new Date("2017-01-26"); alert(date); - // The time is not set, so it's assumed to be midnight GMT and - // is adjusted according to the timezone the code is run in - // So the result could be - // Thu Jan 26 2017 11:00:00 GMT+1100 (Australian Eastern Daylight Time) - // or - // Wed Jan 25 2017 16:00:00 GMT-0800 (Pacific Standard Time) - ``` +``` +نجد في هذا المثال أن الوقت غير محدد لذا يكون بتوقيت GMT منتصف الليل، ويحدد وفقًا للمنطقة الزمنية التي تنفذ الشيفرة ضمنها، فالنتيجة يمكن أن تكون Thu Jan 26 2017 11:00:00 للبلدان ذات المنطقة الزمنية GMT+1100 أو يمكن أن تكون Wed Jan 25 2017 16:00:00 للبلدان الواقعة في المنطقة الزمنية GMT-0800. + +*`new Date(year, month, date, hours, minutes, seconds, ms)‎`* +يُنشئ تاريخًا بالمكوّنات الممرّرة حسب المنطقة الزمنية المحلية. أوّل وسيطين إلزاميين أما البقية اختيارية. -`new Date(year, month, date, hours, minutes, seconds, ms)` -: Create the date with the given components in the local time zone. Only the first two arguments are obligatory. - - The `year` must have 4 digits: `2013` is okay, `98` is not. - - The `month` count starts with `0` (Jan), up to `11` (Dec). - - The `date` parameter is actually the day of month, if absent then `1` is assumed. - - If `hours/minutes/seconds/ms` is absent, they are assumed to be equal `0`. +يجب أن يكون العام `year` بأربع خانات: `2013` صح، `98` خطأ. +يبدأ الشهر `month` بالرقم `0` (يناير) وينتهي بالعدد `11` (ديسمبر). +مُعامل التاريخ `date` هو رقم اليوم من الشهر. لو لم يكن موجودًا فسيعدّه الكائن `1`. +لو لم تكن مُعاملات الساعة والدقيقة والثانية والمليثانية `hours/minutes/seconds/ms` موجودة، فسيعدّها الكائن `0`. - For instance: - ```js +مثال: + +``` new Date(2011, 0, 1, 0, 0, 0, 0); // 1 Jan 2011, 00:00:00 - new Date(2011, 0, 1); // the same, hours etc are 0 by default - ``` + new Date(2011, 0, 1); // نفس تلك. الساعات والدقائق وغيرها 0 مبدئيًا +``` - The minimal precision is 1 ms (1/1000 sec): - ```js run + أدنى دقّة للتاريخ هي مليثانية واحدة (واحد من ألف من الثانية): + +``` let date = new Date(2011, 0, 1, 2, 3, 4, 567); alert( date ); // 1.01.2011, 02:03:04.567 - ``` +``` -## Access date components -There are methods to access the year, month and so on from the `Date` object: +## الوصول إلى مكوّنات التاريخ -[getFullYear()](mdn:js/Date/getFullYear) -: Get the year (4 digits) -[getMonth()](mdn:js/Date/getMonth) -: Get the month, **from 0 to 11**. +إليك التوابِع التي تتيح لك الوصول إلى العام والشهر وغيرها داخل كائن `Date`: -[getDate()](mdn:js/Date/getDate) -: Get the day of month, from 1 to 31, the name of the method does look a little bit strange. +- [getFullYear()‎](https://wiki.hsoub.com/JavaScript/Date/getFullYear): يجلب العام (٤ خانات) -[getHours()](mdn:js/Date/getHours), [getMinutes()](mdn:js/Date/getMinutes), [getSeconds()](mdn:js/Date/getSeconds), [getMilliseconds()](mdn:js/Date/getMilliseconds) -: Get the corresponding time components. +- [getMonth()‎](https://wiki.hsoub.com/JavaScript/Date/getMonth): يجلب الشهر، **من 0 إلى 11**. -```warn header="Not `getYear()`, but `getFullYear()`" -Many JavaScript engines implement a non-standard method `getYear()`. This method is deprecated. It returns 2-digit year sometimes. Please never use it. There is `getFullYear()` for the year. -``` +- [getDate()‎](https://wiki.hsoub.com/JavaScript/Date/getDate): يجلب رقم اليوم من الشهر، من 1 إلى 31. قد يبدو الاسم غريبًا قليلًا لك. + +- التوابع [getHours()‎](https://wiki.hsoub.com/JavaScript/Date/getHours) و[getMinutes()‎](https://wiki.hsoub.com/JavaScript/Date/getMinutes) و[getSeconds()‎](https://wiki.hsoub.com/JavaScript/Date/getSeconds) و[getMilliseconds()‎](https://wiki.hsoub.com/JavaScript/Date/getMilliseconds) +: تجلب مكوّنات الوقت حسب كل تابِع. (الساعة/الدقيقة/الثانية/المليثانية) + +**إياك بـ `getYear()‎` بل `getFullYear()‎`** +تقدّم الكثير من محرّكات جافاسكربت التابِع غير القياسي `getYear()‎`. هذا التابِع أصبح بائدًا، فهو يُعيد العام بخانتين أحيانًا. من فضلك لا تستعمله أبدًا، بل `getFullYear()‎` لتجلب العام. -Additionally, we can get a day of week: +كما يمكن أيضًا جلب رقم اليوم من الشهر: -[getDay()](mdn:js/Date/getDay) -: Get the day of week, from `0` (Sunday) to `6` (Saturday). The first day is always Sunday, in some countries that's not so, but can't be changed. +*[getDay()‎](https://wiki.hsoub.com/JavaScript/Date/getDay)* +يجلب رقم اليوم من الأسبوع، بدءًا بِـ `0` (الأحد) وحتى `6` (السبت). أوّل يوم هو الأحد دومًا. صحيح أنّ في بعض الدول هذا غير صحيح، لكن لا يمكن تغيير القيمة إطلاقًا. -**All the methods above return the components relative to the local time zone.** -There are also their UTC-counterparts, that return day, month, year and so on for the time zone UTC+0: [getUTCFullYear()](mdn:js/Date/getUTCFullYear), [getUTCMonth()](mdn:js/Date/getUTCMonth), [getUTCDay()](mdn:js/Date/getUTCDay). Just insert the `"UTC"` right after `"get"`. +**تُعيد كلّ التوابِع أعلاه المكوّنات حسب المنطقة الزمنية المحلية.** -If your local time zone is shifted relative to UTC, then the code below shows different hours: -```js run -// current date +توجد أيضًا مثيلاتها بنظام UTC حيث تُعيد اليوم والشهر والعام وغيرها في المنطقة الزمنية UTF+0:‏ [getUTCFullYear()‎](https://wiki.hsoub.com/JavaScript/Date/getUTCFullYear) و[getUTCMonth()‎](https://wiki.hsoub.com/JavaScript/Date/getUTCMonth) و[getUTCDay()‎](https://wiki.hsoub.com/JavaScript/Date/getUTCDay). ضع كلمة `"UTC"` بعد `"get"` وستجد المثيل المناسب. + + +لو كانت منطقتك الزمنية المحلية بعيدة عن UTC، فستعرض الشيفرة أدناه الساعات مختلفة عن بعضها البعض: + +``` +// التاريخ الحالي let date = new Date(); -// the hour in your current time zone +// الساعة حسب المنطقة الزمنية التي أنت فيها alert( date.getHours() ); -// the hour in UTC+0 time zone (London time without daylight savings) +// ‫الساعة حسب المنطقة الزمنية بتوقيت UTC+0 (أي توقيت لندن بدون التوقيت الصيفي) alert( date.getUTCHours() ); ``` -Besides the given methods, there are two special ones that do not have a UTC-variant: -[getTime()](mdn:js/Date/getTime) -: Returns the timestamp for the date -- a number of milliseconds passed from the January 1st of 1970 UTC+0. +هناك (إضافةً إلى هذه التوابِع) تابِعان آخران مختلفان قليلًا ليس لهما نُسخ بتوقيت UTC: + +*[getTime()‎](https://wiki.hsoub.com/JavaScript/Date/getTime)* +يُعيد ختم التاريخ الزمني، أي عدد المليثوان التي مرّت منذ الأول من يناير عام 1970 بتوقيت UTC+0. -[getTimezoneOffset()](mdn:js/Date/getTimezoneOffset) -: Returns the difference between UTC and the local time zone, in minutes: +*[getTimezoneOffset()‎](https://wiki.hsoub.com/JavaScript/Date/getTimezoneOffset)* +يُعيد الفرق بين المنطقة الزمنية الحالية وتوقيت UTC (بالدقيقة): - ```js run - // if you are in timezone UTC-1, outputs 60 - // if you are in timezone UTC+3, outputs -180 +``` + // ‫لو كانت منطقتك الزمنية UTC-1، فالناتج 60 + // لو كانت منطقتك الزمنية ‫UTC+3، فالناتج ‎-180 alert( new Date().getTimezoneOffset() ); - ``` +``` + -## Setting date components +## ضبط مكوّنات التاريخ -The following methods allow to set date/time components: +تتيح لك التوابِع الآتية ضبط مكوّنات التاريخ والوقت: -- [`setFullYear(year, [month], [date])`](mdn:js/Date/setFullYear) -- [`setMonth(month, [date])`](mdn:js/Date/setMonth) -- [`setDate(date)`](mdn:js/Date/setDate) -- [`setHours(hour, [min], [sec], [ms])`](mdn:js/Date/setHours) -- [`setMinutes(min, [sec], [ms])`](mdn:js/Date/setMinutes) -- [`setSeconds(sec, [ms])`](mdn:js/Date/setSeconds) -- [`setMilliseconds(ms)`](mdn:js/Date/setMilliseconds) -- [`setTime(milliseconds)`](mdn:js/Date/setTime) (sets the whole date by milliseconds since 01.01.1970 UTC) +- العام: [`setFullYear(year, [month], [date])‎`](https://wiki.hsoub.com/JavaScript/Date/setFullYear) +- الشهر: [`setMonth(month, [date])‎`](https://wiki.hsoub.com/JavaScript/Date/setMonth) +- التاريخ: [`setDate(date)‎`](https://wiki.hsoub.com/JavaScript/Date/setDate) +- الساعة: [`setHours(hour, [min], [sec], [ms])‎`](https://wiki.hsoub.com/JavaScript/Date/setHours) +- الدقيقة: [`setMinutes(min, [sec], [ms])‎`](https://wiki.hsoub.com/JavaScript/Date/setMinutes) +- الثانية: [`setSeconds(sec, [ms])‎`](https://wiki.hsoub.com/JavaScript/Date/setSeconds) +- المليثانية: [`setMilliseconds(ms)‎`](https://wiki.hsoub.com/JavaScript/Date/setMilliseconds) +- الوقت بالمليثانية: [`setTime(milliseconds)‎`](https://wiki.hsoub.com/JavaScript/Date/setTime) (تضبط التاريخ كلّه حسب عدد المليثوان منذ 01.01.1970 UTC) -Every one of them except `setTime()` has a UTC-variant, for instance: `setUTCHours()`. -As we can see, some methods can set multiple components at once, for example `setHours`. The components that are not mentioned are not modified. +لدى كلّ تابع منها نسخة بتوقيت UTC (عدا `setTime()‎`). مثال: `setUTCHours()‎`. -For instance: +كما رأيت فيمكن لبعض التوابِع ضبط عدّة مكوّنات في آن واحد مثل `setHours`. المكوّنات التي لا تُمرّر لا تُعدّل. -```js run +مثال: + +``` let today = new Date(); today.setHours(0); -alert(today); // still today, but the hour is changed to 0 +alert(today); // ما زال اليوم نفسه، ولكن الساعة تغيّرت إلى 0 today.setHours(0, 0, 0, 0); -alert(today); // still today, now 00:00:00 sharp. +alert(today); // ما زال اليوم نفسه، ولكنّا عند 00:00:00 تمامًا. ``` -## Autocorrection -The *autocorrection* is a very handy feature of `Date` objects. We can set out-of-range values, and it will auto-adjust itself. +## التصحيح التلقائي -For instance: -```js run -let date = new Date(2013, 0, *!*32*/!*); // 32 Jan 2013 ?!? -alert(date); // ...is 1st Feb 2013! -``` +ميزة *التصحيح التلقائي* في كائنات التواريخ `Date` مفيدة جدًا لنا، إذ يمكن أن نضع قيم تاريخ لامنطقية (مثل الخمسون من هذا الشهر) وسيُعدّلها الكائن بنفسه. -Out-of-range date components are distributed automatically. +مثال: -Let's say we need to increase the date "28 Feb 2016" by 2 days. It may be "2 Mar" or "1 Mar" in case of a leap-year. We don't need to think about it. Just add 2 days. The `Date` object will do the rest: +``` +let date = new Date(2013, 0, 32); // ‫الثاني والثلاثين من يناير 2013؟! +alert(date); // ‫...آه، تقصد الأول من فبراير 2013! +``` -```js run +تترتّب المكوّنات اللامنطقية تلقائيًا. فمثلًا لو أضفت على التاريخ ”28 فبراير 2016“ يومين اثنين، فيمكن أن يكون ”الثاني من مارس“ أو ”الأول من مارس“ لو كانت السنة كبيسة. بدل أن نفكّر بهذا الحساب، نُضيف يومين ونترك الباقي على كائن `Date`: + +``` let date = new Date(2016, 1, 28); -*!* date.setDate(date.getDate() + 2); -*/!* -alert( date ); // 1 Mar 2016 +alert( date ); // ‫1 مارس 2016 ``` -That feature is often used to get the date after the given period of time. For instance, let's get the date for "70 seconds after now": -```js run +غالبًا ما تُستعمل هذه الميزة لنجلب التاريخ بعد فترة محدّدة من الزمن. فلنقل مثلًا نريد تاريخ ”70 ثانية من الآن“: + +``` let date = new Date(); date.setSeconds(date.getSeconds() + 70); -alert( date ); // shows the correct date +alert( date ); // يعرض التاريخ الصحيح ``` +يمكننا أيضًا ضبط القيمة لتكون صفرًا أو حتّى بالسالب. مثال: -We can also set zero or even negative values. For example: - -```js run -let date = new Date(2016, 0, 2); // 2 Jan 2016 +``` +let date = new Date(2016, 0, 2); // ‫2 يناير 2016 -date.setDate(1); // set day 1 of month +date.setDate(1); // نضبط التاريخ على أول يوم من الشهر alert( date ); -date.setDate(0); // min day is 1, so the last day of the previous month is assumed -alert( date ); // 31 Dec 2015 +date.setDate(0); // أقل يوم ممكن هو 1، إذًا فيعدّ الكائن أنّ 0 هو آخر يوم من الشهر الماضي +alert( date ); // ‫31 ديسمبر 2015 ``` -## Date to number, date diff -When a `Date` object is converted to number, it becomes the timestamp same as `date.getTime()`: +## تحويل التاريخ إلى عدد، والفرق بين تاريخين + +حين يتحوّل كائن `Date` إلى عدد يصير ختمًا زمنيًا مطابقًا لختم `date.getTime()‎`: -```js run +``` let date = new Date(); -alert(+date); // the number of milliseconds, same as date.getTime() +alert(+date); // ‫عدد المليثوان، نفس ناتج date.getTime()‎ ``` -The important side effect: dates can be subtracted, the result is their difference in ms. -That can be used for time measurements: +تأثير هذا المهم والخطير هو أنّك تستطيع طرح التواريخ من بعض، والناتج سيكون الفرق بينهما بالمليثانية. يمكن استعمال الطرح لحساب الأوقات: -```js run -let start = new Date(); // start measuring time +``` +let start = new Date(); // نبدأ قياس الوقت -// do the job +// إلى العمل for (let i = 0; i < 100000; i++) { let doSomething = i * i * i; } -let end = new Date(); // end measuring time +let end = new Date(); // ننتهي من قياس الوقت alert( `The loop took ${end - start} ms` ); ``` -## Date.now() - -If we only want to measure time, we don't need the `Date` object. +## التاريخ الآن -There's a special method `Date.now()` that returns the current timestamp. +لو أردنا قياس الوقت فقط فلا نحتاج كائن `Date`، بل هناك تابِعًا خاصًا باسم `Date.now()‎` يُعيد لنا الختم الزمني الحالي. -It is semantically equivalent to `new Date().getTime()`, but it doesn't create an intermediate `Date` object. So it's faster and doesn't put pressure on garbage collection. +يُكافئ هذا التابِع الجملةَ `new Date().getTime()‎` إلّا أنّه لا يُنشئ كائن `Date` يتوسّط العملية، ولهذا هو أسرع ولا يزيد الضغط على عملية كنس المهملات. غالبًا ما يُستعمل التابِع لأنّه أسهل أو لأنّ الأداء في تلك الحالة مهم، مثلما في الألعاب بلغة جافاسكربت أو التطبيقات المتخصّصة الأخرى. -It is used mostly for convenience or when performance matters, like in games in JavaScript or other specialized applications. +ولهذا قد يكون الأفضل كتابة الشيفرة أدناه بدل تلك: -So this is probably better: - -```js run -*!* -let start = Date.now(); // milliseconds count from 1 Jan 1970 -*/!* +``` +let start = Date.now(); // ‫تبدأ المليثوان من تاريخ 1 يناير 1970 -// do the job +// إلى العمل for (let i = 0; i < 100000; i++) { let doSomething = i * i * i; } *!* -let end = Date.now(); // done +let end = Date.now(); // انتهينا */!* -alert( `The loop took ${end - start} ms` ); // subtract numbers, not dates +alert( `The loop took ${end - start} ms` ); // نطرح الأعداد لا التواريخ ``` -## Benchmarking - -If we want a reliable benchmark of CPU-hungry function, we should be careful. +## قياس الأداء -For instance, let's measure two functions that calculate the difference between two dates: which one is faster? +لو أردنا قياس أداء دالة شرهة في استعمال المعالج، فعلينا أن نكون حذرين، هذا لو أردنا التعويل على القياس. -Such performance measurements are often called "benchmarks". +فلنقيس مثلًا دالتين اثنتين تحسبان الفرق بين تاريخين: أيهما أسرع؟ نُطلق على قياسات الأداء هذه... قياسات أداء ”Benchmark“. -```js -// we have date1 and date2, which function faster returns their difference in ms? +``` +// ‫أمامنا date1 وdate2، أيّ دالة ستُعيد الفرق بينهما (بالمليثانية) أسرع من الأخرى؟‫ هذه... function diffSubtract(date1, date2) { return date2 - date1; } -// or +// ‫أم هذه... function diffGetTime(date1, date2) { return date2.getTime() - date1.getTime(); } ``` -These two do exactly the same thing, but one of them uses an explicit `date.getTime()` to get the date in ms, and the other one relies on a date-to-number transform. Their result is always the same. +وظيفة الدالتين متطابقة تمامًا، إلّا أن الثانية تستعمل التابِع `date.getTime()‎` الصريح لتجلب التاريخ بالمليثانية، بينما الأخرى تعتمد على تحويل التاريخ إلى عدد. الناتج متطابق دومًا. -So, which one is faster? +إذًا بهذه المعطيات، أيّ الدالتين أسرع؟ -The first idea may be to run them many times in a row and measure the time difference. For our case, functions are very simple, so we have to do it at least 100000 times. +أوّل فكرة تخطر على البال هو تشغيل كلّ واحدة مرات عديدة متتابعة وقياس فرق الوقت. الدوال (في حالتنا هذه) بسيطة جدًا، ولهذا علينا تشغيل كلّ واحدة مئة ألف مرة على الأقل. -Let's measure: +هيًا نقيس الأداء: -```js run +``` function diffSubtract(date1, date2) { return date2 - date1; } @@ -312,21 +306,13 @@ alert( 'Time of diffSubtract: ' + bench(diffSubtract) + 'ms' ); alert( 'Time of diffGetTime: ' + bench(diffGetTime) + 'ms' ); ``` -Wow! Using `getTime()` is so much faster! That's because there's no type conversion, it is much easier for engines to optimize. - -Okay, we have something. But that's not a good benchmark yet. - -Imagine that at the time of running `bench(diffSubtract)` CPU was doing something in parallel, and it was taking resources. And by the time of running `bench(diffGetTime)` that work has finished. - -A pretty real scenario for a modern multi-process OS. +عجبًا! استعمال التابِع `getTime()‎` أسرع بكثير! يعزو ذلك بسبب انعدام وجود تحويل للنوع (type conversion)، وهذا يسهّل على المحرّكات تحسين الأداء. -As a result, the first benchmark will have less CPU resources than the second. That may lead to wrong results. +جميل، وصلنا إلى شيء، ولكنّ هذا القياس ليس قياسًا طيبًا بعد. تخيّل أنّ المعالج كان ينفّذ أمرًا ما بالتوازي مع تشغيل `bench(diffSubtract)‎` وكان يستهلك الموارد، وما إن شغّلنا `bench(diffGetTime)‎` كان ذلك الأمر قد اكتمل. هذا التخيّل هو تخيّل طبيعي لأمر واقعيّ جدًا حيث اليوم أنظمة التشغيل متعدّدة المهام. بهذا يكون لمرة القياس الأولى موارد معالجة أقل من المرة الثانية، ما قد يؤدّي إلى نتائج قياس خطأ. -**For more reliable benchmarking, the whole pack of benchmarks should be rerun multiple times.** +**إن أردنا التعويل على قياس الأداء، علينا إعادة تشغيل كل قياسات الأداء الموجودة أكثر من مرّة.** هكذا مثلًا: -For example, like this: - -```js run +``` function diffSubtract(date1, date2) { return date2 - date1; } @@ -347,87 +333,414 @@ function bench(f) { let time1 = 0; let time2 = 0; -*!* -// run bench(upperSlice) and bench(upperLoop) each 10 times alternating +// ‫نشغّل bench(upperSlice)‎ وbench(upperLoop)‎ عشر مرات مرّة بمرّة for (let i = 0; i < 10; i++) { time1 += bench(diffSubtract); time2 += bench(diffGetTime); } -*/!* alert( 'Total time for diffSubtract: ' + time1 ); alert( 'Total time for diffGetTime: ' + time2 ); ``` -Modern JavaScript engines start applying advanced optimizations only to "hot code" that executes many times (no need to optimize rarely executed things). So, in the example above, first executions are not well-optimized. We may want to add a heat-up run: +لا تبدأ محرّكات جافاسكربت الحديثة بتطبيق التحسينات المتقدّمة إلّا على ”الشيفرات الحرجة“ التي تتنفّذ أكثر من مرّة (لا داعٍ بتحسين شيفرة نادرة التنفيذ). بهذا في المثال الأول، قد لا تكون مرات التنفيذ الأولى محسّنة كما يجب، وربما علينا إضافة تحمية سريعة: -```js -// added for "heating up" prior to the main loop +``` +// أضفناه لـ”تحمية“ المحرّك قبل الحلقة الأساس bench(diffSubtract); bench(diffGetTime); -// now benchmark +// الآن نقيس for (let i = 0; i < 10; i++) { time1 += bench(diffSubtract); time2 += bench(diffGetTime); } ``` -```warn header="Be careful doing microbenchmarking" -Modern JavaScript engines perform many optimizations. They may tweak results of "artificial tests" compared to "normal usage", especially when we benchmark something very small, such as how an operator works, or a built-in function. So if you seriously want to understand performance, then please study how the JavaScript engine works. And then you probably won't need microbenchmarks at all. +**الزم الحذر متى ما أجريت قياسات أداء على المستوى الذرّي**. -The great pack of articles about V8 can be found at . -``` +تُنفّذ محرّكات جافاسكربت الحديثة عددًا كبيرًا من التحسينات، وقد تُغيّر نتائج ”الاختبارات الصناعية“ موازنةً ”بالاستعمال الطبيعي لها“، خصوصًا حين نقيس أداء ما هو صغير للغاية مثل طريقة عمل مُعامل رياضي، أو دالة مضمّنة في اللغة نفسها. لهذا، لو كنت تريد حقًا فهم الأداء كما يجب، فمن فضلك تعلّم طريقة عمل محرّك جافاسكربت. حينها ربّما لن تحتاج هذه القياسات على المستوى الذرّي، أبدًا. -## Date.parse from a string +يمكنك أن تقرأ بعض المقالات الرائعة حول V8 هنا [http://mrale.ph](mrale.ph). -The method [Date.parse(str)](mdn:js/Date/parse) can read a date from a string. +## تحليل سلسلة نصية باستعمال Date.parse -The string format should be: `YYYY-MM-DDTHH:mm:ss.sssZ`, where: +يمكن أن يقرأ التابِع [Date.parse(str)](https://wiki.hsoub.com/JavaScript/Date/parse) تاريخًا من سلسلة نصية. يجب أن يكون تنسيق تلك السلسلة هكذا: `YYYY-MM-DDTHH:mm:ss.sssZ`، إذ تعني: -- `YYYY-MM-DD` -- is the date: year-month-day. -- The character `"T"` is used as the delimiter. -- `HH:mm:ss.sss` -- is the time: hours, minutes, seconds and milliseconds. -- The optional `'Z'` part denotes the time zone in the format `+-hh:mm`. A single letter `Z` that would mean UTC+0. +- `YYYY-MM-DD` -- التاريخ: اليوم-الشهر-العام. +- يُستعمل المحرف `"T"` فاصِلًا. +- `HH:mm:ss.sss` -- الوقت: المليثانية والثانية والدقيقة والساعة. +- يمثّل الجزء الاختياري `'Z'` المنطقة الزمنية حسب التنسيق `+-hh:mm`. لو وضعت `Z` فقط فذلك يعني UTC+0. -Shorter variants are also possible, like `YYYY-MM-DD` or `YYYY-MM` or even `YYYY`. +يمكنك أيضًا استعمال تنسيقات أقصر مثل `YYYY-MM-DD` أو `YYYY-MM` أو حتّى `YYYY`. -The call to `Date.parse(str)` parses the string in the given format and returns the timestamp (number of milliseconds from 1 Jan 1970 UTC+0). If the format is invalid, returns `NaN`. +باستدعاء `Date.parse(str)` فالسلسلة النصية تُحلّل حسب التنسيق فيها ويُعيد التابِع الختم الزمني (رقم المليثوان منذ الأول من يناير 1970 بتوقيت UTC+0). لو كان التنسيق غير صحيح فيُعيد `NaN`. -For instance: +إليك مثالًا: -```js run +``` let ms = Date.parse('2012-01-26T13:51:50.417-07:00'); -alert(ms); // 1327611110417 (timestamp) +alert(ms); // ‫1327611110417 (ختم زمني) ``` -We can instantly create a `new Date` object from the timestamp: +يمكننا إنشاء كائن `new Date` مباشرةً من الختم الزمني: -```js run +``` let date = new Date( Date.parse('2012-01-26T13:51:50.417-07:00') ); alert(date); ``` -## Summary +## ملخص -- Date and time in JavaScript are represented with the [Date](mdn:js/Date) object. We can't create "only date" or "only time": `Date` objects always carry both. -- Months are counted from zero (yes, January is a zero month). -- Days of week in `getDay()` are also counted from zero (that's Sunday). -- `Date` auto-corrects itself when out-of-range components are set. Good for adding/subtracting days/months/hours. -- Dates can be subtracted, giving their difference in milliseconds. That's because a `Date` becomes the timestamp when converted to a number. -- Use `Date.now()` to get the current timestamp fast. +- يُمثّل التاريخ والوقت في جافاسكربت بكائن [Date](https://wiki.hsoub.com/JavaScript/Date). لا يمكننا إنشاء ”تاريخ فقط“ أو ”وقتًا فقط“، فعلى كائنات التاريخ `Date` احتواء الاثنين معًا. +- تُعدّ الأشهر بدءًا بالصفر (يناير هو الشهر صفر، نعم). +- يُعدّ رقم اليوم من الأسبوع في `getDay()‎` من الصفر أيضًا (وهو يوم الأحد). +- يصحّح كائن التاريخ نفسه تلقائيًا حين تُضبط مكوّناته بقيم لا منطقية. يفيدنا لجمع/طرح الأيام والأشهر والأعوام. +- يمكن طرح التواريخ ومعرفة الفرق بينها بالمليثانية، ذلك لأنّ كائن التاريخ يتحوّل إلى ختم زمني حين يتحوّل إلى عدد. +- استعمل `Date.now()` لو أردت جلب الختم الزمني الحالي بسرعة. -Note that unlike many other systems, timestamps in JavaScript are in milliseconds, not in seconds. +لاحظ بأنّ الأختام الزمنية في جافاسكربت هي بالمليثانية، على العكس من أنظمة عديدة أخرى. -Sometimes we need more precise time measurements. JavaScript itself does not have a way to measure time in microseconds (1 millionth of a second), but most environments provide it. For instance, browser has [performance.now()](mdn:api/Performance/now) that gives the number of milliseconds from the start of page loading with microsecond precision (3 digits after the point): +نجد نفسنا بين الحين والآخر قياسات وقت دقيقة. للأسف فلا توفّر جافاسكربت نفسها طريقة لحساب الوقت بالنانوثانية (1 على مليون من الثانية)، ولكن أغلب بيئاتها توفّر ذلك. فمثلًا تملك المتصفّحات التابِع [performance.now()‎](https://developer.mozilla.org/en-US/docs/Web/API/Performance/now) إذ يُعيد عدد المليثوان منذ بدأ تحميل الصفحة بقدّة تصل إلى المايكروثانية (ثلاث خانات بعد الفاصلة): -```js run +``` alert(`Loading started ${performance.now()}ms ago`); -// Something like: "Loading started 34731.26000000001ms ago" -// .26 is microseconds (260 microseconds) -// more than 3 digits after the decimal point are precision errors, but only the first 3 are correct +// ت‫ظهر هكذا: "Loading started 34731.26000000001ms ago" +``` +تعني ”‎.26“ هنا المايكروثوان (260 مايكروثانية)، فلو زِدت على ثلاث خانات بعد الفاصلة فستجد أخطاءً في دقّة الحساب. أوّل ثلاثة هي الصحيحة فقط. + +تملك لغة Node.js أيضًا وحدة `microtime` وأخرى غيرها. يمكن (تقنيًا) لأيّ جهاز أو بيئة أن تعطينا دقّة وقت أعلى، `Date` لا تقدّم ذلك لا أكثر. + +## تمارين +### إنشاء تاريخ +_الأهمية: 5_ + +أنشِئ كائن `Date` لهذا التاريخ: 20 فبراير 2012، 3:12 صباحًا. المنطقة الزمنية هي المحلية. اعرض التاريخ باستعمال `alert`. + +#### الحل +يستعمل مُنشِئ `new Date` المنطقة الزمنية الحالية. عليك ألا تنسى بأنّ الأشهر تبدأ من الصفر. + +إذًا ففبراير هو الشهر رقم 1. + +``` +let d = new Date(2012, 1, 20, 3, 12); +alert( d ); +``` + +### اعرض اسم اليوم من الأسبوع +_الأهمية: 5_ + +اكتب دالة `getWeekDay(date)` تعرض اسم اليوم من الأسبوع بالتنسيق الإنكليزي القصير: 'MO', 'TU', 'WE', 'TH', 'FR', 'SA', 'SU'. + +مثال: + +``` +let date = new Date(2012, 0, 3); // ‫3 يناير 2012 +alert( getWeekDay(date) ); // ي‫جب أن يطبع "TU" +``` + +#### الحل +يُعيد التابِع `date.getDay()‎` رقم اليوم من الأسبوع، بدءًا من يوم الأحد. + +لنصنع مصفوفة فيها أيام الأسبوع لنعرف اليوم الصحيح من رقمه: + +``` +function getWeekDay(date) { + let days = ['SU', 'MO', 'TU', 'WE', 'TH', 'FR', 'SA']; + + return days[date.getDay()]; +} + +let date = new Date(2014, 0, 3); // ‫3 يناير 2014 +alert( getWeekDay(date) ); // FR +``` + +### اليوم من الأسبوع في أوروبا + +_الأهمية: 4_ + +في الدول الأوروبية، يبدأ الأسبوع بيوم الإثنين (رقم 1) وثمّ الثلاثاء (رقم 2) وحتّى الأحد (رقم 7). اكتب دالة `getLocalDay(date)` تُعيد يوم الأسبوع ”الأوروبي“ من التاريخ `date`. + +``` +let date = new Date(2012, 0, 3); // ‫3 يناير 2012 +alert( getLocalDay(date) ); // يكون يوم ثلاثاء، يجب أن تعرض 2 +``` + +#### الحل + +``` +function getLocalDay(date) { + + let day = date.getDay(); + + if (day == 0) { + // يوم الأحد 0 في أوروبا هو الأخير (7)‏ + day = 7; + } + + return day; +} ``` -Node.js has `microtime` module and other ways. Technically, almost any device and environment allows to get more precision, it's just not in `Date`. +### ما هو التاريخ الذي كان قبل كذا يوم؟ +_الأهمية: 4_ + +أنشِئ دالة `getDateAgo(date, days)` تُعيد بتمرير التاريخ `date` اسم اليوم من الشهر قبل فترة `days` يوم. + +مثال: لو كان اليوم العشرون من الشهر، فتُعيد `getDateAgo(new Date(),1 )‎` التاسع عشر و`getDateAgo(new Date(), 2)‎` الثامن عشر. + +يجب أن نعوّل بأن تعمل الدالة في حال `days=356` وأكثر حتّى: + +``` +let date = new Date(2015, 0, 2); + +alert( getDateAgo(date, 1) ); // ‫1، (1 يناير 2015) +alert( getDateAgo(date, 2) ); // ‫31، (31 ديسمبر 2014) +alert( getDateAgo(date, 365) ); // ‫2، (2 يناير 2014) +``` + +ملاحظة: يجب ألّا تُعدّل الدالة التاريخ `date` المُمرّر. + +#### الحل +الفكرة بسيطة، أن نطرح عدد الأيام من التاريخ `date`: + +``` +function getDateAgo(date, days) { + date.setDate(date.getDate() - days); + return date.getDate(); +} +``` + +ولكن... يجب ألّا تُعدّل الدالة على `date`. هذا مهم إذ أنّ الشيفرة خارج الدالة التي تُعطينا التاريخ لا تريد منّا تغييره. لننفّذ ذلك، علينا نسخ التاريخ هكذا أولًا: + +``` +function getDateAgo(date, days) { + let dateCopy = new Date(date); + + dateCopy.setDate(date.getDate() - days); + return dateCopy.getDate(); +} + +let date = new Date(2015, 0, 2); + +alert( getDateAgo(date, 1) ); // ‫1، (1 يناير 2015) +alert( getDateAgo(date, 2) ); // ‫31، (31 ديسمبر 2014) +alert( getDateAgo(date, 365) ); // ‫2، (2 يناير 2014) +``` + +### آخر يوم من الشهر كذا؟ +_الأهمية: 5_ + +اكتب دالة `getLastDayOfMonth(year, month)` تُعيد آخر يوم من الشهر. أحيانًا يكون الثلاثين، أو الحادي والثلاثين أو الثامن/التاسع عشر من فبراير. + +المُعاملات: + +- `year` -- العام بأربع خانات، مثلًا 2012. +- `month` -- الشهر من 0 إلى 11. + +مثال: `getLastDayOfMonth(2012, 1) = 29` (سنة كبيسة، فبراير). + +#### الحل + +فلنصنع تاريخًا باستعمال الشهر التالي، ولكنّ نمرّر الصفر ليكون رقم اليوم: +``` +function getLastDayOfMonth(year, month) { + let date = new Date(year, month + 1, 0); + return date.getDate(); +} + +alert( getLastDayOfMonth(2012, 0) ); // 31 +alert( getLastDayOfMonth(2012, 1) ); // 29 +alert( getLastDayOfMonth(2013, 1) ); // 28 +``` + +عادةً ما تبدأ التواريخ بالواحد، لكن يمكننا (تقنيًا) تمرير أيّ عدد وسيُعدّل التاريخ نفسه. لذا حين نمرّر 0 نعني بذلك ”يومًا واحد قبل الأول من الشهر“، أي ”اليوم الأخير من الشهر الماضي“. + +### كم من ثانية مضت اليوم؟ +_الأهمية: 5_ + +اكتب دالة `getSecondsToday()‎` تُعيد عدد الثواني منذ بداية هذا اليوم. فمثلًا لو كانت الساعة الآن `10:00 am`، وبدون التوقيت الصيفي، فستعطينا الدالة: + +``` +getSecondsToday() == 36000 // (3600 * 10) +``` + +يجب أن تعمل الدالة مهما كان اليوم. أيّ ألا تحتوي على قيمة داخلها بتاريخ ”اليوم“... اليوم. + +#### الحل +لنعرف عدد الثواني يمكننا توليد تاريخًا باستعمال اليوم الحالي والساعة 00:00:00، وثمّ نطرح منها ”الوقت والتاريخ الآن“. سيكون الفرق حينها بعدد المليثوان منذ بداية هذا اليوم، فنقسمه على 1000 لنعرف الثواني فقط: + +``` +function getSecondsToday() { + let now = new Date(); + + // أنشِئ كائنًا باستعمال اليوم والشهر والسنة حاليًا + let today = new Date(now.getFullYear(), now.getMonth(), now.getDate()); + + let diff = now - today; // الفرق بالمليثانية + return Math.round(diff / 1000); // نحوّله إلى ثوان +} + +alert( getSecondsToday() ); +``` + +الحل الآخر هو جلب الساعة والدقيقة والثانية وتحويلها إلى عدد الثواني: + +``` +function getSecondsToday() { + let d = new Date(); + return d.getHours() * 3600 + d.getMinutes() * 60 + d.getSeconds(); +} +``` + +## كم من ثانية بقت حتّى الغد؟ +_الأهمية: 5_ + +أنشِئ دالة `getSecondsToTomorrow()‎` تُعيد عدد الثواني حتّى يحلّ الغد. فمثلًا لو كان الوقت الآن `23:00`، تُعيد لنا: + +``` +getSecondsToTomorrow() == 3600 +``` + +ملاحظة: يجب أن تعمل الدالة مهما كان اليوم، وألا تعتبر ”اليوم“ هذا اليوم. + +#### الحل +لنعرف عدد المليثوان حتّى قدوم الغد، يمكننا أن نطرح من ”الغد 00:00:00“ التاريخ اليوم. أوّلًا، نولّد هذا ”الغد“ وثمّ ننفّذ الطرح: + +``` +function getSecondsToTomorrow() { + let now = new Date(); + + // تاريخ الغد + let tomorrow = new Date(now.getFullYear(), now.getMonth(), now.getDate()+1); + + let diff = tomorrow - now; // الفرق بالمليثانية + return Math.round(diff / 1000); // نحوّله إلى ثوان +} +``` + +حل بديل: + +``` +function getSecondsToTomorrow() { + let now = new Date(); + let hour = now.getHours(); + let minutes = now.getMinutes(); + let seconds = now.getSeconds(); + let totalSecondsToday = (hour * 60 + minutes) * 60 + seconds; + let totalSecondsInADay = 86400; + + return totalSecondsInADay - totalSecondsToday; +} +``` + +لاحظ أنّ هناك دولًا كثيرة تستعمل التوقيت الصيفي، لذا ستجد هناك أيام فيها 23 أو 25 ساعة. يمكن أن نتعامل مع هذه الأيام بنحوٍ منفصل. + +### تنسيق التاريخ نسبيًا +_الأهمية: 4_ + +اكتب دالة `formatDate(date)` تُنسّق التاريخ `date` حسب الآتي: + +- لو مرّت أقلّ من ثانية من `date`، فتُعيد `"right now"`. +- وإلّا، لو مرّت أقلّ من دقيقة من `date`، فتُعيد `"n sec. ago"`. +- وإلّا، لو أقل من ساعة، فتُعيد `"m min. ago"`. +- وإلّا، فتُعيد التاريخ كاملًا بالتنسيق `"DD.MM.YY HH:mm"`، أي (شَكلًا): `الدقيقة:الساعة العام:الشهر:اليوم` (كلها بخانتين). مثل: `31.12.16 10:00`. + +أمثلة: + +``` +alert( formatDate(new Date(new Date - 1)) ); // "right now" + +alert( formatDate(new Date(new Date - 30 * 1000)) ); // "30 sec. ago" + +alert( formatDate(new Date(new Date - 5 * 60 * 1000)) ); // "5 min. ago" + +// ‫تاريخ الأمس، مثلًا ‎31.12.16, 20:00 +alert( formatDate(new Date(new Date - 86400 * 1000)) ); +``` + +#### الحل +لنجلب الوقت المنقضي منذ `date` وحتّى الآن، سنطرح التاريخين. + +``` +function formatDate(date) { + let diff = new Date() - date; // الفرق بالمليثانية + + if (diff < 1000) { // أقل من ثانية واحدة + return 'right now'; + } + + let sec = Math.floor(diff / 1000); // نحوّل الفرق إلى ثوان + + if (sec < 60) { + return sec + ' sec. ago'; + } + + let min = Math.floor(diff / 60000); // نحوّل الفرق إلى دقائق + if (min < 60) { + return min + ' min. ago'; + } + + // ننسّق التاريخ + // ونُضيف أصفارًا لو كان اليوم/الشهر/الساعة/الدقيقة بخانة واحدة + let d = date; + d = [ + '0' + d.getDate(), + '0' + (d.getMonth() + 1), + '' + d.getFullYear(), + '0' + d.getHours(), + '0' + d.getMinutes() + ].map(component => component.slice(-2)); // نأخذ الخانتين الأخيرتين من كلّ مكوّن + + // ندمج المكوّنات في تاريخ + return d.slice(0, 3).join('.') + ' ' + d.slice(3).join(':'); +} + +alert( formatDate(new Date(new Date - 1)) ); // "right now" + +alert( formatDate(new Date(new Date - 30 * 1000)) ); // "30 sec. ago" + +alert( formatDate(new Date(new Date - 5 * 60 * 1000)) ); // "5 min. ago" + +// ‫تاريخ الأمس، مثلًا ‎31.12.16, 20:00 +alert( formatDate(new Date(new Date - 86400 * 1000)) ); +``` + +حل بديل: + +``` +function formatDate(date) { + let dayOfMonth = date.getDate(); + let month = date.getMonth() + 1; + let year = date.getFullYear(); + let hour = date.getHours(); + let minutes = date.getMinutes(); + let diffMs = new Date() - date; + let diffSec = Math.round(diffMs / 1000); + let diffMin = diffSec / 60; + let diffHour = diffMin / 60; + + // التنسيق + year = year.toString().slice(-2); + month = month < 10 ? '0' + month : month; + dayOfMonth = dayOfMonth < 10 ? '0' + dayOfMonth : dayOfMonth; + + if (diffSec < 1) { + return 'right now'; + } else if (diffMin < 1) { + return `${diffSec} sec. ago` + } else if (diffHour < 1) { + return `${diffMin} min. ago` + } else { + return `${dayOfMonth}.${month}.${year} ${hour}:${minutes}` + } +} +``` + +لاحظ بأنّ هذه الطريقة سيّئة لو أردت دعم اللغات دعمًا صحيحًا (في العربية هناك ثانية واحدة وثانيتين وثلاث ثوان وخمسون ثانية وهكذا). + + +ترجمة -وبتصرف- للفصل [Date and time](https://javascript.info/date) من كتاب [The JavaScript language](https://javascript.info/js) +