Vue基础知识的加深理解

两个重要的小原则

  1. 被Vue管理的函数,最好写成普通函数,这样this才是指向vm或者组件对象
  2. 所有不被Vue所管理的函数(定时器的回调函数、ajax的回调函数、Promise的回调函数),最好写成箭头函数,这样this才是指向vm或者组件实例对象

Vue基础知识的加深理解_第1张图片

 Vue基础知识的加深理解_第2张图片

1. 计算属性与方法的区别

       1. 在vue模板渲染中,当为某个元素绑定方法时,那么模板每次渲染的时候,这个方法都会被执行一次,因为method没有缓存。不同的是,computed计算属性具缓存功能,即当你为元素绑定的是计算属性的时候,由于计算属性具有缓存功能,因此模板每次渲染的时候,不会重新去执行这个计算属性,而是找到这个计算属性的缓存之,当且计算属性依赖的属性发生变化的时候,才会重新去执行这个属性。

        2.由于计算属性的返回值是通过已有属性计算并return的,因此因为这个特别性质,计算属性是不可以进行一些异步操作的。如下:

Vue基础知识的加深理解_第3张图片

       Vue基础知识的加深理解_第4张图片

         3. computed能完成的,watch都可以完成

         4. watch能完成的,computed不一定能完成,例如,watch能进行异步操作

1.1 计算属性的完整写法

        每一个计算属性都具有两个方法,get()与set(),详细写法如下:

    computed: {
      fullName: {
        get(){
          console.log('get被调用了')
          return this.firstName + '-' + this.lastName;
        },
        set(value){
          console.log('set被调用了');
          const str=value;
          str=value.split('-')
          this.firstName=str[0];
          this.lastName=str[1]
        }
      }
    }

       当你所用到的计算数据只考虑读取,不考虑修改的情况,那么可以使用简写方式:

computed:{
  fullName(){
    console.log('get被调用了')
          return this.firstName + '-' + this.lastName;
  }
}

那么,在计算属性中,get与set分别在什么时候开始调用呢?

  • get:当有人读取这个计算属性fullName的时候,get会被调用,且返回值就作为fullName的值
  • set:初次读取fullName时,所依赖的数据发生变化时

1.2 计算属性的相关说明

  1. 定义:要用的属性不存在,需要通过已有属性计算得来
  2. 原理:借助了Object.defineProperty方法提供发getter和setter
  3. get函数什么时候执行?

        (1)初次读取数据时会执行一次(因为具有缓存

        (2)当依赖的数据发生变化时会被再次调用

      4. 优势:与methods实现相比,内部有缓存机制(复用),效率更高,调试更加方便

      5.备注:

           (1)计算属性最终会出现在vm上,直接读取即可

           (2)如果计算属性要被修改,那必须写set函数去相应修改,且set中要引起计算时依赖的数据发生变化

2. watch监视属性

  1. 当被监视的属性变化时,回调函数hander自动调用,进行相关的操作
  2. 监视的属性必须存在,才能进行监视!!
  3. 监视的两种写法

        (1)new Vue时传入的watch配置

        (2)通过vm.$watch监视

2.1 watch监视的表示

watch:{
  isHot:{
    immediate:true,
    //handler什么时候调用?即是isHot发生改变的时候
    handler(newValue,oldValue){
      //这里通常可以根据新旧值之间的变化而执行有些操作
      console.log('isHot被修改了,新值为:'+newvalue+'旧值为:'+oldvalue)
    }
  }
}

ps:

  1. 这里watch监视的是isHot属性,你可以尝试改变isHot的值,你会发现便会触发isHot中的handler函数,控制台输出相应的提示;
  2. isHot是一个对象,其中具有多个配置属性,这里immediate表示isHot被初始化的时候调用,而handler则是isHot发生改变的时候再触发。

2.2 watch深度监听

        深度监听是可以监听对象内部值的变化

  1. Vue中的watch默认不检测对象内部值的变化(它只能监听一层)
  2. 配置deep:true可以监听对象内部值的变化(多层数据结构)

        备注:Vue自身是可以监听对象内部值的改变,但是Vue提供的watch默认不可以

                 使用watch时,根据数据的具体结构,决定是否采用深度监听

注意 :watch的监听建立在已经存在的属性上,注意下面错误示范

 错误方式①:


    data: {
      numbers: {
        a: 1,
        b: 2
      }
    }

    watch: {
     a: {
       handler(){
         console.log('a被改变了')
       }
      }
    }

 错误方式②

Vue基础知识的加深理解_第5张图片

 因此,正确的方式是,为该属性开启深度监听

Vue基础知识的加深理解_第6张图片

 还要要注意一个问题是,当使用简写的方式进行属性监听的时候,只有该属性不需要配置其他属性时,比如深度监听或者初始化时监听

Vue基础知识的加深理解_第7张图片

2.3 vue循环列表中绑定:key的作用

        首先按官网介绍的,可以主要用在Vue的虚拟Dom算法,在新旧节点对比时辨识VNodes。使用key时,它会基于key的变化重新排列元素顺序,并且会移除key不存在的元素。

        有相同父元素的子元素必须有独特的key,重复的key会造成渲染错误。

        vue中循环列表时,绑定:key的值往往有两种方式,一是将循环列表的索引值index作为key,而是根据返回数据中的唯一标识作为key。不管以什么方式绑定key,key的值在列表中必须是唯一的,可以使用index的原因是索引值必然是唯一的。、

        以下举例子说明以index或者唯一标识符绑定key的时的执行流程

2.3.1 index作为key

1.比如有初始数据如下:

   {id:'001',name:'张三',age:'18'},
    {id:'002',name:'李四',age:'19'},
    {id:'003',name:'王五',age:'20'}

2. 在模板使用v-for循坏该数据。以列表li的形式显示

      
  • {item.name} - {item.age}

3. 那么vue会根据数据生成虚拟DOM,生成的虚拟DOM形式如下:

        
  • 张三 - 18
  • 李四 - 19
  • 王五 - 20
  • 4.将虚拟DOM转成真实的DOM

     ---------------------分割:以上是vue从绑定数据且从虚拟DOM转成真实DOM的过程----------------------

    5.接下来说明数据发现变化时,key绑定值位index时数据的改变,改变数据如下

                 {id:'004',name:'老刘',age:'20'},
                 {id:'001',name:'张三',age:'18'},
                 {id:'002',name:'李四',age:'19'},
                 {id:'003',name:'王五',age:'30'}

    6.此时由于数据变化了,那么vue就会重复数据渲染过程,此时根据新数据生成的虚拟DOM如下

                
  • 老刘 - 30
  • 张三 - 18
  • 李四 - 19
  • 王五 - 20
  • 7.将虚拟DOM转成真实DOM

    Vue基础知识的加深理解_第8张图片

     8.接下来解释为什么会出现以上的情况?

            由于在vue中key时虚拟DOM对象的标识,当状态中的数据发生变化时,Vue会根据【新数据】生成【新的虚拟DOM】,随后Vue进行【新虚拟DON】与【旧虚拟DON】的差异比较,其中的比较规则如下:

             (1)在旧虚拟DOM中找到了与新虚拟DOM相同的key:

                 -  旧虚拟DOM中内容没有变化的时候,则直接使用之前的真实DOM(比如上面的input的内容)

                 -  若虚拟DOM中的内容发生变化了,则直接生成新的DOM,随后替换掉页面中之前的真实DOM

            (2)旧虚拟DOM中未找到与新虚拟DOM相同的key

                   - 直接创建新的真实DOM,随后渲染到页面上

            (3) 由于以上例子并没有绑定key值,那么每次循环的时候key值都是自上而下从0-n,而自上而下input值没有发生改变,因此直接复用

    3 Vue中数据绑定的原理

    下面先同个例子来简单说明一下vue中的双向数据绑定

    3.1 例子1

    比如我想通过点击按钮来直接该百年数组persons中某一项的数据:this.persons[0]={id:'001',name:'马老师',age:'50',sex:'男'}

    却发现页面没有跟新:

    Vue基础知识的加深理解_第9张图片

    Vue基础知识的加深理解_第10张图片

             那么我们会发现,直接对persons数组中某一项进行更改是无效的,那么根据Vue双向数据绑定的原理,怎么样才能有效更改呢?

    以下是有效的更改方式:(即是对数组项的指定属性进行修改)

            this.persons[0].name='马老师';
            this.persons[0].age=50;
            this.persons[0].sex="男"

    3.2 例子2

            当你想给vue添加信新的属性时,也不能直接使用this.student.sex='男'进行增加,因此这样新增加的新属性是不具备数据的双向绑定的:

    Vue基础知识的加深理解_第11张图片

     Vue基础知识的加深理解_第12张图片

             那么问题来了,应该怎么样添加新的属性才可以达到响应式呢?根据vue官网提示,当你不是一开始在data中定义的数据,直接添加是不具备响应式的,如果想又不是直接在data中添加,而是后期想用的时候在某个地方添加的时候,可以通过API实现,如下:

                    Vue.set(target,propertyName/index,value)

    1. 参数:

    • target :{Object |Array}
    • propertyName/index:{ staring | number }
    • value :{any}

    2. 返回值:设置的值

    3. 用法:

            向响应式随想中添加一个property,并确保这个新peopety同样是响应式的,且出发视图更新。它必须用于相映式对象上添加新property,因为Vue无法探测普通的新增property(比如this.myObject.newProperty='hi')

    注意:对象不能是Vue实例,或者Vue实例的根数据对象

    实例:

          methods: {
                addSex() {
                    Vue.set(this.student, 'sex', '男')
                }
            }

    Vue基础知识的加深理解_第13张图片

    3.3 例子3

    Vue基础知识的加深理解_第14张图片

     Vue基础知识的加深理解_第15张图片

     3.4 总结

    1. Vue会监视data中所有层次的数据
    2. 如何检测对象中的数据?

                 通过setter实现监视,且要在new Vue时就传入要检测的数据

                 (1)对象中后追加的属性。Vue默认不做响应式处理

                 (2)如需要给后添加的属性做响应式,请使用如下API:

                            Vue.set(target,propertyName/index,value) 或

                            vm.$set(arget,propertyName/index,value)

            3. 如何检测数组中的数据?

                    通过包裹数组跟新元素的方法实现,本质上就是做了两件事:(Vue中对操作数组的相关方法进行包装,可以达到数据双向绑定)

                     (1)调用原生对应的方法对数组进行更新       

                     (2)重新解析模板,进而更新页面。

            4. 在Vue修改数组中的某个元素一定要用如下方法:

                      (1)使用这个API:push()、pop()、shift()、unshift()、splice()、sort()、reverse()、

                      (2)Vue.set() 或者 vm.$set()

            特别注意:Vue.set()和vm.$set()不能给vm或者vm的根数据(vm._data)对象添加属性

    4.收集表单数据

           4.1 核心原理  

            核心是通过v-model指令和给表单元素添加value属性

            注意,在使用v-model指令实现数据双向绑定时,v-model实质上接收到的是表单元素的value值。由于对于输入框比如“input、textarea”用户输入的值即为value值,因此不用额外给input绑定value值。而对于其他非输入框的表单元素(比如:radio、checkbox等),想要使用v-model实现双向绑定时,通常需为其添加value属性,

    例子如下:

    Vue基础知识的加深理解_第16张图片

     Vue基础知识的加深理解_第17张图片

     4.2 总结

    1. ,则v-model收集的是value值,用户输入的就是value值
    2. ,则v-model收集的是value值,且要给标签配置value值

            (1)没有配置input的value属性,那么收集的就是checked(勾选 or 未勾选,是布尔值)

             (2)配置input的value属性:

                    - v-model的初始值是非数字,那么收集的就是checked(勾选 or 未勾选,是布尔值)

                    - v-model的初始值是数组,那么收集的就是value组成的数组

            4.v-model的三个修饰符

              (1)lazy:失去焦点再收集数据

               (2)number:输入字符串转为有效的数字

               (3)trim:过滤首尾空格

    5 过滤器

    6. 指令 

    6.1 v-html指令

    1. 作用:像指定节点中渲染包含html结构的内容
    2. 与插值语法区别

            (1)v-html会替换掉节点中所有的内容,{ {xx}}则不会

            (2)v-html可以识别html结构

          3.严重注意:v-html有安全性问题!!

             (1)在网站上动态渲染任意html是非常危险的,容易倒是xss攻击

             (2)一定要在可信内容中使用v-html,永远不要在用户提交的内容上!(比如一些恶意人会通过其获取你的cookie等)

    6.2 v-cloak指令

    1. 本质是一个特殊属性,Vue实例创建完毕并接管容器后,会删掉v-cloak属性
    2. 使用css配合v-cloak可以解决网速慢时也买你展示出{ {xxx}}的问题

    6.3 v-once指令

    1. v-once所在节点在初次动态渲染后,就视为静态内容了(即只读一次)
    2. 以后数据的改变不会引起v-once所在结构的更新,可以用于优化性能

    Vue基础知识的加深理解_第18张图片

    6.4 v-pre指令

    1.  跳过其所在节点的编译过程
    2. 可利用它跳过:没有使用指令语法、没有使用插值语法的节点,会加快编译

     6.5 自定义指令

    Vue基础知识的加深理解_第19张图片Vue基础知识的加深理解_第20张图片

    Vue基础知识的加深理解_第21张图片

    7 vue的生命周期

            Vue的生命周期按执行顺序有:beforeCreate、created、beforeMount、mounted、beforeUpdate、updated、beforeDestory、destoryed。

    7.1 beforeCreate

            vue在beforeCeate钩子函数中,进行了初始化、因为此时数据监测和数据代理还没开始,因此还无法通过vm访问到data中的数据、methods中的方法。

    Vue基础知识的加深理解_第22张图片

    Vue基础知识的加深理解_第23张图片

    7.2 created

            此时,已经完成数据检测、数据代理,因此可以在这里通过vm访问到data中的数据、methods中配置的方法。

    Vue基础知识的加深理解_第24张图片

    注意:警告created之后,由于Vue并没有开始编译了,需要经过下面的beforeMount、mounted阶段才完成模板解析。

    7.3 beforeMounted

            此阶段vue已经开始解析模板、生成虚拟dom(内存中),页面还不能显示解析好的内容。即此时页面呈现的是未经Vue编译的DOM结构。所有对DOM的操作,最终都不奏效。

    Vue基础知识的加深理解_第25张图片

    7.4 mounted

            经过上面的beforeMount之后,此时vue已经将内存中的虚拟DOM转成真实的DOM插入到页面中了。

    • 页面中呈现的是经过Vue编译的DOM
    • 对DOM的操作均有效(尽避免操作DOM),至此初始化过程结束,
    • 一般在此阶段运行:开启定时器、发送网络请求、订阅消息、绑定自定义事件等初始化操作。

    Vue基础知识的加深理解_第26张图片

     7.5 beforeUpdate

            经过上面的周期函数之后,页面已经渲染完毕,也可以相应数据。

            在这里,如果数据发生变化,那么在这里数据新的,只不过页面是旧的,也就是说页面尚未和数据保持同步更新。

    Vue基础知识的加深理解_第27张图片

    7.6 updated

            这里根据新数据,生成新的虚拟DOM,随后与旧的虚拟DOM进行比较,最终完成页面的更新,即完成了Model->View的更新。此时,数据是新的,页面也是新的。即保持数据和页面同步。

    总结图

    Vue基础知识的加深理解_第28张图片

    Vue基础知识的加深理解_第29张图片

    8 组件

            组件是可以复用的Vue实例,且带有一个名字。

    Vue基础知识的加深理解_第30张图片
            关于VueComponent:

    1. school组件本质上是一个名为VueCompontent的构造函数,其不是程序定义的,是Vue.extend生成的。
    2. 我们只需要写或者,Vue解析时会帮我们创建school组件实例对象。即Vue帮我们执行的:new VueComponent(options)。
    3. 特别注意:每次调用Vue.extend,返回的都是一个全新的VueComponent!!(Vue内部会自动帮我们执行new VueComponent(options)
    4. 关于this的指向:
      1. 组件配置中:
        1. data函数、methods中的函数、watch中的函数、computed中的函数,它们的this指向均是【VueComponent实例对象】

                  2.new Vue()配置中

                            1.data函数、methods中的函数、watch中的函数、computed中的函数,它们的this均是【Vue实例对象】

        5.VueComponent的实例对象,可以简称为vc(组件实例对象)

            Vue实例对象,简称vm

           

    8.1 内置对象

    8.1.1  以下先引入原型对象

    function Demo(){
        this.a=1,
        this.b=2
    }
    
    //创建一个Demo的实例对象
    const d = new Demo()
    
    console.log(Demo.prototype)//显示原型属性
    console.log(d.__proto__)//隐式原型属性

    1.每个对象(或者构造函数)都有一个原型属性prototype

    2.每个对象的实例都有一个隐形原型属性

    3. 构造函数的原型属性和实例的原型属性均指向原型对象

    Demo.prototype === d.__proto__

    4.以下验证`Demo.prototype === d.__proto__`

    //通过显示原型属性操作原型对象,追加一个x属性,值为99
    Demo.prototype.x=99;
    console.log('@',d.__proto__.x)
    //或者直接输出d.x(因为找不到时也会往原型链上查找)
    console.log(d.x)

    8.1.2 Vue与VueCompontent的关系

    1. 一个重要的内置关系:VueConponent.prototype.__proto__ === Vue.prototype
    2. 为什么要这个关系:让组件实例对象vc可以访问Vue原型上的方法和属性

    注意:实例的隐形原型属性,一定是指向自己构造这的原型对象,那么显而易见

            Vue的实例对象vm的隐形属性指向Vue的原型对象

            Vue的原型对象的原型属性又指向Object的原型对象

    Vue基础知识的加深理解_第31张图片

     思考:那么Vue和VueComponent是如何联系起来的呢?

    Vue内部将VueConponent的原型对象指向了Vue的原型对象(目的:使得不管是Vue实例还是VueConponent实例均能公用Vue的方法以及实例)

    1. 

    Vue基础知识的加深理解_第32张图片

     Vue基础知识的加深理解_第33张图片

             比如我要在vc上找x属性,当vc上没有x属性时,会沿着__proto__上去找(这个proto指向VueCompontent的原型对象),当VueCompontent的原型对象上没有找到x时,则往proto上找x,此时,vue中将VueCompontent的原型对象的原型属性proto(指向了Vue原型对象),此时在Vue原型对象上找x,此可以找到,注意,如果在Vue的原型对象中也没有找到的时候,则继续往Vue原型对象的proto上找,即找到Object


    Vue基础知识的加深理解_第34张图片

     Vue基础知识的加深理解_第35张图片

    9.render函数的理解

            在入口文件main.js文件中,默认引入的vue不是完整版的,是vue开发者在vue完整版基础上改动的(这里称之为“残缺”版本),且这个引入的vue是不具备编译模板功能的。因此,当你在main.js入口文件,在创建Vue实例的时候,试图同时实现模板编译时,如下,是会出错的

    Vue基础知识的加深理解_第36张图片

    报错如下:

    Vue基础知识的加深理解_第37张图片

    因此,vue团队引入render函数,实现template模板编译,render函数的本质作用是生成节点

    //完整版的rander函数
    render(createElement){
        return createElement('h1','你好啊') //render函数需要返回值
    }
    
    //简写
    render:(q)=>{
        return q('h1','你好啊')
    }
    
    //精简简写
    render: q=>q(App)//这里的App是import引入的App.vue组件
    
    
    //
    new Vue({
        el:'#app',
        //下面这行代码,完成了将App组件放入容器中
        render:h=>h(App)
    })

    思考:既然是要进行模板编译的,那为什么vue团队一开始不引入完整的,直接实现模板编译功能呢?原因是当你功能实现了之后,你的代码是需要通过webpack或者其他打包工具进行打包的,那么在进行打包之后,此时一些vue文件已经被编译成了浏览器能够识别的html、js、css等文件,那么在线上即是也不需要这个编译功能的。避免多此一举又占内存,因此vue团队一开始就是引入不带编译功能的vue。

    疑问:如果引入的“残缺”版本的vue,那么在其他组件中的