map()
在 JavaScript 1.6 裏,javascript 數組增加了幾個非常有用的方法:indexOf、lastIndexOf、every、 filter、 forEach、 map、 some,其中前兩個可以歸爲元素定位方法,而後面的幾個則可以歸爲迭代(iterative)方法。
遺憾的是:這些新方法並非所有瀏覽器都支持,在這種情況下,我們就需要自己動手了,在這些介紹的文章中,我們同時提供了在不支持這些新特性的瀏覽器中的實現方法。
原生方法如下:
var mappedArray = array.map(callback[, thisObject]);
- callback: 要對每個數組元素執行的回調函數。
- thisObject : 在執行回調函數時定義的this對象。
對數組中的每個元素都執行一次指定的函數(callback),並且以每次返回的結果爲元素創建一個新數組。它只對數組中的非空元素執行指定的函數,沒有賦值或者已經刪除的元素將被忽略。
回調函數可以有三個參數:當前元素,當前元素的索引和當前的數組對象。如參數 thisObject 被傳遞進來,它將被當做回調函數(callback)內部的 this 對象,如果沒有傳遞或者爲null,那麼將會使用全局對象。
map 不會改變原有數組,記住:只有在回調函數執行前傳入的數組元素纔有效,在回調函數開始執行後才添加的元素將被忽略,而在回調函數開始執行到最後一個元素這一期間,數組元素被刪除或者被更改的,將以回調函數訪問到該元素的時間爲準,被刪除的元素將被忽略。
如果瀏覽器不支持map方法,也可以按照下面的方式用prototype去擴展:
<script type="text/javascript">
//擴展原型對象
Array.prototype.map = function(fn){
var a = [];
for(var i = 0; i < this.length; i++){
var value = fn(this[i], i);
if(value == null){
continue; //如果函數fn返回null,則從數組中刪除該項
}
a.push(value);
}
return a;
};
//例子,arr爲原始數組
var arr = [
{name: 'gonn', age: 20, sex: '1', No: '274200'},
{name: 'nowamagic', age: 30, sex: '0', No: '274011'},
{name: 'frie', age: 40, sex: '1', No: '274212'}
];
//使用map更改數組每項的值,可對數組每一項元素內部進行增刪改,也可以通過return null來刪除數組的某項
var arr2 = arr.map(function(item, i){
item.sex = item.sex == '0' ? '女' : '男';
if(item.name == 'tom'){
return null; //刪除name爲tom的項
}
return {
index: i,
name: item.name,
age: item.age + 30 + i,
sex: item.sex
};
});
console.log(arr2);
</script>
在Firefox firebug控制檯輸出:
[
Object { index=0, name="gonn", age=50, 更多...},
Object { index=1, name="nowamagic", age=61, 更多...},
Object { index=2, name="frie", age=72, 更多...}
]
或者以下方式擴展也可以:
if (!Array.prototype.map)
{
Array.prototype.map = function(fun /*, thisp*/)
{
var len = this.length;
if (typeof fun != "function")
throw new TypeError();
var res = new Array(len);
var thisp = arguments[1];
for (var i = 0; i < len; i++)
{
if (i in this)
res[i] = fun.call(thisp, this[i], i, this);
}
return res;
};
}
map()[].map();
基本用法跟forEach
方法類似:
array.map(callback,[ thisObject]);
callback
的參數也類似:
[].map(function(value, index, array) {
// ...
});
map
方法的作用不難理解,“映射”嘛,也就是原數組被“映射”成對應新數組。下面這個例子是數值項求平方:
var data = [1, 2, 3, 4];
var arrayOfSquares = data.map(function (item) {
return item * item;
});
alert(arrayOfSquares); // 1, 4, 9, 16
callback
需要有return
值,如果沒有,就像下面這樣:
var data = [1, 2, 3, 4];
var arrayOfSquares = data.map(function() {});
arrayOfSquares.forEach(console.log);
結果如下圖,可以看到,數組所有項都被映射成了undefined
:
在實際使用的時候,我們可以利用map
方法方便獲得對象數組中的特定屬性值們。例如下面這個例子(之後的兼容demo也是該例子):
var users = [
{name: "張含韻", "email": "[email protected]"},
{name: "江一燕", "email": "[email protected]"},
{name: "李小璐", "email": "[email protected]"}
];
var emails = users.map(function (user) { return user.email; });
console.log(emails.join(", ")); // [email protected], [email protected], [email protected]
Array.prototype
擴展可以讓IE6-IE8瀏覽器也支持map
方法:
if (typeof Array.prototype.map != "function") {
Array.prototype.map = function (fn, context) {
var arr = [];
if (typeof fn === "function") {
for (var k = 0, length = this.length; k < length; k++) {
arr.push(fn.call(context, this[k], k, this));
}
}
return arr;
};
}
解惑 ["1", "2", "3"].map(parseInt) 爲何返回[1,NaN,NaN]
javascript中的parseInt與map函數都是常用的函數,可是 ["1", "2", "3"].map(parseInt) 爲何返回不是[1,2,3]卻是[1,NaN,NaN]?
這涉及到是否深入理解兩個函數的格式與參數含義。
首先根據我對兩個函數用法的瞭解,猜測是由於parseInt(string, radix) 的參數radix必須介於2~36之間,而且字符串string中的數字不能大於radix才能正確返回數字結果值。
我們通過以下javascript代碼測試一下:
- var a=["1", "2", "3", "4","5",6,7,8,9,10,11,12,13,14,15];
- a.map(parseInt);
返回結果爲:[1,NaN,NaN,NaN,NaN,NaN,NaN,NaN,NaN,9,11,13,15,17,19]
正好印證了以上的猜測是正確的,因爲:
parseInt('1',0) = 1,
parseInt('2',1) = NaN,
parseInt('3',2) = NaN,
……
正是由於map的回調函數的參數index索引值作了parseInt的基數radix,導致出現超範圍的radix賦值和不合法的進制解析,纔會返回NaN。
也許你還會懷疑索引值和基數radix對應關係的準確性,這時你可以重新定義parseInt函數,再來測試一下:
- function parseInt(str, radix) {
- return str+'-'+radix;
- };
- var a=["1", "2", "3", "4","5",6,7,8,9,10,11,12,13,14,15];
- a.map(parseInt);
輸出結果爲:["1-0","2-1","3-2","4-3","5-4","6-5","7-6","8-7","9-8","10-9","11-10","12-11","13-12","14-13","15-14"]。
通過此例,再次證明,索引index的起始值從0開始,與radix的對應如前陳述一致,所以纔會出現返回NaN的類型值。
這個實例提醒我們在使用兩個函數parseInt和map時候要格外小心。同時對於IE6-7不支持map函數的情況也要謹慎或者通過prototype擴展處理。
最後再仔細回顧溫習一下:
parseInt() 函數
定義和用法
parseInt() 函數可解析一個字符串,並返回一個整數。
語法
parseInt(string, radix)
參數 | 描述 |
---|---|
string | 必需。要被解析的字符串。 |
radix |
可選。表示要解析的數字的基數。該值介於 2 ~ 36 之間。 如果省略該參數或其值爲 0,則數字將以 10 爲基礎來解析。如果它以 “0x” 或 “0X” 開頭,將以 16 爲基數。 如果該參數小於 2 或者大於 36,則 parseInt() 將返回 NaN。 |
返回值
返回解析後的數字。
說明
當參數 radix 的值爲 0,或沒有設置該參數時,parseInt() 會根據 string 來判斷數字的基數。
舉例,如果 string 以 "0x" 開頭,parseInt() 會把 string 的其餘部分解析爲十六進制的整數。如果string 以 0 開頭,那麼 ECMAScript v3 允許 parseInt() 的一個實現把其後的字符解析爲八進制或十六進制的數字。如果string 以 1 ~ 9 的數字開頭,parseInt() 將把它解析爲十進制的整數。
提示和註釋
註釋:只有字符串中的第一個數字會被返回。
註釋:開頭和結尾的空格是允許的。
提示:如果字符串的第一個字符不能被轉換爲數字,那麼 parseFloat() 會返回 NaN。
實例
在本例中,我們將使用 parseInt() 來解析不同的字符串:
parseInt("10"); //返回 10
parseInt("19",10); //返回 19 (10+9)
parseInt("11",2); //返回 3 (2+1)
parseInt("17",8); //返回 15 (8+7)
parseInt("1f",16); //返回 31 (16+15)
parseInt("010"); //未定:返回 10 或 8
map 方法 (Array) (JavaScript)
對數組的每個元素調用定義的回調函數並返回包含結果的數組。
array1.map(callbackfn[, thisArg])
參數 |
定義 |
---|---|
array1 |
必需。 一個數組對象。 |
callbackfn |
必需。 一個接受最多三個參數的函數。 對於數組中的每個元素,map 方法都會調用 callbackfn函數一次。 |
thisArg |
可選。 可在 callbackfn 函數中爲其引用this 關鍵字的對象。 如果省略thisArg,則 undefined 將用作this 值。 |
其中的每個元素均爲關聯的原始數組元素的回調函數返回值的新數組。
如果 callbackfn 參數不是函數對象,則將引發 TypeError 異常。
對於數組中的每個元素,map 方法都會調用 callbackfn 函數一次(採用升序索引順序)。 不爲數組中缺少的元素調用該回調函數。
除了數組對象之外,map 方法可由具有 length 屬性且具有已按數字編制索引的屬性名的任何對象使用。
回調函數語法
回調函數的語法如下所示:
function callbackfn(value, index, array1)
可使用最多三個參數來聲明回調函數。
下表列出了回調函數參數。
回調參數 |
定義 |
---|---|
value |
數組元素的值。 |
index |
數組元素的數字索引。 |
array1 |
包含該元素的數組對象。 |
修改數組對象
數組對象可由回調函數修改。
下表描述了在 map 方法啓動後修改數組對象所獲得的結果。
map 方法啓動後的條件 |
元素是否傳遞給回調函數 |
---|---|
在數組的原始長度之外添加元素。 |
否。 |
添加元素以填充數組中缺少的元素。 |
是,如果該索引尚未傳遞給回調函數。 |
元素被更改。 |
是,如果該元素尚未傳遞給回調函數。 |
從數組中刪除元素。 |
否,除非該元素已傳遞給回調函數。 |
下面的示例闡釋了 map 方法的用法。
// Define the callback function.
function AreaOfCircle(radius) {
var area = Math.PI * (radius * radius);
return area.toFixed(0);
}
// Create an array.
var radii = [10, 20, 30];
// Get the areas from the radii.
var areas = radii.map(AreaOfCircle);
document.write(areas);
// Output:
// 314,1257,2827
下面的示例闡釋 thisArg 參數的用法,該參數指定對其引用this 關鍵字的對象。
// Define an object that contains a divisor property and
// a remainder function.
var obj = {
divisor: 10,
remainder: function (value) {
return value % this.divisor;
}
}
// Create an array.
var numbers = [6, 12, 25, 30];
// Get the remainders.
// The obj argument specifies the this value in the callback function.
var result = numbers.map(obj.remainder, obj);
document.write(result);
// Output:
// 6,2,5,0
在下面的示例中,內置 JavaScript 方法用作回調函數。
// Apply Math.sqrt(value) to each element in an array.
var numbers = [9, 16];
var result = numbers.map(Math.sqrt);
document.write(result);
// Output: 3,4
map 方法可應用於字符串。 下面的示例闡釋了這一點。
// Define the callback function.
function threeChars(value, index, str) {
// Create a string that contains the previous, current,
// and next character.
return str.substring(index - 1, index + 2);
}
// Create a string.
var word = "Thursday";
// Apply the map method to the string.
// Each array element in the result contains a string that
// has the previous, current, and next character.
// The commented out statement shows an alternative syntax.
var result = [].map.call(word, threeChars);
// var result = Array.prototype.map.call(word, threeChars);
document.write(result);
// Output:
// Th,Thu,hur,urs,rsd,sda,day,ay
在以下文檔模式中受支持:Internet Explorer 9 標準模式、Internet Explorer 10 標準模式和 Internet Explorer 11 標準模式。Windows 應用商店 應用程序中也支持此項。請參閱版本信息。
在以下文檔模式中不受支持:Quirks、Internet Explorer 6 標準模式、Internet Explorer 7 標準模式、Internet Explorer 8 標準模式。
在 JavaScript 1.6 裏,javascript 數組增加了幾個非常有用的方法:indexOf、lastIndexOf、every、 filter、 forEach、 map、 some,其中前兩個可以歸爲元素定位方法,而後面的幾個則可以歸爲迭代(iterative)方法。
遺憾的是:這些新方法並非所有瀏覽器都支持,在這種情況下,我們就需要自己動手了,在這些介紹的文章中,我們同時提供了在不支持這些新特性的瀏覽器中的實現方法。
原生方法如下:
1 |
var mappedArray = array.map(callback[, thisObject]); |
- callback: 要對每個數組元素執行的回調函數。
- thisObject : 在執行回調函數時定義的this對象。
對數組中的每個元素都執行一次指定的函數(callback),並且以每次返回的結果爲元素創建一個新數組。它只對數組中的非空元素執行指定的函數,沒有賦值或者已經刪除的元素將被忽略。
回調函數可以有三個參數:當前元素,當前元素的索引和當前的數組對象。如參數 thisObject 被傳遞進來,它將被當做回調函數(callback)內部的 this 對象,如果沒有傳遞或者爲null,那麼將會使用全局對象。
map 不會改變原有數組,記住:只有在回調函數執行前傳入的數組元素纔有效,在回調函數開始執行後才添加的元素將被忽略,而在回調函數開始執行到最後一個元素這一期間,數組元素被刪除或者被更改的,將以回調函數訪問到該元素的時間爲準,被刪除的元素將被忽略。
如果瀏覽器不支持map方法,也可以按照下面的方式用prototype去擴展:
01 |
<script type= "text/javascript" > |
02 |
//擴展原型對象 |
03 |
Array.prototype.map = function (fn){ |
04 |
var a = []; |
05 |
for ( var i = 0; i < this .length; i++){ |
06 |
var value = fn( this [i], i); |
07 |
if (value == null ){ |
08 |
continue ; //如果函數fn返回null,則從數組中刪除該項 |
09 |
} |
10 |
a.push(value); |
11 |
} |
12 |
return a; |
13 |
}; |
14 |
15 |
//例子,arr爲原始數組 |
16 |
var arr = [ |
17 |
{name: 'gonn' , age: 20, sex: '1' , No: '274200' }, |
18 |
{name: 'nowamagic' , age: 30, sex: '0' , No: '274011' }, |
19 |
{name: 'frie' , age: 40, sex: '1' , No: '274212' } |
20 |
]; |
21 |
|
22 |
//使用map更改數組每項的值,可對數組每一項元素內部進行增刪改,也可以通過return null來刪除數組的某項 |
23 |
var arr2 = arr.map( function (item, i){ |
24 |
item.sex = item.sex == '0' ? '女' : '男' ; |
25 |
if (item.name == 'tom' ){ |
26 |
return null ; //刪除name爲tom的項 |
27 |
} |
28 |
return { |
29 |
index: i, |
30 |
name: item.name, |
31 |
age: item.age + 30 + i, |
32 |
sex: item.sex |
33 |
}; |
34 |
}); |
35 |
|
36 |
console.log(arr2); |
37 |
</script> |
在Firefox firebug控制檯輸出:
1 |
[ |
2 |
Object { index=0, name= "gonn" , age=50, 更多...}, |
3 |
Object { index=1, name= "nowamagic" , age=61, 更多...}, |
4 |
Object { index=2, name= "frie" , age=72, 更多...} |
5 |
] |
或者以下方式擴展也可以:
01 |
if (!Array.prototype.map) |
02 |
{ |
03 |
Array.prototype.map = function (fun /*, thisp*/ ) |
04 |
{ |
05 |
var len = this .length; |
06 |
if ( typeof fun != "function" ) |
07 |
throw new TypeError(); |
08 |
|
09 |
var res = new Array(len); |
10 |
var thisp = arguments[1]; |
11 |
for ( var i = 0; i < len; i++) |
12 |
{ |
13 |
if (i in this ) |
14 |
res[i] = fun.call(thisp, this [i], i, this ); |
15 |
} |
16 |
|
17 |
return res; |
18 |
}; |
19 |
} |
注:map返回的是新數組,它不修改調用的數組。
爲了兼容不支持map的瀏覽器,developer.mozilla.org上給出了map兼容性解決方法。
[javascript] view plaincopy
- // Production steps of ECMA-262, Edition 5, 15.4.4.19
- // Reference: http://es5.github.com/#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 (thisArg) {
- T = thisArg;
- }
- // 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, : true, Enumerable: true, Configurable: true},
- // and false.
- // In browsers that support Object.defineProperty, use the following:
- // Object.defineProperty(A, Pk, { 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;
- };
- }
使用 Map
對象節
var myMap = new Map();
var keyObj = {},
keyFunc = function () {},
keyString = "a string";
// 添加鍵
myMap.set(keyString, "和鍵'a string'關聯的值");
myMap.set(keyObj, "和鍵keyObj關聯的值");
myMap.set(keyFunc, "和鍵keyFunc關聯的值");
myMap.size; // 3
// 讀取值
myMap.get(keyString); // "和鍵'a string'關聯的值"
myMap.get(keyObj); // "和鍵keyObj關聯的值"
myMap.get(keyFunc); // "和鍵keyFunc關聯的值"
myMap.get("a string"); // "和鍵'a string'關聯的值"
// 因爲keyString === 'a string'
myMap.get({}); // undefined, 因爲keyObj !== {}
myMap.get(function() {}) // undefined, 因爲keyFunc !== function () {}
將 NaN
作爲 Map
的鍵節
NaN
也可以作爲Map
對象的鍵。雖然 NaN
和任何值甚至和自己都不相等(NaN !== NaN
返回true),但下面的例子表明,NaN
作爲Map的鍵來說是沒有區別的:
var myMap = new Map();
myMap.set(NaN, "not a number");
myMap.get(NaN); // "not a number"
var otherNaN = Number("foo");
myMap.get(otherNaN); // "not a number"
使用 for..of
方法迭代 Map
節
Map
可以使用for..of
循環來實現迭代:
var myMap = new Map();
myMap.set(0, "zero");
myMap.set(1, "one");
for (var [key, value] of myMap) {
console.log(key + " = " + value);
}
// 將會顯示兩個log。一個是"0 = zero"另一個是"1 = one"
for (var key of myMap.keys()) {
console.log(key);
}
// 將會顯示兩個log。 一個是 "0" 另一個是 "1"
for (var value of myMap.values()) {
console.log(value);
}
// 將會顯示兩個log。 一個是 "zero" 另一個是 "one"
for (var [key, value] of myMap.entries()) {
console.log(key + " = " + value);
}
// 將會顯示兩個log。 一個是 "0 = zero" 另一個是 "1 = one"
使用 forEach()
方法迭代 Map
節
Map
也可以通過forEach()
方法迭代:
myMap.forEach(function(value, key) {
console.log(key + " = " + value);
}, myMap)
// 將會顯示兩個logs。 一個是 "0 = zero" 另一個是 "1 = one"
Map
與數組的關係節
var kvArray = [["key1", "value1"], ["key2", "value2"]];
// 使用常規的Map構造函數可以將一個二維鍵值對數組轉換成一個Map對象
var myMap = new Map(kvArray);
myMap.get("key1"); // 返回值爲 "value1"
// 使用Array.from函數可以將一個Map對象轉換成一個二維鍵值對數組
console.log(Array.from(myMap)); // 輸出和kvArray相同的數組
// 或者在鍵或者值的迭代器上使用Array.from,進而得到只含有鍵或者值的數組
console.log(Array.from(myMap.keys())); // 輸出 ["key1", "key2"]
複製或合併 Maps
節
Map 能像數組一樣被複制:
var original = new Map([
[1, 'one']
]);
var clone = new Map(original);
console.log(clone.get(1)); // one
console.log(original === clone); // false. Useful for shallow comparison
請記住,數據本身未被克隆。
Map對象間可以進行合併,但是會保持鍵的唯一性。
var first = new Map([
[1, 'one'],
[2, 'two'],
[3, 'three'],
]);
var second = new Map([
[1, 'uno'],
[2, 'dos']
]);
// 合併兩個Map對象時,如果有重複的鍵值,則後面的會覆蓋前面的。
// 展開運算符本質上是將Map對象轉換成數組。
var merged = new Map([...first, ...second]);
console.log(merged.get(1)); // uno
console.log(merged.get(2)); // dos
console.log(merged.get(3)); // three
Map對象也能與數組合並:
var first = new Map([
[1, 'one'],
[2, 'two'],
[3, 'three'],
]);
var second = new Map([
[1, 'uno'],
[2, 'dos']
]);
// Map對象同數組進行合併時,如果有重複的鍵值,則後面的會覆蓋前面的。
var merged = new Map([...first, ...second, [1, 'eins']]);
console.log(merged.get(1)); // eins
console.log(merged.get(2)); // dos
console.log(merged.get(3)); // three