Vue.js學(xué)習(xí)筆記(6)

? 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 in 10 展現(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è)

大家也許都知道,Arrayjavascript 的內(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-byconcat 在獲取 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í)的激情,加油!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 這篇筆記主要包含 Vue 2 不同于 Vue 1 或者特有的內(nèi)容,還有我對(duì)于 Vue 1.0 印象不深的內(nèi)容。關(guān)于...
    云之外閱讀 5,173評(píng)論 0 29
  • 1.安裝 可以簡(jiǎn)單地在頁(yè)面引入Vue.js作為獨(dú)立版本,Vue即被注冊(cè)為全局變量,可以在頁(yè)面使用了。 如果希望搭建...
    Awey閱讀 11,278評(píng)論 4 129
  • Vue 實(shí)例 屬性和方法 每個(gè) Vue 實(shí)例都會(huì)代理其 data 對(duì)象里所有的屬性:var data = { a:...
    云之外閱讀 2,365評(píng)論 0 6
  • 這本書(shū)是關(guān)于他跑步和寫(xiě)小說(shuō)以及他的一些人生感悟。我總結(jié)出幾個(gè)詞語(yǔ):開(kāi)店、工作、寫(xiě)小說(shuō),跑步、馬拉松42公里,馬拉松...
    小清新的娟秀閱讀 649評(píng)論 0 1
  • 現(xiàn)在聽(tīng)到許多人說(shuō),現(xiàn)在賺錢(qián)好難,用錢(qián)卻如流水,每個(gè)月的工資就夠吃喝了,所剩無(wú)幾,我想說(shuō),你有思考過(guò)為什么賺不到錢(qián)呢...
    汐顏之美閱讀 937評(píng)論 0 0

友情鏈接更多精彩內(nèi)容