Як працює array.prototype.map ()

JavaScript зараз є повсюдною мовою. Колись обмежене використанням на стороні клієнта, тепер ви можете знайти його на серверах у багатьох варіантах. По мірі зростання JavaScript зростав і його арсенал функцій, якими користувачі можуть користуватися. Здебільшого ви задовольняєтесь цими методами, і лише вкрай рідко ви захочете зробити цей зайвий крок, щоб зрозуміти, що насправді відбувається під капотом.

На цій ноті, давайте вважати , що додатковий крок сьогодні і досліджувати дуже популярну функцію: Array.prototype.map().

Застереження : Я не буду пояснювати, як користуватися map()- наведений нижче приклад це ілюструє, або ви можете знайти численні приклади, коли шукаєте в Google. Натомість, давайте зупинимося на тому, як карта насправді реалізується за лаштунками.

map()Метод створює новий масив з результатом виклику при умови функції по кожному елементу в масиві викликає.

Приклад:

var array1 = [1, 4, 9, 16]; // pass a function to map const map1 = array1.map(x => x * 2); console.log(map1); // expected output: Array [2, 8, 18, 32]

Впровадження

Давайте візьмемо реалізацію прямо з пащі коня і спробуємо розібрати її. Нижче наведено поліфіль MDN. Витратьте трохи часу на розуміння коду, скопіюйте його та запустіть на своєму комп'ютері. Якщо ви початківець / проміжний розробник JavaScript, ви напевно зіткнетеся принаймні з кількома запитаннями.

/*Array.prototype.map implementation*/ Array.prototype.map = function (callback/*, thisArg*/) { var T, A, k; if (this == null) { throw new TypeError('this is null or not defined'); } var O = Object(this); var len = O.length >>> 0; if (typeof callback !== 'function') { throw new TypeError(callback + ' is not a function'); } if (arguments.length > 1) { T = arguments[1]; } A = new Array(len); k = 0; while (k < len) { var kValue, mappedValue; if (k in O) { kValue = O[k]; mappedValue = callback.call(T, kValue, k, O); A[k] = mappedValue; } k++; } return A; };

Я виділив декілька поширених питань, які можуть виникнути в коментарях до коду нижче.

/*Array.prototype.map implementation*/ Array.prototype.map = function (callback/*, thisArg*/) { var T, A, k; if (this == null) { throw new TypeError('this is null or not defined'); } var O = Object(this); var len = O.length >>> 0;// QUESTION 1 : What is the need for this line of code? if (typeof callback !== 'function') { throw new TypeError(callback + ' is not a function'); } if (arguments.length > 1) { T = arguments[1]; } // QUESTION 2 :What is the need for the if condition and why are we assiging T=arguments[1]? A = new Array(len); k = 0; while (k < len) { var kValue, mappedValue; if (k in O) { kValue = O[k]; mappedValue = callback.call(T, kValue, k, O); // QUESTION 3: why do we pass T,k and O when all you need is kvalue? A[k] = mappedValue; } k++; } return A; };

Звернемося до кожного з них, починаючи знизу

ЗАПИТАННЯ 3: Чому ми передаємо T, k та O, коли все, що вам потрібно, це kValue?

mappedValue = callback.call(T, kValue, k, O);

Це найпростіший із трьох питань, тому я вибрав це для початку. У більшості випадків передачі kValue до зворотного виклику буде достатньо, але:

  • Що робити, якщо у вас є варіант використання, коли вам потрібно виконати операцію лише над кожним іншим елементом? Ну, вам потрібен індекс (k) .
  • Подібним чином можуть бути інші випадки використання, коли вам потрібно, щоб сам масив (O) був доступний у зворотному виклику.
  • Чому Т ? Наразі просто знайте, що T передається для підтримки контексту. Ви це зрозумієте краще, коли закінчите з питанням 2.

ЗАПИТАННЯ 2: Для чого потрібна умова if і чому ми призначаємо T = аргументи [1]?

if (arguments.length > 1) { T = arguments[1]; }

Функція map у наведеній вище реалізації має два аргументи: зворотний виклик та необов’язковий thisArg . Зворотний виклик є обов’язковим аргументом, тоді як thisArg необов’язковий.

Можна передати те, що повинно бути значенням "це", всередині зворотного виклику , надавши другий додатковий аргумент. Ось чому код перевіряє, чи існує більше одного аргументу, і призначає другий необов’язковий аргумент змінній, яка може бути передана зворотному виклику.

Для наочності, скажімо, у вас є фіктивна вимога, коли вам потрібно повернути число / 2, якщо воно ділиться на 2, а якщо воно не ділиться на 2, вам потрібно повернути ім’я користувача, що телефонує. Наведений нижче код ілюструє, як це можна зробити:

const myObj = { user: "John Smith" } var x = [10, 7]; let output = x.map(function (n) { if (n % 2 == 0) { return n / 2; } else { return this.user } }, myObj) // myObj is the second optional argument arguments[1] console.log(output); // [5,'John Smith'] //if you run the program without supplying myObj it would be //undefined as it cannot access myObj values console.log(output); // [ 5, undefined ]

ЗАПИТАННЯ 1: Для чого потрібен цей рядок коду?

var len = O.length >>> 0

Це зайняло деякий час, щоб я це зрозумів. У цьому рядку коду багато що відбувається. У JavaScript ви можете перевизначити “це” у функції, викликаючи метод за допомогою виклику . Ви можете зробити це за допомогою bind або apply , а також, але для цього обговорення давайте залишитись на дзвінку.

const anotherObject={length:{}} const myObj = { user: "John Smith" } var x = [10, 7]; let output = x.map.call(anotherObject,function (n) { if (n % 2 == 0) {return n / 2;} else {return this.user} }, myObj)

Коли ви викликаєте за допомогою виклику,першим параметром буде контекст, в якому виконується функція map. Надсилаючи параметр, ви переписуєте “це” всередині карти “це” іншогоObject.

Якщо ви помітили, то довжина властивість anotherObject є порожнім об'єктом , а не ціле число. Якщо ви просто використовуєте O.length замість O.length> >> 0, це призведе до невизначеного значення. Змінюючи нуль, ви фактично перетворюєте будь-які дроби та нецілі числа у цілі. У цьому випадку результат буде примушений до 0.

Для більшості випадків використання ця перевірка не потрібна, але можливо, існує такий крайній випадок, коли подібний сценарій потребує обробки. Хороші програмісти, які розробляли специфікацію, насправді це продумали! Говорячи про специфікацію, ви можете насправді знайти специфікацію того, як кожна функція повинна бути реалізована в Ecmascript тут:

Специфікація мови ECMAScript - Видання ECMA-262 5.1

Цей документ та можливі його переклади можуть бути скопійовані та надані іншим, а також похідні роботи, що коментують

www.ecma-international.org

Специфікація ( крок 3 ) чітко говорить, що довжина повинна бути 32-бітовим цілим без знака. Ось чому ми переносимо нульову заливку, щоб переконатися, що довжина є цілим числом, оскільки сама карта не вимагає, щоб це значення було об’єктом Array.

Це воно!

Я хотів би подякувати кільком людям, я ніколи їх не зустрічав, але вони були люб'язними, щоб витратити час (на інтернет-форумах) і допомогти мені зрозуміти кілька нюансів.

Салатіель Дженезе, Джордан Гарбанд - дякую!

Примітка. Якщо ви застрягли на іншому рядку коду, сміливо вказуйте це в коментарях, і я зроблю все можливе, щоб пояснити.

Дякуємо за ваш час та щасливого кодування!