Phương thức map() giúp tạo ra một mảng mới với các phần tử là kết quả từ việc thực thi một hàm lên từng phần tử của mảng được gọi.

Cú pháp

var new_array = arr.map(function callback(currentValue[, index[, array]]) {
    // Trả về element mới cho new_array
}[, thisArg])

Tham số

callback
Hàm để tạo ra phần tử cho mảng mới, nhận vào ba tham số:
 
currentValue
Giá trị của phần tử trong mảng đang được xử lý
indexOptional
Index của phần tử trong mảng đang được xử lý
arrayOptional
Mảng đang được gọi với map
thisArgOptional
Giá trị gán cho từ khóa this bên trong callback.

Giá trị trả về

Một mảng mới với phần tử là kết quả của hàm callback.

Mô tả

map sẽ gọi hàm callback lên từng phần tử của mảng, theo thứ tự trong mảng, và tạo ra một mảng mới chứa các phần tử kết quả. callback chỉ được gọi cho những vị trí index của mảng được gán giá trị, bao gồm cả undefined. Nó không được gọi cho những vị trí index trống (là các index của mảng chưa bao giờ được khởi tạo, bao gồm index chưa được gán giá trị hoặc đã bị xóa bằng delete).

callback được gọi với ba tham số: giá trị của phần tử, index của phần tử, và chính mảng đang được gọi.

Nếu tham số thisArg được truyền cho map, nó sẽ được gán cho từ khóa this trong hàm callback. Nếu không, giá trị undefined sẽ được dùng cho this với strict mode. Tóm lại, giá trị của từ khóa this trong hàm callback được xác định tuân theo các quy tắc thông thường để xác định this trong một hàm.

map không làm thay đổi mảng ban đầu mà nó được gọi (mặc dù mảng ban đầu vẫn có thể bị thay đổi trong hàm callback).

Các phần tử được map() xử lý được xác định từ đầu trước khi callback được gọi lần đầu tiên. Những phần tử mới được thêm vào sau khi map đã bắt đầu chạy sẽ không được callback gọi đến. Trong lúc map đang chạy, nếu những phần tử hiện tại của mảng bị thay đổi, thì giá trị mới của chúng sẽ được truyền cho hàm callback ngay tại thời điểm callback chạy qua. Những phần tử bị xóa sau khi map đã bắt đầu và trước khi đến lượt nó cũng sẽ bị bỏ qua.

Do thuật toán đã quy định trong đặc tả, nếu như mảng ban đầu đã có sẵn những lỗ trống (index rỗng) thì sau khi được gọi với map, mảng đầu ra cũng sẽ có những index rỗng như mảng ban đầu.

Ví dụ

Map (ánh xạ) một mảng các số thành một mảng các giá trị căn bậc hai

Đoạn code sau sẽ map một mảng các số thành một mảng mới chứa giá trị là căn bậc hai của các số trong mảng ban đầu.

var numbers = [1, 4, 9];
var roots = numbers.map(Math.sqrt);
// mảng roots: [1, 2, 3]
// mảng numbers vẫn là: [1, 4, 9]

Dùng map để format lại các object trong mảng

Đoạn code sau sẽ xử lý một mảng các object và trả về một mảng mới chứa các object đã được format lại:

var kvArray = [{key: 1, value: 10}, 
               {key: 2, value: 20}, 
               {key: 3, value: 30}];

var reformattedArray = kvArray.map(obj =>{ 
   var rObj = {};
   rObj[obj.key] = obj.value;
   return rObj;
});
// reformattedArray bây giờ là: [{1: 10}, {2: 20}, {3: 30}], 

// kvArray vẫn là: 
// [{key: 1, value: 10}, 
//  {key: 2, value: 20}, 
//  {key: 3, value: 30}]

Map một mảng các số sử dụng hàm có tham số

Đoạn code sau minh họa cách thức map hoạt động khi callback có sử dụng tham số. Tham số này sẽ có giá trị của từng phần tử của mảng ban đầu khi map duyệt qua mảng này.

var numbers = [1, 4, 9];
var doubles = numbers.map(function(num) {
  return num * 2;
});

// doubles is now [2, 8, 18]
// numbers is still [1, 4, 9]

Sử dụng map một cách độc lập

Ví dụ sau sẽ minh họa cách dùng map lên một String một cách độc lập để trả về một mảng các mã byte trong bảng ASCII đại diện cho từng ký tự của chuỗi.

var map = Array.prototype.map;
var a = map.call('Hello World', function(x) { 
  return x.charCodeAt(0); 
});
// a now equals [72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100]

Sử dụng map với kết quả của querySelectorAll

Ví dụ sau minh họa cách duyệt qua một tập các object (không phải mảng) trả về từ querySelectorAll. Trong trường hợp này, chúng ta sẽ in ra giá trị của tất cả các option được chọn (của các thẻ select) lên console: 

var elems = document.querySelectorAll('select option:checked');
var values = Array.prototype.map.call(elems, function(obj) {
  return obj.value;
});

Cách trên có thể được giải quyết đơn giản hơn bằng cách sử dụng Array.from().

Trường hợp đặc biệt

(ý tưởng từ bài viết này)

Chúng ta hay dùng map với hàm một tham số (tham số đó chính là phần tử được duyệt). Và có một số hàm đặc biệt cũng thường được gọi với một tham số, mặc dù chúng có thêm tham số phụ không bắt buộc. Những thói quen này có thể dẫn đến những kết quả ngoài dự đoán.

// Lấy ví dụ:
['1', '2', '3'].map(parseInt);
// Bạn sẽ nghĩ rằng kết quả là [1, 2, 3]
// Nhưng kết quả lại là [1, NaN, NaN]

// parseInt thường được dùng với 1 tham số, nhưng nó có thể nhận đến 2 tham số.
// Tham số thứ nhất là một biểu thức giá trị và tham số thứ hai là cơ số
// Với hàm callback của Array.prototype.map, nó sẽ nhận vào 3 tham số:
// phần tử, index, mảng ban đầu
// Tham số thứ 3 sẽ được parseInt bỏ qua, nhưng tham số thứ 2 lại có vai trò
// và sẽ dẫn đến kết quả không mong muốn.

// Sau đây là kết quả thực thi tại từng phần tử: 
// parseInt(string, radix) -> map(parseInt(value, index))
// lần chạy 1 (index là 0): parseInt('1', 0) // cho ta parseInt('1', 0) -> 1
// lần chạy 2 (index là 1): parseInt('2', 1) // cho ta parseInt('2', 1) -> NaN
// lần chạy 3 (index là 2): parseInt('3', 2) // cho ta parseInt('3', 2) -> NaN

function returnInt(element) {
  return parseInt(element, 10);
}

['1', '2', '3'].map(returnInt); // [1, 2, 3]
// Thông qua hàm returnInt, kết quả trả về là mảng các số (như mong muốn)

// Tương tự như trên, nhưng sử dụng một hàm arrow
['1', '2', '3'].map( str => parseInt(str) );

// Một cách nữa đơn giản hơn nhưng tránh được vấn đề hàm nhiều tham số:
['1', '2', '3'].map(Number); // [1, 2, 3]
// Tuy nhiên khác với `parseInt`, cách này sẽ giữ lại định dạng 
// số thập phân hoặc ký hiệu mũ từ chuỗi ban đầu
['1.1', '2.2e2', '3e300'].map(Number); // [1.1, 220, 3e+300]

Một ví dụ khác khi gọi map với parseInt là tham số cho callback:

var xs = ['10', '10', '10'];

xs = xs.map(parseInt);

console.log(xs);
// Kết quả trả về là 10,NaN,2 như đã lý giải ở trên.

Polyfill

map() chỉ được thêm vào đặc tả ECMA-262 với phiên bản lần thứ 5; cho nên nó có thể không tồn tại trong một số hiện thực (implementation) của đặc tả. Bạn có thể work around bằng cách thêm vào đoạn code bên dưới vào đầu script của bạn, cho phép sử dụng hàm map tại những nơi mà nó không được hỗ trợ sẵn. Giải thuật trong polyfill này tương đương với hàm map được mô tả trong đặc tả ECMA-262, 5th edition với điều kiện Object, TypeError, và Array không bị thay đổi và callback.call chính là hàm được định nghĩa trong Function.prototype.call.

Ghi chú bản dịch: những đoạn comment trong code bên dưới trích từ đặc tả ECMA nên sẽ giữ nguyên.

// Production steps of ECMA-262, Edition 5, 15.4.4.19
// Reference: http://es5.github.io/#x15.4.4.19
if (!Array.prototype.map) {

  Array.prototype.map = function(callback/*, thisArg*/) {

    var T, A, k;

    if (this == null) {
      throw new TypeError('this is null or not defined');
    }

    // 1. Let O be the result of calling ToObject passing the |this| 
    //    value as the argument.
    var O = Object(this);

    // 2. Let lenValue be the result of calling the Get internal 
    //    method of O with the argument "length".
    // 3. Let len be ToUint32(lenValue).
    var len = O.length >>> 0;

    // 4. If IsCallable(callback) is false, throw a TypeError exception.
    // See: http://es5.github.com/#x9.11
    if (typeof callback !== 'function') {
      throw new TypeError(callback + ' is not a function');
    }

    // 5. If thisArg was supplied, let T be thisArg; else let T be undefined.
    if (arguments.length > 1) {
      T = arguments[1];
    }

    // 6. Let A be a new array created as if by the expression new Array(len) 
    //    where Array is the standard built-in constructor with that name and 
    //    len is the value of len.
    A = new Array(len);

    // 7. Let k be 0
    k = 0;

    // 8. Repeat, while k < len
    while (k < len) {

      var kValue, mappedValue;

      // a. Let Pk be ToString(k).
      //   This is implicit for LHS operands of the in operator
      // b. Let kPresent be the result of calling the HasProperty internal 
      //    method of O with argument Pk.
      //   This step can be combined with c
      // c. If kPresent is true, then
      if (k in O) {

        // i. Let kValue be the result of calling the Get internal 
        //    method of O with argument Pk.
        kValue = O[k];

        // ii. Let mappedValue be the result of calling the Call internal 
        //     method of callback with T as the this value and argument 
        //     list containing kValue, k, and O.
        mappedValue = callback.call(T, kValue, k, O);

        // iii. Call the DefineOwnProperty internal method of A with arguments
        // Pk, Property Descriptor
        // { Value: mappedValue,
        //   Writable: true,
        //   Enumerable: true,
        //   Configurable: true },
        // and false.

        // In browsers that support Object.defineProperty, use the following:
        // Object.defineProperty(A, k, {
        //   value: mappedValue,
        //   writable: true,
        //   enumerable: true,
        //   configurable: true
        // });

        // For best browser support, use the following:
        A[k] = mappedValue;
      }
      // d. Increase k by 1.
      k++;
    }

    // 9. return A
    return A;
  };
}

Đặc Tả

Đặc tả Trạng thái Ghi chú
ECMAScript 5.1 (ECMA-262)
The definition of 'Array.prototype.map' in that specification.
Standard Định nghĩa lần đầu. Hiện thực trong JavaScript 1.6.
ECMAScript 2015 (6th Edition, ECMA-262)
The definition of 'Array.prototype.map' in that specification.
Standard  
ECMAScript Latest Draft (ECMA-262)
The definition of 'Array.prototype.map' in that specification.
Draft  

Tương thích trình duyệt

Update compatibility data on GitHub
DesktopMobileServer
ChromeEdgeFirefoxInternet ExplorerOperaSafariAndroid webviewChrome for AndroidEdge MobileFirefox for AndroidOpera for AndroidiOS SafariSamsung InternetNode.js
Basic supportChrome Full support YesEdge Full support YesFirefox Full support 1.5IE Full support 9Opera Full support YesSafari Full support YesWebView Android Full support YesChrome Android Full support YesEdge Mobile Full support YesFirefox Android Full support 4Opera Android Full support YesSafari iOS Full support YesSamsung Internet Android Full support Yesnodejs Full support Yes

Legend

Full support  
Full support

Xem thêm

Document Tags and Contributors

Những người đóng góp cho trang này: trongthanh, truonghoangngoclam
Cập nhật lần cuối bởi: trongthanh,