tiantingrui / daily-harvest

记录每日收获
MIT License
2 stars 0 forks source link

JS 数组原理 #10

Open tiantingrui opened 3 years ago

tiantingrui commented 3 years ago

深入JS数组

  1. 数组API 总结归纳
  2. JS中的类数组
  3. 实现数组扁平化
  4. 数组排序
  5. 手写JS数组方法的底层实现
tiantingrui commented 3 years ago

1. 数组API整理归纳

tiantingrui commented 3 years ago

2. JS中的类数组

JS中一直存在一种类数组的对象,他们不能直接调用数组的方法,但是又和数组比较类似,在某些特定的编程场景中会出现。

JS 中的类数组有哪些?

  1. 函数里面的参数对象 arguments
  2. 用 getElementsByTagName/ClassName/Name 获得的 HTMLCollection
  3. 用 querySelector 获得的 NodeList

思考

  1. 类数组是否能使用数组的方法?
  2. 类数组有哪些方式可以转换成数组?

arguments

最常见的类数组对象就是函数中的arguments

先来重点讲讲 arguments 对象,我们在日常开发中经常会遇到各种类数组对象,最常见的便是在函数中使用的 arguments,它的对象只定义在函数体中,包括了函数的参数和其他属性。我们通过一段代码来看下 arguments 的使用方法,如下所示。

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

foo('jack', '18', 'male');

Drawing 0.png

从结果中可以看到,typeof 这个 arguments 返回的是 object,通过 Object.prototype.toString.call 返回的结果是 '[object arguments]',可以看出来返回的不是 '[object array]',说明 arguments 和数组还是有区别的。

length 属性很好理解,它就是函数参数的长度,我们从打印出的代码也可以看得出来。另外可以看到 arguments 不仅仅有一个 length 属性,还有一个 callee 属性,如果在函数内部直接执行调用 callee 的话,那它就会不停地执行当前函数,直到执行到内存溢出。

HTMLCollection

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

NodeList

NodeList 对象是节点的集合,通常是由 querySelector 返回的。

NodeList 可以使用 for of 来迭代。

类数组的应用场景

1. 遍历参数操作

我们在函数内部可以直接获取 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(1,2)                       // 3
add(1,2,3,4);                   // 10

2. 定义链接字符串函数

我们可以通过 arguments 这个例子定义一个函数来连接字符串。这个函数唯一正式声明了的参数是一个字符串,该参数指定一个字符作为衔接点来连接字符串。该函数定义如下。

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"

这段代码说明了,你可以传递任意数量的参数到该函数,并使用每个参数作为列表中的项创建列表进行拼接。从这个例子中也可以看出,我们可以在日常编码中采用这样的代码抽象方式,把需要解决的这一类问题,都抽象成通用的方法,来提升代码的可复用性。

3. 传递参数使用

借助 arguments 将参数从一个函数传递到另一个函数,请看下面这个例子。

// 使用 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

上述代码中,通过在 foo 函数内部调用 apply 方法,用 foo 函数的参数传递给 bar 函数,这样就实现了借用参数的妙用。

如何将类数组转换成数组

1. 类数组借用数组方法转数组

function sum(a, b) {
  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

<!DOCTYPE html>

2. ES6 的方法转数组

对于类数组转换成数组的方式,我们还可以采用 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

从代码中可以看出,Array.from 和 ES6 的展开运算符,都可以把 arguments 这个类数组转换成数组 args,从而实现调用 reduce 方法对参数进行累加操作。其中第二种和第三种都是用 ES6 的展开运算符,虽然写法不一样,但是基本都可以满足多个参数实现累加的效果。

类数组和数组的区别

方法/特征 数组 类数组
自带方法 多个方法
length属性
callee 属性

tiantingrui commented 3 years ago

3. 如何实现数组扁平化?

var arr = [1, [2, [3, 4]]];
// 1. 普通递归实现
const flatten = (arr) => {
  let rst = [];
  for (let i = 0; i < arr.length; i++) {
    if (Array.isArray(arr[i])) {
      rst = rst.concat(flatten(arr[i]));
    } else {
      rst.push(arr[i]);
    }
  }

  return rst;
};

// 2. split 和 toString
const flattent = (arr) => {
  return arr.toString().split(",");
};

// 3. 扩展运算符实现
const flatten = (arr) => {
  while (arr.some((item) => Array.isArray(item))) {
    arr = [].concat(...arr);
  }
  return arr;
};

// 4. ES6 flat
const flatten = (arr) => {
  return arr.flat(Infinity);
};