المسارات
/
JavaScript
JavaScript
/
التمارين
/
تحويل الإحداثيات
تحويل الإحداثيات

تحويل الإحداثيات

تمرين تعلّمي

مقدمة

الإغلاقات نمط برمجي في JavaScript يسمح باستخدام متغيرات من النطاق المعجمي الخارجي داخل كتلة كود متداخلة. تدعم JavaScript الإغلاقات بشفافية، وكثيرًا ما تُستخدم دون أن يعرف المبرمج ما هي.

// Top-level declarations are global-scope
const dozen = 12;

{
  // Braces create a new block-scope
  // Referencing the outer variable is a closure.
  const twoDozen = dozen * 2;
}

// Because of the block-scope declaration, twoDozen is not available here.
twoDozen;
// => Uncaught ReferenceError: twoDozen is not defined

إلى جانب الأقواس المعقوفة {}، تُنشئ الدوال (والأصناف) أيضًا نطاقات جديدة، ويمكنها أن تضمّن القيم:

const dozen = 12;

// Functions create a new function-scope and block-scope.
// Referencing the outer variable here is a closure.
function nDozen(n) {
  // This is declared inside the function scope, and uses the top-level scope.
  // This works, and encloses the value 12.
  const twoDozen = dozen * 2;

  // This only uses the locally declared variable and the passed argument to the parameter `n`
  return (twoDozen / 2) * n;
}

// Because of the function-scope declaration, twoDozen is not available here.
twoDozen;
// => Uncaught ReferenceError: twoDozen is not defined

وكما توضح أمثلة twoDozen، يمكن تضمين القيم في نطاق متداخل (دالة، كتلة، وما إلى ذلك)، لكن لا يمكن انتزاعها من ذلك السياق. وفي معظم الحالات، تقتضي JavaScript الحديثة ألا تتسرب قيمة إلى نطاق خارجي.

الإغلاقات لحفظ الحالة وتمرير القيم

يتيح استخدام إعلان متغير قابل للتغيير (مثل let أو var) حفظ بعض الحالة:

let counter = 0;

// This function closure increments the counter's state in the outer lexical context.
// This way the counter can be shared between many calling contexts.
export function increment() {
  counter += 1;
  return counter;
}

increment();
// => 1

counter;
// => 1

تضمين القيم دون تسريب الحالة

بجمع الفكرتين معًا، تضمين قيمة للحفاظ على الحالة، وعدم تسرّب القيم المضمَّنة إلى الخارج، يمكن إنشاء قيم خاصة.

الأسلوب الأكثر شيوعًا هو إنشاء دالة تُرجع دالة تضمّن بعض الحالة.

export function makeCounter() {
  let counter = 0;

  // This returns a new function that encloses the local variable counter
  return function increment() {
    counter += 1;
    return counter;
  };
}

// Counter did not leak
counter;
// => Uncaught ReferenceError: counter is not defined

// This creates a new counter.
// This assigns the increment function to the variable myFirstCounter.
const myFirstCounter = makeCounter();

typeof myFirstCounter;
// => function

myFirstCounter.name;
// => increment

myFirstCounter();
// => 1
myFirstCounter();
// => 2

// This creates new counter (with new, separate local state / enclosed counter variable)
const mySecondCounter = makeCounter();

mySecondCounter();
// => 1

// It does not affect the first counter.

myFirstCounter();
// => 3
Note

يجد كثير من المبرمجين أن الإغلاقات مفهوم صعب، كما أن إرجاع دالة من دالة ليس شائعًا، بل قد لا يكون ممكنًا أصلًا في بعض لغات البرمجة. وإذا أردت المزيد من مواد القراءة، فإن دليل MDN عن الإغلاقات شامل جدًا.

التعليمات

كانت شركة التصميم التي تعمل بها تعتمد في الأساس على تحويلات CSS لبناء صفحات الويب. وبعد بعض النقاش، اتُّخذ قرار بالبدء في استخدام JavaScript لإجراء بعض الحسابات بشكل ديناميكي. وبما أن بعض زملائك في الفريق أقل خبرة بـ JavaScript، فقد قررت استخدام إغلاق دالة لإنشاء تحويل قابل لإعادة الاستخدام لأزواج إحداثيات {x, y}.

1. انسحاب الإحداثيات

نفّذ الدالة translate2d التي تُرجع دالة تستخدم إغلاقًا لإجراء انسحاب ثنائي الأبعاد قابل للتكرار لزوج إحداثيات.

في الهندسة، يشير الانسحاب إلى تحريك النقاط أو المتجهات أو الأشكال المسافة نفسها في اتجاه واحد. ويمكن تفسيره على أنه إضافة ثابت إلى كل نقطة.

const moveCoordinatesRight2Px = translate2d(2, 0);
const result = moveCoordinatesRight2Px(4, 8);
// result => [6, 8]

2. تغيير مقياس الإحداثيات

نفّذ الدالة scale2d التي تُرجع دالة تستخدم إغلاقًا لإجراء تغيير مقياس ثنائي الأبعاد قابل للتكرار لزوج إحداثيات.

في الهندسة، يشير تغيير المقياس المنتظم إلى تكبير المتجهات أو الأشكال أو تصغيرها في الاتجاه نفسه. ويمكن تفسيره على أنه ضرب كل نقطة في ثابت (عامل القياس).

في هذا التمرين، افترض أن قيم القياس موجبة فقط.

const doubleScale = scale2d(2, 2);
const result = doubleScale(6, -3);
// result => [12, -6]

3. تركيب دوال التحويل

اجمع بين دالتي تحويل لإجراء تحويل قابل للتكرار. ويُسمى هذا غالبًا تركيب الدوال، حيث تُستخدم نتيجة الدالة الأولى 'f(x)' كمدخل للدالة الثانية 'g(x)'.

const moveCoordinatesRight2Px = translate2d(2, 0);
const doubleCoordinates = scale2d(2, 2);
const composedTransformations = composeTransform(
  moveCoordinatesRight2Px,
  doubleCoordinates,
);
const result = composedTransformations(0, 1);
// result => [4, 2]

4. حفظ نتائج الدوال

نفّذ الدالة memoizeTransform. إنها تأخذ دالة لحفظ نتائجها، ثم تُرجع دالة جديدة تتذكر مدخلات الدالة المُعطاة، بحيث يمكن "تذكُّر" آخر قيمة مُرجعة وحسابها مرة واحدة فقط إذا استُدعيت مجددًا بالوسائط نفسها.

يُستخدم حفظ النتائج أحيانًا في البرمجة الديناميكية. فهو يسمح بتنفيذ العمليات المُكلفة مرة واحدة فقط لأن نتائجها محفوظة. لاحظ أنه في هذا التمرين تُحفظ آخر نتيجة فقط، خلافًا لبعض الحلول في البرمجة الديناميكية التي تحفظ كل النتائج.

const tripleScale = scale2d(3, 3);
const memoizedScale = memoizeTransform(tripleScale);

memoizedScale(4, 3); // => [12, 9], this is computed since it hasn't been computed before for the arguments
memoizedScale(4, 3); // => [12, 9], this is remembered, since it was computed already
تعديل عبر GitHub يفتح الرابط في نافذة أو علامة تبويب جديدة
JavaScript Exercism

مستعد لبدء تحويل الإحداثيات؟

سجّل في Exercism لتتعلّم وتتقن JavaScript عبر 37 مفهومًا159 تمرينًا، وإرشاد بشري حقيقي، وكل ذلك مجانًا.