[乾貨👍]從詳細操作js數組到淺析v8中array.js

NO IMAGE

前言

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

所以✍這篇文章本著瞭解一下JavaScript數組的特性,正如標題所寫

「其他文章:」

「通過v8中array.js源碼淺析如何自己實現常見的數組方法」,如果你想提高自己編碼能力,可以留下來看看這篇文章

「閱讀完,你將收穫👏」

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

如果喜歡的話可以點贊/關注,支持一下,希望大家可以看完本文有所收穫

需要下載本文代碼的點GitHub

開始本篇正文吧🉑


[乾貨👍]從詳細操作js數組到淺析v8中array.js

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]]])

用法:

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: 必選,可以傳入 1、類數組(argumentg) 2、可迭代對象(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動圖怎麼解釋的👇

[乾貨👍]從詳細操作js數組到淺析v8中array.js

這可能是最簡單的用法了,下面發散一下思維😼

// 功能型函數通道
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高級用法

「算法與數據結構」鏈表的9個基本操作

相關文章

小邵教你玩轉ES6

小邵教你玩轉JS面向對象

小邵教你玩轉Generator+co/asyncawait

小邵教你玩轉promise源碼