万字长文深入掌握 JS 数组用法 - 浅析 V8 源码 array.js

发表于 4年以前  | 总阅读数:605 次

前言

最近在写面试编程题,经常用到数组,经常想偷个懒,用它提供的方法,奈何还是对数组方法使用不熟练,导致写了很多的垃圾代码,很多地方稍加修改的话肯定变得简洁高效优雅

所以✍这篇文章本着了解一下JavaScript数组的特性,正如标题所写

「通过v8中array.js源码浅析如何自己实现常见的数组方法」,如果你想提高自己编码能力,可以留下来看看这篇文章

「阅读完,你将收获」

  • 对常见数组操作方法更加清晰
  • 能手写常见的数组的方法

❝需要下载本文代码的点GitHub

开始本篇正文吧


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动图怎么解释的

这可能是最简单的用法了,下面发散一下思维

// 功能型函数通道
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字」很多定义借鉴官网,主要是怕误导很多跟我一样属于基础的前端人员,所以用官网的标准术语。

代码有难度的都亲自写了,收获很多,需要锻炼自己代码能力的,可以好好来练一练。

本文由哈喽比特于4年以前收录,如有侵权请联系我们。
文章来源:https://mp.weixin.qq.com/s/4yFqof8_q7kLzo6WSD5Mrw

 相关推荐

刘强东夫妇:“移民美国”传言被驳斥

京东创始人刘强东和其妻子章泽天最近成为了互联网舆论关注的焦点。有关他们“移民美国”和在美国购买豪宅的传言在互联网上广泛传播。然而,京东官方通过微博发言人发布的消息澄清了这些传言,称这些言论纯属虚假信息和蓄意捏造。

发布于:1年以前  |  808次阅读  |  详细内容 »

博主曝三大运营商,将集体采购百万台华为Mate60系列

日前,据博主“@超能数码君老周”爆料,国内三大运营商中国移动、中国电信和中国联通预计将集体采购百万台规模的华为Mate60系列手机。

发布于:1年以前  |  770次阅读  |  详细内容 »

ASML CEO警告:出口管制不是可行做法,不要“逼迫中国大陆创新”

据报道,荷兰半导体设备公司ASML正看到美国对华遏制政策的负面影响。阿斯麦(ASML)CEO彼得·温宁克在一档电视节目中分享了他对中国大陆问题以及该公司面临的出口管制和保护主义的看法。彼得曾在多个场合表达了他对出口管制以及中荷经济关系的担忧。

发布于:1年以前  |  756次阅读  |  详细内容 »

抖音中长视频App青桃更名抖音精选,字节再发力对抗B站

今年早些时候,抖音悄然上线了一款名为“青桃”的 App,Slogan 为“看见你的热爱”,根据应用介绍可知,“青桃”是一个属于年轻人的兴趣知识视频平台,由抖音官方出品的中长视频关联版本,整体风格有些类似B站。

发布于:1年以前  |  648次阅读  |  详细内容 »

威马CDO:中国每百户家庭仅17户有车

日前,威马汽车首席数据官梅松林转发了一份“世界各国地区拥车率排行榜”,同时,他发文表示:中国汽车普及率低于非洲国家尼日利亚,每百户家庭仅17户有车。意大利世界排名第一,每十户中九户有车。

发布于:1年以前  |  589次阅读  |  详细内容 »

研究发现维生素 C 等抗氧化剂会刺激癌症生长和转移

近日,一项新的研究发现,维生素 C 和 E 等抗氧化剂会激活一种机制,刺激癌症肿瘤中新血管的生长,帮助它们生长和扩散。

发布于:1年以前  |  449次阅读  |  详细内容 »

苹果据称正引入3D打印技术,用以生产智能手表的钢质底盘

据媒体援引消息人士报道,苹果公司正在测试使用3D打印技术来生产其智能手表的钢质底盘。消息传出后,3D系统一度大涨超10%,不过截至周三收盘,该股涨幅回落至2%以内。

发布于:1年以前  |  446次阅读  |  详细内容 »

千万级抖音网红秀才账号被封禁

9月2日,坐拥千万粉丝的网红主播“秀才”账号被封禁,在社交媒体平台上引发热议。平台相关负责人表示,“秀才”账号违反平台相关规定,已封禁。据知情人士透露,秀才近期被举报存在违法行为,这可能是他被封禁的部分原因。据悉,“秀才”年龄39岁,是安徽省亳州市蒙城县人,抖音网红,粉丝数量超1200万。他曾被称为“中老年...

发布于:1年以前  |  445次阅读  |  详细内容 »

亚马逊股东起诉公司和贝索斯,称其在购买卫星发射服务时忽视了 SpaceX

9月3日消息,亚马逊的一些股东,包括持有该公司股票的一家养老基金,日前对亚马逊、其创始人贝索斯和其董事会提起诉讼,指控他们在为 Project Kuiper 卫星星座项目购买发射服务时“违反了信义义务”。

发布于:1年以前  |  444次阅读  |  详细内容 »

苹果上线AppsbyApple网站,以推广自家应用程序

据消息,为推广自家应用,苹果现推出了一个名为“Apps by Apple”的网站,展示了苹果为旗下产品(如 iPhone、iPad、Apple Watch、Mac 和 Apple TV)开发的各种应用程序。

发布于:1年以前  |  442次阅读  |  详细内容 »

特斯拉美国降价引发投资者不满:“这是短期麻醉剂”

特斯拉本周在美国大幅下调Model S和X售价,引发了该公司一些最坚定支持者的不满。知名特斯拉多头、未来基金(Future Fund)管理合伙人加里·布莱克发帖称,降价是一种“短期麻醉剂”,会让潜在客户等待进一步降价。

发布于:1年以前  |  441次阅读  |  详细内容 »

光刻机巨头阿斯麦:拿到许可,继续对华出口

据外媒9月2日报道,荷兰半导体设备制造商阿斯麦称,尽管荷兰政府颁布的半导体设备出口管制新规9月正式生效,但该公司已获得在2023年底以前向中国运送受限制芯片制造机器的许可。

发布于:1年以前  |  437次阅读  |  详细内容 »

马斯克与库克首次隔空合作:为苹果提供卫星服务

近日,根据美国证券交易委员会的文件显示,苹果卫星服务提供商 Globalstar 近期向马斯克旗下的 SpaceX 支付 6400 万美元(约 4.65 亿元人民币)。用于在 2023-2025 年期间,发射卫星,进一步扩展苹果 iPhone 系列的 SOS 卫星服务。

发布于:1年以前  |  430次阅读  |  详细内容 »

𝕏(推特)调整隐私政策,可拿用户发布的信息训练 AI 模型

据报道,马斯克旗下社交平台𝕏(推特)日前调整了隐私政策,允许 𝕏 使用用户发布的信息来训练其人工智能(AI)模型。新的隐私政策将于 9 月 29 日生效。新政策规定,𝕏可能会使用所收集到的平台信息和公开可用的信息,来帮助训练 𝕏 的机器学习或人工智能模型。

发布于:1年以前  |  428次阅读  |  详细内容 »

荣耀CEO谈华为手机回归:替老同事们高兴,对行业也是好事

9月2日,荣耀CEO赵明在采访中谈及华为手机回归时表示,替老同事们高兴,觉得手机行业,由于华为的回归,让竞争充满了更多的可能性和更多的魅力,对行业来说也是件好事。

发布于:1年以前  |  423次阅读  |  详细内容 »

AI操控无人机能力超越人类冠军

《自然》30日发表的一篇论文报道了一个名为Swift的人工智能(AI)系统,该系统驾驶无人机的能力可在真实世界中一对一冠军赛里战胜人类对手。

发布于:1年以前  |  423次阅读  |  详细内容 »

AI生成的蘑菇科普书存在可致命错误

近日,非营利组织纽约真菌学会(NYMS)发出警告,表示亚马逊为代表的电商平台上,充斥着各种AI生成的蘑菇觅食科普书籍,其中存在诸多错误。

发布于:1年以前  |  420次阅读  |  详细内容 »

社交媒体平台𝕏计划收集用户生物识别数据与工作教育经历

社交媒体平台𝕏(原推特)新隐私政策提到:“在您同意的情况下,我们可能出于安全、安保和身份识别目的收集和使用您的生物识别信息。”

发布于:1年以前  |  411次阅读  |  详细内容 »

国产扫地机器人热销欧洲,国产割草机器人抢占欧洲草坪

2023年德国柏林消费电子展上,各大企业都带来了最新的理念和产品,而高端化、本土化的中国产品正在不断吸引欧洲等国际市场的目光。

发布于:1年以前  |  406次阅读  |  详细内容 »

罗永浩吐槽iPhone15和14不会有区别,除了序列号变了

罗永浩日前在直播中吐槽苹果即将推出的 iPhone 新品,具体内容为:“以我对我‘子公司’的了解,我认为 iPhone 15 跟 iPhone 14 不会有什么区别的,除了序(列)号变了,这个‘不要脸’的东西,这个‘臭厨子’。

发布于:1年以前  |  398次阅读  |  详细内容 »
 相关文章
Android插件化方案 5年以前  |  237231次阅读
vscode超好用的代码书签插件Bookmarks 2年以前  |  8065次阅读
 目录