vue2_9、列表渲染、数据监视原理

1列表渲染

1.1、基本列表

v-for指令

  • 用于展示列表数据

  • 语法: <li v-for="(item,index) in items " :key="index"> ,这里 key 可以是 index ,更好是遍历的对象的唯一标识。

  • 可遍历:数组、对象、字符串(用的少)、指定次数(用的少)

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>初始vue</title>
    <!-- 引入vue-->
    <script src="/vueBaseJs/vue.js"></script>

</head>
<body>
    <div id="root">
        <!-- 遍历数组-->
        <h2>人员列表</h2>
        <ul>
            <li v-for="(p,index) in personList" :key="p.id">
                {{p.name}} _ {{p.age}}
            </li>
        </ul>

        <!-- 遍历对象-->
        <h2>车配置列表</h2>
        <ul>
            <li v-for="(val,k) of car" ::key="k">
                {{k}} : {{val}}
            </li>
        </ul>

         <!-- 遍历字符串 -->
         <!-- <h2>遍历字符串</h2>
         <ul>
             <li v-for="(char,index) of str" ::key="index">
                 {{index}} _ {{char}}
             </li>
         </ul> -->

         <!-- 遍历次数 -->
         <!-- <h2>遍历次数</h2>
         <ul>
             <li v-for="(number,index) in 5 " ::key="index">
                 {{number}} _ {{index}}
             </li>
         </ul> -->

    </div>

    <script>
        Vue.config.productionTip=false;//阻止vue启动时生成生产提示

        const vm=new Vue({
            el:"#root",
            data:function(){
                return {
                    personList:[
                        {id:001,name:"张三",age:18},
                        {id:002,name:"李四",age:19},
                        {id:003,name:"王五",age:20}
                    ],
                    car:{
                        name:"奥迪rs7",
                        price:"150w",
                        color:"黑色"
                    },
                    str:"Hello World!"              
                }
            },
        });

    </script>

    
</body>
</html>

结果:
image


1.2、v-for的key的原理

index作为key

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>初始vue</title>
    <!-- 引入vue-->
    <script src="/vueBaseJs/vue.js"></script>

</head>
<body>
    <div id="root">
        <button @click.once="add">添加</button>
        <!-- 遍历数组-->
        <h2>人员列表</h2>
        <ul>
            <li v-for="(p,index) in personList" :key="index">
                {{p.name}} _ {{p.age}}
                <input type="text" >
            </li>
        </ul>


    </div>

    <script>
        Vue.config.productionTip=false;//阻止vue启动时生成生产提示

        const vm=new Vue({
            el:"#root",
            data:function(){
                return {
                    personList:[
                        {id:001,name:"张三",age:18},
                        {id:002,name:"李四",age:19},
                        {id:003,name:"王五",age:20}
                    ],          
                }
            },
            methods:{
                add(){
                    const p={id:004,name:"老刘",age:40};
                    this.personList.unshift(p);
                }
            }
        });

    </script>

    
</body>
</html>

点击添加前:
image

点击添加后:
image

为啥会这样:
因为用index作为key:
如果对数据的顺序进行了破坏的操作。就会有下面的问题,而且出现这样问题效率会很低,因为复用的东西变少了。

原因步骤①、如下图:点击添加后,在进行diff比较时候,发现第一个input文本框没有变化,在虚拟dom里面的key为0的节点里还是一个input元素没有变成其它元素,就可以复用,所以就不会重新生成新的元素进行替换。

image

②如下:后面的key的input都复用了真实dom上的,没有重新在虚拟dom上生成新的。
image

③王五重新生成是因为旧的虚拟dom里没有key=3的元素
image

p.id作为键:

如图,修改key
image

原理:
image

总结key的内部原理

面试题:react vue中的key有什么作用? ( key的内部原理)

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

2 .对比规则
a.旧虚拟DOM中找到了与新虚拟DOM相同的key

  • ①.若虚拟DOM中内容没变,直接使用之前的真实DOM节点。
  • ②若虚拟DOM中内容变了,则生成新的真实DOM节点 ,随后替换掉页面中之前的真实DOM中对应的有变化的节点元素。(不是替换掉整个dom文档,而是替换掉里面修改的节点元素)

b. 旧虚拟DOM中未找到与新虚拟DOM相同的key

  • 创建新的真实DOM ,随后渲染到到页面

3 .用index作为key可能会引发的问题
a.若对数据进行逆序添加、逆序删除等破坏顺序操作,会产生没有必要的真实DOM更新 ==> 界面效果没问题,但效率低。

b.若结构中还包含输入类的DOM(如:输入框) :会产生错误DOM更新 ==> 界面有问题。

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



1.3、列表过滤

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>初始vue</title>
    <!-- 引入vue-->
    <script src="/vueBaseJs/vue.js"></script>

</head>
<body>
    <div id="root">
        

        <!-- 遍历数组-->
        <h2>人员列表</h2>
        <input type="text" placeholder="请输入名字" v-model="keyWord" >        
        
        <ul>
            <li v-for="(p,index) in personListTemp " :key="p.id">
                {{p.name}} - {{p.age}} - {{p.sex}}
            </li>

        </ul>


    </div>

    <script>
        Vue.config.productionTip=false;//阻止vue启动时生成生产提示

        ////用watch监视属性实现
        // const vm=new Vue({
        //     el:"#root",
        //     data:function(){
        //         return {
        //             personList:[
        //                 {id:001,name:"马冬梅",age:18,sex:"女"},
        //                 {id:002,name:"周冬雨",age:19,sex:"女"},
        //                 {id:003,name:"周杰伦",age:20,sex:"男"},
        //                 {id:004,name:"温兆伦",age:21,sex:"男"}
        //             ], 
        //             keyWord:"",
        //             personListTemp:[]

        //         }
        //     },
        //     watch:{
        //         keyWord:{
        //             immediate:true,
        //             handler:function(newVal,oldVal){
        //                 this.personListTemp=this.personList.filter(m=>m.name.indexOf(newVal)!=-1);
        //             }
        //         }
        //     }
        // });

        //计算属性实现
        const vm=new Vue({
            el:"#root",
            data:function(){
                return {
                    personList:[
                        {id:001,name:"马冬梅",age:18,sex:"女"},
                        {id:002,name:"周冬雨",age:19,sex:"女"},
                        {id:003,name:"周杰伦",age:20,sex:"男"},
                        {id:004,name:"温兆伦",age:21,sex:"男"}
                    ], 
                    keyWord:"",
                }
            },

            computed:{
                personListTemp:{
                    get(){
                        return this.personList.filter(m=>m.name.indexOf(this.keyWord)!=-1);
                    }
                },
            }
        });

    </script>

    
</body>
</html>

结果:
image



1.4、列表排序

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>初始vue</title>
    <!-- 引入vue-->
    <script src="/vueBaseJs/vue.js"></script>

</head>
<body>
    <div id="root">
        

        <!-- 遍历数组-->
        <h2>人员列表</h2>   
        <input type="text" placeholder="请输入名字" v-model="keyWord" >

        <button @click="sortType=2">年龄升序</button>
        <button @click="sortType=1">年龄降序</button>
        <button @click="sortType=0">原顺序</button>   
        
        <ul>
            <li v-for="(p,index) in personListTemp " :key="p.id">
                {{p.name}} - {{p.age}} - {{p.sex}}
            </li>

        </ul>


    </div>

    <script>
        Vue.config.productionTip=false;//阻止vue启动时生成生产提示

       
        //计算属性实现
        const vm=new Vue({
            el:"#root",
            data:function(){
                return {
                    personList:[
                        {id:001,name:"马冬梅",age:18,sex:"女"},
                        {id:002,name:"周冬雨",age:20,sex:"女"},
                        {id:003,name:"周杰伦",age:17,sex:"男"},
                        {id:004,name:"温兆伦",age:19,sex:"男"}
                    ], 
                    keyWord:"",
                    sortType:0,//0原顺序、1降序、2升序
                }
            },
            methods:{
            },

            computed:{
                personListTemp:{
                    get(){
                        const arr=this.personList.filter(m=>m.name.indexOf(this.keyWord)!=-1);
                        if(this.sortType!==0){
                            this.sortType==1? arr.sort((a,b)=> b.age-a.age): arr.sort((a,b)=> -(b.age-a.age));
                        }
                        return arr;
                    }
                },
            }
        });

    </script>

    
</body>
</html>

image



1.5、Vue数据监视

更新时的问题

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>初始vue</title>
    <!-- 引入vue-->
    <script src="/vueBaseJs/vue.js"></script>

</head>
<body>
    <div id="root">
        

        <!-- 遍历数组-->
        <h2>人员列表</h2>   
        <button @click="updataMa">更新 马冬梅的信息</button>
        
        <ul>
            <li v-for="(p,index) in personList " :key="p.id">
                {{p.name}} - {{p.age}} - {{p.sex}}
            </li>

        </ul>


    </div>

    <script>
        Vue.config.productionTip=false;//阻止vue启动时生成生产提示

        //vue监测数据改变的原理:vue底层自己会有监视,监视data里的数据是否改变,改变了就去更新用到改变的数据的地方。

       
        //计算属性实现
        const vm=new Vue({
            el:"#root",
            data:function(){
                return {
                    personList:[
                        {id:001,name:"马冬梅",age:18,sex:"女"},
                        {id:002,name:"周冬雨",age:20,sex:"女"},
                        {id:003,name:"周杰伦",age:17,sex:"男"},
                        {id:004,name:"温兆伦",age:19,sex:"男"}
                    ], 
                    keyWord:"",
                    sortType:0,//0原顺序、1降序、2升序
                }
            },
            methods:{
                updataMa(){
                    // this.personList[0].name="马宝国";//有效
                    // this.personList[0].age=60;//有效
                    // this.personList[0].sex="男"//有效
					//有效是因为student.personList[0]是对象,对象上的name属性vue有给它配置set属性作为数据代理。如果student.personList[0]不是对象,也就是数组里存放的是字符串,那么就无法监视

                    this.personList[0]={id:001,name:"马宝国",age:60,sex:"男"};//没有效果,因为这个赋值的方式不是响应式的,主要是数组里的下标vue没有给它配置set数据代理
                    
                }
            }
        });

    </script>

    
</body>
</html>

如下图:更新时候页面上并没有更新:
image

解决方法:
更改如下代码即可,因为vue对能修改数组本身的方法才进行数据监视,
image


简单模拟vue中的监测对象变化的原理

    <script type="text/javascript">

        //模拟创建Vue时配置对象里面的data数据
        let data={
            name:"南职院",
            address:"南充"
        }

        //创建监视的构造函数
        function Observer(obj){
            //将对象中的属性汇总成一个属性数组
            const keyArr=Object.keys(obj);

            keyArr.forEach((key,index)=>{

                //这里this是Observer的实例,因为上面是箭头函数,如果是funtion形式的话,this就变成window了。
                Object.defineProperty(this,key,{
                    get(){
                        return obj[key];
                    },
                    set(value){
                        console.log(key+"被改变了,我要去解析模板生成虚拟dom,进行比较.....开始忙了");
                        obj[key]=value;
                    }
                });
            });
        }

        //创建一个监视的实例对象,用于监视data中的属性变化。在vue中相当于将data格式加工为vm实例里的_data格式
        const obs=new Observer(data);

        //模拟创建好vm实例
        const vm={}

        //将实例的_data和data都赋值为加工后的样子
        vm._data=data=obs;
        console.log(vm);
        
    </script>

Vue.set();和vm.$set()

非响应式添加属性

image

Vue.set():响应式添加属性

添加的属性就和vue生成虚拟dom时候加工data里的数据一样,都有get和set监听。
image

vm.$set()方法和Vue.set()方法一致,只不过一个是构造函数身上的,一个是实例身上的。
vm.$set(vm.student,"sex","女");


Vue监视数据的原理:

  1. vue会监视data中所有层次的数据。

  2. 如何监测对象中的数据?
    通过setter实现监视,且要在new Vue时就传入要监测的数据。
    (1).对象中后追加的属性,Vue默认不做响应式处理
    (2).如需给后添加的属性做响应式,请使用如下API:
    Vue.set(target,propertyName/index,value)
    vm.$set(target,propertyName/index,value)

  3. 如何监测数组中的数据?
    通过包裹数组更新元素的方法实现,本质就是做了两件事:
    (1).调用原生对应的方法对数组进行更新。
    (2).重新解析模板,进而更新页面。

  4. 在Vue修改数组中的某个元素一定要用如下方法:
    1.使用这些API:push()、pop()、shift()、unshift()、splice()、sort()、reverse()
    2.Vue.set() 或 vm.$set()

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

image

image

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script src="../vueBaseJs/vue.js"></script>
</head>
<body>

    <div id="root">

        <button @click="student.age++;">年龄+1岁</button> <br/>
        <button @click="addSex">添加性别属性,默认值:男</button> <br/>
        <button @click="student.sex='未知'">修改性别</button> <br/>
        <button @click="addFriend">在列表首位添加一个朋友</button> <br/>        
        <button @click="updateFirstFriendName">修改第一个朋友的名字为:张三</button> <br/>
        <button @click="student.hobby.push('运动')">添加一个爱好</button> <br/>
        <button @click="updateFirstHobby">修改第一个爱好为:开车</button> <br/>
        <button @click="removeSmoke">过滤掉爱好中的抽烟</button> <br/>

        <br/>

        <h2>学生信息</h2>
        <h3>姓名:{{student.name}}</h3>

        <h3>年龄:{{student.age}}</h3>

        <h3 v-if="student.sex">性别:{{student.sex}}</h3>


        <h4>爱好:</h4>
        <ul>
            <li v-for="(val,index) in student.hobby" ::key="index">
                {{val}}
            </li>
        </ul>

        <h4>朋友们:</h4>
        <ul>
            <li v-for="(v,index) in student.friends " :key="index">
                {{v.name +":"+v.age}}

            </li>
        </ul>

    </div>

    <script type="text/javascript">
        Vue.config.productionTip=false;//阻止vue启动时生成生产提示

        const vm=new Vue({
            el:"#root",
            data:function(){
                return{
                    student:{
                        name:"tom",
                        age:18,
                        friends:[
                            {name:"jeer",age:35},
                            {name:"maker",age:30}
                        ],
                        hobby:["抽烟","喝酒","上网"],
                    },                    
                }
            },
            methods:{
                addSex(){
                    this.$set(this.student,"sex","男");
                },
                addFriend(){
                    this.student.friends.unshift({name:"queen",age:38});
                },
                updateFirstFriendName(){
                    this.student.friends[0].name='张三';
                    //student.friends[0]是对象,对象上的name属性vue有给它配置set属性作为数据代理。
                },
                updateFirstHobby(){
                    //this.student.hobby.splice(0,1,"开车");//方式1
                    this.$set(this.student.hobby,0,"开车");
                },
                removeSmoke(){
                    this.student.hobby=this.student.hobby.filter(m=> m!="抽烟");
                }
            }
        });

        
    </script>
    
</body>
</html>

数据劫持

就是将data里的数据转为vm里_data的格式,为每个属性添加getter和setter的操作就叫数据劫持。劫持就是修改属性被set发现,让后set执行操作。
image

posted @ 2022-04-03 17:54  青仙  阅读(137)  评论(0编辑  收藏  举报