Vue.js v-for指令详解:遍历对象、数组与性能优化
本文深入解析Vue.js中v-for指令的使用,涵盖对象与数组的遍历语法、整数循环及与组件结合的高级用法。重点讨论v-for与v-if的优先级差异,并详细剖析Vue重写数组方法以触发视图更新的底层原理,包括原型链劫持的实现方式,帮助开发者避免常见陷阱并提升应用性能。
近期在研究Vue.js时,发现v-for循环渲染涉及的知识点丰富且关键,特此整理笔记以作记录。
v-for指令基本概述
一、基本概述
Vue.js通过v-for指令实现循环渲染,该指令依据Vue实例中的数据信息,遍历所需数据并渲染对应内容。它支持遍历数组和对象类型的数据。由于JavaScript中的数组本质上也是对象,因此遍历这两类数据时,语法相似但存在细微差别。
遍历对象与数组详解
(一)遍历对象
<div id="app">
<ul>
<li v-for="(val, key, index) in me">{{index}}. {{key}}: {{val}}</li>
</ul>
</div>
...
var vm = new Vue({
el: '#app',
data: {
me: {
name: 'Dale',
age: 22,
sex: 'male',
height: 170
}
}
});
v-for接收的参数相对复杂,主要可拆解为三个部分:
(1)括号内的遍历结果信息(val, key, index)
其中,val代表遍历得到的属性值,key代表属性名,index代表遍历次序。key和index均为可选参数,若无需使用,指令可简化为v-for="val in me"。
(2)遍历关键词in
in关键字可被of替代,官方解释称“它是最接近JavaScript迭代器的语法”,但在实际使用中两者并无区别。
(3)被遍历对象me
me是绑定在实例data属性上的一个属性,它拥有特定的执行环境,即我们熟悉的Vue实例。在模板中,我们可以通过this访问到它,这里的me实际上等同于this.me,直接在模板中写this.me也是可行的。
渲染结果如下:
<div id="app">
<ul>
<li>0. name: Dale</li>
<li>1. age: 22</li>
<li>2. sex: male</li>
<li>3. height: 170</li>
</ul>
</div>
(二)遍历数组
<div id="app">
<ul>
<li v-for="(item, index) in items">{{index}}. {{item}}</li>
</ul>
</div>
...
var vm = new Vue({
el: '#app',
data: {
items: ['apple', 'tomato', 'banana', 'watermelon']
}
});
与遍历对象类似,主要区别在于对象的key和index是一致的,因此只需获取index即可。上述代码的渲染结果如下:
<div id="app">
<ul>
<li>0. apple</li>
<li>1. tomato</li>
<li>2. banana</li>
<li>3. watermelon</li>
</ul>
</div>
(三)遍历“整数”
理论上,整数并非可遍历单元,但Vue提供了一种便捷方式来减少重复代码。
整数遍历与组件应用
<div id="app">
<ul>
<li v-for="n in num">{{n}}</li>
</ul>
</div>
...
var vm = new Vue({
el: '#app',
data: {
num: 3
}
});
渲染结果如下:
<div id="app">
<ul>
<li>1</li>
<li>2</li>
<li>3</li>
</ul>
</div>
二、实际应用
(一)对象、数组与组件
在实际应用中,单独使用数组或对象描述数据的情况较少,更常见的模式是结合数组和对象:
<div id="app">
<ul class="persons">
<li v-for="person in persons">name: {{person.name}}, age: {{person.age}};</li>
</ul>
</div>
...
var vm = new Vue({
el: '#app',
data: {
persons: [
{
name: 'Dale',
age: 22
},
{
name: 'Tim',
age: 30
},
{
name: 'Rex',
age: 23
}
]
}
});
这本质上是遍历一个数组,但数组的每个元素都是一个对象(如上述的person)。我们可以通过[]和.两种方式访问对象属性,例如name和age。渲染结果如下:
<div id="app">
<ul class="persons">
<li>name: Dale, age: 22;</li>
<li>name: Tim, age: 30;</li>
<li>name: Rex, age: 23;</li>
</ul>
</div>
实际上,更常用且强大的模式是将组件与数组/对象结合使用,操作方式与上述类似。
v-for与v-if组合使用
(二)与v-if组合
带有v-for指令的标签也可以同时添加v-if指令,但需注意,v-for的优先级更高。渲染模板时,相当于对每次遍历的结果进行条件判断。
<div id="app">
<ul class="persons">
<li v-for="person in persons" v-if="person.age >= 23">name: {{person.name}}, age: {{person.age}};</li>
</ul>
</div>
...
var vm = new Vue({
el: '#app',
data: {
persons: [
{
name: 'Dale',
age: 22
},
{
name: 'Tim',
age: 30
},
{
name: 'Rex',
age: 23
}
]
}
});
此处先遍历persons的所有元素,然后检查每次得到的person的age是否大于或等于23,若是则输出,否则不输出。渲染结果如下:
<div id="app">
<ul class="persons">
<li>name: Tim, age: 30;</li>
<li>name: Rex, age: 23;</li>
</ul>
</div>
若希望v-if指令的优先级更高,可在v-for指令所绑定的标签的父级上添加v-if指令。
三、注意事项
key属性与数组更新机制
(一)key
与v-for类似,若不绑定key属性,Vue默认会重用元素以提高性能。若无需此默认行为,可显式绑定一个唯一的key。
(二)数据到视图的更新
Vue的视图更新依赖于getter/setter。如果直接修改、增加或删除数组元素,并不会触发视图更新。为此,Vue重写了以下方法:
- push
- pop
- shift
- unshift
- splice
- sort
- reverse
通过这些方法修改数据时,将触发视图更新。
new Vue({
data: {
arr: [1, 2, 3]
}
});
例如前述情况,若想要执行arr的push等方法,由于push是数组类型数据从Array.prototype.push继承而来的,因此通常有两种实现方式。
数组变异方法实现原理
(1)修改Array.prototype
Array.prototype.push = function () {
console.log(1);
}
([]).push(); // 1
此处修改了Array.prototype上的push方法,实际上整个prototype属性均可被重写,如Array.prototype = xxx。这样做的好处是,在一处进行修改后,所有数组类型均可直接使用重写后的方法,实现和使用都非常简单;但副作用也明显,容易影响其他代码。其他使用这段代码的地方,除了功能实现可能受影响外,效率上也会较大影响,因为原生JS的代码经过特殊优化,我们重写实现,效率肯定会受到影响。最重要的是,如果大家的代码在同一个环境下运行,然后都尝试重写同一个方法的话,最终结果不言而喻。
(2)增加自有方法
var arr = [];
arr.push = function () {
console.log(1);
}
arr.push(); // 1
Array.prototype.push.toString(); // "function push() { [native code] }"
此处修改了arr的push方法,但不涉及Array.prototype.push。因为读写一个对象的属性/方法时,JS总是先尝试访问“ownproperty”,即hasOwnProperty所检测的内容,此处姑且将其称为“自有属性(方法)”。读取数据时,如果没有读取到内容,那么JS会尝试向上搜索__proto__上的数据;写数据时,如果有这个自有属性,则会将其覆盖,如果没有,则将其作为自有属性添加到改对象上,而不会尝试将其添加到__proto__上。这样的规则,也是为了防止“子类”以外修改“父类”的属性、方法等。这种实现方式虽然可以避免上面修改Array.prototype的一系列缺点,但是它的问题就更加明显,因为每次创建这样一个“数组”,就要重新实现/绑定这样一系列方法,它所带来的开发效率、性能问题不容小觑。
(3)Vue的实现方式
var arr = [];
new Vue({
data: {
arr: arr
}
});
arr.push.toString(); // "function mutator() {var arguments$1 = arguments;... 这是 vue 自己的实现
Array.prototype.push.toString(); // "function push() { [native code] }"... 这是浏览器原生的实现
arr.hasOwnProperty('push'); // false 说明不是自有属性
以上说明Vue既不是修改了Array.prototype.push,又不是修改了自有属性。但我们通过instanceof操作符检查时,arr又是Array的一个实例,那么它到底是怎么弄的实现的呢?或者说Vue的push藏在哪儿呢?
var base = [];
var arr = [];
base.push = function () {
console.log(1);
};
arr.__proto__ = base;
arr.push(); // 1
arr.__proto__.push(); // 1
arr.__proto__.push.toString(); // "function push() { [native code] }"
实际上,Vue是利用了类似上面的方式,先创建了一个Array的实例,也就是一个数组类型的基础对象base,然后为它添加了一个自有方法push,最后用base覆盖了需要扩展的arr对象的__proto__属性。

这里需要重写push等方法的数组,我们只需要将其__proto__指向base数组,在读新创建的数组的push时,发现它并没有这样一个自有方法,那么它就尝试读__proto__上的方法,发现__proto__属性(也即base数组)上有这样一个自有方法,那么它就不必再向上搜索而直接使用base.push。
通过这种方式,我们不必为每一个数组重写一遍push方法,也不必去修改Array.prototype,看起来倒像是一个两全其美的方法。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持本站。
前端大屏自适应方案:缩放与视口单位对比
2026-10-31 18:19:27
前端大文件分片下载实现:基于localForage与Vue的完整方案
2026-10-31 16:11:47
Vue3 中使用 VueUse 实现指定区域全屏阅读功能
2026-10-31 13:55:05
基于Vue与Pinia实现页面Loading动画及请求拦截处理
2026-10-31 13:54:42
Vue封装Echarts通用组件:Vue2/Vue3 JS/TS三种模板及常用配置事件详解
2026-10-31 11:38:03
