数组の学习

本文详细介绍了ES6新增的Array.of和Array.from方法,讨论了数组的判断、改变与不改变自身的方法,展示了数组遍历、扁平化、排序等实用技巧,以及多种数组方法的底层实现,涵盖了数组构造器、类数组对象、数据结构应用等内容。
摘要由CSDN通过智能技术生成

Array 构造器

Array 构造器用于创建一个新的数组

new Array(arg1, arg2,…),参数长度为 0 或长度大于等于 2 时,传入的参数将按照顺序依次成为新数组的第 0 至第 N 项(参数长度为 0 时,返回空数组);

new Array(len),当 len 不是数值时,处理同上,返回一个只包含 len 元素一项的数组;当 len 为数值时,len 最大不能超过 32 位无符号整型,即需要小于 2 的 32 次方(len 最大为 Math.pow(2,32)),否则将抛出 RangeError。

ES6 新增的构造方法:Array.of 和 Array.from

Array.of

Array.of方法用于将一组值(不管是数字还是其他),转换为数组。

Array.of(8.0); // [8]
Array(8.0); // [empty × 8]
Array.of(8.0, 5); // [8, 5]
Array(8.0, 5); // [8, 5]
Array.of('8'); // ["8"]
Array('8'); // ["8"]

Array.from

Array.from方法用于将两对象转为真正的数组:类似数组的对象(array-like object)和可遍历(iterable)的对象(包括 ES6 新增的数据结构 Set 和 Map)。

注意:是返回新的数组,不改变原对象

三个参数:

  1. 类似数组的对象,必选;

  2. 加工函数,新生成的数组会经过该函数的加工再返回;

  3. this 作用域,表示加工函数执行时 this 的值。

var obj = {0: 'a', 1: 'b', 2:'c', length: 3};
Array.from(obj, function(value, index){
  console.log(value, index, this, arguments.length);
  return value.repeat(3);   //必须指定返回值,否则返回 undefined
}, obj);
// a 0 {0: "a", 1: "b", 2: "c", length: 3} 2
// b 1 {0: "a", 1: "b", 2: "c", length: 3} 2
// c 2 {0: "a", 1: "b", 2: "c", length: 3} 2
//  ["aaa", "bbb", "ccc"]

// String
Array.from('abc');         // ["a", "b", "c"]
// Set
Array.from(new Set(['abc', 'def'])); // ["abc", "def"]
// Map
Array.from(new Map([[1, 'ab'], [2, 'de']])); 
// [[1, 'ab'], [2, 'de']]

Array 的判断

Array.isArray 用来判断一个变量是否为数组类型

  • 在 ES6 提供该方法之前,我们至少有如下 5 种方式去判断一个变量是否为数组。

    var a = [];
    // 1.基于instanceof
    a instanceof Array;
    // 2.基于constructor
    a.constructor === Array;
    // 3.基于Object.prototype.isPrototypeOf
    Array.prototype.isPrototypeOf(a);
    // 4.基于getPrototypeOf
    Object.getPrototypeOf(a) === Array.prototype;
    // 5.基于Object.prototype.toString
    Object.prototype.toString.apply(a) === '[object Array]';
    
  • ES6 之后新增了一个 Array.isArray 方法,能直接判断数据类型是否为数组,但是如果 isArray 不存在,那么 Array.isArray 的 polyfill 通常可以这样写:

    if (!Array.isArray){
      Array.isArray = function(arg){
        return Object.prototype.toString.call(arg) === '[object Array]';
      };
    }
    

改变自身的方法

pop、push、reverse、shift、sort、splice、unshift,以及两个 ES6 新增的方法 copyWithin 和 fill

// pop方法
var array = ["cat", "dog", "cow", "chicken", "mouse"];
var item = array.pop();
console.log(array); // ["cat", "dog", "cow", "chicken"]
console.log(item); // mouse
// push方法
var array = ["football", "basketball",  "badminton"];
var i = array.push("golfball");
console.log(array); 
// ["football", "basketball", "badminton", "golfball"]
console.log(i); // 4
// reverse方法
var array = [1,2,3,4,5];
var array2 = array.reverse();
console.log(array); // [5,4,3,2,1]
console.log(array2===array); // true
// shift方法
var array = [1,2,3,4,5];
var item = array.shift();
console.log(array); // [2,3,4,5]
console.log(item); // 1
// unshift方法
var array = ["red", "green", "blue"];
var length = array.unshift("yellow");
console.log(array); // ["yellow", "red", "green", "blue"]
console.log(length); // 4
// sort方法
var array = ["apple","Boy","Cat","dog"];
var array2 = array.sort();
console.log(array); // ["Boy", "Cat", "apple", "dog"]
console.log(array2 == array); // true
// splice方法
var array = ["apple","boy"];
var splices = array.splice(1,1);
console.log(array); // ["apple"]
console.log(splices); // ["boy"]
// copyWithin方法
var array = [1,2,3,4,5]; 
var array2 = array.copyWithin(0,3);
console.log(array===array2,array2);  // true [4, 5, 3, 4, 5]
// fill方法
var array = [1,2,3,4,5];
var array2 = array.fill(10,0,3);
console.log(array===array2,array2); 
// true [10, 10, 10, 4, 5], 可见数组区间[0,3]的元素全部替换为10

不改变自身的方法

concat、join、slice、toString、toLocateString、indexOf、lastIndexOf、未形成标准的 toSource,以及 ES7 新增的方法 includes

// concat方法
var array = [1, 2, 3];
var array2 = array.concat(4,[5,6],[7,8,9]);
console.log(array2); // [1, 2, 3, 4, 5, 6, 7, 8, 9]
console.log(array); // [1, 2, 3], 可见原数组并未被修改
// join方法
var array = ['We', 'are', 'Chinese'];
console.log(array.join()); // "We,are,Chinese"
console.log(array.join('+')); // "We+are+Chinese"
// slice方法
var array = ["one", "two", "three","four", "five"];
console.log(array.slice()); // ["one", "two", "three","four", "five"]
console.log(array.slice(2,3)); // ["three"]
// toString方法
var array = ['Jan', 'Feb', 'Mar', 'Apr'];
var str = array.toString();
console.log(str); // Jan,Feb,Mar,Apr
// tolocalString方法
var array= [{name:'zz'}, 123, "abc", new Date()];
var str = array.toLocaleString();
console.log(str); // [object Object],123,abc,2016/1/5 下午1:06:23
// indexOf方法
var array = ['abc', 'def', 'ghi','123'];
console.log(array.indexOf('def')); // 1
// includes方法
var array = [-0, 1, 2];
console.log(array.includes(+0)); // true
console.log(array.includes(1)); // true
var array = [NaN];
console.log(array.includes(NaN)); // true

数组遍历的方法

不会改变自身的遍历方法一共有 12 个,分别为 forEach、every、some、filter、map、reduce、reduceRight,以及 ES6 新增的方法 entries、find、findIndex、keys、values

// forEach方法
var array = [1, 3, 5];
var obj = {name:'cc'};
var sReturn = array.forEach(function(value, index, array){
  array[index] = value;
  console.log(this.name); // cc被打印了三次, this指向obj
},obj);
console.log(array); // [1, 3, 5]
console.log(sReturn); // undefined, 可见返回值为undefined
// every方法
var o = {0:10, 1:8, 2:25, length:3};
var bool = Array.prototype.every.call(o,function(value, index, obj){
  return value >= 8;
},o);
console.log(bool); // true
// some方法
var array = [18, 9, 10, 35, 80];
var isExist = array.some(function(value, index, array){
  return value > 20;
});
console.log(isExist); // true 
// map 方法
var array = [18, 9, 10, 35, 80];
array.map(item => item + 1);
console.log(array);  // [19, 10, 11, 36, 81]
// filter 方法
var array = [18, 9, 10, 35, 80];
var array2 = array.filter(function(value, index, array){
  return value > 20;
});
console.log(array2); // [35, 80]
// reduce方法
var array = [1, 2, 3, 4];
var s = array.reduce(function(previousValue, value, index, array){
  return previousValue * value;
},1);
console.log(s); // 24
// ES6写法更加简洁
array.reduce((p, v) => p * v); // 24
// reduceRight方法 (和reduce的区别就是从后往前累计)
var array = [1, 2, 3, 4];
array.reduceRight((p, v) => p * v); // 24
// entries方法
var array = ["a", "b", "c"];
var iterator = array.entries();
console.log(iterator.next().value); // [0, "a"]
console.log(iterator.next().value); // [1, "b"]
console.log(iterator.next().value); // [2, "c"]
console.log(iterator.next().value); // undefined, 迭代器处于数组末尾时, 再迭代就会返回undefined
// find & findIndex方法
var array = [1, 3, 5, 7, 8, 9, 10];
function f(value, index, array){
  return value%2==0;     // 返回偶数
}
function f2(value, index, array){
  return value > 20;     // 返回大于20的数
}
console.log(array.find(f)); // 8
console.log(array.find(f2)); // undefined
console.log(array.findIndex(f)); // 4
console.log(array.findIndex(f2)); // -1
// keys方法
[...Array(10).keys()];     // [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
[...new Array(10).keys()]; // [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
// values方法
var array = ["abc", "xyz"];
var iterator = array.values();
console.log(iterator.next().value);//abc
console.log(iterator.next().value);//xyz

reduce 到底能解决什么问题呢?
两个参数:
首先是 callback(一个在数组的每一项中调用的函数,接受四个参数):
previousValue(上一次调用回调函数时的返回值,或者初始值)
currentValue(当前正在处理的数组元素)
currentIndex(当前正在处理的数组元素下标)
array(调用 reduce() 方法的数组)
然后是 initialValue(可选的初始值,作为第一次调用回调函数时传给 previousValue 的值)

```javascript
/* 题目:数组 arr = [1,2,3,4] 求数组的和:*/
// 第一种方法:
var arr = [1,2,3,4];
var sum = 0;
arr.forEach(function(e){sum += e;}); // sum = 10
// 第二种方法
var arr = [1,2,3,4];
var sum = 0;
arr.map(function(obj){sum += obj});
// 第三种方法
var arr = [1,2,3,4];
arr.reduce(function(pre,cur){return pre + cur});
```

题目: var arr = [ {name: ‘brick1’}, {name: ‘brick2’}, {name: ‘brick3’} ]

希望最后返回到 arr 里面每个对象的 name 拼接数据为 ‘brick1, brick2 & brick3’ ,如果用 reduce 如何实现呢?

var arr =  [ {name: 'one'}, {name: 'two'}, {name: 'three'} ];
arr.reduce(function(prev, current, index, array){
  if (index === 0){
    return current.name;
  } else if (index === array.length - 1){
    return prev + ' & ' + current.name;
  } else {
    return prev + ', ' + current.name;
  }
}, '');
// 返回结果 "one, two & three"

所有插入元素的方法,比如 push、unshift 一律返回数组新的长度;

所有删除元素的方法,比如 pop、shift、splice 一律返回删除的元素,或者返回删除的多个元素组成的数组;

部分遍历方法,比如 forEach、every、some、filter、map、find、findIndex,它们都包含 function(value,index,array){} 和 thisArg 这样两个形参。

类数组

函数里面的参数对象 arguments;

用 getElementsByTagName/ClassName/Name 获得的 HTMLCollection;

用 querySelector 获得的 NodeList。

arguments

它的对象只定义在函数体中,包括了函数的参数和其他属性

function foo(name, age, sex) {
    console.log(arguments);
    console.log(typeof arguments);
    console.log(Object.prototype.toString.call(arguments));
}
foo('jack', '18', 'male');

通过console.log(arguments);打印结果可以看到有一个属性为callee,callee就是函数自身,如果在函数内部直接执行调用 callee 的话,那它就会不停地执行当前函数,直到执行到内存溢出

HTMLCollection

HTMLCollection 简单来说是 HTML DOM 对象的一个接口,这个接口包含了获取到的 DOM 元素集合,返回的类型是类数组对象,如果用 typeof 来判断的话,它返回的是 ‘object’。它是及时更新的,当文档中的 DOM 变化时,它也会随之变化

NodeList

NodeList 对象是节点的集合,通常是由 querySlector 返回的。虽然 NodeList 不是一个数组,但是可以使用 for…of 来迭代。

类数组的应用场景

遍历参数操作 定义链接字符串函数 传递参数使用

遍历参数操作

我们在函数内部可以直接获取 arguments 这个类数组的值,那么也可以对于参数进行一些操作

function add() {
    var sum = 0,
        len = arguments.length;
    for(var i = 0; i < len; i++){
        sum += arguments[i];
    }
    return sum;
}
add()                           // 0
add(1)                          // 1
add(12)                       // 3
add(1,2,3,4);                   // 10

定义链接字符串函数

function myConcat(separa) {
  var args = Array.prototype.slice.call(arguments, 1);
  return args.join(separa);
}
myConcat(", ", "red", "orange", "blue");
// "red, orange, blue"
myConcat("; ", "elephant", "lion", "snake");
// "elephant; lion; snake"
myConcat(". ", "one", "two", "three", "four", "five");
// "one. two. three. four. five"

传递参数使用

// 使用 apply 将 foo 的参数传递给 bar
function foo() {
    bar.apply(this, arguments);
}
function bar(a, b, c) {
   console.log(a, b, c);
}
foo(1, 2, 3)   //1 2 3

如何将类数组转换成数组

  1. 借助apply 和call 方法

    var arrayLike = { 
      0: 'java',
      1: 'script',
      length: 2
    } 
    // 用 call 的方法来借用 Array 原型链上的 push 方法,可以实现一个类数组的 push 方法
    Array.prototype.push.call(arrayLike, 'jack', 'lily'); 
    console.log(typeof arrayLike); // 'object'
    console.log(arrayLike);
    // {0: "java", 1: "script", 2: "jack", 3: "lily", length: 4}
    
    function sum(a, b) {
      // 将 arguments 通过借用Array 原型链上数组的方法转换为真正的数组,最后都又通过数组的 reduce 方法实现了参数转化的真数组 args 的相加,最后返回预期的结果。
      let args = Array.prototype.slice.call(arguments);
      // let args = [].slice.call(arguments); // 这样写也是一样效果
      console.log(args.reduce((sum, cur) => sum + cur));
    }
    sum(1, 2);  // 3
    function sum(a, b) {
      let args = Array.prototype.concat.apply([], arguments);
      console.log(args.reduce((sum, cur) => sum + cur));
    }
    sum(1, 2);  // 3
    
  2. ES6的方法转数组

    Array.from 方法 展开运算符的方法

    function sum(a, b) {
      let args = Array.from(arguments);
      console.log(args.reduce((sum, cur) => sum + cur));
    }
    sum(1, 2);    // 3
    function sum(a, b) {
      let args = [...arguments];
      console.log(args.reduce((sum, cur) => sum + cur));
    }
    sum(1, 2);    // 3
    function sum(...args) {
      console.log(args.reduce((sum, cur) => sum + cur));
    }
    sum(1, 2);    // 3
    

实现数组扁平化的 6 种方式

数组扁平化: 将一个嵌套多层的数组 array(嵌套可以是任何层数)转换为只有一层的数组

方式一: 普通的递归

function flatten(arr) {
  const res = []
  for (let i = 0; i < arr.length; i++) {
    if (Array.isArray(arr[i])) {
      res.push(...flatten(arr[i]))
    } else {
      res.push(arr[i])
    }
  }
  return res
}

方式二:利用 reduce 函数迭代

function flatten(arr) {
  return arr.reduce((pre, curr) => pre.concat(Array.isArray(curr) ? flatten(curr) : curr),[])
}

方式三:扩展运算符实现

function flatten(arr) {
  while (arr.some(a => Array.isArray(a))) {
    arr = [].concat(...arr)
  }
  return arr
}

方式四:split 和 toString 共同处理

function flatten(arr) {
  return arr.toString().split(',')
}
// 输出结果:[ '1', '2', '3', '4', '5' ]

方式五:调用 ES6 中的 flat

function flatten(arr) {
  return arr.flat(Infinity);
}

方式六:正则和 JSON 方法共同处理

function flatten(arr) {
  let str = JSON.stringify(arr);
  str = str.replace(/(\[|\])/g, '')
  return JSON.parse(`[${str}]`)
}

数组排序

实现方式

数据结构算法中,根据他们的特性可以分为两种类型:比较类排序和非比较类排序
比较类排序:通过比较来决定元素间的相对次序,其时间复杂度不能突破 O(nlogn),因此也称为非线性时间比较类排序。
非比较类排序:不通过比较来决定元素间的相对次序,它可以突破基于比较排序的时间下界,以线性时间运行,因此也称为线性时间非比较类排序。
排序算法

数据结构部分展开说明

sort 排序实现原理

1. 基本使用

sort 方法是对数组元素进行排序,默认排序顺序是先将元素转换为字符串,然后再进行排序

语法: arr.sort([compareFunction])

其中 compareFunction 用来指定按某种顺序进行排列的函数
如果省略不写,元素按照转换为字符串的各个字符的 Unicode 位点进行排序
如果指明了 compareFunction 参数 ,那么数组会按照调用该函数的返回值排序

2. 底层实现

V8源码Sort部分:https://github.com/v8/v8/blob/98d735069d0937f367852ed968a33210ceb527c2/src/js/array.js#L709

当 n<=10 时,采用插入排序;

当 n>10 时,采用三路快速排序;

10<n<=1000,采用中位数作为哨兵元素;

n>1000,每隔 200~215 个元素挑出一个元素,放到一个新数组中,然后对它排序,找到中间位置的数,以此作为中位数。

JS数组多个方法的底层实现

Array.push

ECMA数组的push标准: https://tc39.es/ecma262/#sec-array.prototype.push

根据相关描述,将其转换为如下代码:

Array.prototype.push = function(...items) {
  let O = Object(this);  // ECMA 中提到的先转换为对象
  let len = this.length >>> 0;
  let argCount = items.length >>> 0;
  // 2 ^ 53 - 1 为JS能表示的最大正整数
  if (len + argCount > 2 ** 53 - 1) {
    throw new TypeError("The number of array is over the max value")
  }
  for(let i = 0; i < argCount; i++) {
    O[len + i] = items[i];
  }
  let newLength = len + argCount;
  O.length = newLength;
  return newLength;
}

关键点就在于给数组本身循环添加新的元素 item,然后调整数组的长度 length 为最新的长度,即可完成 push 的底层实现

Array.pop

ECMA数组的pop标准: https://tc39.es/ecma262/#sec-array.prototype.pop

根据相关描述,将其转换为如下代码:

Array.prototype.pop = function() {
    let O = Object(this);
    let len = this.length >>> 0;
    if (len === 0) {
        O.length = 0;
        return undefined
    }
    len--;
    let value = O[len];
    delete O[len];
    O.length = len;
    return value
}

其核心思路还是在于删掉数组自身的最后一个元素,index 就是数组的 len 长度,然后更新最新的长度,最后返回的元素的值,即可达到想要的效果。另外就是在当长度为 0 的时候,如果执行 pop 操作,返回的是 undefined,需要做一下特殊处理

Array.map

ECMA数组的map标准: https://tc39.es/ecma262/#sec-array.prototype.map

根据相关描述,将其转换为如下代码:

Array.prototype.map = function(callbackfn, thisArg) {
    if (this === null || this === undefined) {
        throw new TypeError('Cannot read property "map" of null');
    }
    if (Object.prototype.toString.call(callbackfn) !== '[Object Function]') {
        throw new TypeError(callbackfn + 'is not a function')
    }
    let O = Object(this);
    let T = thisArg;
    let len = this.length >>> 0;
    let A = new Array(len);
    for (let k = 0; k < len; k++) {
        if (k in O) {
            let kValue = O[k];
            // 依次传入this,当前项,当前索引,整个数组
            let mappedValue = callbackfn.call(T, kValue, k, O);
            A[k] = mappedValue
        }
    }
    return A
}

循环遍历实现 map 的思路,将处理过后的 mappedValue 赋给一个新定义的数组 A,最后返回这个新数组 A,并不改变原数组的值。

Array.reduce

ECMA数组的reduce标准: https://tc39.es/ecma262/#sec-array.prototype.reduce

根据相关描述,将其转换为如下代码:

Array.prototype.reduce = function (callbackfn, initialValue) {
  if (this === null || this === undefined) {
    throw new TypeError('Cannot read property "reduce" of null');
  }
  if (Object.prototype.toString.call(callbackfn) !== "[Object Function]") {
    throw new TypeError(callbackfn + "is not a function");
  }
  let O = Object(this);
  let len = this.length >>> 0;
  if (len === 0 && initialValue === undefined) {
    throw new TypeError("Reduce of empty array with no initial value");
  }
  let k = 0;
  let accumulator = initialValue;
  if (accumulator === undefined) {
    for (; k < len; k++) {
      if (k in O) {
        accumulator = O[k];
        k++;
        break;
      }
    }
    throw new Error("Each element of the array is empty");
  }
  for (; k < len; k++) {
    if (k in O) {
      // 注意 reduce 的核心累加器
      accumulator = callbackfn.call(undefined, accumulator, O[k], O);
    }
  }
  return accumulator;
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值