? fengyu學(xué)習(xí)
又是一個(gè)周末,繼續(xù)我的Vue學(xué)習(xí)之旅
上次學(xué)習(xí)了一些 v-if 的用法,這回又輪到誰(shuí)了呢?
當(dāng)然是我們大名鼎鼎的列表渲染 v-for 指令
1、v-for 展現(xiàn)數(shù)據(jù)類(lèi)型
使用 v-for 指令可以展現(xiàn):數(shù)組、對(duì)象、數(shù)值。
數(shù)組:[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>
對(duì)象:{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>
- 注意用法:對(duì)象的 value 值是直接被遍歷出來(lái)的,獲取 key 值,需要使用 Mustache語(yǔ)法:{{ }} + $key
數(shù)值:num(正整數(shù))
<!-- 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
in10 展現(xiàn)的數(shù)字為 0-9,且 num 必須為正整數(shù),才會(huì)進(jìn)行遍歷(如果為0,遍歷數(shù)為0次,我把這認(rèn)為是不會(huì)遍歷)。如果小伙伴們嘗試使用負(fù)數(shù),會(huì)得到以下提示:Uncaught RangeError: Invalid array length
v-for 的展現(xiàn)數(shù)據(jù)類(lèi)型就說(shuō)到這,如果小伙伴們發(fā)現(xiàn)他還有什么神奇的功效,可以告訴我哦!
下面詳細(xì)說(shuō)說(shuō) v-for 展現(xiàn)數(shù)組這個(gè)重頭戲!
v-for 與數(shù)組變動(dòng)檢測(cè)
大家也許都知道,Array 是 javascript 的內(nèi)置對(duì)象。
他擁有一些很好使的內(nèi)置方法:push()、pop()、shift()、unshift()、splice()、sort()、reverse()
這本來(lái)是一件方便數(shù)組語(yǔ)義化改變的功德無(wú)量大好事,但是可是為難了我們的Vue.js了
引用一段教程里的原話
因?yàn)?JavaScript 的限制,Vue.js 不能檢測(cè)到下面數(shù)組變化:
1、直接用索引設(shè)置元素,如 vm.items[0] = {};
2、修改數(shù)據(jù)的長(zhǎng)度,如 vm.items.length = 0。
我們?cè)賮?lái)看看 V8 引擎實(shí)現(xiàn)的 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;
}
以上這段代碼的重點(diǎn)在于下面兩句
array[i+n] = %\_Arguments(i);
array.length = new_length;
這兩句話是不是似曾相識(shí),沒(méi)錯(cuò),這就是 Vue.js 不能檢查到的數(shù)組變化。
那么我們親愛(ài)的 Vue.js 是不是就對(duì)這些 內(nèi)置函數(shù) 束手無(wú)策了呢?
答案:No、No、No。作為一個(gè)如此帥氣的框架,怎么可能就此低頭。
下面讓我們看證據(jù):(Vue.js 源碼目錄 src/observer/array.js )
粘一段核心的代碼,如果大家有興趣,可以去對(duì)應(yīng)的目錄,自己研究!
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點(diǎn)
inserted 的獲取
ob.observeArray(inserted) //改變vm
ob.dep.notify() //通知變化
通過(guò)學(xué)習(xí)源碼,我們可以看到,作者通過(guò)復(fù)寫(xiě)了 Array.prototype 中的內(nèi)置方法,實(shí)現(xiàn)了數(shù)組變動(dòng)檢測(cè)。
當(dāng)然 Array.prototype 中還有一些返回新得數(shù)組對(duì)象的方法,比方說(shuō) concat() 、slice()
如果我們使用這些方法生成的新數(shù)組,直接賦值給老數(shù)組,并不會(huì)重新繪制 DOM ,而是使用最少的操作,實(shí)現(xiàn)頁(yè)面更新。
與此同理的是,當(dāng)我們使用 track-by 指定索引時(shí),新數(shù)組對(duì)舊數(shù)組直接賦值時(shí),也會(huì)使用最小的操作,實(shí)現(xiàn)頁(yè)面更新。
先上業(yè)務(wù)代碼:
concat:
<div id="vue_demo1">
<span>編號(hào)</span>
<span>姓名</span>
<span>成績(jī)</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: '強(qiáng)狗', score:100},
{name:'車(chē)小瘦', score:100}]);
}
</script>
track-by:
<!-- v-for track-by -->
<div id="vue_demo2">
<span>編號(hào)</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: '分?jǐn)?shù)',
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>
這兩段代碼都不會(huì)導(dǎo)致 DOM 的完全重繪,而是在現(xiàn)有的基礎(chǔ)上,進(jìn)行變換,下面我們來(lái)探究一下原理!
v-for中的重量級(jí)方法: 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方法:通過(guò)新舊數(shù)據(jù)的比對(duì),使用最少的DOM操作次數(shù),來(lái)完成新數(shù)據(jù)的展現(xiàn)
哇瑟,這么棒嗎?那他究竟是如何實(shí)現(xiàn)的呢!
源碼有點(diǎn)長(zhǎng),我就不粘了,小伙伴們想研究,請(qǐng)參見(jiàn)(源碼目錄:/directives/public/for.js 需要和 /directives.js 配合理解 )
說(shuō)一說(shuō)我的理解:
- 使用
getCachedFrag()方法判斷元素是否可以reuse
// if中是使用track-by中設(shè)置的key值,來(lái)確認(rèn)元素是否可以復(fù)用
// else里則是數(shù)組使用 `concat()` 等方法賦值后,可以在value中通過(guò)this.id獲取到frag元素,而直接使用一個(gè)值相同的變量賦值,不能獲取到frag元素
if (key || trackByKey || primitive) {
var id = getTrackByKey(index, key, value, trackByKey)
frag = this.cache[id]
} else {
frag = value[this.id]
}
不能
reuse的數(shù)據(jù),deleteCachedFrag+remove移除掉使用
findPrevFrag+move方法將舊元素移動(dòng)到正確的位置使用
insert方法插入新的數(shù)據(jù)執(zhí)行
nextTickHandler配合watch更新視圖
所以 track-by 與 concat 在獲取 frag 元素時(shí),邏輯不同,其他處理均是異曲同工的!
可能有朋友不知道什么值可以作為 track-by 的索引值,嘗試使用 $index 、這樣可以高效的利用原數(shù)據(jù)!
引用一句教程中的原話,是使用 $index 作為 track-by 的值所需要注意的地方:
這讓數(shù)據(jù)替換非常高效,但是也會(huì)付出一定的代價(jià)。
因?yàn)檫@時(shí) DOM 節(jié)點(diǎn)不再映射數(shù)組元素順序的改變,不能同步臨時(shí)狀態(tài)(比如 <input> 元素的值)以及組件的私有狀態(tài)。
因此,如果 v-for 塊包含 <input> 元素或子組件,要小心使用 track-by="$index"
結(jié)語(yǔ)
原本是不想這么早接觸 Vue.js 的源碼的,但是 v-for 指令的教程中的所提到高效,復(fù)用 DOM。
不通過(guò)查看源碼的方式,不能得到太好的理解。
盡管我現(xiàn)在理解的也并不完整,但算是略知一二了。
明天又要上班了,保持住學(xué)習(xí)的激情,加油!