Skip to content

Property flags and descriptors #22

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
May 27, 2020
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
162 changes: 81 additions & 81 deletions 1-js/07-object-properties/01-property-descriptors/article.md
Original file line number Diff line number Diff line change
@@ -1,40 +1,40 @@

# Property flags and descriptors
# رايات الخصائص و واصفاتها

As we know, objects can store properties.
كما نعلم, الكائنات يمكن ان تُخزن الخصائص.

Until now, a property was a simple "key-value" pair to us. But an object property is actually a more flexible and powerful thing.
حتى الآن, الخاصيه كانت لنا زوجاً بسيطاً من "المفاتيح-القيم". و لكن خاصية الكائن هى حقاً اكثر مرونة و قوة.

In this chapter we'll study additional configuration options, and in the next we'll see how to invisibly turn them into getter/setter functions.
فى هذا القسم سوف ندرس خصائص ضبط إضافية, وفي الفصل الّذي يليه سنرى كيف نحوّلها إلى دوال جلب/ضبط (Setters/Getters) أيضًا.

## Property flags
## رايات الخصائص

Object properties, besides a **`value`**, have three special attributes (so-called "flags"):
خصائص الكائنات, بالإضافة الى **`قيمتها`**, لديها ثلاث سمات مميزة اخرى (لذلك تسمى "flags" او رايات) :

- **`writable`** -- if `true`, the value can be changed, otherwise it's read-only.
- **`enumerable`** -- if `true`, then listed in loops, otherwise not listed.
- **`configurable`** -- if `true`, the property can be deleted and these attributes can be modified, otherwise not.
- **`writable` : قابلة التعديل** -- إذا كانت `true`, يمكن تغيير القيمة, غير ذلك فالقيمة للقراءة فقط.
- **`enumerable` : قابلة الإحصاء** -- إذا كانت `true`, سوف يظهر مفتاح الخاصية ضمن مفاتيح الكائن عند إستخدام **`for..in`**, غير ذلك فلن يظهر.
- **`configurable` : قابلة إعادة الضبط** -- إذا كانت `true`, فيمكن حذف الخاصية وتعديل هذه السمات, غير ذلك فلا.

We didn't see them yet, because generally they do not show up. When we create a property "the usual way", all of them are `true`. But we also can change them anytime.
لم نري تلك الرايات ختي الآن, لأنهم بشكل عام لا يظهرون. عندما نقوم بعمل خاصية "بالطريقة العادية", فكل هذه السمات تكون بقيمة `true`. و لكن يمكننا طبعاً تغييرها متى أردنا.

First, let's see how to get those flags.
اولاً, دعنا نري كيف يمكننا الحصول علي تلك الرايات.

The method [Object.getOwnPropertyDescriptor](mdn:js/Object/getOwnPropertyDescriptor) allows to query the *full* information about a property.
الطريقة [Object.getOwnPropertyDescriptor](mdn:js/Object/getOwnPropertyDescriptor) تسمح بالإستعلام *الكامل* عن المعلومات الخاصة بأيّ خاصية.

The syntax is:
و صياغتها تكون كالآتي:
```js
let descriptor = Object.getOwnPropertyDescriptor(obj, propertyName);
```

`obj`
: The object to get information from.
: الكائن الّذي سنجلب معلوماته.

`propertyName`
: The name of the property.
: اسم الخاصية الّتي نريدها.

The returned value is a so-called "property descriptor" object: it contains the value and all the flags.
القيمة العائدة تسمي بكائن "واصف الخصائص" : و هي تحتوي علي القيمه و جميع الرايات.

For instance:
اليك مثالاً:

```js run
let user = {
Expand All @@ -44,7 +44,7 @@ let user = {
let descriptor = Object.getOwnPropertyDescriptor(user, 'name');

alert( JSON.stringify(descriptor, null, 2 ) );
/* property descriptor:
/* واصف الخاصية:
{
"value": "John",
"writable": true,
Expand All @@ -54,23 +54,23 @@ alert( JSON.stringify(descriptor, null, 2 ) );
*/
```

To change the flags, we can use [Object.defineProperty](mdn:js/Object/defineProperty).
لتغيير الرايات, يمكننا إستخدام [Object.defineProperty](mdn:js/Object/defineProperty).

The syntax is:
و صياغتها تكون كالآتي:

```js
Object.defineProperty(obj, propertyName, descriptor)
```

`obj`, `propertyName`
: The object and its property to apply the descriptor.
: الكائن الّذي سنطبّق عليه الواصِف، واسم الخاصية.

`descriptor`
: Property descriptor object to apply.
: واصِف الخصائص الّذي سنطبّقه على الكائن.

If the property exists, `defineProperty` updates its flags. Otherwise, it creates the property with the given value and flags; in that case, if a flag is not supplied, it is assumed `false`.
لو كانت الخاصية موجوده, `defineProperty` سوف تقوم بتحديث راياتها. غير ذلك, وإلّا فسيُنشئ الخاصية بهذه القيمة الممرّرة والرايات كذلك; في هذه الحالة, إذا كانت الراية غير موجوده, سوف يُفترض قيمتها بـ `false`.

For instance, here a property `name` is created with all falsy flags:
إليك مثالاً, هنا الخاصية `name` سوف يتم إنشائها حيث تكون كل راياتها تساوى **`false`**:

```js run
let user = {};
Expand All @@ -96,13 +96,13 @@ alert( JSON.stringify(descriptor, null, 2 ) );
*/
```

Compare it with "normally created" `user.name` above: now all flags are falsy. If that's not what we want then we'd better set them to `true` in `descriptor`.
قارن ذلك مع `user.name` "التي انشأناها بشكل طبيعي" بالإعلي: الآن كل الرايات لديها القيمة `false`. إذا لم يكن هذا ما نريدة إذا سوف يكون من الأفضل ضبط قيمتهم بـ `true` في `descriptor`.

Now let's see effects of the flags by example.
نرى الآن تأثيرات هذه الرايات في هذا المثال.

## Non-writable
## منع قابلية التعديل

Let's make `user.name` non-writable (can't be reassigned) by changing `writable` flag:
لنجعل `user.name` غير قابلة للتعديل (لا يمكن إسناد قيمة لها) عن طريق تغيير قيمة الراية `writable` :

```js run
let user = {
Expand All @@ -116,25 +116,25 @@ Object.defineProperty(user, "name", {
});

*!*
user.name = "Pete"; // Error: Cannot assign to read only property 'name'
user.name = "Pete"; // خطأ: لا يمكن إسناد القيم إلى الخاصية ‫ `name` إذ هي للقراءة فقط
*/!*
```

Now no one can change the name of our user, unless they apply their own `defineProperty` to override ours.
الآن يستحيل على أيّ شخص تعديل اسم هذا المستخدم, إلا عند تطبيق `defineProperty` لتعديل ما فعلناه نحن.

```smart header="Errors appear only in strict mode"
In the non-strict mode, no errors occur when writing to non-writable properties and such. But the operation still won't succeed. Flag-violating actions are just silently ignored in non-strict.
```smart header="لا تظهر الأخطاء إلّا في الوضع الصارم `strict mode`"
في الوضع الغير صارم `non-strict mode`, لا يحدث أخطاء عند التعديل علي خاصية غير قابلة للتعديل. و لكن العمليه لن تتم بنجاح أيضاً. أخطاء خرق الرايه يتم تجاهلها بصمت في الوضع الغير صارم `non-strict`.
```

Here's the same example, but the property is created from scratch:
إليك نفس المثال, و لكن سوف يتم إنشاء الخاصية من الصفر:

```js run
let user = { };

Object.defineProperty(user, "name", {
*!*
value: "John",
// for new properties we need to explicitly list what's true
// لو كانت الخصائص جديدة فعلينا إسناد قيمها إسنادًا صريحًا
enumerable: true,
configurable: true
*/!*
Expand All @@ -144,11 +144,11 @@ alert(user.name); // John
user.name = "Pete"; // Error
```

## Non-enumerable
## منع قابلية الإحصاء

Now let's add a custom `toString` to `user`.
الآن دعنا نضيف الطريقة `toString` الى الكائن `user`.

Normally, a built-in `toString` for objects is non-enumerable, it does not show up in `for..in`. But if we add a `toString` of our own, then by default it shows up in `for..in`, like this:
عادةً, لا يمكننا إستخدام `toString` مع الكائنات و ذلك لإنها غير قابلة للإحصاء, و هي لا تظهر عند إستخدام `for..in`. و لكن إذا قمنا بإضافة `toString` الخاصة بنا, إذا بشكل افتراضي سوف تظهر عند إستخدام `for..in`, كما فى المثال التالي:

```js run
let user = {
Expand All @@ -158,11 +158,11 @@ let user = {
}
};

// By default, both our properties are listed:
// بشكل إفتراضي, كلا الخاصيتين سوف يتم عرضهم:
for (let key in user) alert(key); // name, toString
```

If we don't like it, then we can set `enumerable:false`. Then it won't appear in a `for..in` loop, just like the built-in one:
لو لم نرد ذلك, يمكننا وضع `enumerable:false`. و سوف لن تظهر عند إستخدام `for..in`, كما فى الوضع العادى:

```js run
let user = {
Expand All @@ -179,24 +179,24 @@ Object.defineProperty(user, "toString", {
});

*!*
// Now our toString disappears:
// الآن toString اختفت:
*/!*
for (let key in user) alert(key); // name
```

Non-enumerable properties are also excluded from `Object.keys`:
الخصائص الغير قابلة للإحصاء يتم استثناءها من `Object.keys`:

```js
alert(Object.keys(user)); // name
```

## Non-configurable
## منع قابلية إعادة الضبط

The non-configurable flag (`configurable:false`) is sometimes preset for built-in objects and properties.
راية عدم الضبط (`configurable:false`) احياناً يتم إعدادها مسبقاً في بعض الكائنات والخصائص المضمّنة في اللغة.

A non-configurable property can not be deleted.
الخاصية الغير قابلة للإحصاء لا يمكن حذفها.

For instance, `Math.PI` is non-writable, non-enumerable and non-configurable:
فمثلاً, `Math.PI` غير قابلة للتعديل, غير قابلة للإحصاء و غير قابلة لإعادة الضبط:

```js run
let descriptor = Object.getOwnPropertyDescriptor(Math, 'PI');
Expand All @@ -211,23 +211,23 @@ alert( JSON.stringify(descriptor, null, 2 ) );
}
*/
```
So, a programmer is unable to change the value of `Math.PI` or overwrite it.
لذا, لن يستطيع المبرمج تغيير قيمة `Math.PI` أو التعديل عليها.

```js run
Math.PI = 3; // Error
Math.PI = 3; // خطأ

// delete Math.PI won't work either
// delete Math.PI لن تعمل أيضًا
```

Making a property non-configurable is a one-way road. We cannot change it back with `defineProperty`.
إن تفعيل خاصيّة منع قابلية إعادة الضبط هو قرار لا عودة فيه. فلا يمكننا تغيير الراية (إتاحة قابلية إعادة الضبط) باستعمال `defineProperty`.

To be precise, non-configurability imposes several restrictions on `defineProperty`:
1. Can't change `configurable` flag.
2. Can't change `enumerable` flag.
3. Can't change `writable: false` to `true` (the other way round works).
4. Can't change `get/set` for an accessor property (but can assign them if absent).
وللدقّة فهذا المنع يضع تقييدات أخرى على `defineProperty`:
1. منع تغيير راية قابلية إعادة الضبط `configurable`.
2. منع تغيير راية قابلية الإحصاء `enumerable`.
3. منع تغيير راية قابلية التعديل `writable: false` الي `true` (و لكن العكس ممكن).
4. منع تغيير ضابط وجالب واصف الوصول `get/set` (ولكن يمكن إسناد قيم إليه).

Here we are making `user.name` a "forever sealed" constant:
هنا سوف نحدد الخاصية `user.name` ثابتة للأبد:

```js run
let user = { };
Expand All @@ -239,26 +239,26 @@ Object.defineProperty(user, "name", {
});

*!*
// won't be able to change user.name or its flags
// all this won't work:
// لن يمكن تغيير user.name او الرايات الخاصه بها
// كل ذلك لن يعمل:
// user.name = "Pete"
// delete user.name
// defineProperty(user, "name", { value: "Pete" })
Object.defineProperty(user, "name", {writable: true}); // Error
Object.defineProperty(user, "name", {writable: true}); // خطأ
*/!*
```

```smart header="\"Non-configurable\" doesn't mean \"non-writable\""
Notable exception: a value of non-configurable, but writable property can be changed.
```smart header="\"منع قابلية إعادة الضبط\" لا يعني \"منع قابلية التعديل\""
إستثناء ملحوظ: قيمة الخاصية التى لديها منع إعادة الضبط, و لكن لديها قابلية التعديل , تلك القيمة يمكن تغييرها.

The idea of `configurable: false` is to prevent changes to property flags and its deletion, not changes to its value.
الفكره وراء `configurable: false` لمنع تغيير رايات الخاصية او حذفها, ليس لتغيير قيمتها.
```

## Object.defineProperties

There's a method [Object.defineProperties(obj, descriptors)](mdn:js/Object/defineProperties) that allows to define many properties at once.
يوجد طريقة [Object.defineProperties(obj, descriptors)](mdn:js/Object/defineProperties) و التي تسمح بتعريف كثير من الخصائص مره واحده.

The syntax is:
و صياغتها تكون كالآتي:

```js
Object.defineProperties(obj, {
Expand All @@ -268,7 +268,7 @@ Object.defineProperties(obj, {
});
```

For instance:
مثال علي ذلك:

```js
Object.defineProperties(user, {
Expand All @@ -278,54 +278,54 @@ Object.defineProperties(user, {
});
```

So, we can set many properties at once.
أي أنّنا نقدر على ضبط أكثر من خاصية معًا.

## Object.getOwnPropertyDescriptors

To get all property descriptors at once, we can use the method [Object.getOwnPropertyDescriptors(obj)](mdn:js/Object/getOwnPropertyDescriptors).
لجلب كلّ واصفات الخصائص معًا, يمكننا إستعمال الطريقة [Object.getOwnPropertyDescriptors(obj)](mdn:js/Object/getOwnPropertyDescriptors).

Together with `Object.defineProperties` it can be used as a "flags-aware" way of cloning an object:
بدمجه مع `Object.defineProperties` يمكن إستخدامها لنسخ الكائنات "ونحن على علمٍ براياتها":

```js
let clone = Object.defineProperties({}, Object.getOwnPropertyDescriptors(obj));
```

Normally when we clone an object, we use an assignment to copy properties, like this:
فعادةً حين ننسخ كائنًا, نستعمل الإسناد لنسخ الخصائص، هكذا:

```js
for (let key in user) {
clone[key] = user[key]
}
```

...But that does not copy flags. So if we want a "better" clone then `Object.defineProperties` is preferred.
...و لكن هذا لا ينسخ الرايات. لذا إذا كنا نريد نسخ "أفضل" سيكون إستخدام `Object.defineProperties` أفضل.

Another difference is that `for..in` ignores symbolic properties, but `Object.getOwnPropertyDescriptors` returns *all* property descriptors including symbolic ones.
إختلاف آخر و ذلك أن `for..in` تتجاهل الخصائص الرمزية (Symbolic Properties), و لكن `Object.getOwnPropertyDescriptors` تُعيد *كل* واصِفات الخصائص بما فيها الرمزية.

## Sealing an object globally
## إغلاق الكائنات على المستوى العام

Property descriptors work at the level of individual properties.
تعمل واصِفات الخصائص على مستوى الخصائص منفردةً. هناك أيضًا توابِع تقصر الوصول إلى الكائن كلّه.

There are also methods that limit access to the *whole* object:
يوجد ايضاً تحدد الدخول الى الكائن *كله* :

[Object.preventExtensions(obj)](mdn:js/Object/preventExtensions)
: Forbids the addition of new properties to the object.
: يمنع إضافة خصائص جديدة إلى الكائن.

[Object.seal(obj)](mdn:js/Object/seal)
: Forbids adding/removing of properties. Sets `configurable: false` for all existing properties.
: يمنع إضافة الخصائص وإزالتها. يقوم بوضع `configurable: false` لكل الخصائص الموجودة.

[Object.freeze(obj)](mdn:js/Object/freeze)
: Forbids adding/removing/changing of properties. Sets `configurable: false, writable: false` for all existing properties.
: يمنع إضافة الخصائص أو إزالتها أو تغييرها. يقوم بوضع `configurable: false, writable: false` لكل الخصائص الموجودة.

And also there are tests for them:
كما أنّ هناك توابِع أخرى تفحص تلك المزايا:

[Object.isExtensible(obj)](mdn:js/Object/isExtensible)
: Returns `false` if adding properties is forbidden, otherwise `true`.
: يُعيد `false` لو كان ممنوعًا إضافة الخصائص, غير ذلك `true`.

[Object.isSealed(obj)](mdn:js/Object/isSealed)
: Returns `true` if adding/removing properties is forbidden, and all existing properties have `configurable: false`.
: يُعيد `true` لو كان ممنوعًا إضافة الخصائص أو إزالتها، وكانت كلّ خصائص الكائن الموجودة ممنوعة من قابلية إعادة الضبط `configurable: false`.

[Object.isFrozen(obj)](mdn:js/Object/isFrozen)
: Returns `true` if adding/removing/changing properties is forbidden, and all current properties are `configurable: false, writable: false`.
: يُعيد `true` إذا كان إضافة/حذف/تعديل الخصائص ممنوعاً, و كل الخصائص الحالية `configurable: false, writable: false`.

These methods are rarely used in practice.
أمّا على أرض الواقع، فنادرًا ما نستعمل تلك الطرق.