• 首页 首页 icon
  • 工具库 工具库 icon
    • IP查询 IP查询 icon
  • 内容库 内容库 icon
    • 快讯库 快讯库 icon
    • 精品库 精品库 icon
    • 问答库 问答库 icon
  • 更多 更多 icon
    • 服务条款 服务条款 icon

Vue 尚硅谷学习笔记五Vue监视数据原理、列表渲染、key的原理、数组过滤和排序

武飞扬头像
guocong
帮助3

0. 作者有话说

本章包含了部分Vue底层原理知识,比如key的原理,Vue如何监视数据,掌握了这些知识可以帮助我们更好的理解Vue。

1. 数组 列表显示

v-for 指令

  1. 用于展示列表数据
  2. 语法:v-for="(item, index) in xxx" :key="yyy"
  3. 可遍历:数组、对象、字符串(用的很少)、指定次数(用的很少)
  • 数组: (item, index)
  • 对象: (value, key)
  • 字符串:(char, index)
  • 数字:(number, index)

2. key的原理

2.1 虚拟DOM中key的作用

key是虚拟DOM对象的标识,当数据发生变化时,Vue会根据新数据生成新的虚拟DOM, 随后Vue进行新虚拟DOM与旧虚拟DOM的差异比较,比较规则如下:

2.2 对比规则

  1. 旧虚拟DOM中找到了与新虚拟DOM相同的key
  • 若虚拟DOM中内容没变, 直接使用之前的真实DOM
  • 若虚拟DOM中内容变了, 则生成新的真实DOM,随后替换掉页面中之前的真实DOM
  1. 旧虚拟DOM中未找到与新虚拟DOM相同的key创建新的真实DOM,随后渲染到到页面。

2.3 用index作为key可能会引发的问题

  1. 若对数据进行:逆序添加、逆序删除等破坏顺序操作: 会产生没有必要的真实DOM更新 ==> 界面效果没问题, 但效率低
  2. 如果结构中还包含输入类的DOM: 会产生错误DOM更新 ==> 界面有问题

2.4 开发中如何选择key

  1. 最好使用每条数据的唯一标识作为key, 比如id、手机号、身份证号、学号等唯一值。
  2. 如果不存在对数据的逆序添加、逆序删除等破坏顺序操作,仅用于渲染列表用于展示,使用index作为key是没有问题的。

3. 数组过滤与排序

3.1 数组过滤

  1. filter()返回一个新的数组,包含了过滤出来的数据

  2. 实现方法:watch与计算属性

  3. 注意:'abc'.indexOf('a')输出为 0 ,'abc'.indexOf('')输出也为 0,说明所有字符串都包含空字符

<body>
  <!-- 准备好一个容器-->
  <div id="root">
    <h2>人员列表</h2>
    <input type="text" placeholder="请输入名字" v-model="keyWord">
    <ul>
      <li v-for="(p,index) of filPerons" :key="index">
        {{p.name}}-{{p.age}}-{{p.sex}}
      </li>
    </ul>
  </div>

  <script type="text/javascript">
    Vue.config.productionTip = false

    //用watch实现
    //#region 
    /* new Vue({
      el:'#root',
      data:{
        keyWord:'',
        persons:[
          {id:'001',name:'马冬梅',age:19,sex:'女'},
          {id:'002',name:'周冬雨',age:20,sex:'女'},
          {id:'003',name:'周杰伦',age:21,sex:'男'},
          {id:'004',name:'温兆伦',age:22,sex:'男'}
        ],
        filPerons:[]
      },
      watch:{
        keyWord:{
          immediate:true, // 初始化就调用handler方法,使页面初始有数据
          handler(val){
            this.filPerons = this.persons.filter((p)=>{
              return p.name.indexOf(val) !== -1
            })
          }
        }
      }
    }) */
    //#endregion

    //用computed实现
    new Vue({
      el: '#root',
      data: {
        keyWord: '',
        persons: [{
            id: '001',
            name: '马冬梅',
            age: 19,
            sex: '女'
          },
          {
            id: '002',
            name: '周冬雨',
            age: 20,
            sex: '女'
          },
          {
            id: '003',
            name: '周杰伦',
            age: 21,
            sex: '男'
          },
          {
            id: '004',
            name: '温兆伦',
            age: 22,
            sex: '男'
          }
        ]
      },
      computed: {
        filPerons() {
          return this.persons.filter((p) => {
            return p.name.indexOf(this.keyWord) !== -1
          })
        }
      }
    })
  </script>

3.2 列表排序

computed:{ 
    filPerons(){ 
        const arr = this.persons.filter((p) => { 
            return p.name.indexOf(this.keyWord) !== -1 
        }) 
        //判断一下是否需要排序
        if(this.sortType){ 
            arr.sort((p1,p2) => { 
                return this.sortType === 1 ? p2.age-p1.age : p1.age-p2.age 
             }) 
         } 
         return arr 
     } 
}

4. Vue监视数据的原理

参考:Vue监测数据变化(包括数组)的原理

Vue实例化对象挂载到根元素后,生成全局Vue对象实例,Vue对象在实例化过程中会传入配置对象(options),options中包括data、methods、computed、watch等等。至于Vue是如何对data中数据的变化进行监测的,原理大致如下:

4.1 普通数据的监视原理

1. data对象加工成_data对象,并存入Vue实例

Vue首先对options中的data对象进行加工,生成_data对象,并存入全局Vue实例中,_data对象并不是单纯对data对象进行简单复制:

  • 怎么加工:递归为每一个非对象类型的数据添加响应式的get、set方法(reactive getter、reactive setter)(数据劫持
  • reactive getter和reactive setter怎么添加?(会用到数据代理)
  • 怎么存入vm._data = data = obs
let data = {
    key1:value1,
    key2:value2,
}

// 创建一个观察者实例对象,用于监测data的数据变化
const obs = new Observer(data)

function Observer(obj){
    // 汇总obj中所有属性,形成一个数组keys
    const keys = Object.keys(obj)
    // 遍历keys数组(此处为简化示例,未体现属性是一个对象或一个数组进行递归的情况)
    keys.forEach((key)=>{
        Object.defineProperty(this, key,{
            get(){
                return obj[key]
            },

            set(value){
                obj[key] = value
            }
        })
    })
}

2. Vue全局实例对象代理_data对象,实现对_data中属性的直接操作

加工生成_data对象,并存入Vue实例对象后,Vue实例对象会通过数据代理的方式,对_data对象进行代理。

为每个对象设置getter和setter,从而实现通过this.key1 = new_value1 以及 const value = this.key1 的形式,直接对_data中的key1进行修改和读取, 即 vm.name = vm._data.name

所谓数据代理,就是通过一个对象代理对另一个对象中属性的操作【读/写】,至于数据代理的实现形式,当然就是使用原生JS的Object.defineProperty() 方法,具体的代理过程可以查看 【Vue 尚硅谷学习笔记(二)】数据代理与事件处理 学新通

3. 数据变化监测效果

每个具有reactive setter的数据发生变化时,都会调用这个reactive setter,而这个reactive setter被调用时,会触发重新解析模板、生成新的虚拟DOM,、新旧虚拟DOM对比,更新内容映射到真实DOM、重新渲染这一套流程。

4.2 动态新增可被监测的属性 Vue.set()或 vm.$set()

项目开发过程中,可能会出现新增属性的需求,而新增的属性也必须是响应式的,因此需要用到 Vue.set(targetObject,attributeName,attributeValue)方法或vm.$set(targetObject,attributeName,attributeValue)进行响应式属性的动态添加,所谓响应式属性也就是这个属性会被添加到_data对象中,并且具有相应的reactive getter和reactive setter,最后被存入到Vue对象实例里。

需要注意,由于Vue本身有规定,不能把一个响应式的属性直接添加到Vue实例身上,而data对象中的所有属性最终都会通过数据代理添加到Vue实例身上,因此Vue.set() 方法和 this.$set() 方法的targetObject这个参数不能是Vue实例本身,也不能是data对象,只能是data对象中的二级属性对象。

Vue.set() 和 vm.$set() 不能给 vm 或 vm 的根数据对象 添加属性

4.3 Vue监测数组数据的变化

  1. Vue不会为数组元素添加响应式的getter和setter所以通过下标更改数组数据是无法被Vue所监测到的

  2. Vue 重写了数组中的一系列改变数组内部数据的方法(先调用原生,再更新界面),因此针对数组,只有通过调用pushpopshiftunshiftsplicereversesort这7个改变原数组本身的API,才会引起Vue的响应。

this.persons[index] = newP; //并没有改变persons本身,数组内部发生了变化,但是没有调用变异方法,vue不会更新界面
  1. 相比之下,也有非变更方法,例如 filter()concat() slice()。它们不会变更原始数组,而总是返回一个新数组。当使用非变更方法时,可以用新数组替换旧数组。
  2. slice()splice()前者不改变原数组,后者改变原数组。splice()  方法通过移除或者替换已存在的元素和/或添加新元素就地改变一个数组的内容。slice()  方法返回一个新的数组对象,这一对象是一个由 start 和 end 决定的原数组的浅拷贝(包括 start,不包括 end),其中 start 和 end 代表了数组元素的索引。原始数组不会被改变。
let fpersons = persons.filter(
  p => p.name.includes(searchName)
)
<body>
  <div id="demo">
    <h2>测试:v-for遍历数组</h2>
    <ul>
      <li v-for="(p, index) in persons" :key="index">
        {{index}}---{{p.name}}---{{p.age}}
        <button @click="deleteP(index)">删除</button>
        <button @click="updateP(index, {name:'Cat', age: 20})">更新</button>
      </li>
    </ul>

    <h2>测试:v-for遍历对象</h2>
    <ul>
      <li v-for="(value, key) in persons[1]" :key="key">
        {{value}}---{{key}}
      </li>
    </ul>

  </div>

  <script src="../js/vue.js"></script>
  <script>
    // Vue本身只是监视了persons的改变,没有监视数组内部数据的改变
    // Vue 重写了数组中的一系列改变数组内部数据的方法(先调用原生,再更新界面)
    new Vue({
      el: '#demo',
      data: {
        persons: [{
            name: 'Tom',
            age: 18
          },
          {
            name: 'Jack',
            age: 19
          },
          {
            name: 'Marry',
            age: 16
          },
          {
            name: 'Rose',
            age: 12
          },
        ]
      },
      methods: {
        deleteP(index) {
          // 删除persons中指定idnex的p(有数据绑定)
          this.persons.splice(index, 1);
        },
        updateP(index, newP) {
          // this.persons[index] = newP; //数据(数组内部)变了,界面没有变化(没有数据绑定)
          //并没有改变persons本身,数组内部发生了变化,但是没有调用变异方法,vue不会更新界面
          // this.persons = []  //界面有变化,改变了persons
          this.persons.splice(index, 1, newP);
        }
      }
    })
  </script>
</body>

这篇好文章是转载于:学新通技术网

  • 版权申明: 本站部分内容来自互联网,仅供学习及演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,请提供相关证据及您的身份证明,我们将在收到邮件后48小时内删除。
  • 本站站名: 学新通技术网
  • 本文地址: /boutique/detail/tanhfhbbjj
系列文章
更多 icon
同类精品
更多 icon
继续加载