Array.from()方法在DOM操作中具有重要作用,能够将类数组对象转换为真正的数组。
主要应用场景包括:
1)安全操作DOM集合,避免实时集合导致的循环问题;
2)批量处理元素样式和数据;
3)优化大数据量DOM操作性能;
4)扩展DOM集合功能,支持数组方法。
通过转换为数组,开发者可以更安全、高效地操作DOM,同时获得更清晰的代码结构和更好的性能表现。
在实际开发中,当需要对DOM元素集合进行复杂操作时,应优先考虑使用Array.from()进行转换。
关联阅读推荐
Array.from() 转换为数组的实际开发场景举例
1. DOM操作场景
场景1:批量修改元素样式
html
<div class="box">
<p class="item">批量修改元素样式</p>
<p class="item">Lorem ipsum dolor sit amet consectetur, adipisicing elit. Dolor ad repudiandae quibusdam! Harum nam, voluptatum eaque ipsum voluptates autem laudantium perspiciatis nihil delectus tempore tenetur, officia deleniti reiciendis repellendus! Similique!</p>
</div>
<script>
// ❌ 不好的做法:直接操作HTMLCollection
let items = document.getElementsByClassName('item');
for (let i = 0; i < items.length; i++) {
items[i].style.color = 'red'; // 每次循环都重新查询DOM
}
// ✅ 好的做法:转换为数组
let items = document.getElementsByClassName("item");
console.log(items);
// 转换为数组
let itemArray = Array.from(items); // 一次性获取快照
console.log(itemArray);
// 使用map创建新数据
let itemData = itemArray.map((item) => ({
//text: item.textContent,
//id: item.id,
element: item,
}));
console.log(itemData);
// 方法1:使用Array.from()转换后的数组
itemArray.forEach((item) => {
item.style.color = "red";
item.style.fontWeight = "bold";
});
// 方法2:使用map创建的新数据
itemData.forEach((item) => {
item.element.style.fontSize = "2rem";
});
</script>

场景2:安全地删除多个元素
html
<ul id="todo-list">
<li class="todo-item completed">任务1 ✓</li>
<li class="todo-item">任务2</li>
<li class="todo-item completed">任务3 ✓</li>
<li class="todo-item completed">任务4 ✓</li>
</ul>
<script>
// ❌ 危险做法:直接操作实时集合
function removeCompletedItemsBad() {
let items = document.getElementsByClassName('completed');
for (let i = 0; i < items.length; i++) {
items[i].remove(); // Bug! 每次删除都会改变集合长度
// 第一次循环后:i=0, length=3
// 第二次循环后:i=1, length=2 → 循环结束,漏删一个!
}
}
// ✅ 正确做法:先转换为数组
function removeCompletedItemsGood() {
let completedItems = document.getElementsByClassName('completed');
let completedArray = Array.from(completedItems); // 创建静态快照
// 现在可以安全地删除
completedArray.forEach(item => {
item.remove(); // 或 item.parentNode.removeChild(item)
});
console.log(`删除了 ${completedArray.length} 个已完成项目`);
}
// 更优雅的写法
const removeCompletedItems = () => {
Array.from(document.getElementsByClassName('completed'))
.forEach(item => item.remove());
};
</script>
2. 数据处理场景
场景3:从DOM元素提取数据
html
<table id="product-table">
<tr><td data-price="100">产品A</td><td>¥100</td></tr>
<tr><td data-price="200">产品B</td><td>¥200</td></tr>
<tr><td data-price="150">产品C</td><td>¥150</td></tr>
</table>
<script>
// 提取所有产品价格并计算总价
function calculateTotal() {
let priceCells = document.querySelectorAll('[data-price]');
let prices = Array.from(priceCells)
.map(cell => parseFloat(cell.getAttribute('data-price')));
let total = prices.reduce((sum, price) => sum + price, 0);
let average = prices.length > 0 ? total / prices.length : 0;
return {
total,
average,
count: prices.length,
prices // 保留原始数据用于其他操作
};
}
// 使用
let result = calculateTotal();
console.log(`总价: ¥${result.total}, 平均价: ¥${result.average.toFixed(2)}`);
// 进一步处理:筛选高价产品
let expensiveProducts = Array.from(document.querySelectorAll('[data-price]'))
.filter(cell => parseFloat(cell.getAttribute('data-price')) > 150)
.map(cell => ({
name: cell.textContent,
price: cell.getAttribute('data-price')
}));
</script>
场景4:表单数据处理
html
<form id="user-form">
<input type="text" name="username" value="张三">
<input type="email" name="email" value="zhangsan@example.com">
<input type="checkbox" name="interests" value="sports" checked> 运动
<input type="checkbox" name="interests" value="music" checked> 音乐
<select name="country">
<option value="CN" selected>中国</option>
<option value="US">美国</option>
</select>
</form>
<script>
// 收集表单数据
function collectFormData(formId) {
const form = document.getElementById(formId);
// 获取所有带name属性的表单元素
const formElements = Array.from(form.querySelectorAll('[name]'));
// 转换为表单数据对象
const formData = {};
formElements.forEach(element => {
const name = element.name;
const type = element.type;
switch (type) {
case 'checkbox':
case 'radio':
if (element.checked) {
formData[name] = formData[name] || [];
formData[name].push(element.value);
}
break;
case 'select-multiple':
formData[name] = Array.from(element.selectedOptions)
.map(option => option.value);
break;
default:
formData[name] = element.value;
}
});
return formData;
}
// 使用
const data = collectFormData('user-form');
console.log(data);
// 输出: {username: "张三", email: "zhangsan@example.com", interests: ["sports", "music"], country: "CN"}
// 转换为JSON并发送到服务器
fetch('/api/save', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
});
</script>
3. 性能优化场景
场景5:大数据量DOM操作
javascript
// 假设需要处理1000+个元素
function updateLargeDataset() {
// 获取所有需要更新的元素
let items = document.querySelectorAll('.data-item');
// ❌ 性能较差:多次读写DOM
// items.forEach(item => { // 即使使用forEach,每次也操作真实DOM
// item.style.opacity = '0.5';
// item.classList.add('updated');
// });
// ✅ 优化:先转换为数组,批量处理
let itemArray = Array.from(items);
// 在内存中处理数据
const processedData = itemArray.map((item, index) => {
return {
element: item,
newValue: calculateNewValue(index), // 复杂计算
shouldUpdate: needsUpdate(item)
};
});
// 批量更新DOM(减少重排重绘)
const fragment = document.createDocumentFragment();
processedData.forEach(data => {
if (data.shouldUpdate) {
const clone = data.element.cloneNode(true);
clone.textContent = data.newValue;
clone.classList.add('processed');
fragment.appendChild(clone);
}
});
// 一次性替换
const container = document.getElementById('data-container');
container.innerHTML = '';
container.appendChild(fragment);
}
场景6:分页或虚拟滚动
javascript
class VirtualList {
constructor(container, items, pageSize = 50) {
this.container = container;
this.allItems = Array.from(items); // 转换为静态数组
this.pageSize = pageSize;
this.currentPage = 0;
}
renderPage(page) {
const start = page * this.pageSize;
const end = start + this.pageSize;
const pageItems = this.allItems.slice(start, end);
// 清空容器
this.container.innerHTML = '';
// 添加当前页元素
pageItems.forEach(item => {
this.container.appendChild(item.cloneNode(true));
});
this.currentPage = page;
}
nextPage() {
if ((this.currentPage + 1) * this.pageSize < this.allItems.length) {
this.renderPage(this.currentPage + 1);
}
}
filterItems(filterFn) {
// 使用数组的filter方法
this.allItems = this.allItems.filter(filterFn);
this.renderPage(0);
}
}
// 使用
const listItems = document.querySelectorAll('.list-item');
const virtualList = new VirtualList(
document.getElementById('list-container'),
listItems,
30
);
virtualList.renderPage(0);
// 添加过滤功能
document.getElementById('filter-btn').addEventListener('click', () => {
virtualList.filterItems(item =>
item.textContent.includes('重要')
);
});
4. 功能扩展场景
场景7:添加自定义方法
javascript
// 扩展DOM元素集合的功能
function enhanceNodeList(nodeList) {
const array = Array.from(nodeList);
return {
// 原始数组
elements: array,
// 自定义方法
hide: function() {
this.elements.forEach(el => el.style.display = 'none');
return this; // 支持链式调用
},
show: function() {
this.elements.forEach(el => el.style.display = '');
return this;
},
addClass: function(className) {
this.elements.forEach(el => el.classList.add(className));
return this;
},
removeClass: function(className) {
this.elements.forEach(el => el.classList.remove(className));
return this;
},
// 数据操作
getData: function(attribute) {
return this.elements.map(el => el.getAttribute(attribute));
},
// 事件绑定
on: function(event, handler) {
this.elements.forEach(el => el.addEventListener(event, handler));
return this;
}
};
}
// 使用
const $$ = selector =>
enhanceNodeList(document.querySelectorAll(selector));
// 链式操作
$$('.buttons')
.addClass('btn-primary')
.on('click', function() {
console.log('按钮被点击');
})
.getData('id'); // 获取所有ID
场景8:实现拖拽排序
html
<ul id="sortable-list">
<li class="sortable-item" data-id="1">项目1</li>
<li class="sortable-item" data-id="2">项目2</li>
<li class="sortable-item" data-id="3">项目3</li>
<li class="sortable-item" data-id="4">项目4</li>
</ul>
<script>
class SortableList {
constructor(containerSelector) {
this.container = document.querySelector(containerSelector);
this.items = Array.from(this.container.children); // 转换为数组
this.setupEvents();
}
setupEvents() {
this.items.forEach(item => {
item.setAttribute('draggable', true);
item.addEventListener('dragstart', (e) => {
e.dataTransfer.setData('text/plain', item.dataset.id);
item.classList.add('dragging');
});
item.addEventListener('dragend', () => {
item.classList.remove('dragging');
});
});
this.container.addEventListener('dragover', (e) => {
e.preventDefault();
const draggingItem = document.querySelector('.dragging');
const siblings = Array.from(this.container.children)
.filter(child => child !== draggingItem);
const nextSibling = siblings.find(sibling => {
return e.clientY <= sibling.offsetTop + sibling.offsetHeight / 2;
});
this.container.insertBefore(draggingItem, nextSibling);
// 更新内部数组
this.items = Array.from(this.container.children);
});
}
getOrder() {
// 返回当前顺序的ID数组
return this.items.map(item => item.dataset.id);
}
saveOrder() {
const order = this.getOrder();
localStorage.setItem('list-order', JSON.stringify(order));
}
loadOrder() {
const savedOrder = JSON.parse(localStorage.getItem('list-order'));
if (savedOrder) {
// 按照保存的顺序重新排列
savedOrder.forEach(id => {
const item = this.items.find(item => item.dataset.id === id);
if (item) {
this.container.appendChild(item);
}
});
this.items = Array.from(this.container.children);
}
}
}
// 初始化
const sortableList = new SortableList('#sortable-list');
</script>
5. 实际项目中的最佳实践
场景9:React/Vue组件中的使用
javascript
// React组件示例
function ProductList({ products }) {
// 在useEffect中安全操作DOM
useEffect(() => {
// 获取所有价格元素并高亮高价商品
const priceElements = Array.from(
document.querySelectorAll('.product-price')
);
const processedPrices = priceElements.map(element => {
const price = parseFloat(element.textContent.replace('¥', ''));
return {
element,
price,
isExpensive: price > 1000
};
});
// 批量更新
processedPrices.forEach(({ element, isExpensive }) => {
if (isExpensive) {
element.classList.add('expensive');
element.style.fontWeight = 'bold';
}
});
// 清理函数
return () => {
processedPrices.forEach(({ element }) => {
element.classList.remove('expensive');
element.style.fontWeight = '';
});
};
}, [products]);
return (
<div className="product-list">
{products.map(product => (
<div key={product.id} className="product-item">
<h3>{product.name}</h3>
<span className="product-price">¥{product.price}</span>
</div>
))}
</div>
);
}
场景10:构建工具函数库
javascript
// utils/dom.js - DOM操作工具库
export const $ = {
// 获取元素数组
all: (selector, context = document) =>
Array.from(context.querySelectorAll(selector)),
// 批量设置属性
setAttributes: (elements, attributes) => {
const elementArray = Array.from(elements);
elementArray.forEach(el => {
Object.entries(attributes).forEach(([key, value]) => {
el.setAttribute(key, value);
});
});
},
// 批量设置样式
setStyles: (elements, styles) => {
const elementArray = Array.from(elements);
elementArray.forEach(el => {
Object.assign(el.style, styles);
});
},
// 批量添加事件监听器
addEvent: (elements, event, handler, options) => {
const elementArray = Array.from(elements);
elementArray.forEach(el => {
el.addEventListener(event, handler, options);
});
},
// 批量移除事件监听器
removeEvent: (elements, event, handler, options) => {
const elementArray = Array.from(elements);
elementArray.forEach(el => {
el.removeEventListener(event, handler, options);
});
},
// 数据提取
extractData: (elements, dataKey) => {
return Array.from(elements).map(el =>
el.dataset[dataKey] || el.getAttribute(`data-${dataKey}`)
);
}
};
// 使用示例
import { $ } from './utils/dom.js';
// 批量操作
$('.buttons')
.forEach(btn => console.log(btn.textContent));
$.setAttributes($('.links'), {
'target': '_blank',
'rel': 'noopener noreferrer'
});
$.addEvent($('.modal-triggers'), 'click', showModal);
总结:为什么要使用 Array.from()
| 场景 | 好处 | 具体表现 |
|---|---|---|
| 安全操作 | 避免实时集合的陷阱 | 循环中删除元素不会出错 |
| 性能优化 | 减少DOM查询次数 | 一次获取,多次使用 |
| 功能丰富 | 使用数组方法 | map、filter、reduce、find等 |
| 代码清晰 | 链式调用 | 可读性更好,逻辑更清晰 |
| 内存效率 | 可控的生命周期 | 可以及时释放不再需要的引用 |
| 框架兼容 | 与现代框架配合 | 更容易集成到React/Vue等框架中 |
黄金法则 :当你需要对DOM元素集合进行遍历、筛选、转换 等复杂操作时,先使用 Array.from() 转换为数组,然后再操作。