© fengyu学习
又是一个周末,继续我的Vue学习之旅
上次学习了一些 v-if 的用法,这回又轮到谁了呢?
当然是我们大名鼎鼎的列表渲染 v-for 指令
1、v-for 展现数据类型
使用 v-for 指令可以展现:数组、对象、数值。
数组:[1, 2, 3, 4, 5]
<!-- v-for Array -->
<div id="vue_demo_array" v-for="item in array">
{{item}}
</div>
<script>
var demo3 = new Vue({
el: '#vue_demo_array',
data: {
array: [1, 2, 3, 4, 5]
}
})
</script>
对象:{key1: value1, key2: value2}
<!-- v-for Array -->
<div id="vue_demo_object" v-for="value in object">
{{$key}} : {{value}}
</div>
<script>
var vue_demo_object = new Vue({
el: '#vue_demo_object',
data: {
object: {
name: '封小胖',
job: '前端小菜'
}
}
})
</script>
- 注意用法:对象的 value 值是直接被遍历出来的,获取 key 值,需要使用 Mustache语法:{{ }} + $key
数值:num(正整数)
<!-- v-for Num -->
<div id="vue_demo_num" v-for="value in 10">
{{value}}
</div>
<script>
var vue_demo_num = new Vue({
el: '#vue_demo_num'
})
</script>
-
注意:value
in
10 展现的数字为 0-9,且 num 必须为正整数,才会进行遍历(如果为0,遍历数为0次,我把这认为是不会遍历)。如果小伙伴们尝试使用负数,会得到以下提示:Uncaught RangeError: Invalid array length
v-for 的展现数据类型就说到这,如果小伙伴们发现他还有什么神奇的功效,可以告诉我哦!
下面详细说说 v-for 展现数组这个重头戏!
v-for 与数组变动检测
大家也许都知道,Array 是 javascript 的内置对象。
他拥有一些很好使的内置方法:push()
、pop()
、shift()
、unshift()
、splice()
、sort()
、reverse()
这本来是一件方便数组语义化改变的功德无量大好事,但是可是为难了我们的Vue.js了
引用一段教程里的原话
因为 JavaScript 的限制,Vue.js 不能检测到下面数组变化:
1、直接用索引设置元素,如 vm.items[0] = {};
2、修改数据的长度,如 vm.items.length = 0。
我们再来看看 V8 引擎实现的 Array.prototype.push 方法
// Appends the arguments to the end of the array and returns the new
// length of the array. See ECMA-262, section 15.4.4.7.
function ArrayPush() {
CHECK_OBJECT_COERCIBLE(this, "Array.prototype.push");
if (%IsObserved(this))
return ObservedArrayPush.apply(this, arguments);
var array = TO_OBJECT(this);
var n = TO_LENGTH_OR_UINT32(array.length);
var m = %_ArgumentsLength();
// It appears that there is no enforced, absolute limit on the number of
// arguments, but it would surely blow the stack to use 2**30 or more.
// To avoid integer overflow, do the comparison to the max safe integer
// after subtracting 2**30 from both sides. (2**31 would seem like a
// natural value, but it is negative in JS, and 2**32 is 1.)
if (m > (1 << 30) || (n - (1 << 30)) + m > kMaxSafeInteger - (1 << 30)) {
throw MakeTypeError(kPushPastSafeLength, m, n);
}
for (var i = 0; i < m; i++) {
array[i+n] = %_Arguments(i);
}
var new_length = n + m;
array.length = new_length;
return new_length;
}
以上这段代码的重点在于下面两句
array[i+n] = %\_Arguments(i);
array.length = new_length;
这两句话是不是似曾相识,没错,这就是 Vue.js 不能检查到的数组变化。
那么我们亲爱的 Vue.js 是不是就对这些 内置函数 束手无策了呢?
答案:No、No、No。作为一个如此帅气的框架,怎么可能就此低头。
下面让我们看证据:(Vue.js 源码目录 src/observer/array.js )
粘一段核心的代码,如果大家有兴趣,可以去对应的目录,自己研究!
const arrayProto = Array.prototype
export const arrayMethods = Object.create(arrayProto)
.forEach(function (method) {
// cache original method
var original = arrayProto[method]
def(arrayMethods, method, function mutator () {
// avoid leaking arguments:
// http://jsperf.com/closure-with-arguments
var i = arguments.length
var args = new Array(i)
while (i--) {
args[i] = arguments[i]
}
var result = original.apply(this, args)
var ob = this.__ob__
var inserted
switch (method) {
case 'push':
inserted = args
break
case 'unshift':
inserted = args
break
case 'splice':
inserted = args.slice(2)
break
}
if (inserted) ob.observeArray(inserted)
// notify change
ob.dep.notify()
return result
})
})
以上代码中非常重要的3点
inserted 的获取
ob.observeArray(inserted) //改变vm
ob.dep.notify() //通知变化
通过学习源码,我们可以看到,作者通过复写了 Array.prototype 中的内置方法,实现了数组变动检测。
当然 Array.prototype 中还有一些返回新得数组对象的方法,比方说 concat()
、slice()
如果我们使用这些方法生成的新数组,直接赋值给老数组,并不会重新绘制 DOM ,而是使用最少的操作,实现页面更新。
与此同理的是,当我们使用 track-by 指定索引时,新数组对旧数组直接赋值时,也会使用最小的操作,实现页面更新。
先上业务代码:
concat:
<div id="vue_demo1">
<span>编号</span>
<span>姓名</span>
<span>成绩</span>
<template v-for="data in studentList">
<br/>
<span>{{$index+1}}</span>
<span>{{data.name}}</span>
<span>{{data.score}}</span>
</template>
</div>
<script>
var demo1 = new Vue({
el: '#vue_demo1',
data: {
studentList: [
{name: '封小胖', score:60},
{name: '娄三胖', score:70},
{name: '胡五胖', score:80}
]
}
});
</script>
<script>
function vueConcat(){
demo1.studentList = demo1.studentList.concat([{name: '李大胖', score:90},
{name: '强狗', score:100},
{name:'车小瘦', score:100}]);
}
</script>
track-by:
<!-- v-for track-by -->
<div id="vue_demo2">
<span>编号</span>
<span>姓名</span>
<span>{{attrName}}</span>
<template v-for="data in studentList" track-by="name">
<br/>
<span>{{$index+1}}</span>
<input v-on:input="changeName" :value="data.name" />
<span>{{data.score}}</span>
</template>
</div>
<script>
var demo2 = new Vue({
el: '#vue_demo2',
data: {
attrName: '分数',
studentList: [
{name: '封小胖', score:60},
{name: '娄三胖', score:70},
{name: '胡五胖', score:80}
],
changeName: function(){
console.log('name is change');
}
}
})
</script>
<script>
//查看track by 效果
function vueTrackBy(){
demo2.studentList = [
{name: '封小胖', score:90},
{name: '娄三胖', score:95},
{name: '胡五胖', score:100}
]
}
</script>
这两段代码都不会导致 DOM 的完全重绘,而是在现有的基础上,进行变换,下面我们来探究一下原理!
v-for中的重量级方法: Diff
* Diff, based on new data and old data, determine the
* minimum amount of DOM manipulations needed to make the
* DOM reflect the new data Array.
且让我试着翻译一下:
Diff方法:通过新旧数据的比对,使用最少的DOM操作次数,来完成新数据的展现
哇瑟,这么棒吗?那他究竟是如何实现的呢!
源码有点长,我就不粘了,小伙伴们想研究,请参见(源码目录:/directives/public/for.js 需要和 /directives.js 配合理解 )
说一说我的理解:
- 使用
getCachedFrag()
方法判断元素是否可以reuse
// if中是使用track-by中设置的key值,来确认元素是否可以复用
// else里则是数组使用 `concat()` 等方法赋值后,可以在value中通过this.id获取到frag元素,而直接使用一个值相同的变量赋值,不能获取到frag元素
if (key || trackByKey || primitive) {
var id = getTrackByKey(index, key, value, trackByKey)
frag = this.cache[id]
} else {
frag = value[this.id]
}
不能
reuse
的数据,deleteCachedFrag
+remove
移除掉使用
findPrevFrag
+move
方法将旧元素移动到正确的位置使用
insert
方法插入新的数据执行
nextTickHandler
配合watch
更新视图
所以 track-by
与 concat
在获取 frag 元素时,逻辑不同,其他处理均是异曲同工的!
可能有朋友不知道什么值可以作为 track-by
的索引值,尝试使用 $index 、这样可以高效的利用原数据!
引用一句教程中的原话,是使用 $index 作为 track-by
的值所需要注意的地方:
这让数据替换非常高效,但是也会付出一定的代价。
因为这时 DOM 节点不再映射数组元素顺序的改变,不能同步临时状态(比如 <input> 元素的值)以及组件的私有状态。
因此,如果 v-for 块包含 <input> 元素或子组件,要小心使用 track-by="$index"
结语
原本是不想这么早接触 Vue.js 的源码的,但是 v-for 指令的教程中的所提到高效,复用 DOM。
不通过查看源码的方式,不能得到太好的理解。
尽管我现在理解的也并不完整,但算是略知一二了。
明天又要上班了,保持住学习的激情,加油!