從詳細操做js數組到淺析v8中array.js

前言

最近在寫面試編程題,常常用到數組,常常想偷個懶,用它提供的方法,奈何仍是對數組方法使用不熟練,致使寫了不少的垃圾代碼,不少地方稍加修改的話確定變得簡潔高效優雅👊javascript

因此✍這篇文章本着瞭解一下JavaScript數組的特性,正如標題所寫前端

經過v8中array.js源碼淺析如何本身實現常見的數組方法,若是你想提升本身編碼能力,能夠留下來看看這篇文章java

閱讀完,你將收穫👏git

  • 對常見數組操做方法更加清晰
  • 能手寫常見的數組的方法

若是喜歡的話能夠點贊/關注,支持一下,但願你們能夠看完本文有所收穫

須要下載本文代碼的點GitHubgithub

開始本篇正文吧🉑web


備忘錄

Array基礎

要想手寫數組方法,先補一補基礎,得先會使用它們api面試

建立一個數組

//字面量
            let demo = [1, 2, 3]
            // 構造器
            let demo1 = Array(),
                demo2 = Array(3),
                demo3 = Array(1,2,3),
                demo4 = new Array(1,2,3);
複製代碼

構造函數上的方法

Array.of()

簡單理解就是建立一個新數組的實例,能夠看看與Array構造函數區別算法

語法:編程

Array.of(element0[, element1[, ...[, elementN]]])
複製代碼

用法:api

Array.of(7);       // [7] 
Array.of(1, 2, 3); // [1, 2, 3]

Array(7);          // [ , , , , , , ]
Array(1, 2, 3);    // [1, 2, 3]
複製代碼

二者區別:Array.of(7) 建立一個具備單個元素 7 的數組,而 Array(7) 建立一個長度爲7的空數組(**注意:**這是指一個有7個空位(empty)的數組,而不是由7個undefined組成的數組)。


Array.isArray()

Array.isArray() 用於肯定傳遞的值是不是一個 Array

Array.isArray([1, 2, 3]);  
// true
Array.isArray({foo: 123}); 
// false
Array.isArray("foobar");   
// false
Array.isArray(undefined);  
// false
複製代碼

手動實現

// Array.isArray
            if(!Array.isArray){
                Array.isArray = obj => Object.prototype.toString.call(obj) === '[object Array]'
            }
複製代碼

判斷JS數據類型,能夠看看我以前寫的博客 聊一聊typeof instanceof 實現原理


Array.from()

Array.from() 方法從一個相似數組或可迭代對象建立一個新的,淺拷貝的數組實例。

Array.from(arrayLike[, mapFn[, thisArg]])
複製代碼
參數
  • arrayLike: 必選,能夠傳入 一、類數組(argumentg) 二、可迭代對象(set,map)。
  • mapFn: 可選,至關於Array.from(arrayLike).map(mapFn, thisArg)。
  • thisArg: 可選,執行回調函數mapFn時候的this對象。很是有用,利於解耦。能夠把被處理的數據和對象分離,thisArg中定義處理函數handle,用來在mapFn中返回調用handle以後的結果。
用法

String

// Array.from()
            const demo = Array.from('123')
            console.log(demo) //[ 'a', 'b', 'c' ]
複製代碼

new Set()

const Array_demo = Array.from(new Set([1,2,3,4,1,2,3]))
            console.log(Array_demo)  // [1,2,3,4]
複製代碼

new Map()

const map = new Map([[1, 2], [2, 4], [4, 8]]);
Array.from(map);
// [[1, 2], [2, 4], [4, 8]]
複製代碼

類數組

const fn = (function() {
    const demo = Array.from(arguments);
    console.log(demo);
})(1,2,3); // [ 1, 2, 3 ]
複製代碼

數組去重合並

let fn = function () {
                console.log(arguments)
                const Arr_new = [].concat.apply([],arguments)
                return Array.from(new Set(Arr_new))
            }
            const   demo1 = [1, 2, 3, 4],
                    demo2 = [4,5,6,2,2,2],
                    demo3 = [1,2,3,4,2,2,3,4];
            console.log(fn(demo1,demo2,demo3))
            // [1,2,3,4,5,6]
複製代碼

充分利用第三個參數thisArg

const obj = {
            handle: x => x * 4 
        }
        console.log(Array.from([11, 22, 33], function (x) {
            return this.handle(x)
        }, obj))
        // [44, 88, 132]
複製代碼
思路
  • 判斷arrayLike是否爲空
  • 根據mapFn判斷是否爲構造函數,爲構造函數,每次遍歷時,讓arr[i] = mapFn(iValue,i), 不是構造函數時,arr[i] = iValue
  • 判斷thisArg是否存在,存在的話 arr[i] = mapFn.call(thisArg, iValue,i)

參考源碼在V8中array.js第1763行開啓Array.from之旅

/** * 實現Array.from * toInteger方法:返回一個整數 * toLength方法: 保證len數字合法[0~Number.MAX_SAFE_INTEGER] * Number.MAX_SAFE_INTEGER = Math.pow(2,53) - 1 * 判斷arrayLike 爲 空 拋出錯誤 * mapFn非空而且不是構造函數拋出錯誤 * 每次遍歷arrayLike,若是mapFn存在, arr[i] = mapFn(iValue,i) 不存在的話 arr[i] = iValue * 判斷thisArg是否存在,存在的話 arr[i] = mapFn.call(thisArg, iValue,i) * */
        Array.myfrom = (function () {
            const toStr = Object.prototype.toString
            const isCallable = fn => typeof fn === 'function' || toStr.call(fn) === '[object Function]'
            
            const toInteger = value => {
                const v = Number(value)
                if(isNaN(v))    return 0
                // 無窮大或者0 直接返回
                if(v === 0 || !isFinite(v)) return v
                return (v > 0 ? 1 : -1) * Math.floor(Math.abs(v))
            }
            // 最大的範圍Number.MAX_SAFE_INTEGER
            const maxSafeInteger = Number.MAX_SAFE_INTEGER
            
            const toLength = value => {
                const len = toInteger(value)
                return Math.min(maxSafeInteger, Math.max(0, len))
            }
            return function myfrom (arrayLike/*, mapFn, thisArg*/) {
                const that = this
                if(arrayLike === null) throw new TypeError("Array.from requires an array-like object - not null or undefined")
                
                const items = Object(arrayLike)
                let thisArg = ''
                // 判斷mapFn是否undefined, 這裏最好不要直接使用undefined,由於undefined不是保留字,
                // 頗有可能undefined是個值 最好用 void 0 或者 void undefined 
                const mapFn = arguments.length > 1 ? arguments[1] : void 0
                if( typeof mapFn !== 'undefined') {
                    // 接下來判斷第二個參數是否是構造函數
                    if( !isCallable(mapFn) ) throw new TypeError("Array.from when provided mapFn must be a function")
                    if( arguments.length > 2) thisArg = arguments[2]
                }
                const len = toLength(items.length)
                const arr = isCallable(that) ? Object(new that(len)) : new Array(len)

                let i = 0,
                    iValue;
                while ( i < len) {
                    iValue = items[i]
                    if(mapFn) arr[i] = typeof thisArg === 'undefined' ? mapFn(iValue,i) : mapFn.call(thisArg, iValue, i)
                    else 
                        arr[i] = iValue
                    i++
                }
                arr.length = len
                return arr
            }
        })()
複製代碼

👍不得不說,把Array.from()實現出來後,其實收穫不少東西的。


常見方法

爲了簡單記憶,方便查找,將主要方法分爲三類 : 數組可遍歷方法,會修改原數組方法,返回新數組方法。

遍歷方法

js中遍歷數組並不會改變原始數組的方法總共有12個:

ES5:
    forEach、every 、some、 filter、map、reduce、reduceRight、
    ES6:
    find、findIndex、keys、values、entries
複製代碼

forEach()

語法:

array.forEach(callback(currentValue, index, arr), thisArg)
複製代碼

參數:

callback:爲數組中每一個元素執行的函數,該函數接收一至三個參數
 		  currentValue 數組中正在處理的當前元素
 		  index (可選)  數組中正在處理的當前元素的索引
 		  arr (可選)    forEach() 方法正在操做的數組
 thisArg      可選參數,當執行回調函數callback,用做this值
複製代碼

講一講thisArg用法吧

function Counter() {
  this.sum = 0;
  this.count = 0;
}
Counter.prototype.add = function(array) {
  array.forEach(function(entry) {
    this.sum += entry;
    ++this.count;
  }, this);
  // ^---- Note
};

const obj = new Counter();
obj.add([2, 5, 9]);
obj.count;
// 3 === (1 + 1 + 1)
obj.sum;
// 16 === (2 + 5 + 9)
複製代碼

很明顯,第9行中傳入的this,決定了forEach回調函數中this指向的問題。

第14行,obj調用了add方法,因此this指向的就是obj,也就是forEach中this指向的就是obj這個對象了。

**注意:**若是使用箭頭函數表達式來傳入函數參數, thisArg 參數會被忽略,由於箭頭函數在詞法上綁定了 this

看看源碼v8中array.js第1258行開始forEach之旅

咱們試着模仿寫一個:

/** * Array.prototype.forEach(callback, thisArg) * 除了拋出異常外,沒法終止或者跳出forEach()循環 * 遍歷數組 **/
        Array.prototype.myforEach = function (callback, thisArg) {
            if( this == null ) throw new TypeError("this is null or not defined")
            let newArr = Object(this)
            let len = newArr.length >>> 0
            if( typeof callback !== 'function' ) throw new TypeError(callback + ' is not a function');
            let thatArg = arguments.length >= 2 ? arguments[1] : void 0
            let k = 0

            while( k < len ) {
                
                if(k in newArr){ 
                    callback.call(thatArg, newArr[k], k, newArr);
                }
                k++
            }
            return void 0
        }
複製代碼

從代碼角度來看,你須要注意的點:

  • 沒法中途退出循環,每次你都是調用回調函數的,return只能退出本次回調
  • 該方法返回的是undefined, 即便你return 一個值也沒有用
  • thisArg改變的是回調函數中的this,從源碼中能夠看出來,還有就是若是回調函數是箭頭函數的話,咱們知道箭頭函數是沒法改變this的,因此會忽略thisArg

every()

定義:

測試一個數組內的全部元素是否都能經過某個指定函數的測試。它返回一個布爾值。

語法:

array.every(function(currentValue, index, arr), thisArg) 複製代碼

參數:

callback:爲數組中每一個元素執行的函數,該函數接收一至三個參數
 		  currentValue 數組中正在處理的當前元素
 		  index (可選)  數組中正在處理的當前元素的索引
 		  arr (可選)    every() 方法正在操做的數組
 thisArg      可選參數,當執行回調函數callback,用做this值
複製代碼

用法:

function isBigEnough(element, index, array) {
  return element >= 10;
}
[12, 5, 8, 130, 44].every(isBigEnough);   // false
[12, 54, 18, 130, 44].every(isBigEnough); // true
複製代碼

看看源碼v8中array.js第1322行開始every之旅

/** * Array.prototype.every(callback, thisArg) **/
        Array.prototype.myevery = function (callback, thisArg) {
            if( this == null ) throw new TypeError("this is null or not defined")
            let newArr = Object(this)
            let len = newArr.length >>> 0
            if( typeof callback !== 'function' ) throw new TypeError(callback + ' is not a function');
            let thatArg = arguments.length >= 2 ? arguments[1] : void 0
            let k = 0

            while( k < len ) {
                
                if(k in newArr){ 
                    let testResult = callback.call(thatArg, newArr[k], k, newArr);
                    if( !testResult ) return false
                }
                k++
            }
            return true
        }
複製代碼

從代碼角度來看,你須要注意的點:

  • 空數組的狀況下,只要第一個參數是回調函數,一切狀況返回爲true
  • 要每次返回值都爲true,最後返回true,不然爲false
  • 若是thisArg參數的話,則callback 被調用時的 this 值,在非嚴格模式下爲全局對象,在嚴格模式下傳入 undefined,詳見 this 條目。
  • every不會改變原數組
  • every 遍歷的元素範圍在第一次調用 callback 以前就已肯定了。在調用 every 以後添加到數組中的元素不會被 callback 訪問到。若是數組中存在的元素被更改,則他們傳入 callback 的值是 every 訪問到他們那一刻的值。那些被刪除的元素或歷來未被賦值的元素將不會被訪問到。

some

定義:

測試數組中是否是至少有1個元素經過了被提供的函數測試。它返回的是一個Boolean類型的值

語法:

array.some(function(currentValue, index, arr), thisArg) 複製代碼

參數:

callback:爲數組中每一個元素執行的函數,該函數接收一至三個參數
 		  currentValue 數組中正在處理的當前元素
 		  index (可選)  數組中正在處理的當前元素的索引
 		  arr (可選)    some() 方法正在操做的數組
 thisArg      可選參數,當執行回調函數callback,用做this值
複製代碼

用法:

function isBiggerThan10(element, index, array) {
  return element > 10;
}
[2, 5, 8, 1, 4].some(isBiggerThan10);  // false
[12, 5, 8, 1, 4].some(isBiggerThan10); // true

//此例中爲模仿 includes() 方法, 若元素在數組中存在, 則回調函數返回值爲 true :
var fruits = ['apple', 'banana', 'mango', 'guava'];

function checkAvailability(arr, val) {
  return arr.some(function(arrVal) {
    return val === arrVal;
  });
}

checkAvailability(fruits, 'kela');   // false
checkAvailability(fruits, 'banana'); // true
複製代碼

看看源碼v8中array.js第1298行開始some之旅

/** * 測試數組中是否是至少有1個元素經過了被提供的函數測試 * Array.prototype.some(callback, thisArg) **/
        Array.prototype.mysome = function (callback, thisArg) {
            if (this == null) throw new TypeError("this is null or not defined")
            let newArr = Object(this)
            let len = newArr.length >>> 0
            if (typeof callback !== 'function') throw new TypeError(callback + ' is not a function');
            let thatArg = arguments.length >= 2 ? arguments[1] : void 0

            for (let i = 0; i < len; i++) {
                if (i in newArr && callback.call(thatArg, newArr[i], i, newArr))
                    return true
            }
            return false
        }
複製代碼

從代碼角度來看,你須要注意的點:

  • some不會改變原數組
  • 若是用一個空數組進行測試,在任何狀況下它返回的都是false
  • 若是你回調函數沒有返回值,每次都是undefined,最後調用some結果返回也是false
  • 傳入thisArg,回調函數中的this值,取決於this指向規則。

filter

定義:

建立一個新數組, 其包含經過所提供函數實現的測試的全部元素。

語法:

let newArray = array.filter(function(currentValue, index, arr), thisArg) 複製代碼

參數:

callback:爲數組中每一個元素執行的函數,該函數接收一至三個參數
 		  currentValue 數組中正在處理的當前元素
 		  index (可選)  數組中正在處理的當前元素的索引
 		  arr (可選)    filter() 方法正在操做的數組
 thisArg      可選參數,當執行回調函數callback,用做this值
複製代碼

用法:

function isBigEnough(element) {
  return element >= 10;
}
var filtered = [12, 5, 8, 130, 44].filter(isBigEnough);
// filtered is [12, 130, 44] 

var fruits = ['apple', 'banana', 'grapes', 'mango', 'orange'];

/** * Array filters items based on search criteria (query) */
function filterItems(query) {
  return fruits.filter(function(el) {
      return el.toLowerCase().indexOf(query.toLowerCase()) > -1;
  })
}

console.log(filterItems('ap')); // ['apple', 'grapes']
console.log(filterItems('an')); // ['banana', 'mango', 'orange']
複製代碼

看看源碼v8中array.js第1245行開始filter之旅

/** * 建立一個新數組, 其包含經過所提供函數實現的測試的全部元素。 * Array.prototype.filter(callback, thisArg) * */
        Array.prototype.myfilter = function (callback, thisArg) {
            if (this == null) throw new TypeError("this is null or not defined")
            let newArr = Object(this)
            let len = newArr.length >>> 0
            if (typeof callback !== 'function') throw new TypeError(callback + ' is not a function');
            let thatArg = arguments.length >= 2 ? arguments[1] : void 0,
                resultArr = new Array(len),
                count = 0

            for (let i = 0; i < len; i++) {
                if (i in newArr) {
                    if (typeof thatArg === 'undefined' && callback(newArr[i], i, newArr)) 
                        resultArr[count++] = newArr[i]
                    if (typeof thatArg !== 'undefined' && callback.call(thatArg, newArr[i], i, newArr)) 
                        resultArr[count++] = newArr[i]
                }
            }
            resultArr.length = count
            return resultArr
        }
複製代碼

從代碼角度來看,你須要注意的點:

  • 自定義回調函數要有Boolean返回值,不寫默認返回undefined,則轉Boolean爲false
  • 不會修改原始數組,可是會返回一個新數組,包含經過所提供函數實現的測試因此元素
  • 沒有任何元素經過的話,返回空數組
  • filter 不會改變原數組,它返回過濾後的新數組
  • filter 遍歷的元素範圍在第一次調用 callback 以前就已經肯定了。在調用 filter 以後被添加到數組中的元素不會被 filter 遍歷到。若是已經存在的元素被改變了,則他們傳入 callback 的值是 filter 遍歷到它們那一刻的值。被刪除或歷來未被賦值的元素不會被遍歷到。
  • 若是爲 filter 提供一個 thisArg 參數,則它會被做爲 callback 被調用時的 this 值。不然,callbackthis 值在非嚴格模式下將是全局對象,嚴格模式下爲 undefinedcallback 函數最終觀察到的 this 值是根據一般函數所看到的 "this"的規則肯定的。
  • 特別注意箭頭函數中this指向

map

定義:

建立一個新數組,其結果是該數組中的每一個元素是調用一次提供的回調函數後的返回值。

語法:

let newArray = array.map(function(currentValue, index, arr), thisArg) 複製代碼

參數:

callback:爲數組中每一個元素執行的函數,該函數接收一至三個參數
 		  currentValue 數組中正在處理的當前元素
 		  index (可選)  數組中正在處理的當前元素的索引
 		  arr (可選)    map() 方法正在操做的數組
 thisArg      可選參數,當執行回調函數callback,用做this值
複製代碼

用法:

//數組中每一個元素的平方根
var numbers = [1, 4, 9];
var roots = numbers.map(Math.sqrt);
// roots的值爲[1, 2, 3], numbers的值仍爲[1, 4, 9]

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

// doubles數組的值爲: [2, 8, 18]
// numbers數組未被修改: [1, 4, 9]

//演示如何在一個 String 上使用 map 方法獲取字符串中每一個字符所對應的 ASCII 碼組成的數組:
var map = Array.prototype.map
var a = map.call("Hello World", function(x) { 
  return x.charCodeAt(0); 
})
// a的值爲[72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100]
複製代碼

看看源碼v8中array.js第1333行開始map之旅

/** * 一個由原數組每一個元素執行回調函數的結果組成的新數組 * Array.prototype.map(callback, thisArg) * */
        Array.prototype.mymap = function (callback, thisArg) {
            if (this == null) throw new TypeError("this is null or not defined")
            let newArr = Object(this)
            let len = newArr.length >>> 0
            if (typeof callback !== 'function') throw new TypeError(callback + ' is not a function');
            let thatArg = arguments.length >= 2 ? arguments[1] : void 0,
                resultArr = new Array(len),
                mappedValue

            for (let i = 0; i < len; i++) {
                if (i in newArr) {
                    // 可能會有疑惑的地方
                    mappedValue = callback.call(thatArg, newArr[i], i, newArr)
                    resultArr[i] = mappedValue
                }
            }
            return resultArr
        }
複製代碼

可能有疑惑的地方就是第17行了吧,爲何能夠直接寫這樣子,不須要考慮thisArg爲void 0的狀況,我當時是考慮分狀況考慮的,可是後面想想,哪怕是undefined值,你map執行的是回調函數,回調函數的this取值,非嚴格模式下,是window,

var numbers = [1, 4, 9];
        var doubles = numbers.map(function (num) {
            console.log(this)    // window
            return num * 2;
        }, void 0);
複製代碼

👆在控制檯運行代碼,你會發現傳入thisArg,當值爲undefined時,結果仍是window,嚴格模式下固然就是undefined了,這個留給讀者去思考

從代碼角度來看,你須要注意的點:

  • map不修改調用它的原數組自己(固然能夠在 callback 執行時改變原數組)
  • 回調函數不返回值時,最後新數組的每一個值都爲undefined
  • this的值最終相對於callback函數的可觀察性是依據this規則,也就是this指向問題
  • 由於map生成一個新數組,當你不打算使用返回的新數組卻使用map是違背設計初衷的,請用forEach或者for-of替代。
  • map 方法處理數組元素的範圍是在 callback 方法第一次調用以前就已經肯定了。調用map方法以後追加的數組元素不會被callback訪問。若是存在的數組元素改變了,那麼傳給callback的值是map訪問該元素時的值。

reduce

定義:

對數組中的每一個元素執行一個由您提供的reducer函數(升序執行),將其結果彙總爲單個返回值。

語法:

let result = array.reduce(callback(accumulator, currentValue, currentIndex, array), initialValue)
複製代碼

參數:

callback:爲數組中每一個元素執行的函數,該函數接收一至4個參數
 		    accumulator 累計器
 currentValue 當前值
 currentIndex 當前索引
 array 數組
 initialValue      
做爲第一次調用 callback函數時的第一個參數的值。 若是沒有提供初始值,則將使用數組中的第一個元素。 在沒有初始值的空數組上調用 reduce 將報錯。
複製代碼

用法:

用例的只是簡單用法,更多的reduce高級用法,最後有參考連接👇

const arr = [3, 5, 1, 4, 2];
const a = arr.reduce((t, v) => t + v);
// 等同於
const b = arr.reduce((t, v) => t + v, 0);
複製代碼

看看gif動圖怎麼解釋的👇

這多是最簡單的用法了,下面發散一下思惟😼

// 功能型函數通道
const double = x => x + x;
const triple = x => 3 * x;
const quadruple = x => 4 * x;

// Function composition enabling pipe functionality
const pipe = (...functions) => input => functions.reduce(
    (acc, fn) => fn(acc),
    input
);

// Composed functions for multiplication of specific values
const multiply6 = pipe(double, triple);
const multiply9 = pipe(triple, triple);
const multiply16 = pipe(quadruple, quadruple);
const multiply24 = pipe(double, triple, quadruple);

// Usage
multiply6(6); // 36
multiply9(9); // 81
multiply16(16); // 256
multiply24(10); // 240
複製代碼

有時候,用好reduce真的是使開發變得高效起來✊

看看源碼v8中array.js**第1505行開始reduce之旅

/** * 對數組中的每一個元素執行一個由您提供的reducer函數(升序執行),將其結果彙總爲單個返回值 * Array.prototype.reduce(callback, initialValue) * */
        Array.prototype.myreduce = function (callback /*, initialValue*/ ) {
            if (this == null) throw new TypeError("this is null or not defined")
            let newArr = Object(this)
            let len = newArr.length >>> 0
            if (typeof callback !== 'function') throw new TypeError(callback + ' is not a function');
            let initialValue,
                k = 0

            if (arguments.length >= 2) {
                initialValue = arguments[1]
            } else {
                while (k < len && !(k in newArr))
                    k++
                if (k >= len)
                    throw new TypeError('Reduce of empty array with no initial value')
                initialValue = newArr[k++]
            }

            for (let i = k; i < len; i++) {
                if (i in newArr) {
                    initialValue = callback(initialValue, newArr[i], i, newArr)
                }
            }
            return initialValue
        }
複製代碼

從代碼角度來看,你須要注意的點:

  • 回調函數第一次執行時,accumulatorcurrentValue的取值有兩種狀況:若是調用reduce()時提供了initialValueaccumulator取值爲initialValuecurrentValue取數組中的第一個值;若是沒有提供 initialValue,那麼accumulator取數組中的第一個值,currentValue取數組中的第二個值。
  • 若是沒有提供initialValue,reduce 會從索引1的地方開始執行 callback 方法,跳過第一個索引。若是提供initialValue,從索引0開始。
  • 若是數組爲空且沒有提供initialValue,會拋出TypeError
  • 若是數組僅有一個元素(不管位置如何)而且沒有提供initialValue, 或者有提供initialValue可是數組爲空,那麼此惟一值將被返回而且callback不會被執行。

reduce功能太強大了,有興趣的人,能夠好好去了解一下✊

固然了,有了此次代碼的解析,相信你對reduce有了更深的認識


reduceRight

從右向左累加,跟reduce類似,源碼的實現天然就會了✍

定義:

接受一個函數做爲累加器(accumulator)和數組的每一個值(從右到左)爲單個值。,將其結果彙總爲單個返回值。

語法:

let result = array.reduceRight(callback(accumulator, currentValue, currentIndex, array), initialValue)
複製代碼

參數:

callback:爲數組中每一個元素執行的函數,該函數接收一至4個參數
 		    accumulator 上一次調用回調函數時,回調函數返回的值。
 currentValue 當前值
 currentIndex 當前索引
 array 數組
 initialValue      
首次調用 callback 函數時,累加器 accumulator 的值。若是未提供該初始值,則將使用數組中的最後一個元素,並跳過該元素。
複製代碼

用法:

這裏就舉個跟reduce的區別吧👏

var a = ['1', '2', '3', '4', '5']; 
var left  = a.reduce(function(prev, cur) { return prev + cur; }); 
var right = a.reduceRight(function(prev, cur) { return prev + cur; }); 

console.log(left);  // "12345"
console.log(right); // "54321"
複製代碼

看看源碼v8中array.js**第1505行開始reduceRight之旅

實現的話,就拿一個相似指針的下標,從數組最後一位從後往前模擬😹 注意邊界值就行

find findIndex

本方法在ECMAScript 6規範中被加入,可能不存在於某些實現中。

定義:

**find:**返回數組中知足提供的測試函數的第一個元素的值。不然返回 undefined

findIndex:數組中經過提供測試函數的第一個元素的索引。不然,返回-1。

語法:

let ele = array.find(function(elemnet, index, arr), thisArg) let eleIndex = array.findIndex(function(elemnet, index, arr), thisArg) 複製代碼

參數:

二者語法類似

callback:爲數組中每一個元素執行的函數,該函數接收一至三個參數
 		  elemnet 數組中正在處理的當前元素
 		  index (可選)  數組中正在處理的當前元素的索引
 		  arr (可選)     find方法正在操做的數組
 thisArg      可選參數,當執行回調函數callback,用做this值
複製代碼

find用法:

//尋找數組中的質數
function isPrime(element, index, array) {
  var start = 2;
  while (start <= Math.sqrt(element)) {
    if (element % start++ < 1) { return false; } } return element > 1; } console.log([4, 6, 8, 12].find(isPrime)); // undefined, not found console.log([4, 5, 8, 12].find(isPrime)); // 5 複製代碼

findIndex用法:

//找數組中首個質數元素的索引 不存在素數返回-1
function isPrime(element, index, array) {
  var start = 2;
  while (start <= Math.sqrt(element)) {
    if (element % start++ < 1) { return false; } } return element > 1; } console.log([4, 6, 8, 12].findIndex(isPrime)); // -1, not found console.log([4, 6, 7, 12].findIndex(isPrime)); // 2 複製代碼

看看源碼v8中array.js**第1633行開始find之旅

/** * 返回數組中知足提供的測試函數的第一個元素的值。不然返回 undefined * Array.prototype.find(callback, thisArg) * */
        Array.prototype.myfind = function (callback /*, thisArg */ ) {
            if (this == null) throw new TypeError("this is null or not defined")
            let newArr = Object(this)
            let len = newArr.length >>> 0
            if (typeof callback !== 'function') throw new TypeError(callback + ' is not a function');

            let thatArg = arguments.length >= 2 ? arguments[1] : void 0

            for (let i = 0; i < len; i++) {
                if (i in newArr && callback.call(thatArg, newArr[i], i, newArr))
                    return newArr[i]
            }
            return void 0
        }
複製代碼

findIndex函數實現的原理如出一轍,返回是下標就行❗

從代碼角度來看,你須要注意的點:

  • find方法不會改變原始數組
  • 在第一次調用 callback函數時會肯定元素的索引範圍,所以在 find方法開始執行以後添加到數組的新元素將不會被 callback函數訪問到。
  • 若是數組中一個還沒有被callback函數訪問到的元素的值被callback函數所改變,那麼當callback函數訪問到它時,它的值是將是根據它在數組中的索引所訪問到的當前值。被刪除的元素仍舊會被訪問到,可是其返回值已是undefined了。
  • 我看了不少關於find函數說法,我我的認爲不指定thisArg參數的話,回調函數this指向並非一直都是undefined,更合理的說話,this符合this指向規則

keys & values & entries

定義:

keys()方法返回一個包含數組中每一個索引鍵的**Array Iterator**對象。

values() 方法返回一個新的 Array Iterator 對象,該對象包含數組每一個索引的值

entries() 方法返回一個新的Array Iterator對象,該對象包含數組中每一個索引的鍵/值對。

語法:

arr.entries()
複製代碼

用法:

三者用法類似,舉其中一個例子說明吧

const array1 = ['a', 'b', 'c'];
const iterator1 = array1.entries();
const iterator2 = array1.values();
const iterator3 = array1.keys();
console.log(iterator1);
/*Array Iterator {} __proto__:Array Iterator next:ƒ next() Symbol(Symbol.toStringTag):"Array Iterator" __proto__:Object */	
複製代碼

iterator.next()

var arr = ["a", "b", "c"]; 
var iterator = arr.entries();
console.log(iterator.next());

/*{value: Array(2), done: false} done:false value:(2) [0, "a"] __proto__: Object */
// iterator.next()返回一個對象,對於有元素的數組,
// 是next{ value: Array(2), done: false };
// next.done 用於指示迭代器是否完成:在每次迭代時進行更新並且都是false,
// 直到迭代器結束done纔是true。
// next.value是一個["key","value"]的數組,是返回的迭代器中的元素值。
複製代碼

使用for…of 循環

var arr = ["a", "b", "c"];
var iterator = arr.entries();
// undefined

for (let e of iterator) {
    console.log(e);
}

// [0, "a"] 
// [1, "b"] 
// [2, "c"]
複製代碼

內容好多,但願能夠仔細看看🉑


改變原始數組方法

splice

定義:

經過刪除或替換現有元素或者原地添加新的元素來修改數組,並以數組形式返回被修改的內容,注意此方法會改變原數組

語法:

array.splice(start,deleteCount,item1,.....,itemX)
複製代碼

參數:

start: 指定修改的開始位置(從0計數)
 1. 若是超出了數組的長度,則從數組末尾開始添加內容
 2. 若是是負值,則表示從數組末位開始的第幾位(從-1計數,這意味着-n是倒數第n個元素,而且等價於array.length-n)
 3. 若是負數的絕對值大於數組的長度,則表示開始位置爲第0位
deleteCount(可選) : 整數,表示要移除的數組元素個數	
 1. 若是 deleteCount 大於 start 以後的元素的總數,則從 start 後面的元素都將被 刪除(含第 start 位)
 2. 若是 deleteCount 被省略了,或者它的值大於等於array.length - start(也就是 說,若是它大於或者等於start以後的全部元素的數量),那麼start以後數組的全部元素都會被刪除。
 3. 若是 deleteCount 是 0 或者負數,則不移除元素。這種狀況下,至少應添加一個新 元素。
item1, item2, ...(可選) 
要添加進數組的元素,從start 位置開始。若是不指定,則 splice() 將只刪除數組元素。
複製代碼

用法:

//從第 2 位開始刪除 0 個元素,插入「drum」
var myFish = ["angel", "clown", "mandarin", "sturgeon"];
var removed = myFish.splice(2, 0, "drum");

// 運算後的 myFish: ["angel", "clown", "drum", "mandarin", "sturgeon"]
// 被刪除的元素: [], 沒有元素被刪除
// 從第 2 位開始刪除 0 個元素,插入「drum」 和 "guitar"
var removed2 = myFish.splice(2, 0, 'drum', 'guitar');
// 運算後的 myFish: ["angel", "clown", "drum", "guitar", "mandarin", "sturgeon"]
// 被刪除的元素: [], 沒有元素被刪除
複製代碼

從第 2 位開始刪除 1 個元素,插入「trumpet」

var myFish = ['angel', 'clown', 'drum', 'sturgeon'];
var removed = myFish.splice(2, 1, "trumpet");

// 運算後的 myFish: ["angel", "clown", "trumpet", "sturgeon"]
// 被刪除的元素: ["drum"]
複製代碼

從第 0 位開始刪除 2 個元素,插入"parrot"、"anemone"和"blue"

var myFish = ['angel', 'clown', 'trumpet', 'sturgeon'];
var removed = myFish.splice(0, 2, 'parrot', 'anemone', 'blue');

// 運算後的 myFish: ["parrot", "anemone", "blue", "trumpet", "sturgeon"]
// 被刪除的元素: ["angel", "clown"]
複製代碼

從倒數第 2 位開始刪除 1 個元素

var myFish = ['angel', 'clown', 'mandarin', 'sturgeon'];
var removed = myFish.splice(-2, 1);

// 運算後的 myFish: ["angel", "clown", "sturgeon"]
// 被刪除的元素: ["mandarin"]
複製代碼

從第 2 位開始刪除全部元素

var myFish = ['angel', 'clown', 'mandarin', 'sturgeon'];
var removed = myFish.splice(2);

// 運算後的 myFish: ["angel", "clown"]
// 被刪除的元素: ["mandarin", "sturgeon"]
複製代碼

看看源碼v8中array.js**第876行開始splice之旅

這應該就是簡單的模擬一下吧,惟一煩躁的就是邊界值


sort

定義:

對數組的元素進行排序,並返回數組,注意此方法會改變原數組

語法:

arr.sort([compareFunction])
複製代碼

參數:

compareFunction 可選
1. 用來指定按某種順序進行排列的函數。若是省略,元素按照轉換爲的字符串的各個字符的Unicode位點進行排序。
2. 指明瞭compareFunction,
3. 若是 compareFunction(a, b) 小於 0 ,那麼 a 會被排列到 b 以前;
4. 若是 compareFunction(a, b) 等於 0 , a 和 b 的相對位置不變。
5. 若是 compareFunction(a, b) 大於 0 , b 會被排列到 a 以前。
複製代碼

用法:

var numbers = [4, 2, 5, 1, 3];
numbers.sort(function(a, b) {
  return a - b;
});
console.log(numbers);

//也能夠寫成:
var numbers = [4, 2, 5, 1, 3]; 
numbers.sort((a, b) => a - b); 
console.log(numbers);

// [1, 2, 3, 4, 5]
複製代碼

對非 ASCII 字符排序

//當排序非 ASCII 字符的字符串(如包含相似 e, é, è, a, ä 等字符的字符串)。
//一些非英語語言的字符串須要使用 String.localeCompare。這個函數能夠將函數排序到正確的順序。
var items = ['réservé', 'premier', 'cliché', 'communiqué', 'café', 'adieu'];
items.sort(function (a, b) {
  return a.localeCompare(b);
});

// items is ['adieu', 'café', 'cliché', 'communiqué', 'premier', 'réservé']
複製代碼

排序是一門學問,這裏面有不少的內容,好比一個算法的事件複雜度,空間複雜度,之後待更新吧。


pop

定義:

從數組中刪除最後一個元素,並返回該元素的值。此方法更改數組的長度。

語法:

arr.pop()
//從數組中刪除的元素(當數組爲空時返回undefined)。
複製代碼

描述:

1. pop 方法從一個數組中刪除並返回最後一個元素。
2. pop方法根據 length屬性來肯定最後一個元素的位置。
3. 若是不包含length屬性或length屬性不能被轉成一個數值,會將length置爲0,並返回undefined。
4. 若是你在一個空數組上調用 pop(),它返回  undefined。
複製代碼

用法:

let myFish = ["angel", "clown", "mandarin", "surgeon"];

let popped = myFish.pop();

console.log(myFish); 
// ["angel", "clown", "mandarin"]

console.log(popped); 
// surgeon
複製代碼

🚀🚀🚀 過過過


shift

定義:

從數組中刪除第一個元素,並返回該元素的值。此方法更改數組的長度。

語法:

arr.shift()
//從數組中刪除的元素; 若是數組爲空則返回undefined 。 
複製代碼

描述:

1. shift 方法移除索引爲 0 的元素(即第一個元素),並返回被移除的元素,其餘元素的索引值隨之減 1
2. 若是 length 屬性的值爲 0 (長度爲 0),則返回 undefined。
3. shift 方法並不侷限於數組:這個方法可以經過 call 或 apply 方法做用於相似數組的對象上
4. 對於沒有 length 屬性(從0開始的一系列連續的數字屬性的最後一個)的對象,調用該方法可能沒有任何意義。
複製代碼

用法:

let myFish = ['angel', 'clown', 'mandarin', 'surgeon'];

console.log('調用 shift 以前: ' + myFish);
// "調用 shift 以前: angel,clown,mandarin,surgeon"

var shifted = myFish.shift(); 

console.log('調用 shift 以後: ' + myFish); 
// "調用 shift 以後: clown,mandarin,surgeon" 

console.log('被刪除的元素: ' + shifted); 
// "被刪除的元素: angel"
複製代碼

🚀🚀🚀 應該沒有難點


unshift

定義:

將一個或多個元素添加到數組的開頭,並返回該數組的新長度(該方法修改原有數組

語法:

arr.unshift(element1, ..., elementN)
// element要添加到數組開頭的元素或多個元素。
複製代碼

描述:

1. unshift 方法會在調用它的類數組對象的開始位置插入給定的參數。
2. unshift 特地被設計成具備通用性;這個方法可以經過 call 或 apply 方法做用於類數組對象上
3. 不過對於沒有 length 屬性(表明從0開始的一系列連續的數字屬性的最後一個)的對象,調用該方法可能沒有任何意義。
4. 注意, 若是傳入多個參數,它們會被以塊的形式插入到對象的開始位置,它們的順序和被做爲參數傳入時的順序一致
5. ,傳入多個參數調用一次 unshift ,和傳入一個參數調用屢次 unshift (例如,循環調用),它們將獲得不一樣的結果。例如:
複製代碼

用法:

let arr = [4,5,6];
arr.unshift(1,2,3);
console.log(arr); // [1, 2, 3, 4, 5, 6]

arr = [4,5,6]; // 重置數組
arr.unshift(1);
arr.unshift(2);
arr.unshift(3);
console.log(arr); // [3, 2, 1, 4, 5, 6]
複製代碼

再看一個例子

arr.unshift(0); // result of the call is 3, which is the new array length
// arr is [0, 1, 2]

arr.unshift(-2, -1); // the new array length is 5
// arr is [-2, -1, 0, 1, 2]

arr.unshift([-4, -3]); // the new array length is 6
// arr is [[-4, -3], -2, -1, 0, 1, 2]

arr.unshift([-7, -6], [-5]); // the new array length is 8
// arr is [ [-7, -6], [-5], [-4, -3], -2, -1, 0, 1, 2 ]
複製代碼

🚀🚀🚀 應該沒有難點


push

定義:

將一個或多個元素添加到數組的末尾,並返回該數組的新長度

語法:

arr.push(element1, ..., elementN)
// element要添加到數組末尾的元素或多個元素。
// 放回值:當調用該方法時,新的 length 屬性值將被返回。
複製代碼

描述:

1. push 方法具備通用性。該方法和 call() 或 apply() 一塊兒使用時,可應用在相似數組的對象上。
2. push 方法根據 length 屬性來決定從哪裏開始插入給定的值。
3. 若是 length 不能被轉成一個數值,則插入的元素索引爲 0,包括 length 不存在時。當 length 不存在時,將會建立它。
複製代碼

用法:

添加元素到數組

var sports = ["soccer", "baseball"];
var total = sports.push("football", "swimming");

console.log(sports); 
// ["soccer", "baseball", "football", "swimming"]

console.log(total);  
// 4
複製代碼

像數組同樣使用對象

var obj = {
    length: 0,

    addElem: function addElem (elem) {
        // obj.length is automatically incremented 
        // every time an element is added.
        [].push.call(this, elem);
    }
};

// Let's add some empty objects just to illustrate.
obj.addElem({});
obj.addElem({});
console.log(obj.length);
// → 2
//注意,儘管 obj 不是數組,可是 push 方法成功地使 obj 的 length 屬性增加了,就像咱們處理一個實際的數組同樣。
複製代碼

過吧,應該沒有難點須要將的🚀🚀🚀


reverse

定義:

將數組中元素的位置顛倒,並返回該數組。數組的第一個元素會變成最後一個,數組的最後一個元素變成第一個。該方法會改變原數組。

語法:

arr.reverse()
// 放回值:顛倒後的數組
複製代碼

描述:

1. reverse 方法顛倒數組中元素的位置,改變了數組,並返回該數組的引用。
2. reverse方法是特地類化的;此方法可被 called 或 applied於相似數組對象。
3. 對象若是不包含反映一系列連續的、基於零的數值屬性中的最後一個長度的屬性,則該對象可能不會以任何有意義的方式運行。
複製代碼

用法:

顛倒數組中的元素

const a = [1, 2, 3];

console.log(a); // [1, 2, 3]

a.reverse(); 

console.log(a); // [3, 2, 1]
複製代碼

顛倒類數組中的元素

onst a = {0: 1, 1: 2, 2: 3, length: 3};

console.log(a); // {0: 1, 1: 2, 2: 3, length: 3}

Array.prototype.reverse.call(a); //same syntax for using apply()

console.log(a); // {0: 3, 1: 2, 2: 1, length: 3}
複製代碼

copyWithin

定義:

淺複製數組的一部分到同一數組中的另外一個位置,並返回它,不會改變原數組的長度。

語法:

array.copyWithin(target, start = 0, end = this.length)
// 放回值:改變後的數組。
複製代碼

參數:

target
1. 0 爲基底的索引,複製序列到該位置。若是是負數,target 將從末尾開始計算。
2. 若是 target 大於等於 arr.length,將會不發生拷貝。若是 target 在 start 以後,複製的序列將被修改以符合 arr.length。

start
1. 0 爲基底的索引,開始複製元素的起始位置。若是是負數,start 將從末尾開始計算。
2. 若是 start 被忽略,copyWithin 將會從0開始複製。
end
1. 0 爲基底的索引,開始複製元素的結束位置。copyWithin 將會拷貝到該位置,但不包括 end 這個位置的元素。若是是負數, end 將從末尾開始計算。
2. 若是 end 被忽略,copyWithin 方法將會一直複製至數組結尾(默認爲 arr.length)。
複製代碼

注意:

1. 參數 target、start 和 end 必須爲整數。
2. 若是 start 爲負,則其指定的索引位置等同於 length+start,length 爲數組的長度。end 也是如此。
3. copyWithin 是一個可變方法,它不會改變 this 的長度 length,可是會改變 this 自己的內容,且須要時會建立新的屬性。
複製代碼

用法:

const a = [1, 2, 3];
[1, 2, 3, 4, 5].copyWithin(-2)
// [1, 2, 3, 1, 2]

[1, 2, 3, 4, 5].copyWithin(0, 3)
// [4, 5, 3, 4, 5]

[1, 2, 3, 4, 5].copyWithin(0, 3, 4)
// [4, 2, 3, 4, 5]

[1, 2, 3, 4, 5].copyWithin(-2, -3, -1)
// [1, 2, 3, 3, 4]

[].copyWithin.call({length: 5, 3: 1}, 0, 3);
// {0: 1, 3: 1, length: 5}
console.log(a); // [1, 2, 3]

a.reverse(); 

console.log(a); // [3, 2, 1]
複製代碼

我可能不會用這個方法解決問題吧,看着我都頭疼❌


fill

定義:

用一個固定值填充一個數組中從起始索引到終止索引內的所有元素。不包括終止索引。

語法:

arr.fill(value, start, end )
// 放回值:修改後的數組。
複製代碼

參數:

value
1. 用來填充數組元素的值。

start (可選)
1. 起始索引,默認值爲0。

end  (可選)
1. 終止索引,默認值爲 this.length。
複製代碼

描述:

1. 若是 start 是個負數, 則開始索引會被自動計算成爲 length+start,其中 length 是 this 對象的 length 屬性值
2. fill 方法故意被設計成通用方法, 該方法不要求 this 是數組對象。
3. fill 方法是個可變方法, 它會改變調用它的 this 對象自己, 而後返回它, 而並非返回一個副本。
4. 當一個對象被傳遞給 fill方法的時候, 填充數組的是這個對象的引用。 
複製代碼

用法:

[1, 2, 3].fill(4);               // [4, 4, 4]
[1, 2, 3].fill(4, 1);            // [1, 4, 4]
[1, 2, 3].fill(4, 1, 2);         // [1, 4, 3]
[1, 2, 3].fill(4, 1, 1);         // [1, 2, 3]
[1, 2, 3].fill(4, 3, 3);         // [1, 2, 3]
[1, 2, 3].fill(4, -3, -2);       // [4, 2, 3]
[1, 2, 3].fill(4, NaN, NaN);     // [1, 2, 3]
[1, 2, 3].fill(4, 3, 5);         // [1, 2, 3]
Array(3).fill(4);                // [4, 4, 4]
[].fill.call({ length: 3 }, 4);  // {0: 4, 1: 4, 2: 4, length: 3}
// Objects by reference.
var arr = Array(3).fill({}) // [{}, {}, {}];
// 須要注意若是fill的參數爲引用類型,會致使都執行都一個引用類型
// 如 arr[0] === arr[1] 爲true
arr[0].hi = "hi"; // [{ hi: "hi" }, { hi: "hi" }, { hi: "hi" }]
複製代碼

看看源碼v8中array.js第1700行開始fill之旅

if (!Array.prototype.fill) {
  Object.defineProperty(Array.prototype, 'fill', {
    value: function(value) {

      // Steps 1-2.
      if (this == null) {
        throw new TypeError('this is null or not defined');
      }

      var O = Object(this);

      // Steps 3-5.
      var len = O.length >>> 0;

      // Steps 6-7.
      var start = arguments[1];
      var relativeStart = start >> 0;

      // Step 8.
      var k = relativeStart < 0 ?
        Math.max(len + relativeStart, 0) :
        Math.min(relativeStart, len);

      // Steps 9-10.
      var end = arguments[2];
      var relativeEnd = end === undefined ?
        len : end >> 0;

      // Step 11.
      var final = relativeEnd < 0 ?
        Math.max(len + relativeEnd, 0) :
        Math.min(relativeEnd, len);

      // Step 12.
      while (k < final) {
        O[k] = value;
        k++;
      }
      // Step 13.
      return O;
    }
  });
}
複製代碼

碼了四個小時,我碼不動了✍✍✍,看看別人規範寫法吧,放過我吧😭


不改變原始數組方法

slice

定義:

返回一個新的數組對象,這一對象是一個由 beginend 決定的原數組的淺拷貝(包括 begin,不包括end)。原始數組不會被改變。

關於深淺拷貝,能夠看看我這篇面試如何寫出一個滿意的深拷貝(適合初級前端)

語法:

arr.slice([begin[, end]])
複製代碼

參數:

begin (可選)
 1. 提取起始處的索引(從 0 開始),從該索引開始提取原數組元素。
 2. 若是該參數爲負數,則表示從原數組中的倒數第幾個元素開始提取
 3. slice(-2) 表示提取原數組中的倒數第二個元素到最後一個元素(包含最後一個元素)
 4. 若是省略 begin,則 slice 從索引 0 開始。
 5. 若是 begin 大於原數組的長度,則會返回空數組。	
 
 end   (可選)
 1.	 slice(1,4) 會提取原數組中從第二個元素開始一直到第四個元素的全部元素 (索引爲 1, 2, 3的元素)
 2. 若是該參數爲負數, 則它表示在原數組中的倒數第幾個元素結束抽取。
 3. 若是 end 被省略,則 slice 會一直提取到原數組末尾。
 4. 若是 end 大於數組的長度,slice 也會一直提取到原數組末尾。
複製代碼

用法:

返回現有數組的一部分

var fruits = ['Banana', 'Orange', 'Lemon', 'Apple', 'Mango'];
var citrus = fruits.slice(1, 3);

// fruits contains ['Banana', 'Orange', 'Lemon', 'Apple', 'Mango']
// citrus contains ['Orange','Lemon']
複製代碼

當數組中存在引用類型的值時,淺拷貝的是引用類型地址

// 使用 slice 方法從 myCar 中建立一個 newCar。
var myHonda = { color: 'red', wheels: 4, engine: { cylinders: 4, size: 2.2 } };
var myCar = [myHonda, 2, "cherry condition", "purchased 1997"];
var newCar = myCar.slice(0, 2);
newCar[0].color = 'blue';
console.log(myHonda.color)  // bule
複製代碼

類數組對象轉換爲數組

function list() {
  return Array.prototype.slice.call(arguments);
}

var list1 = list(1, 2, 3); // [1, 2, 3]
//你也能夠簡單的使用 [].slice.call(arguments) 來代替
複製代碼

看看源碼v8中array.js第762行開始slice之旅

(function () {
 'use strict';
  var _slice = Array.prototype.slice;

  try {
    // Can't be used with DOM elements in IE < 9
    _slice.call(document.documentElement);
  } catch (e) { // Fails in IE < 9
    // This will work for genuine arrays, array-like objects, 
    // NamedNodeMap (attributes, entities, notations),
    // NodeList (e.g., getElementsByTagName), HTMLCollection (e.g., childNodes),
    // and will not fail on other DOM objects (as do DOM elements in IE < 9)
    Array.prototype.slice = function(begin, end) {
      // IE < 9 gets unhappy with an undefined end argument
      end = (typeof end !== 'undefined') ? end : this.length;

      // For native Array objects, we use the native slice function
      if (Object.prototype.toString.call(this) === '[object Array]'){
        return _slice.call(this, begin, end); 
      }
      // For array like object we handle it ourselves.
      var i, cloned = [],
        size, len = this.length;
      // Handle negative value for "begin"
      var start = begin || 0;
      start = (start >= 0) ? start : Math.max(0, len + start);
      // Handle negative value for "end"
      var upTo = (typeof end == 'number') ? Math.min(end, len) : len;
      if (end < 0) {
        upTo = len + end;
      }
      // Actual expected size of the slice
      size = upTo - start;
      if (size > 0) {
        cloned = new Array(size);
        if (this.charAt) {
          for (i = 0; i < size; i++) {
            cloned[i] = this.charAt(start + i);
          }
        } else {
          for (i = 0; i < size; i++) {
            cloned[i] = this[start + i];
          }
        }
      }

      return cloned;
    };
  }
}());
複製代碼

我更多的以爲這個是一個模擬的過程,惟一有點難把握的就是邊界值的肯定,因此找來了一份規範下的代碼,大家能夠參考一下。

從代碼角度來看,你須要注意的點:

  • 用法的話,看看參數一章節就行啦🈯
  • 關於深淺拷貝的問題,若是該元素是個對象引用 (不是實際的對象),slice 會拷貝這個對象引用到新的數組裏。兩個對象引用都引用了同一個對象。若是被引用的對象發生改變,則新的和原來的數組中的這個元素也會發生改變。
  • 若是向兩個數組任一中添加了新元素,則另外一個不會受到影響。
  • 深淺拷貝,能夠看看這篇文章面試如何寫出一個滿意的深拷貝(適合初級前端)

join

定義:

將一個數組(或一個類數組對象)的全部元素鏈接成一個字符串並返回這個字符串。若是數組只有一個項目,那麼將返回該項目而不使用分隔符。

語法:

arr.join(separator)
複製代碼

參數:

separator (可選)
指定一個字符串來分隔數組的每一個元素。
若是須要,將分隔符轉換爲字符串。
若是缺省該值,數組元素用逗號(,)分隔。
若是separator是空字符串(""),則全部元素之間都沒有任何字符。
複製代碼

用法:

使用四種不一樣的分隔符鏈接數組元素

var a = ['Wind', 'Rain', 'Fire'];
var myVar1 = a.join();      // myVar1的值變爲"Wind,Rain,Fire"
var myVar2 = a.join(', ');  // myVar2的值變爲"Wind, Rain, Fire"
var myVar3 = a.join(' + '); // myVar3的值變爲"Wind + Rain + Fire"
var myVar4 = a.join('');    // myVar4的值變爲"WindRainFire"
複製代碼

鏈接類數組對象

function f(a, b, c) {
  var s = Array.prototype.join.call(arguments);
  console.log(s); // '1,a,true'
}
f(1, 'a', true);
複製代碼

看看源碼v8中array.js第468行開始join之旅

待更新吧,思路取出數組或者類數組對象每一項,最後去跟separator完成字符串的拼接便可


toString

定義:

返回一個字符串,表示指定的數組及其元素。

語法:

arr.toString()
複製代碼

當一個數組被做爲文本值或者進行字符串鏈接操做時,將會自動調用其 toString 方法。

用法:

const array1 = [1, 2, 'a', '1a'];
console.log(array1.toString());
// expected output: "1,2,a,1a"
複製代碼

concat

定義:

用於合併兩個或多個數組。此方法不會更改現有數組,而是返回一個新數組。

語法:

var newArr =oldArray.concat(arrayX,arrayX,......,arrayX)
複製代碼

參數:

arrayx(可選)
將數組和/或值鏈接成新數組。
若是省略了valueN參數參數,則concat會返回一個它所調用的已存在的數組的淺拷貝。
複製代碼

用法:

如下代碼將兩個數組合併爲一個新數組:

var alpha = ['a', 'b', 'c'];
var numeric = [1, 2, 3];

alpha.concat(numeric);
// result in ['a', 'b', 'c', 1, 2, 3]
複製代碼

鏈接三個數組

var num1 = [1, 2, 3],
    num2 = [4, 5, 6],
    num3 = [7, 8, 9];
var nums = num1.concat(num2, num3);
console.log(nums); 
// results in [1, 2, 3, 4, 5, 6, 7, 8, 9]
複製代碼

將值鏈接到數組

var alpha = ['a', 'b', 'c'];

var alphaNumeric = alpha.concat(1, [2, 3]);

console.log(alphaNumeric); 
// results in ['a', 'b', 'c', 1, 2, 3]
複製代碼

注意:

  • concat方法不會改變this或任何做爲參數提供的數組,而是返回一個淺拷貝
  • concat將對象引用複製到新數組中。 原始數組和新數組都引用相同的對象。 也就是說,若是引用的對象被修改,則更改對於新數組和原始數組都是可見的。 這包括也是數組的數組參數的元素。
  • 數組/值在鏈接時保持不變。此外,對於新數組的任何操做(僅當元素不是對象引用時)都不會對原始數組產生影響,反之亦然。

indexOf

定義:

返回在數組中能夠找到一個給定元素的第一個索引,若是不存在,則返回-1。

語法:

array.indexOf(searchElement,fromIndex)
複製代碼

參數:

searchElement  (必選)  要查找的元素
fromIndex 
1. 開始查找的位置。若是該索引值大於或等於數組長度,意味着不會在數組裏查找,返回-1。
2. 若是參數中提供的索引值是一個負值,則將其做爲數組末尾的一個抵消,即-1表示從最後一個元素開始查找
3. 注意:若是參數中提供的索引值是一個負值,並不改變其查找順序,查找順序仍然是從前向後查詢數組。若是抵消後的索引值仍小於0,則整個數組都將會被查詢。其默認值爲0.
4. 採用的是嚴格等於 === 
複製代碼

用法:

indexOf方法肯定多個值在數組中的位置

var array = [2, 5, 9];
array.indexOf(2);     // 0
array.indexOf(7);     // -1
array.indexOf(9, 2);  // 2
array.indexOf(2, -1); // -1
array.indexOf(2, -3); // 0
複製代碼

找出指定元素出現的全部位置

var indices = [];
var array = ['a', 'b', 'a', 'c', 'a', 'd'];
var element = 'a';
var idx = array.indexOf(element);
while (idx != -1) {
  indices.push(idx);
  idx = array.indexOf(element, idx + 1);
}
console.log(indices);
// [0, 2, 4]
複製代碼

indexOf()不能識別NaN

let a = ['啦啦', 2, 4, 24, NaN]
        console.log(a.indexOf('啦')); // -1 
        console.log(a.indexOf(NaN)); // -1 
        console.log(a.indexOf('啦啦')); // 0
複製代碼

看看源碼v8中array.js第1411行開始indexOf之旅

if (!Array.prototype.indexOf) {
  Array.prototype.indexOf = function(searchElement, fromIndex) {

    var k;
    // 1. Let O be the result of calling ToObject passing
    // the this value as the argument.
    if (this == null) {
      throw new TypeError('"this" is null or not defined');
    }

    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 len is 0, return -1.
    if (len === 0) {
      return -1;
    }
    // 5. If argument fromIndex was passed let n be
    // ToInteger(fromIndex); else let n be 0.
    var n = +fromIndex || 0;

    if (Math.abs(n) === Infinity) {
      n = 0;
    }
    // 6. If n >= len, return -1.
    if (n >= len) {
      return -1;
    }
    // 7. If n >= 0, then Let k be n.
    // 8. Else, n<0, Let k be len - abs(n).
    // If k is less than 0, then let k be 0.
    k = Math.max(n >= 0 ? n : len - Math.abs(n), 0);
    // 9. Repeat, while k < len
    while (k < len) {
      if (k in O && O[k] === searchElement) {
        return k;
      }
      k++;
    }
    return -1;
  };
}
複製代碼

這是一份規範下的代碼,大家能夠參考一下,我就不寫了,我還要去約會吶,沒時間了🙏

lastIndexOf

定義:

返回指定元素(也即有效的 JavaScript 值或變量)在數組中的最後一個的索引,若是不存在則返回 -1。從數組的後面向前查找,從 fromIndex 處開始。

語法:

arr.lastIndexOf(searchElement,fromIndex)
複製代碼

參數:

searchElement  (必選)  要查找的元素
fromIndex 
1. 今後位置開始逆向查找
2. 默認爲數組的長度減 1(arr.length - 1),即整個數組都被查找
3. 若是該值大於或等於數組的長度,則整個數組會被查找。若是爲負值,將其視爲從數組末尾向前的偏移
4. 即便該值爲負,數組仍然會被從後向前查找。若是該值爲負時,其絕對值大於數組長度,則方法返回 -1,即數組不會被查找。
複製代碼

用法:

數組中該元素最後一次出現的索引,如未找到返回-1。

var array = [2, 5, 9, 2];
var index = array.lastIndexOf(2);
// index is 3
index = array.lastIndexOf(7);
// index is -1
index = array.lastIndexOf(2, 3);
// index is 3
index = array.lastIndexOf(2, 2);
// index is 0
index = array.lastIndexOf(2, -2);
// index is 0
index = array.lastIndexOf(2, -1);
// index is 3
複製代碼

看上一個indexOf怎麼實現的吧🤳,沒時間了。


總結

欲哭無淚🤥,碼了一天+代碼才補完這些JS數組知識,發現1.2W字👍👍👍不少定義借鑑官網,主要是怕誤導不少跟我同樣屬於基礎的前端人員,因此用官網的標準術語。

代碼有難度的都親自寫了,收穫不少,須要鍛鍊本身代碼能力的,能夠好好來練一練。

若是喜歡的話能夠點贊👍👍👍/關注,支持一下,但願你們能夠看完本文有所收穫

參考

MDN_Array

JS數組奇巧淫技

V8源碼

詳解JS遍歷

25個你不得不知道的數組reduce高級用法

相關文章
相關標籤/搜索