methods中this的指向

不能使用箭头函数

我们在 methods 中要使用 data 返回对象中的数据:

  • 首先,需要明确一点,vue中this必须是有值的,我们通过{{ this.message }} 的方式读取vue中的变量,让我们能够在模板语法和方法中可以使用data中的变量,所以this必须是有值的,并且可以通过this获取到data返回对象中的数据。
  • 那么,this值可以是window吗?
    • 不可以,因为如果等于window,就无法读取data对象中的值
    • 但如果我们使用箭头函数定义方法,那么this的值就是window
  • 为什么箭头函数中的this是window?
    • 这里涉及到箭头函数中this的指向规则,箭头函数没有自己的this,箭头函数的this是自己上层的this
    • 而vue中methods的上层this就是window,所以箭头函数中this是window

      普通函数的this指向

methods中定义的普通函数,它们的内部this指向是vue单独处理的,vue2与vue3的实现差不多,有一个专门的bind函数用来给methds中的函数绑定this,this是一个叫publicThis的对象,里面是vue自己实现的,需要绑定到this上的一些变量。

伪代码如下:

  1. methods:{
  2. btnClick: function(){
  3. console.log(this)
  4. }
  5. }
  6. // vue内部实现
  7. ctx[btnClick] = btnClick.bind(publicThis)

所以我们模板中写的 onCLick=”this.btnClick” 实际上是从 ctx 这个数组中找到 btnClick 这个方法

模板语法

React的开发模式:

  • React使用的jsx,所以对应的代码都是编写的类似于js的一种语法
  • 之后通过Babel将jsx编译成React.createElement函数调用

Vue也支持jsx的开发模式:

  • 但大多数情况下,使用基于HTML的模板语法
  • 在模板中,允许开发者以声明式的方式将DOM和底层组件实例的数据绑定到一起
  • 在底层的实现中,Vue将模板编译成虚拟DOM渲染函数

    基本指令

    v-once

v-once用于指定元素或者组件只渲染一次:

  • 当数据发生变化时,元素或者组件以及其所有的子元素将视为静态内容并且跳过;
  • 该指令可用于性能优化
  • 不过一般不用

    v-text

v-text和直接使用 {{ }} 插值语法效果一致。

v-html

默认情况下,如果我们展示的内容本身是html,vue并不会对其做特殊处理,而是作为字符串处理,如果想作为真正的html解析,需要使用v-html。

v-pre

v-pre用于跳过元素和它的子元素的编译过程,显示原始的Mustache标签:

  • 跳过不需要编译的节点,加快编译速度
  • 就是template中写的什么,页面就展示什么
  • 基本用不上

    v-bind属性直接绑定一个对象

    给div绑定属性,可以使用这个语法
    1. <template>
    2. <div v-bind="info"></div>
    3. </template>
    4. <script>
    5. data() {
    6. return {
    7. info: {
    8. name: "jujuul",
    9. age: 18,
    10. height: 180
    11. }
    12. }
    13. }
    14. </script>
    这个方法在自定义组件时非常有用。

    v-on绑定多个事件

    给一个div绑定多个事件,可以使用以下的语法
    1. <div v-on="{click: btnClick, mousemove: mouseMove}"></div>
    可以使用语法糖,不过不够直观
    1. <div @="{click: btnClick, mousemove: mouseMove}"></div>

    条件渲染

    条件渲染的三个指令

    在某些情况下,我们需要根据当前的条件决定某些元素或组件是否需要渲染,这时就要用到条件渲染v-if。

v-if、v-else-if、v-else用于条件渲染:

  • 只有当条件为true时,才会被渲染
  • 这三个指令与if、else if、else类型

v-if的渲染原理:

  • v-if是惰性的
  • 当条件为false时,其判断的内容不会被渲染或者会被销毁掉
  • 当条件为true时,才会真正渲染条件块中的内容

    条件渲染结合template

    因为v-if是一个指令,必须要添加到一个元素上:

  • 但是如果我们希望切换的是多个元素呢?

  • 此时我们渲染div,但是我们并不希望div这种元素被渲染
  • 这个时候我们可以使用template

示例如下:

  1. // 使用div
  2. <div v-if="isShow">
  3. <div>1</div>
  4. <div>2</div>
  5. </div>
  6. // 使用template
  7. <template v-if="isShow">
  8. <div>1</div>
  9. <div>2</div>
  10. </template>

上面两段代码的区别是,使用div会多出一层DOM层级,使用template则不会。
使用template能够减少DOM层级,稍微提升性能。

v-show和v-if的区别

v-show和v-if用法看起来是一致的,也是根据一个条件决定是否显示元素或者组件。
首先,在用法上的区别:

  • v-show不支持template
  • v-show不能和v-else一起使用

其次,本质的区别:

  • v-show元素无论是否需要显示到浏览器上,它的DOM实际都是有渲染的,只是通过CSS的diplay属性来进行切换
  • v-if当条件为false时,其对应的元素根本不会被渲染到DOM中

开发中的选择:

  • 如果元素需要频繁的切换显示和隐藏的状态,使用v-show
  • 不然就使用v-if

    列表渲染

    在真实开发中,我们往往会从服务器拿到一些数据,并且对其进行渲染

  • 这个时候我们可以使用v-for来完成

  • v-for类似于JavaScript的for循环,可以用于遍历一组数据

    v-for基本使用

    v-for的基本格式是“item in 数组”

  • 数组通常是来自data或者prop,也可以是其他方式

  • item是我们给每项元素其的一个别名,这个别名可以自定义

    v-for结合tmplate

    和v-if结合template类似,目的也是为了少渲染div元素,减少DOM层级,提高性能。

    数组更新检测

    Vue将被侦听的数组的变更方法进行了包裹,所以它们也将会触发视图更新,这些方法包括:

  • push()

  • pop()
  • shift()
  • unshift()
  • splice()
  • sort()
  • reverse()

上面的方法会直接修改原来的数组,但是某些方法不会替换原来的数组,而是会生成新的数组,比如filter()、concat()和slice()。

v-for中的key的作用

在使用v-for进行列表渲染时,我们通常会给元素或者组件绑定一个key属性。
这个key属性有什么作用呢?官方的解释是:

  • key属性主要用在Vue的虚拟DOM算法,在新旧nodes对比时辨识VNodes
  • 如果不使用key,Vue会使用一种最大限度减少动态元素并且尽可能的尝试就地修改/复用相同类型元素的算法
  • 而使用key时,他会基于key的变化重新排列元素顺序,并且会移除/销毁key不存在的元素

    认识VNode

    我们先来解释一下VNode的概念:

  • VNode全称是Virtual Node,也就是虚拟节点

  • 事实上无论是组件还是元素,它们最终在Vue中表现出来的都是一个个VNode
  • VNode的本质是一个JavaScript对象

image.png
image.png
image.png

虚拟DOM

如果我们不是一个简单的div,而是有一大堆的元素,那么它们应该会形成一个VNode tree:
image.png
image.png

没有key的diff执行过程(源码)

如果没有绑定key,那么在vue更新的过程中,会调用patchUnkeyChildren方法,这个方法最主要的是接收两个参数(还有其它参数,不过不是很重要),一个是c1,旧的VNodes集合,一个是c2,新的VNodes集合。patchUnkeyCHildren方法会先获取这新旧两个集合的长度,然后取最小值,循环遍历。这里取最小值循环是因为如果取大的那个,会因为小的那个长度不够最终发生越界(下标错误)。
比如我们的一个数组arr=[‘a’,’b’,’c’,’d’],我们通过v-for在页面中遍历显示,这时我们插入f到数组中第三位,这时旧的VNodes就是[‘a’,’b’,’c’,’d’],而新的VNodes则是[‘a’,’b’,’f’,’c’,’d’]。长度分别是旧VNodes为4,新VNodes为5,这时会有一个长度为4的循环,然后循环比较新旧VNodes,也就是oldVNodes[0]和newVNodes[0]比较,如果这两个VNode类型相同,内容相同,那么就不会做更新,直到oldVNodes[2]和newVNodes[2]比较,虽然类型可能都是div,但是innerHTML的值不同,所以会更新innerHTML的值,后面的oldVNodes[3]和newVNodes[3]也要更新,然后就遍历完了。遍历完后,vue会做一个判断,如果旧的VNodes长度大于新的VNodes,那么嗲用unmountChildren方法,把旧VNodes不在刚才循环中的元素全部删除;如果新VNodes长度大于旧VNodes,那么调用mountChildren方法将多出来的部分生成新的VNode,并且挂载到新的VNodes上,最后生成真实DOM就实现了DOM节点的更新。

有key的diff执行过程(源码)

如果有key,会调用patchKeyedChildren,这个方法和patchUnkeyChildren方法接收的参数类似,主要的也是c1旧VNodes和c2新VNodes集合。也是先获取长度,不过这里不用比较,而是先存下来留着后面用到的时候再使用。patchKeyedChildren和patchUnkeyChild两个方法内部执行也是有很大不同的。

  1. 首先,从头部遍历,并且是使用while而不是for循环

通过变量n1获取c1中的参数,n2获取c2中的参数,比较n1[i]和n2[i]的值,判断节点类型和key值是否相同,相同就不需要更新,i++继续遍历,不同就直接跳出循环。

  1. 然后vue会从尾部开始向前进行遍历

也是通过n1获取c1中最后的参数,n2获取c2中最后的参数,比较n1[e1]和n2[e2]的值,这里的e1和e2分别是oldVNodes的长度和newVNodes的长度,比较也是比较节点类型和key值,相同就不需要更新,e1—,e2—继续遍历,不同就直接跳出循环。

  1. 上面两步从前后两边遍历完了全部节点,这里做新节点的添加

找到需要挂载的新节点,给patch方法传入两个参数,第一个为null,第二个为需要挂载的节点,在patch方法中,第一个参为null就是进行挂载

  1. 这一步是如果newVNodes长度小于oldVNodes,那么做卸载处理

把新节点中不存在,旧节点中存在的节点卸载掉

  1. 前面两步都是有序的,而这里用来处理乱序的

这种不知道具体顺序的,最简单粗暴的方法是删除全部旧节点,按序生成全部新节点,但是这种效率非常低。vue的实现是,尽可能在就节点中查找和新节点匹配的节点,如果找到,会创建一个新数组,把旧节点放入新数组,就是极可能的使用旧的节点(必须要确保节点类型和key值相同),都比较完以后,如果旧节点没有出现在新结点中,那么就移除掉,如果新结点中有旧节点中不存在的,就新增节点。