天天看點

JavaScript數組所有API全解密

全文共13k+字,系統講解了javascript數組的各種特性和api。

數組是一種非常重要的資料類型,它文法簡單、靈活、高效。 在多數程式設計語言中,數組都充當着至關重要的角色,以至于很難想象沒有數組的程式設計語言會是什麼模樣。特别是javascript,它天生的靈活性,又進一步發揮了數組的特長,豐富了數組的使用場景。可以豪不誇張地說,不深入地了解數組,不足以寫javascript。

截止es7規範,數組共包含33個标準的api方法和一個非标準的api方法,使用場景和使用方案紛繁複雜,其中有不少淺坑、深坑、甚至神坑。下面将從array構造器及es6新特性開始,逐漸幫助你掌握數組。

聲明:以下未特别标明的方法均為es5已實作的方法。

array構造器用于建立一個新的數組。通常,我們推薦使用對象字面量建立數組,這是一個好習慣,但是總有對象字面量乏力的時候,比如說,我想建立一個長度為8的空數組。請比較如下兩種方式:

<code>// 使用array構造器</code>

<code>var</code> <code>a = array(8);</code><code>// [undefined × 8]</code>

<code>// 使用對象字面量</code>

<code>var</code> <code>b = [];</code>

<code>b.length = 8;</code><code>// [undefined × 8]</code>

array構造器明顯要簡潔一些,當然你也許會說,對象字面量也不錯啊,那麼我保持沉默。

如上,我使用了array(8)而不是new array(8),這會有影響嗎?實際上,并沒有影響,這得益于array構造器内部對this指針的判斷,els5_html規範是這麼說的:

when array is called as a function rather than as a constructor, it creates and initialises a new array object. thus the function call array(…) is equivalent to the object creation expression new array(…) with the same arguments.

從規範來看,浏覽器内部大緻做了如下類似的實作:

<code>function</code> <code>array(){</code>

<code>  </code><code>// 如果this不是array的執行個體,那就重新new一個執行個體</code>

<code>  </code><code>if</code><code>(!(</code><code>this</code> <code>instanceof</code> <code>arguments.callee)){</code>

<code>    </code><code>return</code> <code>new</code> <code>arguments.callee();</code>

<code>  </code><code>}</code>

<code>}</code>

上面,我似乎跳過了對array構造器文法的介紹,沒事,接下來我補上。

array構造器根據參數長度的不同,有如下兩種不同的處理:

new array(arg1, arg2,…),參數長度為0或長度大于等于2時,傳入的參數将按照順序依次成為新數組的第0至n項(參數長度為0時,傳回空數組)。

new array(len),當len不是數值時,處理同上,傳回一個隻包含len元素一項的數組;當len為數值時,根據如下規範,len最大不能超過32位無符号整型,即需要小于2的32次方(len最大為math.pow(2,32) -1或-1&gt;&gt;&gt;0),否則将抛出rangeerror。

if the argument len is a number and touint32(len) is equal to len, then the length property of the newly constructed object is set to touint32(len). if the argument len is a number and touint32(len) is not equal to len, a rangeerror exception is thrown.

以上,請注意array構造器對于單個數值參數的特殊處理,如果僅僅需要使用數組包裹?? 若幹參數,不妨使用array.of,具體請移步下一節。

如果你想學習前端可以來這個群,首先是二九一,中間是八五一,最後是一八九,裡面可以學習交流,也有資料可以下載下傳。

鑒于數組的常用性,es6專門擴充了數組構造器array ,新增2個方法:array.of、array.from。下面展開來聊。

array.of

array.of用于将參數依次轉化為數組中的一項,然後傳回這個新數組,而不管這個參數是數字還是其它。它基本上與array構造器功能一緻,唯一的差別就在單個數字參數的處理上。如下:

<code>array.of(8.0);</code><code>// [8]</code>

<code>array(8.0);</code><code>// [undefined × 8]</code>

參數為多個,或單個參數不是數字時,array.of 與 array構造器等同。

<code>array.of(8.0, 5);</code><code>// [8, 5]</code>

<code>array(8.0, 5);</code><code>// [8, 5]</code>

<code>array.of(</code><code>'8'</code><code>);</code><code>// ["8"]</code>

<code>array(</code><code>'8'</code><code>);</code><code>// ["8"]</code>

是以,若是需要使用數組包裹元素,推薦優先使用array.of方法。

目前,以下版本浏覽器提供了對array.of的支援。

chrome

firefox

edge

safari

45+

25+

9.0+

即使其他版本浏覽器不支援也不必擔心,由于array.of與array構造器的這種高度相似性,實作一個polyfill十分簡單。如下:

<code>if</code> <code>(!array.of){</code>

<code>  </code><code>array.of =</code><code>function</code><code>(){</code>

<code>    </code><code>return</code> <code>array.prototype.slice.call(arguments);</code>

<code>  </code><code>};</code>

array.from

文法:array.from(arraylike[, processingfn[, thisarg]])

array.from的設計初衷是快速便捷的基于其他對象建立新數組,準确來說就是從一個類似數組的可疊代對象建立一個新的數組執行個體,說人話就是,隻要一個對象有疊代器,array.from就能把它變成一個數組(當然,是傳回新的數組,不改變原對象)。

從文法上看,array.from擁有3個形參,第一個為類似數組的對象,必選。第二個為加工函數,新生成的數組會經過該函數的加工再傳回。第三個為this作用域,表示加工函數執行時this的值。後兩個參數都是可選的。我們來看看用法。

<code>var</code> <code>obj = {0:</code><code>'a'</code><code>, 1:</code><code>'b'</code><code>, 2:</code><code>'c'</code><code>, length: 3};</code>

<code>array.from(obj,</code><code>function</code><code>(value, index){</code>

<code>  </code><code>console.log(value, index,</code><code>this</code><code>, arguments.length);</code>

<code>  </code><code>return</code> <code>value.repeat(3);</code><code>//必須指定傳回值,否則傳回undefined</code>

<code>}, obj);</code>

執行結果如下:

JavaScript數組所有API全解密

可以看到加工函數的this作用域被obj對象取代,也可以看到加工函數預設擁有兩個形參,分别為疊代器目前元素的值和其索引。

注意,一旦使用加工函數,必須明确指定傳回值,否則将隐式傳回undefined,最終生成的數組也會變成一個隻包含若幹個undefined元素的空數組。

實際上,如果不需要指定this,加工函數完全可以是一個箭頭函數。上述代碼可以簡化如下:

<code>array.from(obj, (value) =&gt; value.repeat(3));</code>

除了上述obj對象以外,擁有疊代器的對象還包括這些:string,set,map,arguments 等,array.from統統可以處理。如下所示:

<code>// string</code>

<code>array.from(</code><code>'abc'</code><code>);</code><code>// ["a", "b", "c"]</code>

<code>// set</code>

<code>array.from(</code><code>new</code> <code>set([</code><code>'abc'</code><code>,</code><code>'def'</code><code>]));</code><code>// ["abc", "def"]</code>

<code>// map</code>

<code>array.from(</code><code>new</code> <code>map([[1,</code><code>'abc'</code><code>], [2,</code><code>'def'</code><code>]]));</code><code>// [[1</code>

<code>,</code><code>'abc'</code><code>], [2,</code><code>'def'</code><code>]]</code>

<code>// 天生的類數組對象arguments</code>

<code>function</code> <code>fn(){</code>

<code>  </code><code>return</code> <code>array.from(arguments);</code>

<code>fn(1, 2, 3);</code><code>// [1, 2, 3]</code>

到這你可能以為array.from就講完了,實際上還有一個重要的擴充場景必須提下。比如說生成一個從0到指定數字的新數組,array.from就可以輕易的做到。

<code>array.from({length: 10}, (v, i) =&gt; i);</code><code>// [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]</code>

後面我們将會看到,利用數組的keys方法實作上述功能,可能還要簡單一些。

目前,以下版本浏覽器提供了對array.from的支援。

opera

32+

array.isarray

顧名思義,array.isarray用來判斷一個變量是否數組類型。js的弱類型機制導緻判斷變量類型是初級前端開發者面試時的必考題,一般我都會将其作為考察候選人第一題,然後基于此展開。在es6提供該方法之前,es5至少有如下5種方式判斷一個值是否數組:

<code>var</code> <code>a = [];</code>

<code>// 1.基于instanceof</code>

<code>a</code><code>instanceof</code> <code>array;</code>

<code>// 2.基于constructor</code>

<code>a.constructor === array;</code>

<code>// 3.基于object.prototype.isprototypeof</code>

<code>array.prototype.isprototypeof(a);</code>

<code>// 4.基于getprototypeof</code>

<code>object.getprototypeof(a) === array.prototype;</code>

<code>// 5.基于object.prototype.tostring</code>

<code>object.prototype.tostring.apply(a) ===</code><code>'[object array]'</code><code>;</code>

以上,除了object.prototype.tostring外,其它方法都不能正确判斷變量的類型。

要知道,代碼的運作環境十分複雜,一個變量可能使用渾身解數去迷惑它的創造者。且看:

<code>var</code> <code>a = {</code>

<code>  </code><code>__proto__: array.prototype</code>

<code>};</code>

<code>// 分别在控制台試運作以下代碼</code>

<code>a</code><code>instanceof</code> <code>array;</code><code>// true</code>

<code>a.constructor === array;</code><code>// true</code>

<code>array.prototype.isprototypeof(a);</code><code>// true</code>

<code>object.getprototypeof(a) === array.prototype;</code><code>// true</code>

以上,4種方法将全部傳回true,為什麼呢?我們隻是手動指定了某個對象的__proto__屬性為array.prototype,便導緻了該對象繼承了array對象,這種毫不負責任的繼承方式,使得基于繼承的判斷方案瞬間土崩瓦解。

不僅如此,我們還知道,array是堆資料,變量指向的隻是它的引用位址,是以每個頁面的array對象引用的位址都是不一樣的。iframe中聲明的數組,它的構造函數是iframe中的array對象。如果在iframe聲明了一個數組x,将其指派給父頁面的變量y,那麼在父頁面使用y instanceof array ,結果一定是false的。而最後一種傳回的是字元串,不會存在引用問題。實際上,多頁面或系統之間的互動隻有字元串能夠暢行無阻。

鑒于上述的兩點原因,故筆者推薦使用最後一種方法去撩面試官(别提是我說的),如果你還不信,這裡恰好有篇文章跟我持有相同的觀點:

回到es6,使用array.isarray則非常簡單,如下:

<code>array.isarray([]);</code><code>// true</code>

<code>array.isarray({0:</code><code>'a'</code><code>, length: 1});</code><code>// false</code>

目前,以下版本浏覽器提供了對array.isarray的支援。

5+

4+

9+

10.5+

實際上,通過object.prototype.tostring去判斷一個值的類型,也是各大主流庫的标準。是以array.isarray的polyfill通常長這樣:

<code>if</code> <code>(!array.isarray){</code>

<code>  </code><code>array.isarray =</code><code>function</code><code>(arg){</code>

<code>    </code><code>return</code> <code>object.prototype.tostring.call(arg) ===</code><code>'[object array]'</code><code>;</code>

es6對數組的增強不止是展現在api上,還包括文法糖。比如說for of,它就是借鑒其它語言而成的文法糖,這種基于原數組使用for of生成新數組的文法糖,叫做數組推導。數組推導最初起早在es6的草案中,但在第27版(2014年8月)中被移除,目前隻有firefox v30+支援,推導有風險,使用需謹慎。所幸如今這些語言都還支援推導:coffeescript、python、haskell、clojure,我們可以從中一窺端倪。這裡我們以python的for in推導打個比方:

<code># python for in 推導</code>

<code>a = [1, 2, 3, 4]</code>

<code>print [i * i</code><code>for</code> <code>i</code><code>in</code> <code>a</code><code>if</code> <code>i == 3]</code> <code># [9]</code>

如下是spidermonkey引擎(firefox)之前基于es4規範實作的數組推導(與python的推導十分相似):

<code>[i * i</code><code>for</code> <code>(i of a)]</code><code>// [1, 4, 9, 16]</code>

es6中數組有關的for of在es4的基礎上進一步演化,for關鍵字居首,in在中間,最後才是運算表達式。如下:

<code>[</code><code>for</code> <code>(i of [1, 2, 3, 4]) i * i]</code><code>// [1, 4, 9, 16]</code>

同python的示例,es6中數組有關的for of也可以使用if語句:

<code>// 單個if</code>

<code>[</code><code>for</code> <code>(i of [1, 2, 3, 4])</code><code>if</code> <code>(i == 3) i * i]</code><code>// [9]</code>

<code>// 甚至是多個if</code>

<code>[</code><code>for</code> <code>(i of [1, 2, 3, 4])</code><code>if</code> <code>(i &gt; 2)</code><code>if</code> <code>(i &lt; 4) i * i]</code><code>// [9]</code>

更為強大的是,es6數組推導還允許多重for of。

<code>[</code><code>for</code> <code>(i of [1, 2, 3])</code><code>for</code> <code>(j of [10, 100]) i * j]</code><code>// [10, 100, 20, 200, 30, 300]</code>

甚至,數組推導還能夠嵌入另一個數組推導中。

<code>[</code><code>for</code> <code>(i of [1, 2, 3]) [</code><code>for</code> <code>(j of [10, 100]) i * j] ]</code><code>// [[10, 100], [20, 200], [30, 300]]</code>

對于上述兩個表達式,前者和後者唯一的差別,就在于後者的第二個推導是先傳回數組,然後與外部的推導再進行一次運算。

除了多個數組推導嵌套外,es6的數組推導還會為每次疊代配置設定一個新的作用域(目前firefox也沒有為每次疊代建立新的作用域):

<code>// es6規範</code>

<code>[</code><code>for</code> <code>(x of [0, 1, 2]) () =&gt; x][0]()</code><code>// 0</code>

<code>// firefox運作</code>

<code>[</code><code>for</code> <code>(x of [0, 1, 2]) () =&gt; x][0]()</code><code>// 2</code>

通過上面的執行個體,我們看到使用數組推導來建立新數組比foreach,map,filter等周遊方法更加簡潔,隻是非常可惜,它不是标準規範。

es6不僅新增了對array構造器相關api,還新增了8個原型的方法。接下來我會在原型方法的介紹中穿插着es6相關方法的講解,請耐心往下讀。

繼承的常識告訴我們,js中所有的數組方法均來自于array.prototype,和其他構造函數一樣,你可以通過擴充 array 的 prototype 屬性上的方法來給所有數組執行個體增加方法。

值得一說的是,array.prototype本身就是一個數組。

<code>array.isarray(array.prototype);</code><code>// true</code>

<code>console.log(array.prototype.length);</code><code>// 0</code>

以下方法可以進一步驗證:

<code>console.log([].__proto__.length);</code><code>// 0</code>

<code>console.log([].__proto__);</code><code>// [symbol(symbol.unscopables): object]</code>

有關symbol(symbol.unscopables)的知識,這裡不做詳述,具體請移步後續章節。

數組原型提供的方法非常之多,主要分為三種,一種是會改變自身值的,一種是不會改變自身值的,另外一種是周遊方法。

由于 array.prototype 的某些屬性被設定為[[dontenum]],是以不能用一般的方法進行周遊,我們可以通過如下方式擷取 array.prototype 的所有方法:

<code>object.getownpropertynames(array.prototype);</code><code>// ["length", "constructor", "tostring", "tolocalestring", "join", "pop", "push", "reverse", "shift", "unshift", "slice", "splice", "sort", "filter", "foreach", "some", "every", "map", "indexof", "lastindexof", "reduce", "reduceright", "copywithin", "find", "findindex", "fill", "includes", "entries", "keys", "concat"]</code>

改變自身值的方法(9個)

基于es6,改變自身值的方法一共有9個,分别為pop、push、reverse、shift、sort、splice、unshift,以及兩個es6新增的方法copywithin 和 fill。

對于能改變自身值的數組方法,日常開發中需要特别注意,盡量避免在循環周遊中去改變原數組的項。接下來,我們一起來深入地了解這些方法。

pop

pop() 方法删除一個數組中的最後的一個元素,并且傳回這個元素。如果是棧的話,這個過程就是棧頂彈出。

<code>var</code> <code>array = [</code><code>"cat"</code><code>,</code><code>"dog"</code><code>,</code><code>"cow"</code><code>,</code><code>"chicken"</code><code>,</code><code>"mouse"</code><code>];</code>

<code>var</code> <code>item = array.pop();</code>

<code>console.log(array);</code><code>// ["cat", "dog", "cow", "chicken"]</code>

<code>console.log(item);</code><code>// mouse</code>

由于設計上的巧妙,pop方法可以應用在類數組對象上,即 鴨式辨型. 如下:

<code>var</code> <code>o = {0:</code><code>"cat"</code><code>, 1:</code><code>"dog"</code><code>, 2:</code><code>"cow"</code><code>, 3:</code><code>"chicken"</code><code>, 4:</code><code>"mouse"</code><code>, length:5}</code>

<code>var</code> <code>item = array.prototype.pop.call(o);</code>

<code>console.log(o);</code><code>// object {0: "cat", 1: "dog", 2: "cow", 3: "chicken", length: 4}</code>

但如果類數組對象不具有length屬性,那麼該對象将被建立length屬性,length值為0。如下:

<code>var</code> <code>o = {0:</code><code>"cat"</code><code>, 1:</code><code>"dog"</code><code>, 2:</code><code>"cow"</code><code>, 3:</code><code>"chicken"</code><code>, 4:</code><code>"mouse"</code><code>}</code>

<code>console.log(array);</code><code>// object {0: "cat", 1: "dog", 2: "cow", 3: "chicken", 4: "mouse", length: 0}</code>

<code>console.log(item);</code><code>// undefined</code>

push

push()方法添加一個或者多個元素到數組末尾,并且傳回數組新的長度。如果是棧的話,這個過程就是棧頂壓入。

文法:arr.push(element1, ..., elementn)

<code>var</code> <code>array = [</code><code>"football"</code><code>,</code><code>"basketball"</code><code>,</code><code>"volleyball"</code><code>,</code><code>"table tennis"</code><code>,</code><code>"badminton"</code><code>];</code>

<code>var</code> <code>i = array.push(</code><code>"golfball"</code><code>);</code>

<code>console.log(array);</code><code>// ["football", "basketball", "volleyball", "table tennis", "badminton", "golfball"]</code>

<code>console.log(i);</code><code>// 6</code>

同pop方法一樣,push方法也可以應用到類數組對象上,如果length不能被轉成一個數值或者不存在length屬性時,則插入的元素索引為0,且length屬性不存在時,将會建立它。

<code>var</code> <code>o = {0:</code><code>"football"</code><code>, 1:</code><code>"basketball"</code><code>};</code>

<code>var</code> <code>i = array.prototype.push.call(o,</code><code>"golfball"</code><code>);</code>

<code>console.log(o);</code><code>// object {0: "golfball", 1: "basketball", length: 1}</code>

<code>console.log(i);</code><code>// 1</code>

實際上,push方法是根據length屬性來決定從哪裡開始插入給定的值。

<code>var</code> <code>o = {0:</code><code>"football"</code><code>, 1:</code><code>"basketball"</code><code>,length:1};</code>

<code>console.log(o);</code><code>// object {0: "football", 1: "golfball", length: 2}</code>

<code>console.log(i);</code><code>// 2</code>

利用push根據length屬性插入元素這個特點,可以實作數組的合并,如下:

<code>var</code> <code>array = [</code><code>"football"</code><code>,</code><code>"basketball"</code><code>];</code>

<code>var</code> <code>array2 = [</code><code>"volleyball"</code><code>,</code><code>"golfball"</code><code>];</code>

<code>var</code> <code>i = array.prototype.push.apply(array,array2);</code>

<code>console.log(array);</code><code>// ["football", "basketball", "volleyball", "golfball"]</code>

<code>console.log(i);</code><code>// 4</code>

reverse

reverse()方法颠倒數組中元素的位置,第一個會成為最後一個,最後一個會成為第一個,該方法傳回對數組的引用。

文法:arr.reverse()

<code>var</code> <code>array = [1,2,3,4,5];</code>

<code>var</code> <code>array2 = array.reverse();</code>

<code>console.log(array);</code><code>// [5,4,3,2,1]</code>

<code>console.log(array2===array);</code><code>// true</code>

同上,reverse 也是鴨式辨型的受益者,颠倒元素的範圍受length屬性制約。如下:

<code>var</code> <code>o = {0:</code><code>"a"</code><code>, 1:</code><code>"b"</code><code>, 2:</code><code>"c"</code><code>, length:2};</code>

<code>var</code> <code>o2 = array.prototype.reverse.call(o);</code>

<code>console.log(o);</code><code>// object {0: "b", 1: "a", 2: "c", length: 2}</code>

<code>console.log(o === o2);</code><code>// true</code>

如果 length 屬性小于2 或者 length 屬性不為數值,那麼原類數組對象将沒有變化。即使 length 屬性不存在,該對象也不會去建立 length 屬性。特别的是,當 length 屬性較大時,類數組對象的『索引』會盡可能的向 length 看齊。如下:

<code>var</code> <code>o = {0:</code><code>"a"</code><code>, 1:</code><code>"b"</code><code>, 2:</code><code>"c"</code><code>,length:100};</code>

<code>console.log(o);</code><code>// object {97: "c", 98: "b", 99: "a", length: 100}</code>

shift

shift()方法删除數組的第一個元素,并傳回這個元素。

文法:arr.shift()

<code>var</code> <code>item = array.shift();</code>

<code>console.log(array);</code><code>// [2,3,4,5]</code>

<code>console.log(item);</code><code>// 1</code>

同樣受益于鴨式辨型,對于類數組對象,shift仍然能夠處理。如下:

<code>var</code> <code>o = {0:</code><code>"a"</code><code>, 1:</code><code>"b"</code><code>, 2:</code><code>"c"</code><code>, length:3};</code>

<code>var</code> <code>item = array.prototype.shift.call(o);</code>

<code>console.log(o);</code><code>// object {0: "b", 1: "c", length: 2}</code>

<code>console.log(item);</code><code>// a</code>

如果類數組對象length屬性不存在,将添加length屬性,并初始化為0。如下:

<code>var</code> <code>o = {0:</code><code>"a"</code><code>, 1:</code><code>"b"</code><code>, 2:</code><code>"c"</code><code>};</code>

<code>console.log(o);</code><code>// object {0: "a", 1: "b", 2:"c" length: 0}</code>

sort

sort()方法對數組元素進行排序,并傳回這個數組。sort方法比較複雜,這裡我将多花些篇幅來講這塊。

文法:arr.sort([comparefn])

comparefn是可選的,如果省略,數組元素将按照各自轉換為字元串的unicode(萬國碼)位點順序排序,例如"boy"将排到"apple"之前。當對數字排序的時候,25将會排到8之前,因為轉換為字元串後,"25"将比"8"靠前。例如:

<code>var</code> <code>array = [</code><code>"apple"</code><code>,</code><code>"boy"</code><code>,</code><code>"cat"</code><code>,</code><code>"dog"</code><code>];</code>

<code>var</code> <code>array2 = array.sort();</code>

<code>console.log(array);</code><code>// ["boy", "cat", "apple", "dog"]</code>

<code>console.log(array2 == array);</code><code>// true</code>

<code>array = [10, 1, 3, 20];</code>

<code>var</code> <code>array3 = array.sort();</code>

<code>console.log(array3);</code><code>// [1, 10, 20, 3]</code>

如果指明了comparefn,數組将按照調用該函數的傳回值來排序。若 a 和 b 是兩個将要比較的元素:

若 comparefn(a, b) &lt; 0,那麼a 将排到 b 前面;

若 comparefn(a, b) = 0,那麼a 和 b 相對位置不變;

若 comparefn(a, b) &gt; 0,那麼a , b 将調換位置;

如果數組元素為數字,則排序函數comparefn格式如下所示:

<code>function</code> <code>compare(a, b){</code>

<code>  </code><code>return</code> <code>a-b;</code>

如果數組元素為非ascii字元的字元串(如包含類似 e、é、è、a、? 或中文字元等非英文字元的字元串),則需要使用string.localecompare。下面這個函數将排到正确的順序。

<code>var</code> <code>array = [</code><code>'互'</code><code>,</code><code>'聯'</code><code>,</code><code>'網'</code><code>,</code><code>'改'</code><code>,</code><code>'變'</code><code>,</code><code>'世'</code><code>,</code><code>'界'</code><code>];</code>

<code>var</code> <code>array = [</code><code>'互'</code><code>,</code><code>'聯'</code><code>,</code><code>'網'</code><code>,</code><code>'改'</code><code>,</code><code>'變'</code><code>,</code><code>'世'</code><code>,</code><code>'界'</code><code>];</code><code>// 重新指派,避免幹擾array2</code>

<code>var</code> <code>array3 = array.sort(</code><code>function</code> <code>(a, b) {</code>

<code>  </code><code>return</code> <code>a.localecompare(b);</code>

<code>});</code>

<code>console.log(array2);</code><code>// ["世", "互", "變", "改", "界", "網", "聯"]</code>

<code>console.log(array3);</code><code>// ["變", "改", "互", "界", "聯", "世", "網"]</code>

如上,『網際網路改變世界』這個數組,sort函數預設按照數組元素unicode字元串形式進行排序,然而實際上,我們期望的是按照拼音先後順序進行排序,顯然string.localecompare 幫助我們達到了這個目的。

為什麼上面測試中需要重新給array指派呢,這是因為sort每次排序時改變的是數組本身,并且傳回數組引用。如果不這麼做,經過連續兩次排序後,array2 和 array3 将指向同一個數組,最終影響我們測試。array重新指派後就斷開了對原數組的引用。

同上,sort一樣受益于鴨式辨型,比如:

<code>var</code> <code>o = {0:</code><code>'互'</code><code>,1:</code><code>'聯'</code><code>,2:</code><code>'網'</code><code>,3:</code><code>'改'</code><code>,4:</code><code>'變'</code><code>,5:</code><code>'世'</code><code>,6:</code><code>'界'</code><code>,length:7};</code>

<code>array.prototype.sort.call(o,</code><code>function</code><code>(a, b){</code>

<code>console.log(o);</code><code>// object {0: "變", 1: "改", 2: "互", 3: "界", 4: "聯", 5: "世", 6: "網", length: 7}, 可見同上述排序結果一緻</code>

注意:使用sort的鴨式辨型特性時,若類數組對象不具有length屬性,它并不會進行排序,也不會為其添加length屬性。

<code>var</code> <code>o = {0:</code><code>'互'</code><code>,1:</code><code>'聯'</code><code>,2:</code><code>'網'</code><code>,3:</code><code>'改'</code><code>,4:</code><code>'變'</code><code>,5:</code><code>'世'</code><code>,6:</code><code>'界'</code><code>};</code>

<code>console.log(o);</code><code>// object {0: "互", 1: "聯", 2: "網", 3: "改", 4: "變", 5: "世", 6: "界"}, 可見并未添加length屬性</code>

comparefn 如果需要對數組元素多次轉換以實作排序,那麼使用map輔助排序将是個不錯的選擇。基本思想就是将數組中的每個元素實際比較的值取出來,排序後再将數組恢複。

<code>// 需要被排序的數組</code>

<code>var</code> <code>array = [</code><code>'dog'</code><code>,</code><code>'cat'</code><code>,</code><code>'boy'</code><code>,</code><code>'apple'</code><code>];</code>

<code>// 對需要排序的數字和位置的臨時存儲</code>

<code>var</code> <code>mapped = array.map(</code><code>function</code><code>(el, i) {</code>

<code>  </code><code>return</code> <code>{ index: i, value: el.tolowercase() };</code>

<code>})</code>

<code>// 按照多個值排序數組</code>

<code>mapped.sort(</code><code>function</code><code>(a, b) {</code>

<code>  </code><code>return</code> <code>+(a.value &gt; b.value) || +(a.value === b.value) - 1;</code>

<code>// 根據索引得到排序的結果</code>

<code>var</code> <code>result = mapped.map(</code><code>function</code><code>(el){</code>

<code>  </code><code>return</code> <code>array[el.index];</code>

<code>console.log(result);</code><code>// ["apple", "boy", "cat", "dog"]</code>

實際上,ecmascript規範中并未規定具體的sort算法,這就勢必導緻各個浏覽器不盡相同的sort算法,請看sort方法在chrome浏覽器下表現:

<code>var</code> <code>array = [{ n:</code><code>"a"</code><code>, v: 1 }, { n:</code><code>"b"</code><code>, v: 1 }, { n:</code><code>"c"</code><code>, v: 1 }, { n:</code><code>"d"</code><code>, v: 1 }, { n:</code><code>"e"</code><code>, v: 1 }, { n:</code><code>"f"</code><code>, v: 1 }, { n:</code><code>"g"</code><code>, v: 1 }, { n:</code><code>"h"</code><code>, v: 1 }, { n:</code><code>"i"</code><code>, v: 1 }, { n:</code><code>"j"</code><code>, v: 1 }, { n:</code><code>"k"</code><code>, v: 1 }, ];</code>

<code>array.sort(</code><code>function</code> <code>(a, b) {</code>

<code>    </code><code>return</code> <code>a.v - b.v;</code>

<code>for</code> <code>(</code><code>var</code> <code>i = 0,len = array.length; i &lt; len; i++) {</code>

<code>    </code><code>console.log(array[i].n);</code>

<code>// f a c d e b g h i j k</code>

由于v值相等,array數組排序前後應該不變,然而chrome卻表現異常,而其他浏覽器(如ie 或 firefox) 表現正常。

這是因為v8引擎為了高效排序(采用了不穩定排序)。即數組長度超過10條時,會調用另一種排序方法(快速排序);而10條及以下采用的是插入排序,此時結果将是穩定的,如下:

<code>var</code> <code>array = [{ n:</code><code>"a"</code><code>, v: 1 }, { n:</code><code>"b"</code><code>, v: 1 }, { n:</code><code>"c"</code><code>, v: 1 }, { n:</code><code>"d"</code><code>, v: 1 }, { n:</code><code>"e"</code><code>, v: 1 }, { n:</code><code>"f"</code><code>, v: 1 }, { n:</code><code>"g"</code><code>, v: 1 }, { n:</code><code>"h"</code><code>, v: 1 }, { n:</code><code>"i"</code><code>, v: 1 }, { n:</code><code>"j"</code><code>, v: 1 },];</code>

<code>  </code><code>return</code> <code>a.v - b.v;</code>

<code>  </code><code>console.log(array[i].n);</code>

<code>// a b c d e f g h i j</code>

從a 到 j 剛好10條資料。

那麼我們該如何規避chrome浏覽器的這種"bug"呢?其實很簡單,隻需略動手腳,改變排序方法的傳回值即可,如下:

<code>  </code><code>return</code> <code>a.v - b.v || array.indexof(a)-array.indexof(b);</code>

使用數組的sort方法需要注意一點:各浏覽器的針對sort方法内部算法實作不盡相同,排序函數盡量隻傳回-1、0、1三種不同的值,不要嘗試傳回true或false等其它數值,因為可能導緻不可靠的排序結果。

sort方法傳入的排序函數如果傳回布爾值會導緻什麼樣的結果呢?

以下是常見的浏覽器以及腳本引擎:

browser name

ecmascript engine

internet explorer 6 - 8

jscript

internet explorer 9 - 10

chakra

spidermonkey, ionmonkey, tracemonkey

v8

safair

javascriptcore(squirrelfish extreme)

carakan

分析以下代碼,預期将數組元素進行升序排序:

<code>var</code> <code>array = [7, 6, 5, 4, 3, 2, 1, 0, 8, 9];</code>

<code>var</code> <code>comparefn =</code><code>function</code> <code>(x, y) {</code>

<code>  </code><code>return</code> <code>x &gt; y;</code>

<code>array.sort(comparefn);</code>

代碼中,comparefn 函數傳回值為 bool 類型,并非為規範規定的 -1、0、1 值。那麼執行此代碼,各 js 腳本引擎實作情況如何?

輸出結果

是否符合預期

[2, 3, 5, 1, 4, 6, 7, 0, 8, 9]

[0, 1, 3, 8, 2, 4, 9, 5, 6, 7]

chakra &amp; javascriptcore

[7, 6, 5, 4, 3, 2, 1, 0, 8, 9]

spidermonkey

[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

根據表中資料可見,當數組内元素個數小于等于 10 時,現象如下:

jscript &amp; carakan 排序結果有誤

chakra &amp; javascriptcore 看起來沒有進行排序

spidermonkey 傳回了預期的正确結果

v8 暫時看起來排序正确

将數組元素擴大至 11 位,現象如下:

<code>var</code> <code>array = [7, 6, 5, 4, 3, 2, 1, 0, 10, 9, 8];</code>

javascript引擎

[2, 3, 5, 1, 4, 6, 7, 0, 8, 9, 10]

[0, 1, 3, 8, 2, 4, 9, 5, 10, 6, 7]

[7, 6, 5, 4, 3, 2, 1, 0, 10, 8, 9]

ionmonkey

[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

[5, 0, 1, 2, 3, 4, 6, 7, 8, 9, 10]

根據表中資料可見,當數組内元素個數大于 10 時:

v8 排序結果由正确轉為不正确

splice

splice()方法用新元素替換舊元素的方式來修改數組。它是一個常用的方法,複雜的數組操作場景通常都會有它的身影,特别是需要維持原數組引用時,就地删除或者新增元素,splice是最适合的。

文法:arr.splice(start,deletecount[, item1[, item2[, …]]])

start 指定從哪一位開始修改内容。如果超過了數組長度,則從數組末尾開始添加内容;如果是負值,則其指定的索引位置等同于 length+start (length為數組的長度),表示從數組末尾開始的第 -start 位。

deletecount 指定要删除的元素個數,若等于0,則不删除。這種情況下,至少應該添加一位新元素,若大于start之後的元素總和,則start及之後的元素都将被删除。

itemn 指定新增的元素,如果預設,則該方法隻删除數組元素。

傳回值 由原數組中被删除元素組成的數組,如果沒有删除,則傳回一個空數組。

下面來舉栗子說明:

<code>var</code> <code>array = [</code><code>"apple"</code><code>,</code><code>"boy"</code><code>];</code>

<code>var</code> <code>splices = array.splice(1,1);</code>

<code>console.log(array);</code><code>// ["apple"]</code>

<code>console.log(splices);</code><code>// ["boy"] ,可見是從數組下标為1的元素開始删除,并且删除一個元素,由于itemn預設,故此時該方法隻删除元素</code>

<code>array = [</code><code>"apple"</code><code>,</code><code>"boy"</code><code>];</code>

<code>splices = array.splice(2,1,</code><code>"cat"</code><code>);</code>

<code>console.log(array);</code><code>// ["apple", "boy", "cat"]</code>

<code>console.log(splices);</code><code>// [], 可見由于start超過數組長度,此時從數組末尾開始添加元素,并且原數組不會發生删除行為</code>

<code>splices = array.splice(-2,1,</code><code>"cat"</code><code>);</code>

<code>console.log(array);</code><code>// ["cat", "boy"]</code>

<code>console.log(splices);</code><code>// ["apple"], 可見當start為負值時,是從數組末尾開始的第-start位開始删除,删除一個元素,并且從此處插入了一個元素</code>

<code>splices = array.splice(-3,1,</code><code>"cat"</code><code>);</code>

<code>console.log(splices);</code><code>// ["apple"], 可見即使-start超出數組長度,數組預設從首位開始删除</code>

<code>splices = array.splice(0,3,</code><code>"cat"</code><code>);</code>

<code>console.log(array);</code><code>// ["cat"]</code>

<code>console.log(splices);</code><code>// ["apple", "boy"], 可見當deletecount大于數組start之後的元素總和時,start及之後的元素都将被删除</code>

同上, splice一樣受益于鴨式辨型, 比如:

<code>var</code> <code>o = {0:</code><code>"apple"</code><code>,1:</code><code>"boy"</code><code>,length:2};</code>

<code>var</code> <code>splices = array.prototype.splice.call(o,1,1);</code>

<code>console.log(o);</code><code>// object {0: "apple", length: 1}, 可見對象o删除了一個屬性,并且length-1</code>

<code>console.log(splices);</code><code>// ["boy"]</code>

注意:如果類數組對象沒有length屬性,splice将為該類數組對象添加length屬性,并初始化為0。(此處忽略舉例,如果需要請在評論裡回報)

如果需要删除數組中一個已存在的元素,可參考如下:

<code>var</code> <code>array = [</code><code>'a'</code><code>,</code><code>'b'</code><code>,</code><code>'c'</code><code>];</code>

<code>array.splice(array.indexof(</code><code>'b'</code><code>),1);</code>

unshift

unshift() 方法用于在數組開始處插入一些元素(就像是棧底插入),并傳回數組新的長度。

文法:arr.unshift(element1, ..., elementn)

<code>var</code> <code>array = [</code><code>"red"</code><code>,</code><code>"green"</code><code>,</code><code>"blue"</code><code>];</code>

<code>var</code> <code>length = array.unshift(</code><code>"yellow"</code><code>);</code>

<code>console.log(array);</code><code>// ["yellow", "red", "green", "blue"]</code>

<code>console.log(length);</code><code>// 4</code>

如果給unshift方法傳入一個數組呢?

<code>var</code> <code>length = array.unshift([</code><code>"yellow"</code><code>]);</code>

<code>console.log(array);</code><code>// [["yellow"], "red", "green", "blue"]</code>

<code>console.log(length);</code><code>// 4, 可見數組也能成功插入</code>

同上,unshift也受益于鴨式辨型,呈上栗子:

<code>var</code> <code>o = {0:</code><code>"red"</code><code>, 1:</code><code>"green"</code><code>, 2:</code><code>"blue"</code><code>,length:3};</code>

<code>var</code> <code>length = array.prototype.unshift.call(o,</code><code>"gray"</code><code>);</code>

<code>console.log(o);</code><code>// object {0: "gray", 1: "red", 2: "green", 3: "blue", length: 4}</code>

注意:如果類數組對象不指定length屬性,則傳回結果是這樣的 object {0: "gray", 1: "green", 2: "blue", length: 1},shift會認為數組長度為0,此時将從對象下标為0的位置開始插入,相應位置屬性将被替換,此時初始化類數組對象的length屬性為插入元素個數。

copywithin(es6)

copywithin() 方法基于ecmascript 2015(es6)規範,用于數組内元素之間的替換,即替換元素和被替換元素均是數組内的元素。

文法:arr.copywithin(target, start[, end = this.length])

taget 指定被替換元素的索引,start 指定替換元素起始的索引,end 可選,指的是替換元素結束位置的索引。

如果start為負,則其指定的索引位置等同于length+start,length為數組的長度。end也是如此。

注:目前隻有firefox(版本32及其以上版本)實作了該方法。

<code>var</code> <code>array2 = array.copywithin(0,3);</code>

<code>console.log(array===array2,array2);</code><code>// true [4, 5, 3, 4, 5]</code>

<code>console.log(array.copywithin(0,3,4));</code><code>// [4, 2, 3, 4, 5]</code>

<code>console.log(array.copywithin(0,-2,-1));</code><code>// [4, 2, 3, 4, 5]</code>

同上,copywithin一樣受益于鴨式辨型,例如:

<code>var</code> <code>o = {0:1, 1:2, 2:3, 3:4, 4:5,length:5}</code>

<code>var</code> <code>o2 = array.prototype.copywithin.call(o,0,3);</code>

<code>console.log(o===o2,o2);</code><code>// true object { 0=4,  1=5,  2=3,  更多...}</code>

如需在firefox之外的浏覽器使用copywithin方法,請參考 polyfill。

fill(es6)

fill() 方法基于ecmascript 2015(es6)規範,它同樣用于數組元素替換,但與copywithin略有不同,它主要用于将數組指定區間内的元素替換為某個值。

文法:arr.fill(value, start[, end = this.length])

value 指定被替換的值,start 指定替換元素起始的索引,end 可選,指的是替換元素結束位置的索引。

注:目前隻有firefox(版本31及其以上版本)實作了該方法。

<code>var</code> <code>array2 = array.fill(10,0,3);</code>

<code>console.log(array===array2,array2);</code><code>// true [10, 10, 10, 4, 5], 可見數組區間[0,3]的元素全部替換為10</code>

<code>// 其他的舉例請參考copywithin</code>

同上,fill 一樣受益于鴨式辨型,例如:

<code>var</code> <code>o2 = array.prototype.fill.call(o,10,0,2);</code>

<code>console.log(o===o2,o2);</code><code>true</code> <code>object { 0=10,  1=10,  2=3,  更多...}</code>

如需在firefox之外的浏覽器使用fill方法,請參考 polyfill。

基于es7,不會改變自身的方法一共有9個,分别為concat、join、slice、tostring、tolocatestring、indexof、lastindexof、未标準的tosource以及es7新增的方法includes。

concat

concat() 方法将傳入的數組或者元素與原數組合并,組成一個新的數組并傳回。

文法:arr.concat(value1, value2, ..., valuen)

<code>var</code> <code>array = [1, 2, 3];</code>

<code>var</code> <code>array2 = array.concat(4,[5,6],[7,8,9]);</code>

<code>console.log(array2);</code><code>// [1, 2, 3, 4, 5, 6, 7, 8, 9]</code>

<code>console.log(array);</code><code>// [1, 2, 3], 可見原數組并未被修改</code>

同上,concat 一樣受益于鴨式辨型,但其效果可能達不到我們的期望,如下:

<code>var</code> <code>o = {0:</code><code>"a"</code><code>, 1:</code><code>"b"</code><code>, 2:</code><code>"c"</code><code>,length:3};</code>

<code>var</code> <code>o2 = array.prototype.concat.call(o,</code><code>'d'</code><code>,{3:</code><code>'e'</code><code>,4:</code><code>'f'</code><code>,length:2},[</code><code>'g'</code><code>,</code><code>'h'</code><code>,</code><code>'i'</code><code>]);</code>

<code>console.log(o2);</code><code>// [{0:"a", 1:"b", 2:"c", length:3}, 'd', {3:'e', 4:'f', length:2}, 'g', 'h', 'i']</code>

可見,類數組對象合并後傳回的是依然是數組,并不是我們期望的對象。

join

join() 方法将數組中的所有元素連接配接成一個字元串。

文法:arr.join([separator = ',']) separator可選,預設預設為逗号。

<code>var</code> <code>array = [</code><code>'we'</code><code>,</code><code>'are'</code><code>,</code><code>'chinese'</code><code>];</code>

<code>console.log(array.join());</code><code>// "we,are,chinese"</code>

<code>console.log(array.join(</code><code>'+'</code><code>));</code><code>// "we+are+chinese"</code>

<code>console.log(array.join(</code><code>''</code><code>));</code><code>// "wearechinese"</code>

同上,join 一樣受益于鴨式辨型,如下:

<code>var</code> <code>o = {0:</code><code>"we"</code><code>, 1:</code><code>"are"</code><code>, 2:</code><code>"chinese"</code><code>, length:3};</code>

<code>console.log(array.prototype.join.call(o,</code><code>'+'</code><code>));</code><code>// "we+are+chinese"</code>

<code>console.log(array.prototype.join.call(</code><code>'abc'</code><code>));</code><code>// "a,b,c"</code>

slice

slice() 方法将數組中一部分元素淺複制存入新的數組對象,并且傳回這個數組對象。

文法:arr.slice(start[, end])

參數 start 指定複制開始位置的索引,end如果有值則表示複制結束位置的索引(不包括此位置)。

如果 start 的值為負數,假如數組長度為 length,則表示從 length+start 的位置開始複制,此時參數 end 如果有值,隻能是比 start 大的負數,否則将傳回空數組。

<code>var</code> <code>array = [</code><code>"one"</code><code>,</code><code>"two"</code><code>,</code><code>"three"</code><code>,</code><code>"four"</code><code>,</code><code>"five"</code><code>];</code>

<code>var</code> <code>array2 = array.slice(2,3);</code>

<code>console.log(array2);</code><code>// ["three"]</code>

淺複制 是指當對象的被複制時,隻是複制了對象的引用,指向的依然是同一個對象。下面來說明slice為什麼是淺複制。

<code>var</code> <code>array = [{color:</code><code>"yellow"</code><code>}, 2, 3];</code>

<code>var</code> <code>array2 = array.slice(0,1);</code>

<code>console.log(array2);</code><code>// [{color:"yellow"}]</code>

<code>array[0][</code><code>"color"</code><code>] =</code><code>"blue"</code><code>;</code>

<code>console.log(array2);</code><code>// [{color:"bule"}]</code>

由于slice是淺複制,複制到的對象隻是一個引用,改變原數組array的值,array2也随之改變。

同時,稍微利用下 slice 方法第一個參數為負數時的特性,我們可以非常友善的拿到數組的最後一項元素,如下:

<code>console.log([1,2,3].slice(-1));</code><code>//[3]</code>

同上,slice 一樣受益于鴨式辨型。如下:

<code>var</code> <code>o = {0:{</code><code>"color"</code><code>:</code><code>"yellow"</code><code>}, 1:2, 2:3, length:3};</code>

<code>var</code> <code>o2 = array.prototype.slice.call(o,0,1);</code>

<code>console.log(o2);</code><code>// [{color:"yellow"}] ,毫無違和感...</code>

鑒于ie9以下版本對于該方法支援性并不是很好,如需更好的支援低版本ie浏覽器,請參考polyfill。

tostring

tostring() 方法傳回數組的字元串形式,該字元串由數組中的每個元素的 tostring() 傳回值經調用 join() 方法連接配接(由逗号隔開)組成。

文法: arr.tostring()

<code>var</code> <code>array = [</code><code>'jan'</code><code>,</code><code>'feb'</code><code>,</code><code>'mar'</code><code>,</code><code>'apr'</code><code>];</code>

<code>var</code> <code>str = array.tostring();</code>

<code>console.log(str);</code><code>// jan,feb,mar,apr</code>

當數組直接和字元串作連接配接操作時,将會自動調用其tostring() 方法。

<code>var</code> <code>str = [</code><code>'jan'</code><code>,</code><code>'feb'</code><code>,</code><code>'mar'</code><code>,</code><code>'apr'</code><code>] +</code><code>',may'</code><code>;</code>

<code>console.log(str);</code><code>// "jan,feb,mar,apr,may"</code>

<code>// 下面我們來試試鴨式辨型</code>

<code>var</code> <code>o = {0:</code><code>'jan'</code><code>, 1:</code><code>'feb'</code><code>, 2:</code><code>'mar'</code><code>, length:3};</code>

<code>var</code> <code>o2 = array.prototype.tostring.call(o);</code>

<code>console.log(o2);</code><code>// [object object]</code>

<code>console.log(o.tostring()==o2);</code><code>// true</code>

可見,array.prototype.tostring()方法處理類數組對象時,跟類數組對象直接調用object.prototype.tostring() 方法結果完全一緻,說好的鴨式辨型呢?

根據es5語義,tostring() 方法是通用的,可被用于任何對象。如果對象有一個join() 方法,将會被調用,其傳回值将被傳回,沒有則調用object.prototype.tostring(),為此,我們給o對象添加一個join方法。如下:

<code>var</code> <code>o = {</code>

<code>  </code><code>0:</code><code>'jan'</code><code>,</code>

<code>  </code><code>1:</code><code>'feb'</code><code>,</code>

<code>  </code><code>2:</code><code>'mar'</code><code>,</code>

<code>  </code><code>length:3,</code>

<code>  </code><code>join:</code><code>function</code><code>(){</code>

<code>    </code><code>return</code> <code>array.prototype.join.call(</code><code>this</code><code>);</code>

<code>console.log(array.prototype.tostring.call(o));</code><code>// "jan,feb,mar"</code>

tolocalestring

tolocalestring() 類似tostring()的變型,該字元串由數組中的每個元素的 tolocalestring() 傳回值經調用 join() 方法連接配接(由逗号隔開)組成。

文法:arr.tolocalestring()

數組中的元素将調用各自的 tolocalestring 方法:

object:object.prototype.tolocalestring()

number:number.prototype.tolocalestring()

date:date.prototype.tolocalestring()

<code>var</code> <code>array= [{name:</code><code>'zz'</code><code>}, 123,</code><code>"abc"</code><code>,</code><code>new</code> <code>date()];</code>

<code>var</code> <code>str = array.tolocalestring();</code>

<code>console.log(str);</code><code>// [object object],123,abc,2016/1/5 下午1:06:23</code>

其鴨式辨型的寫法也同tostring 保持一緻,如下:

<code>  </code><code>0:123,</code>

<code>  </code><code>1:</code><code>'abc'</code><code>,</code>

<code>  </code><code>2:</code><code>new</code> <code>date(),</code>

<code>console.log(array.prototype.tolocalestring.call(o));</code><code>// 123,abc,2016/1/5 下午1:16:50</code>

indexof

indexof() 方法用于查找元素在數組中第一次出現時的索引,如果沒有,則傳回-1。

文法:arr.indexof(element, fromindex=0)

element 為需要查找的元素。

fromindex 為開始查找的位置,預設預設為0。如果超出數組長度,則傳回-1。如果為負值,假設數組長度為length,則從數組的第 length + fromindex項開始往數組末尾查找,如果length + fromindex&lt;0 則整個數組都會被查找。 indexof使用嚴格相等(即使用 === 去比對數組中的元素)。

<code>var</code> <code>array = [</code><code>'abc'</code><code>,</code><code>'def'</code><code>,</code><code>'ghi'</code><code>,</code><code>'123'</code><code>];</code>

<code>console.log(array.indexof(</code><code>'def'</code><code>));</code><code>// 1</code>

<code>console.log(array.indexof(</code><code>'def'</code><code>,-1));</code><code>// -1 此時表示從最後一個元素往後查找,是以查找失敗傳回-1</code>

<code>console.log(array.indexof(</code><code>'def'</code><code>,-4));</code><code>// 1 由于4大于數組長度,此時将查找整個數組,是以傳回1</code>

<code>console.log(array.indexof(123));</code><code>// -1, 由于是嚴格比對,是以并不會比對到字元串'123'</code>

得益于鴨式辨型,indexof 可以處理類數組對象。如下:

<code>var</code> <code>o = {0:</code><code>'abc'</code><code>, 1:</code><code>'def'</code><code>, 2:</code><code>'ghi'</code><code>, length:3};</code>

<code>console.log(array.prototype.indexof.call(o,</code><code>'ghi'</code><code>,-4));</code><code>//2</code>

然而該方法并不支援ie9以下版本,如需更好的支援低版本ie浏覽器(ie6~8), 請參考 polyfill。

lastindexof

lastindexof() 方法用于查找元素在數組中最後一次出現時的索引,如果沒有,則傳回-1。并且它是indexof的逆向查找,即從數組最後一個往前查找。

文法:arr.lastindexof(element, fromindex=length-1)

fromindex 為開始查找的位置,預設預設為數組長度length-1。如果超出數組長度,由于是逆向查找,則查找整個數組。如果為負值,則從數組的第 length + fromindex項開始往數組開頭查找,如果length + fromindex&lt;0 則數組不會被查找。 同 indexof 一樣,lastindexof 也是嚴格比對數組元素。 舉例請參考 indexof ,不再詳述,相容低版本ie浏覽器(ie6~8),請參考 polyfill。 includes(es7)

includes() 方法基于ecmascript 2016(es7)規範,它用來判斷目前數組是否包含某個指定的值,如果是,則傳回 true,否則傳回 false。

文法:arr.includes(element, fromindex=0)

fromindex 表示從該索引位置開始查找 element,預設為0,它是正向查找,即從索引處往數組末尾查找。

<code>var</code> <code>array = [1, 2, nan];</code>

<code>console.log(array.includes(1));</code><code>// true</code>

<code>console.log(array.includes(nan));</code><code>// true</code>

<code>console.log(array.includes(2,-4));</code><code>// true</code>

該方法同樣受益于鴨式辨型。如下:

<code>var</code> <code>o = {0:</code><code>'a'</code><code>, 1:</code><code>'b'</code><code>, 2:</code><code>'c'</code><code>, length:3};</code>

<code>var</code> <code>bool = array.prototype.includes.call(o,</code><code>'a'</code><code>);</code>

<code>console.log(bool);</code><code>// true</code>

該方法隻有在chrome 47、opera 34、safari 9版本及其更高版本中才被實作。如需支援其他浏覽器,請參考 polyfill。

tosource

tosource() 方法是非标準的,該方法傳回數組的源代碼,目前隻有 firefox 實作了它。

文法:arr.tosource()

<code>console.log(array.tosource());</code><code>// ["a", "b", "c"]</code>

<code>// 測試鴨式辨型</code>

<code>console.log(array.prototype.tosource.call(o));</code><code>// ["a","b","c"]</code>

基于es6,不會改變自身的方法一共有12個,分别為foreach、every、some、filter、map、reduce、reduceright 以及es6新增的方法entries、find、findindex、keys、values。

foreach

foreach() 方法指定數組的每項元素都執行一次傳入的函數,傳回值為undefined。

文法:arr.foreach(fn, thisarg)

fn 表示在數組每一項上執行的函數,接受三個參數:

value 目前正在被處理的元素的值

index 目前元素的數組索引

array 數組本身

thisarg 可選,用來當做fn函數内的this對象。

foreach 将為數組中每一項執行一次 fn 函數,那些已删除,新增或者從未指派的項将被跳過(但不包括值為 undefined 的項)。

周遊過程中,fn會被傳入上述三個參數。

<code>var</code> <code>array = [1, 3, 5];</code>

<code>var</code> <code>obj = {name:</code><code>'cc'</code><code>};</code>

<code>var</code> <code>sreturn = array.foreach(</code><code>function</code><code>(value, index, array){</code>

<code>  </code><code>array[index] = value * value;</code>

<code>  </code><code>console.log(</code><code>this</code><code>.name);</code><code>// cc被列印了三次</code>

<code>},obj);</code>

<code>console.log(array);</code><code>// [1, 9, 25], 可見原數組改變了</code>

<code>console.log(sreturn);</code><code>// undefined, 可見傳回值為undefined</code>

得益于鴨式辨型,雖然foreach不能直接周遊對象,但它可以通過call方式周遊類數組對象。如下:

<code>var</code> <code>o = {0:1, 1:3, 2:5, length:3};</code>

<code>array.prototype.foreach.call(o,</code><code>function</code><code>(value, index, obj){</code>

<code>  </code><code>console.log(value,index,obj);</code>

<code>  </code><code>obj[index] = value * value;</code>

<code>},o);</code>

<code>// 1 0 object {0: 1, 1: 3, 2: 5, length: 3}</code>

<code>// 3 1 object {0: 1, 1: 3, 2: 5, length: 3}</code>

<code>// 5 2 object {0: 1, 1: 9, 2: 5, length: 3}</code>

<code>console.log(o);</code><code>// object {0: 1, 1: 9, 2: 25, length: 3}</code>

參考前面的文章 詳解js周遊 中 foreach的講解,我們知道,foreach無法直接退出循環,隻能使用return 來達到for循環中continue的效果,并且foreach不能在低版本ie(6~8)中使用,相容寫法請參考 polyfill。

every

every() 方法使用傳入的函數測試所有元素,隻要其中有一個函數傳回值為 false,那麼該方法的結果為 false;如果全部傳回 true,那麼該方法的結果才為 true。是以 every 方法存在如下規律:

若需檢測數組中存在元素大于100 (即 one &gt; 100),那麼我們需要在傳入的函數中構造 "false" 傳回值 (即傳回 item &lt;= 100),同時整個方法結果為 false 才表示數組存在元素滿足條件;(簡單了解為:若是單項判斷,可用 one false ===&gt; false)

若需檢測數組中是否所有元素都大于100 (即all &gt; 100)那麼我們需要在傳入的函數中構造 "true" 傳回值 (即傳回 item &gt; 100),同時整個方法結果為 true 才表示數組所有元素均滿足條件。(簡單了解為:若是全部判斷,可用 all true ===&gt; true)

文法同上述foreach,具體還可以參考 詳解js周遊 中every的講解。

以下是鴨式辨型的寫法:

<code>var</code> <code>o = {0:10, 1:8, 2:25, length:3};</code>

<code>var</code> <code>bool = array.prototype.every.call(o,</code><code>function</code><code>(value, index, obj){</code>

<code>  </code><code>return</code> <code>value &gt;= 8;</code>

every 一樣不能在低版本ie(6~8)中使用,相容寫法請參考 polyfill。

some

some() 方法剛好同 every() 方法相反,some 測試數組元素時,隻要有一個函數傳回值為 true,則該方法傳回 true,若全部傳回 false,則該方法傳回 false。some 方法存在如下規律:

若需檢測數組中存在元素大于100 (即 one &gt; 100),那麼我們需要在傳入的函數中構造 "true" 傳回值 (即傳回 item &gt; 100),同時整個方法結果為 true 才表示數組存在元素滿足條件;(簡單了解為:若是單項判斷,可用 one true ===&gt; true)

若需檢測數組中是否所有元素都大于100(即 all &gt; 100),那麼我們需要在傳入的函數中構造 "false" 傳回值 (即傳回 item &lt;= 100),同時整個方法結果為 false 才表示數組所有元素均滿足條件。(簡單了解為:若是全部判斷,可用 all false ===&gt; false)

你注意到沒有,some方法與includes方法有着異曲同工之妙,他們都是探測數組中是否擁有滿足條件的元素,一旦找到,便傳回true。多觀察和總結這種微妙的關聯關系,能夠幫助我們深入了解它們的原理。

some 的鴨式辨型寫法可以參照every,同樣它也不能在低版本ie(6~8)中使用,相容寫法請參考 polyfill。

filter

filter() 方法使用傳入的函數測試所有元素,并傳回所有通過測試的元素組成的新數組。它就好比一個過濾器,篩掉不符合條件的元素。

文法:arr.filter(fn, thisarg)

<code>var</code> <code>array = [18, 9, 10, 35, 80];</code>

<code>var</code> <code>array2 = array.filter(</code><code>function</code><code>(value, index, array){</code>

<code>  </code><code>return</code> <code>value &gt; 20;</code>

<code>console.log(array2);</code><code>// [35, 80]</code>

filter一樣支援鴨式辨型,具體請參考every方法鴨式辨型寫法。其在低版本ie(6~8)的相容寫法請參考 polyfill。

map

map() 方法周遊數組,使用傳入函數處理每個元素,并傳回函數的傳回值組成的新數組。

文法:arr.map(fn, thisarg)

參數介紹同 foreach 方法的參數介紹。

具體用法請參考 詳解js周遊 中 map 的講解。

map 一樣支援鴨式辨型, 具體請參考every方法鴨式辨型寫法。

其在低版本ie(6~8)的相容寫法請參考 polyfill。

reduce

reduce() 方法接收一個方法作為累加器,數組中的每個值(從左至右) 開始合并,最終為一個值。

文法:arr.reduce(fn, initialvalue)

fn 表示在數組每一項上執行的函數,接受四個參數:

previousvalue 上一次調用回調傳回的值,或者是提供的初始值

value 數組中目前被處理元素的值

index 目前元素在數組中的索引

array 數組自身

initialvalue 指定第一次調用 fn 的第一個參數。

當 fn 第一次執行時:

如果 initialvalue 在調用 reduce 時被提供,那麼第一個 previousvalue 将等于 initialvalue,此時 item 等于數組中的第一個值;

如果 initialvalue 未被提供,那麼 previousvaule 等于數組中的第一個值,item 等于數組中的第二個值。此時如果數組為空,那麼将抛出 typeerror。

如果數組僅有一個元素,并且沒有提供 initialvalue,或提供了 initialvalue 但數組為空,那麼fn不會被執行,數組的唯一值将被傳回。

<code>var</code> <code>array = [1, 2, 3, 4];</code>

<code>var</code> <code>s = array.reduce(</code><code>function</code><code>(previousvalue, value, index, array){</code>

<code>  </code><code>return</code> <code>previousvalue * value;</code>

<code>},1);</code>

<code>console.log(s);</code><code>// 24</code>

<code>// es6寫法更加簡潔</code>

<code>array.reduce((p, v) =&gt; p * v);</code><code>// 24</code>

以上回調被調用4次,每次的參數和傳回見下表:

callback

previousvalue

currentvalue

index

array

return value

第1次

1

[1,2,3,4]

第2次

2

第3次

3

6

第4次

4

24

reduce 一樣支援鴨式辨型,具體請參考every方法鴨式辨型寫法。

reduceright

reduceright() 方法接收一個方法作為累加器,數組中的每個值(從右至左)開始合并,最終為一個值。除了與reduce執行方向相反外,其他完全與其一緻,請參考上述 reduce 方法介紹。

entries(es6)

entries() 方法基于ecmascript 2015(es6)規範,傳回一個數組疊代器對象,該對象包含數組中每個索引的鍵值對。

文法:arr.entries()

<code>var</code> <code>array = [</code><code>"a"</code><code>,</code><code>"b"</code><code>,</code><code>"c"</code><code>];</code>

<code>var</code> <code>iterator = array.entries();</code>

<code>console.log(iterator.next().value);</code><code>// [0, "a"]</code>

<code>console.log(iterator.next().value);</code><code>// [1, "b"]</code>

<code>console.log(iterator.next().value);</code><code>// [2, "c"]</code>

<code>console.log(iterator.next().value);</code><code>// undefined, 疊代器處于數組末尾時, 再疊代就會傳回undefined</code>

很明顯,entries 也受益于鴨式辨型,如下:

<code>var</code> <code>iterator = array.prototype.entries.call(o);</code>

由于該方法基于es6,是以目前并不支援所有浏覽器,以下是各浏覽器支援版本:

browser

firefox (gecko)

internet explorer

basic support

38

未實作

25

7.1

find&amp;findindex(es6)

find() 方法基于ecmascript 2015(es6)規範,傳回數組中第一個滿足條件的元素(如果有的話), 如果沒有,則傳回undefined。

findindex() 方法也基于ecmascript 2015(es6)規範,它傳回數組中第一個滿足條件的元素的索引(如果有的話)否則傳回-1。

文法:arr.find(fn, thisarg),arr.findindex(fn, thisarg)

我們發現它們的文法與foreach等十分相似,其實不光文法,find(或findindex)在參數及其使用注意事項上,均與foreach一緻。是以此處将略去 find(或findindex)的參數介紹。下面我們來看個例子?? :

<code>var</code> <code>array = [1, 3, 5, 7, 8, 9, 10];</code>

<code>function</code> <code>f(value, index, array){</code>

<code>  </code><code>return</code> <code>value%2==0;</code><code>// 傳回偶數</code>

<code>function</code> <code>f2(value, index, array){</code>

<code>  </code><code>return</code> <code>value &gt; 20;</code><code>// 傳回大于20的數</code>

<code>console.log(array.find(f));</code><code>// 8</code>

<code>console.log(array.find(f2));</code><code>// undefined</code>

<code>console.log(array.findindex(f));</code><code>// 4</code>

<code>console.log(array.findindex(f2));</code><code>// -1</code>

由于其鴨式辨型寫法也與foreach方法一緻,故此處略去。

相容性上我沒有詳測,可以知道的是,最新版的chrome v47,以及firefox的版本25均實作了它們。

keys(es6)

keys() 方法基于ecmascript 2015(es6)規範,傳回一個數組索引的疊代器。(浏覽器實際實作可能會有調整)

文法:arr.keys()

<code>var</code> <code>array = [</code><code>"abc"</code><code>,</code><code>"xyz"</code><code>];</code>

<code>var</code> <code>iterator = array.keys();</code>

<code>console.log(iterator.next());</code><code>// object {value: 0, done: false}</code>

<code>console.log(iterator.next());</code><code>// object {value: 1, done: false}</code>

<code>console.log(iterator.next());</code><code>// object {value: undefined, done: false}</code>

索引疊代器會包含那些沒有對應元素的索引,如下:

<code>var</code> <code>array = [</code><code>"abc"</code><code>, ,</code><code>"xyz"</code><code>];</code>

<code>var</code> <code>sparsekeys = object.keys(array);</code>

<code>var</code> <code>densekeys = [...array.keys()];</code>

<code>console.log(sparsekeys);</code><code>// ["0", "2"]</code>

<code>console.log(densekeys); </code><code>// [0, 1, 2]</code>

其鴨式辨型寫法請參考上述 entries 方法。

前面我們用array.from生成一個從0到指定數字的新數組,利用keys也很容易實作。

<code>[...array(10).keys()];</code><code>// [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]</code>

<code>[...</code><code>new</code> <code>array(10).keys()];</code><code>// [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]</code>

由于array的特性,new array 和 array 對單個數字的處理相同,是以以上兩種均可行。

keys基于es6,并未完全支援,以下是各浏覽器支援版本:

values(es6)

values() 方法基于ecmascript 2015(es6)規範,傳回一個數組疊代器對象,該對象包含數組中每個索引的值。其用法基本與上述 entries 方法一緻。

文法:arr.values()

遺憾的是,現在沒有浏覽器實作了該方法,是以下面将就着看看吧。

<code>var</code> <code>iterator = array.values();</code>

<code>console.log(iterator.next().value);</code><code>//abc</code>

<code>console.log(iterator.next().value);</code><code>//xyz</code>

symbol.iterator(es6)

該方法基于ecmascript 2015(es6)規範,同 values 方法功能相同。

文法:arr[symbol.iterator]()

<code>var</code> <code>iterator = array[symbol.iterator]();</code>

<code>console.log(iterator.next().value);</code><code>// abc</code>

<code>console.log(iterator.next().value);</code><code>// xyz</code>

由于該方法基于es6,并未完全支援,以下是各浏覽器支援版本:

以上,array.prototype 的各方法基本介紹完畢,這些方法之間存在很多共性。比如:

所有插入元素的方法, 比如 push、unshift,一律傳回數組新的長度;

所有删除元素的方法,比如 pop、shift、splice 一律傳回删除的元素,或者傳回删除的多個元素組成的數組;

部分周遊方法,比如 foreach、every、some、filter、map、find、findindex,它們都包含function(value,index,array){} 和 thisarg 這樣兩個形參。

array.prototype 的所有方法均具有鴨式辨型這種神奇的特性。它們不止可以用來處理數組對象,還可以處理類數組對象。

例如 javascript 中一個純天然的類數組對象字元串(string),像join方法(不改變目前對象自身)就完全适用,可惜的是 array.prototype 中很多方法均會去試圖修改目前對象的 length 屬性,比如說 pop、push、shift, unshift 方法,操作 string 對象時,由于string對象的長度本身不可更改,這将導緻抛出typeerror錯誤。

還記得麼,array.prototype本身就是一個數組,并且它的長度為0。

後續章節我們将繼續探索array的一些事情。感謝您的閱讀!

本問就讨論這麼多内容,大家有什麼問題或好的想法歡迎在下方參與留言和評論。

繼續閱讀