Vue.js学习笔记(6)

© fengyu学习

又是一个周末,继续我的Vue学习之旅

上次学习了一些 v-if 的用法,这回又轮到谁了呢?

当然是我们大名鼎鼎的列表渲染 v-for 指令

1、v-for 展现数据类型

使用 v-for 指令可以展现:数组、对象、数值。

数组:[1, 2, 3, 4, 5]


{{item}}

对象:{key1: value1, key2: value2}


{{$key}} : {{value}}
  • 注意用法:对象的 value 值是直接被遍历出来的,获取 key 值,需要使用 Mustache语法:{{ }} + $key

数值:num(正整数)


{{value}}
  • 注意:value in 10 展现的数字为 0-9,且 num 必须为正整数,才会进行遍历(如果为0,遍历数为0次,我把这认为是不会遍历)。如果小伙伴们尝试使用负数,会得到以下提示:

    Uncaught RangeError: Invalid array length

v-for 的展现数据类型就说到这,如果小伙伴们发现他还有什么神奇的功效,可以告诉我哦!

下面详细说说 v-for 展现数组这个重头戏!

v-for 与数组变动检测

大家也许都知道,Arrayjavascript 的内置对象。

他拥有一些很好使的内置方法: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:

编号 姓名 成绩

track-by:


编号 姓名 {{attrName}}

这两段代码都不会导致 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-byconcat 在获取 frag 元素时,逻辑不同,其他处理均是异曲同工的!

可能有朋友不知道什么值可以作为 track-by 的索引值,尝试使用 $index 、这样可以高效的利用原数据!

引用一句教程中的原话,是使用 $index 作为 track-by 的值所需要注意的地方:

这让数据替换非常高效,但是也会付出一定的代价。

因为这时 DOM 节点不再映射数组元素顺序的改变,不能同步临时状态(比如  元素的值)以及组件的私有状态。

因此,如果 v-for 块包含  元素或子组件,要小心使用 track-by="$index"

结语

原本是不想这么早接触 Vue.js 的源码的,但是 v-for 指令的教程中的所提到高效,复用 DOM

不通过查看源码的方式,不能得到太好的理解。

尽管我现在理解的也并不完整,但算是略知一二了。

明天又要上班了,保持住学习的激情,加油!

你可能感兴趣的:(Vue.js学习笔记(6))