JS數組中那些你知道或不知道的

JS中的Array

ecma-262中的定義:

Array對象是一種特殊對象,它會對數組索引屬性鍵進行特殊處理。javascript

每一個Array對象都有一個不可配置的length屬性,其最大值是232 - 1html

Array()

當且僅當不帶參數調用Array構造函數時,此描述才適用。

執行過程:java

  1. 定義 numberOfArgs 傳遞給此函數的調用的實參數量;
  2. 斷言: numberOfArgs 爲 0;
  3. 若是 NewTargetundefined ,就設置 newTarget活動函數對象(active-function-object,正在運行的執行上下文的函數組件) ,而且讓 newTarget 成爲 NewTarget
  4. 原型 proto 怎麼辦?經過原生方法GetPrototypeFromConstructor(newTarget, "%ArrayPrototype%")來構造;
  5. 返回原生方法ArrayCreate(0, proto)

魚頭注:NewTarget是啥?NewTarget是原生Class FunctionCallbackInfo(函數調用的callback上下文的信息)內的一個不變量,用來定義構造調用時的返回值(new.target)。node

Array(len)

當且僅當使用一個參數調用Array構造函數時,此描述才適用。

執行過程:git

  1. 定義 numberOfArgs 爲傳遞給此函數的調用的實參數量;
  2. 斷言: numberOfArgs 爲1;
  3. 若是 NewTargetundefined ,就設置 newTarget活動函數對象 ,而且讓 newTarget 成爲 NewTarget
  4. 原型 proto 怎麼辦?經過原生方法GetPrototypeFromConstructor(newTarget, "%ArrayPrototype%")來構造;
  5. 而後定義arrayArrayCreate(0, proto)
  6. 若是 len 的類型不是個Number,則:github

    1. 定義 defineStatusCreateDataProperty(array, "0", len)
    2. 斷言:defineStatus爲真;
    3. intLen(初始化長度) 爲1。
  7. 或者:面試

    1. 定義intLenToUint32(len)(原生方法,將len轉換成0到232-1之間的整數值)
    2. 若是intLen不等於len,拋出RangeError異常。
  8. 執行Set(array, "length", intLen, true)(原生方法,給對象的屬性賦值)
  9. 返回array

Array(...items)

當且僅當使用至少兩個參數調用Array構造函數時,此描述才適用。

執行過程:算法

  1. 定義 numberOfArgs 爲傳遞給此函數的調用的實參數量;
  2. 斷言: numberOfArgs 大於等於2;
  3. 若是 NewTargetundefined ,就設置 newTarget活動函數對象 ,而且讓 newTarget 成爲 NewTarget
  4. 原型 proto 怎麼辦?經過原生方法GetPrototypeFromConstructor(newTarget, "%ArrayPrototype%")來構造;
  5. 而後定義arrayArrayCreate(numberOfArgs, proto)
  6. 定義 k 爲0;
  7. 定義 items爲 正序傳入參數的 零源(zero-origined) 列表;
  8. 重複,當 k 小於 numberOfArgs數組

    1. 定義 PkToSting(k)
    2. 定義 itemKitem[k]
    3. 定義 defineStatusCreateDataProperty(array, Pk, itemK);
    4. 斷言:defineStatus爲真;
    5. k 加1。
  9. 斷言: arraylength 值爲 numberOfArgs
  10. 返回 array

empty

上面的三種狀況以上即是構造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) 時:

  1. 用原生方法 GetPrototypeFromConstructor 生成原型 proto
  2. 判斷 args 的類型;
  3. 若是爲 undefined,則直接返回建立數組的原生方法 ArrayCreate
  4. 若是爲 number,則用原生方法 Set 建立 args 長度的數組,並經過原生方法 CloneElementAt 來建立 argsempty 做爲數組元素,若是args 大於 232 - 1 的話,會報錯;
  5. 若是爲其餘類型,則把 args 變成數組元素,並用 原生方法 CreateDataProperty 建立參數,而後返回建立數組的原生方法 ArrayCreate

類型轉換

類型轉換是一個常常出如今一些網上常見面試題或者奇技淫巧中的內容。那麼關於數組的類型轉換,又是怎樣的呢?

首先咱們要知道,在 JS 中類型轉換隻有三種狀況,分別是:

  • 轉換爲布爾值
  • 轉換爲數字
  • 轉換爲字符串

轉換爲原始類型

對象在轉換類型的時候,會執行原生方法ToPrimitive

其算法以下:

  1. 若是已是 原始類型,則返回當前值;
  2. 若是須要轉 字符串 則先調用toSting方法,若是此時是 原始類型 則直接返回,不然再調用valueOf方法並返回結果;
  3. 若是不是 字符串,則先調用valueOf方法,若是此時是 原始類型 則直接返回,不然再調用toString方法並返回結果;
  4. 若是都沒有 原始類型 返回,則拋出 TypeError類型錯誤。

固然,咱們能夠經過重寫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-0NaN,則返回 false;其餘狀況則返回 true
String 若是參數爲空字符串,則返回 false;不然返回 true
Symbol 返回 true
Object 返回 true

轉換爲數字

對象轉換爲數字的規則以下表:

參數類型 結果
Undefined 返回 NaN
Null Return +0.
Boolean 若是參數爲 true,則返回 1false則返回 +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"

因此各位是否理解上述隱式轉換的答案呢?

關於API使用的一些經驗與思考

JS數組自帶了不少的方法,在現代工程化數據驅動的理念下,這些方法都是很是重要的。

loops

forEachArray 方法中比較基本的一個,做用也很簡單,與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則沒有這個問題。

reduce

Array ES5 API reducereduceRight,能夠輕鬆實現並歸元素的功能,例子以下:

若是咱們須要實現一個這樣的對象:

{
    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的言論,其實之前的瀏覽器也是這種狀況。

詳情能夠看知乎的這篇評論:https://www.zhihu.com/question/54637225/answer/140362071

性能對好比下:

如下代碼測試環境爲:Chrome 55.0.2883 / Windows 7 0.0.0

image
image

因此在9012年的現在,結果又會是如何呢?

如下代碼測試環境爲:Chrome 73.0.3683 / Windows 10 0.0.0

image

image

經過上面的對比,結果已經很明顯了,咱們要知道,現代的瀏覽器性能優化已經作得比之前好不少了,再加上電子設備自己的硬件也愈來愈好,因此代碼塊的性能不是咱們首要的考慮因素。

在跟同行溝通的過程當中,常常會看到有人爲了扣那麼一個兩個表達式的性能而煩惱,實際上是這是沒有任何須要,緣由也如上,咱們應該優化的是咱們表達式是否清晰明瞭,是否適合後期維護或拓展。

若是你喜歡探討技術,或者對本文有任何的意見或建議,很是歡迎加魚頭微信好友一塊兒探討,固然,魚頭也很是但願能跟你一塊兒聊生活,聊愛好,談天說地。
魚頭的微信號是:krisChans95
也能夠掃碼添加好友,備註「SF」就行
https://fish-pond-1253945200.cos.ap-guangzhou.myqcloud.com/img/base/wx-qrcode1.jpg

相關文章
相關標籤/搜索