- 做者:陳大魚頭
- github: KRISACHAN
ecma-262中的定義:javascript
Array對象是一種特殊對象,它會對數組索引屬性鍵進行特殊處理。html
每一個Array對象都有一個不可配置的length屬性,其最大值是232 - 1。java
當且僅當不帶參數調用Array構造函數時,此描述才適用。node
執行過程:git
GetPrototypeFromConstructor(newTarget, "%ArrayPrototype%")
來構造;ArrayCreate(0, proto)
。魚頭注:NewTarget是啥?NewTarget是原生Class FunctionCallbackInfo(函數調用的callback上下文的信息)內的一個不變量,用來定義構造調用時的返回值(new.target)。github
當且僅當使用一個參數調用Array構造函數時,此描述才適用。面試
執行過程:算法
GetPrototypeFromConstructor(newTarget, "%ArrayPrototype%")
來構造;ArrayCreate(0, proto)
;當且僅當使用至少兩個參數調用Array構造函數時,此描述才適用。數組
執行過程:瀏覽器
GetPrototypeFromConstructor(newTarget, "%ArrayPrototype%")
來構造;ArrayCreate(numberOfArgs, proto)
;上面的三種狀況以上即是構造Array()時不一樣狀況的具體實現。可是咱們從上面的斷言能夠知道,構造結果有可能爲真,有可能爲假。還有是定義指定長度數組時會出現什麼事呢?
在V8源碼 3.28.71(node0.12.18)中 Array 有個CloneElementAt的方法。定義以下:
在指定索引處克隆元素時,若是克隆失敗,則返回一個空句柄(任何緣由)。
從這句話咱們能夠知道,當咱們構造一個指定長度的 Array 時,因爲有長度,因此會開闢相應下標的空間,可是由於該下標並無元素,因此就會返回empty,任何緣由構造數組元素失敗時,都會返回一個empty。
示例以下:
var arr = new Array(10);
arr // [empty × 10]
複製代碼
上面是 ECMA 上的定義以及 V8 源碼的容錯處理,其實簡單來講就是:
調用 Array(args)
時:
GetPrototypeFromConstructor
生成原型 proto
;args
的類型;undefined
,則直接返回建立數組的原生方法 ArrayCreate
;number
,則用原生方法 Set
建立 args
長度的數組,並經過原生方法 CloneElementAt
來建立 args 個 empty 做爲數組元素,若是args
大於 232 - 1 的話,會報錯;args
變成數組元素,並用 原生方法 CreateDataProperty
建立參數,而後返回建立數組的原生方法 ArrayCreate
。類型轉換是一個常常出如今一些網上常見面試題或者奇技淫巧中的內容。那麼關於數組的類型轉換,又是怎樣的呢?
首先咱們要知道,在 JS 中類型轉換隻有三種狀況,分別是:
對象在轉換類型的時候,會執行原生方法ToPrimitive。
其算法以下:
toSting
方法,若是此時是 原始類型 則直接返回,不然再調用valueOf
方法並返回結果;valueOf
方法,若是此時是 原始類型 則直接返回,不然再調用toString
方法並返回結果;固然,咱們能夠經過重寫Symbol.toPrimitive
來制定轉換規則,此方法在轉原始類型時調用優先級最高。
// 下面例子來自YCK的小冊
const data = {
valueOf () {
return 1;
},
toString () {
return '1';
},
[Symbol.toPrimitive]() {
return 2;
}
};
data + 1 // 3
複製代碼
對象轉換爲布爾值的規則以下表:
參數類型 | 結果 |
---|---|
Undefined | 返回 false 。 |
Null | 返回 false 。 |
Boolean | 返回 當前參數。 |
Number | 若是參數爲+0 、-0 或NaN ,則返回 false ;其餘狀況則返回 true 。 |
String | 若是參數爲空字符串,則返回 false ;不然返回 true 。 |
Symbol | 返回 true 。 |
Object | 返回 true 。 |
對象轉換爲數字的規則以下表:
參數類型 | 結果 |
---|---|
Undefined | 返回 NaN 。 |
Null | Return +0. |
Boolean | 若是參數爲 true ,則返回 1 ;false 則返回 +0 。 |
Number | 返回當前參數。 |
String | 先調用 ToPrimitive,再調用 ToNumber,而後返回結果。 |
Symbol | 拋出 TypeError 錯誤。 |
Object | 先調用 ToPrimitive,再調用 ToNumber,而後返回結果。 |
對象轉換爲字符串的規則以下表:
參數類型 | 結果 |
---|---|
Undefined | 返回 "undefined" 。 |
Null | 返回 "null" 。 |
Boolean | 若是參數爲 true ,則返回 "true" ;不然返回 "false" 。 |
Number | 調用 NumberToString,而後返回結果。 |
String | 返回 當前參數。 |
Symbol | 拋出 TypeError 錯誤。 |
Object | 先調用 ToPrimitive,再調用 ToString,而後返回結果。 |
因此經過上面的轉換規則,咱們是否可以輕鬆地看懂如下的隱式轉換呢?
[1,2,3] + {a: 1, b: 2} // "1,2,3[object Object]"
[1,2,3] + 1 // "1,2,31"
[1,2,3] + true // "1,2,3true"
[1,2,3] + undefined // "1,2,3undefined"
[1,2,3] + null // "1,2,3null"
[1,2,3] + '123' // "1,2,3123"
[1,2,3] + Symbol('biu') // "Uncaught TypeError"
複製代碼
因此各位是否理解上述隱式轉換的答案呢?
JS數組自帶了不少的方法,在現代工程化數據驅動的理念下,這些方法都是很是重要的。
forEach
是 Array 方法中比較基本的一個,做用也很簡單,與for
,就是遍歷,循環。不一樣的是,forEach
能夠選擇自定義上下文環境。例子以下:
var arr1 = [1, 2, 3];
var arr2 = [5, 6, 7];
arr1.forEach(function (e, i, a) {
console.log(e, this); // this爲arr2
}, arr2);
複製代碼
可是若是forEach
的回調函數是用箭頭函數定義的,那麼就沒法改變它本來指向的上下文環境。例子以下:
var arr1 = [1, 2, 3];
var arr2 = [5, 6, 7];
arr1.forEach((e, i, a) => {
console.log(e, this); // this爲window對象
}, arr2);
複製代碼
因此若是咱們要實現如下這個功能:
<!-- 點擊當前li時,當前li文字變色,其他兄弟li變回默認顏色 -->
<ul>
<li class="1">1</li>
<li class="2">2</li>
<li class="3">3</li>
<li class="4">4</li>
<li class="5">5</li>
</ul>
<script> 'use strict'; var ul = document.querySelector('ul'); ul.onClick = event => { var cls = event.target.className; ul.querySelectorAll('li').forEach(el => { el.style.color = (cls === el.className ? '#FFF' : '#FF0'); }); }; </script>
複製代碼
在ES6之前的環境中,若是直接用for
循環,會出現只能獲取到最後一個元素的問題,可是用forEach
則沒有這個問題。
Array ES5 API reduce
跟reduceRight
,能夠輕鬆實現並歸元素的功能,例子以下:
若是咱們須要實現一個這樣的對象:
{
a: 1,
b: 2,
c: 3
...
};
複製代碼
那麼用reduce就會變得很簡單:
var newArr = 'a,b,c,d,e,f'.split(',').reduce((acc, cur, idx) => {
let o = {};
if (Object.prototype.toString.call(acc) !== '[object Object]') {
o[cur] = idx;
} else {
let newO = {};
newO[cur] = idx;
o = {
...acc,
...newO,
};
};
return o;
}, 'a');
複製代碼
上面演示了經過JS數組API實現的一些功能,因此與for
循環比性能如何呢?
var arr = new Array(100);
arr.forEach(data => {
console.log(data);
});
for (var i = 0; i < arr.length; ++i) {
console.log(arr[i]);
};
複製代碼
因此哪一個更耗時間呢?
在公佈結果以前,其實網上一直流傳着for循環性能比forEach性能好,考慮性能少用forEach的言論,其實之前的瀏覽器也是這種狀況。
詳情能夠看知乎的這篇評論:www.zhihu.com/question/54…
性能對好比下:
如下代碼測試環境爲:Chrome 55.0.2883 / Windows 7 0.0.0
因此在9012年的現在,結果又會是如何呢?
如下代碼測試環境爲:Chrome 73.0.3683 / Windows 10 0.0.0
經過上面的對比,結果已經很明顯了,咱們要知道,現代的瀏覽器性能優化已經作得比之前好不少了,再加上電子設備自己的硬件也愈來愈好,因此代碼塊的性能不是咱們首要的考慮因素。
在跟同行溝通的過程當中,常常會看到有人爲了扣那麼一個兩個表達式的性能而煩惱,實際上是這是沒有任何須要,緣由也如上,咱們應該優化的是咱們表達式是否清晰明瞭,是否適合後期維護或拓展。