Array.from() 转换为数组的实际开发场景举例

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() 转换为数组,然后再操作。

相关推荐
Irene19917 小时前
insertAdjacentHTML() 详解
javascript·dom
海市公约2 天前
JavaScript零基础入门指南:从语法到实战的核心知识点解析
javascript·ecmascript·前端开发·dom·bom·定时器与事件·js语法实战
Beginner x_u10 天前
Vue 事件机制全面解析:原生事件、自定义事件与 DOM 冒泡完全讲透
前端·javascript·vue.js·dom
1024肥宅13 天前
浏览器相关 API:DOM 操作全解析
前端·浏览器·dom
韩曙亮20 天前
【Web APIs】元素可视区 client 系列属性 ② ( 立即执行函数 )
前端·javascript·dom·client·web apis·立即执行函数·元素可视区
韩曙亮21 天前
【Web APIs】元素偏移量 offset 系列属性 ④ ( offset 属性案例 - 放大镜效果 )
前端·javascript·css·html·offset·dom·web apis
韩曙亮22 天前
【Web APIs】BOM 浏览器对象模型 ⑥ ( location 对象 | location 常用属性和方法 | URL 简介 )
前端·javascript·dom·url·bom·location·浏览器对象模型
AAA阿giao24 天前
深入解析 OOP 考题之 EditInPlace 类:从零开始掌握面向对象编程实战
前端·javascript·dom
vivo互联网技术1 个月前
浅谈 AI 搜索前端打字机效果的实现方案演进
前端·vue·dom