ملاحظة حول العناصر التكرارية





يوم جيد ، أيها الأصدقاء!



هذه المذكرة ليس لها قيمة عملية معينة. من ناحية أخرى ، فإنه يستكشف بعض ميزات "الحدود" لجافا سكريبت التي قد تجدها ممتعة. ينصحك



دليل أنماط JavaScript من Goggle بإعطاء الأولوية لـ-of كلما أمكن ذلك. لا يشجع



Airbnb JavaScript Style Guide على استخدام التكرارات. بدلاً من الحلقات for-in و for-of ، يجب عليك استخدام وظائف ذات ترتيب أعلى مثل الخريطة () ، كل () ، عامل التصفية () ، البحث () ، findIndex () ، تقليل () ، بعض () للتكرار عبر المصفوفات والكائن .keys () ، Object.values ​​() ، Object.entries () للتكرار عبر مصفوفات الكائنات. المزيد عن ذلك لاحقًا.



دعنا نعود إلى جوجل. ماذا تعني عبارة "حيثما أمكن"؟



لنلق نظرة على بعض الأمثلة.



لنفترض أن لدينا مصفوفة مثل هذا:



const users = ["John", "Jane", "Bob", "Alice"];


ونريد إخراج قيم عناصرها إلى وحدة التحكم. كيف نفعل ذلك؟



//  
log = (value) => console.log(value);

// for
for (let i = 0; i < users.length; i++) {
  log(users[i]); // John Jane Bob Alice
}

// for-in
for (const item in users) {
  log(users[item]);
}

// for-of
for (const item of users) {
  log(item);
}

// forEach()
users.forEach((item) => log(item));

// map()
//   -   
//       forEach()
users.map((item) => log(item));


كل شيء يعمل بشكل رائع دون أي جهد إضافي من جانبنا.



الآن ، افترض أن لدينا كائنًا مثل هذا:



const person = {
  name: "John",
  age: 30,
  job: "developer",
};


ونريد أن نفعل الشيء نفسه.



// for
for (let i = 0; i < Object.keys(person).length; i++) {
  log(Object.values(person)[i]); // John 30 developer
}

// for-in
for (const i in person) {
  log(person[i]);
}

// for-of & Object.values()
for (const i of Object.values(person)) {
  log(i);
}

// Object.keys() & forEach()
Object.keys(person).forEach((i) => log(person[i]));

// Object.values() & forEach()
Object.values(person).forEach((i) => log(i));

// Object.entries() & forEach()
Object.entries(person).forEach((i) => log(i[1]));


انظر الفرق؟ علينا اللجوء إلى حيل إضافية ، والتي تتمثل في تحويل كائن إلى مصفوفة بطريقة أو بأخرى ، للأسباب التالية:



  for (const value of person) {
    log(value); // TypeError: person is not iterable
  }


ماذا يخبرنا هذا الاستثناء؟ تقول أن الكائن "الشخص" ، مثله مثل أي كائن آخر ، ليس كيانًا متكررًا أو ، كما يقولون ، كيانًا متكررًا (متكررًا).



تمت كتابة العناصر التكرارية والمكررات جيدًا في هذا القسم من دروس JavaScript التعليمية الحديثة. بعد إذنك ، لن أقوم بنسخ ولصق. ومع ذلك ، أوصي بشدة بقضاء 20 دقيقة في قراءته. خلاف ذلك ، فإن العرض التقديمي الإضافي لن يكون له معنى كبير بالنسبة لك.



لنفترض أننا لا نحب أن الأشياء غير قابلة للتكرار ، ونريد تغيير ذلك. كيف نفعل ذلك؟



هذا مثال قدمه إيليا كانتور:



//   
const range = {
  from: 1,
  to: 5,
};

//    Symbol.iterator
range[Symbol.iterator] = function () {
  return {
    //  
    current: this.from,
    //  
    last: this.to,

    //    
    next() {
      //     
      if (this.current <= this.last) {
        //   ,    
        return { done: false, value: this.current++ };
      } else {
        //    ,      
        return { done: true };
      }
    },
  };
};

for (const num of range) log(num); // 1 2 3 4 5
// !


بشكل أساسي ، المثال المقدم هو مولد تم إنشاؤه باستخدام مكرر. لكن العودة إلى موضوعنا. قد تبدو وظيفة تحويل كائن عادي إلى متكرر كما يلي:



const makeIterator = (obj) => {
  //    "size",   "length" 
  Object.defineProperty(obj, "size", {
    value: Object.keys(obj).length,
  });

  obj[Symbol.iterator] = (
    i = 0,
    values = Object.values(obj)
  ) => ({
    next: () => (
      i < obj.size
        ? { done: false, value: values[i++] }
        : { done: true }
    ),
  });
};


نحن نفحص:



makeIterator(person);

for (const value of person) {
  log(value); // John 30 developer
}


حدث! الآن يمكننا بسهولة تحويل مثل هذا الكائن إلى مصفوفة ، وكذلك الحصول على عدد عناصره من خلال خاصية "الحجم":



const arr = Array.from(person);

log(arr); // ["John", 30, "developer"]

log(arr.size); // 3


يمكننا تبسيط كود وظيفتنا باستخدام مُنشئ بدلاً من مكرر:



const makeGenerator = (obj) => {
  //   
  //   
  Object.defineProperty(obj, "isAdult", {
    value: obj["age"] > 18,
  });

  obj[Symbol.iterator] = function* () {
    for (const i in this) {
      yield this[i];
    }
  };
};

makeGenerator(person);

for (const value of person) {
  log(value); // John 30 developer
}

const arr = [...person];

log(arr); // ["John", 30, "developer"]

log(person.isAdult); // true


هل يمكننا استخدام طريقة "التالي" مباشرة بعد إنشاء القابل للتكرار؟



log(person.next().value); // TypeError: person.next is not a function


من أجل الحصول على هذه الفرصة ، يجب علينا أولاً استدعاء Symbol.iterator للكائن:



const iterablePerson = person[Symbol.iterator]();

log(iterablePerson.next()); // { value: "John", done: false }
log(iterablePerson.next().value); // 30
log(iterablePerson.next().value); // developer
log(iterablePerson.next().done); // true


تجدر الإشارة إلى أنه إذا كنت بحاجة إلى إنشاء كائن قابل للتكرار ، فمن الأفضل تحديد Symbol.iterator فيه على الفور. باستخدام الكائن لدينا كمثال:



const person = {
  name: "John",
  age: 30,
  job: "developer",

  [Symbol.iterator]: function* () {
    for (const i in this) {
      yield this[i];
    }
  },
};


المضي قدما. الى اين اذهب؟ في Metaprogramming. ماذا لو أردنا الحصول على قيم خصائص الكائن حسب الفهرس ، كما هو الحال في المصفوفات؟ وماذا لو أردنا أن تكون خصائص معينة لكائن ما غير قابلة للتغيير. دعنا ننفذ هذا السلوك باستخدام وكيل . لماذا استخدام الوكيل؟ حسنًا ، فقط لأننا نستطيع:



const makeProxy = (obj, values = Object.values(obj)) =>
  new Proxy(obj, {
    get(target, key) {
      //     
      key = parseInt(key, 10);
      //    ,      0    
      if (key !== NaN && key >= 0 && key < target.size) {
        //   
        return values[key];
      } else {
        //  ,    
        throw new Error("no such property");
      }
    },
    set(target, prop, value) {
      //     "name"   "age"
      if (prop === "name" || prop === "age") {
        //  
        throw new Error(`this property can't be changed`);
      } else {
        //     
        target[prop] = value;
        return true;
      }
    },
  });

const proxyPerson = makeProxy(person);
//  
log(proxyPerson[0]); // John
//    
log(proxyPerson[2]); // Error: no such property
//   
log((proxyPerson[2] = "coding")); // true
//    
log((proxyPerson.name = "Bob")); // Error: this property can't be changed


ما هي الاستنتاجات التي يمكن أن نستخلصها من كل هذا؟ يمكنك بالطبع إنشاء كائن قابل للتكرار بنفسك (إنه JavaScript ، يا حبيبي) ، ولكن السؤال هو لماذا. نتفق مع دليل Airbnb على أن هناك أكثر من طرق أصلية كافية لحل مجموعة كاملة من المهام المتعلقة بالتكرار عبر المفاتيح وقيم الكائنات ، وليست هناك حاجة إلى "إعادة اختراع العجلة". يمكن توضيح الدليل من Google من خلال حقيقة أن حلقة for-of يجب أن تكون مفضلة لمصفوفات ومصفوفات الكائنات ، وبالنسبة للكائنات على هذا النحو ، يمكنك استخدام حلقة for-in ، ولكن أفضل - وظائف مضمنة.



أتمنى أن تجد شيئًا ممتعًا لنفسك. شكرآ لك على أهتمامك.



All Articles