将字符串转换为数组与类数组对象的深度解析:面试中的双重挑战

168 阅读5分钟

前言🐟

在面试中,面试官可能会通过一道看似简单的题目——如何将字符串转换为数组——来考察你的编程基础和对语言特性的理解。然而,为了进一步测试你的知识广度,他们可能会顺势引出另一个相关的话题——类数组对象。这不仅考察你对字符串操作的理解,还考验你对 JavaScript 中更复杂的数据结构的掌握。

面试中的程序逻辑题

在面试场景中,所涵盖的题目类型丰富多样,不仅仅局限于算法题,还会涉及到程序逻辑题。程序逻辑题着重于对某个特定需求的实现与达成。今日,我们就聚焦于这样一道简单却又典型的需求题:将字符串转换为数组。当面对这一问题时,大家脑海中率先浮现的思路会是什么呢?又能够构思出几种不同的解决方案呢?

想必很多人都会先想到,字符串内置的一个API,他就是 split 方法

split 方法

split 是最常用的方法之一,它可以将字符串按照指定的分隔符分割成数组。如果不传入分隔符,split('') 会将字符串按字符分割。

const str = "hello";
const charArray = str.split(''); // ['h', 'e', 'l', 'l', 'o']

这里需要注意对于某些特殊字符(如 Unicode 字符),可能需要额外处理。为了正确处理 Unicode 字符,推荐使用 Array.from

const str = "😊"; // Unicode 字符
const charArray = str.split(''); // ['\ud83d', '\ude0a'] (不正确)
const charArrayCorrect = Array.from(str); // ['😊'] (正确)

但是他还是不够优雅,简洁,这个时候就轮到ES6引入的强大工具 扩展运算符 登场了

扩展运算符

扩展运算符(spread)是三个点(...)。它能将具有 iterable 属性(即可迭代对象)的对象进行展开,这里我们展示的是 字符串的展开

const str = "hello";
const charArray = [...str]; // ['h', 'e', 'l', 'l', 'o']

 Array.from() 方法

Array.from() 方法在 JavaScript 的数组处理工具库中占据着重要的一席之地。它的设计初衷便是为了将那些类数组对象或者可迭代对象(字符串恰好属于可迭代对象的范畴)高效地转换为真正意义上的数组

const str = "hello";
const charArray = Array.from(str);
console.log(charArray); 
// 输出 ['h', 'e', 'l', 'l', 'o'],成功将字符串转换为数组

解构赋值

解构赋值是一种更高级的语法,可以直接从字符串中提取字符,并将其赋值给多个变量。虽然这种方法不能直接生成数组,但可以通过结合数组字面量来实现。

const str = "hello";
const [char1, char2, char3, char4, char5] = str;
console.log([char1, char2, char3, char4, char5]); // ['h', 'e', 'l', 'l', 'o']
类数组对象:NodeList 和 HTMLCollection 的 forEach 之谜

NodeListHTMLCollection 是 DOM API 中的类数组对象,它们具有类似数组的特性(如 length 属性和按索引访问元素),但并不是真正的数组。尽管它们的原型链中没有 Array.prototype,但在现代浏览器中,它们却可以使用 forEach 方法。这背后的原因是什么呢?

类数组它是什么?

1. 类数组对象的定义

类数组对象是指那些具有某些数组特征的对象,但它们并不是真正的数组。它们通常具有以下特点:

  • length 属性:表示对象中元素的数量。
  • 按索引访问:可以通过数字索引访问元素,例如 nodeList[0]
  • 缺少数组方法:类数组对象不继承自 Array.prototype,因此它们没有数组的内置方法(如 pushpopmap 等)。

NodeListHTMLCollection 是最常见的类数组对象:

  • NodeList:由 document.querySelectorAll() 返回,表示一组 DOM 元素。
  • HTMLCollection:由 document.getElementsByClassName() 或 document.getElementsByTagName() 返回,也表示一组 DOM 元素。

2. 为什么类数组对象不能直接使用数组方法?

类数组对象的原型链中没有 Array.prototype,因此它们无法直接访问数组的内置方法。例如,NodeList.prototypeHTMLCollection.prototype 都不是 Array.prototype 的子类,因此它们没有继承 joinmapfilter 等数组方法。


const nodeList = document.querySelectorAll('div');
console.log(nodeList.__proto__); // NodeListPrototype { ... }
console.log(NodeList.prototype.__proto__); // Object.prototype { ... }

const htmlCollection = document.getElementsByTagName('div');
console.log(htmlCollection.__proto__); // HTMLCollectionPrototype { ... }
console.log(HTMLCollection.prototype.__proto__); // Object.prototype { ... }
3. 现代浏览器中的扩展

在现代浏览器中,NodeListHTMLCollection 已经被扩展,支持了一些数组方法(如 forEach)。这是因为在较新的浏览器实现中,NodeListHTMLCollection 被设计为“活的”集合,并且它们的行为更接近于真正的数组。因此,你可以直接在这些对象上调用 forEach,而不需要显式地转换为数组。


const nodeList = document.querySelectorAll('div');
nodeList.forEach((element) => {
  console.log(element);
});

然而,这种行为并不是所有浏览器都支持的,尤其是旧版本的浏览器(如 IE)。因此,在编写跨浏览器兼容的代码时,最好还是使用 Array.prototype.forEach.call() 或将类数组对象转换为数组后再使用 forEach

4. 如何在类数组对象上使用 forEach

即使在不支持 forEach 的浏览器中,你也可以通过以下几种方式在类数组对象上使用 forEach

  • 借用 Array.prototype.forEach.call()

    const nodeList = document.querySelectorAll('div');
    Array.prototype.forEach.call(nodeList, (element) => {
      console.log(element);
    });
    
  • 使用 Array.from() 或扩展运算符 ...

    // 使用 Array.from()
    const nodeList = document.querySelectorAll('div');
    Array.from(nodeList).forEach((element) => {
      console.log(element);
    });
    
    // 使用扩展运算符 ...
    const nodeList = document.querySelectorAll('div');
    [...nodeList].forEach((element) => {
      console.log(element);
    });
    

END

  • 展开运算符 是 ES6 引入的强大工具,使得代码更加优雅和简洁,适用于多种场景。
  • 将字符串转字符数组 有多种方法,包括 split、解构赋值、扩展运算符和 Array.from,其中扩展运算符是最简洁的方式。
  • 类数组对象 如 NodeList 和 HTMLCollection 虽然不是真正的数组,但在现代浏览器中已经被扩展,支持了 forEach 方法。为了确保跨浏览器兼容性,建议使用 Array.prototype.forEach.call() 或将类数组对象转换为数组后再使用 forEach

希望这篇文章能帮助你更好地理解展开运算符、类数组对象以及 NodeListHTMLCollectionforEach 之谜。如果你有更多关于 JavaScript 或其他编程问题,欢迎在评论区留言讨论!