Vue.js v-for指令详解:遍历对象、数组与性能优化

作者:小布丁时间:2026-10-22 06:26:53浏览:549

本文深入解析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__属性。

v-for指令基本概述

这里需要重写push等方法的数组,我们只需要将其__proto__指向base数组,在读新创建的数组的push时,发现它并没有这样一个自有方法,那么它就尝试读__proto__上的方法,发现__proto__属性(也即base数组)上有这样一个自有方法,那么它就不必再向上搜索而直接使用base.push。

通过这种方式,我们不必为每一个数组重写一遍push方法,也不必去修改Array.prototype,看起来倒像是一个两全其美的方法。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持本站。

作者最新文章