Vue2(四):Vue监测数据的原理(对象,数组),Vue.set的使用方法

文章讨论了Vue中如何实现数据的响应式更新,包括单属性修改、数组元素替换的差异,以及Vue.set和数组方法在监测和修改过程中的作用。重点讲解了setter和getter在监测数据变化中的关键作用以及Vue对数组元素的特殊处理方式。
摘要由CSDN通过智能技术生成

一、更新时的一个问题

如果我要点击按钮实现更新冯万宁儿的信息,那么如果一个属性一个属性地改,可以修改成功,并且Vue也会检测到并更新到页面。

但是我如果直接把要修改的信息写在this.persons[0] = { id: 001, name: '冯千宁儿', age: 66, sex: '女' };这里,就不行,Vue监测不到并且不更新到页面,这是为什么?

    <!-- 准备好一个容器 -->
    <div id="root">
        <h1>人员列表</h1>
        <button @click="updateNing">点击更新冯万宁儿</button>
        <ul>
            <li v-for="(p,index) in persons" :key="p.id">
                {{p.name}}----{{p.age}}----{{p.sex}}
            </li>
        </ul>
    </div>

    <script>
        const vm = new Vue({
            el: '#root',
            data: {
                keyword: '',
                persons: [
                    { id: 001, name: '冯万宁儿', age: 23, sex: '男' },
                    { id: 002, name: '屁及万儿', age: 18, sex: '男' },
                    { id: 003, name: '及丽热巴', age: 10, sex: '女' },
                    { id: 004, name: '冯小刚儿', age: 60, sex: '男' }
                ]
            },
            methods: {
                updateNing() {
                    // this.persons[0].name = '冯千宁儿';     //奏效
                    // this.persons[0].age = 66;        //奏效
                    // this.persons[0].sex = '女';      //奏效
                     this.persons[0] = { id: 001, name: '冯千宁儿', age: 66, sex: '女' };
                //上面这么写,也奏效,数据实际上已经改了,但是Vue监测不到所以没更新到页面,为啥捏?
                }
            }
        })
    </script>

二、原生js模拟Vue监视data对象中的数据

用原生js写一个Vue监视data数据改变的效果

可以看出,Vue监视数据的原理,就是靠setter

<script type="text/javascript">
        let data = {
            name : 'cgp',
            age : 18
        }

        // 创建一个监视的实例对象,用于监视data中属性的变化
        const obs = new Observer(data)
        console.log(obs);

        // 准备一个vm实例对象
        let vm = {}

        vm._data = data = obs
        function Observer(obj){
            // 汇总对象中所有的属性形成一个数组
            const keys = Object.keys(obj)
            // 遍历
            keys.forEach((k)=>{
                Object.defineProperty(this,k,{
                    get(){
                        return obj[k]
                    },
                    set(val){
                        console.log('${k}被改了,我要去解析模板,生成虚拟dom....');
                        obj[k]=val
                    }
                })
            })
        }
    </script>

这只是个模拟,实际上Vue做了更多事情,比如:
1、在setter中不是简单的输出,而是重新解析模板,生成虚拟DOM,新旧虚拟DOM对比,更新页面实现响应式。
2、Vue中vm还对_data做了数据代理,我可以直接vm.name,不用vm._data.name
3、我这么写,如果数据是套娃的(对象中还有对象还有对象),就监测不到。但是Vue就可以,不管你数据藏得多深,Vue都能找到,每个数据都有自己的getter和setter。只要有数据被改,就会实现响应式。(这里面好像涉及了深拷贝的知识?

三、Vue监测对象中的数据

 1、监视对象中的数据

1、vue会监视data中所有层次的数据,不管你藏得有多深。
2、如何监测对象中的数据?
通过setter实现监视,且要在new Vue时就传入要监测的数据。
(1).对象中后追加的属性,Vue默认不做响应式处理
(2).如需给后添加的属性做响应式,请使用如下API:

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

2、Vue.set的使用

用法:

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

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

_data在收集data中的数据之前,先做了一个加工(这个加工也可以称之为数据劫持),那就是给每个数据匹配一个getter和setter,前面说了,Vue监视数据的原理,就是靠setter。所以如果我们后期手动直接给_data添加属性(注意区别手动添加和从data中收集),是无法实现响应式的,因为没办法给它setter,只有从data中收集过来的属性才能通过一个鱿鱼西定义的好像是叫Observer什么什么的方法给它搞个setter。

Vue.set()是有局限性的!!!它只能给data中的某个对象追加属性,不能给vm 或 vm的根数据对象(data或_data) 添加属性!!!
也就是说第一个参数不能是 Vue 实例,或者 Vue 实例的根数据对象(_data)

例如给学生添加性别:

    <div id="root">
        <h1>学校信息</h1>
        <h2>学校名称:{{name}}</h2>
        <h2>学校地址:{{address}}</h2>
        <hr>
        <h1>学生信息</h1>
        <button @click="add">添加性别</button>
        <h2>学生姓名:{{student.name}}</h2>
        <h2 v-if="student.sex">学生性别:{{student.sex}}</h2>
        <h2>学生年龄:真实{{student.age.rAge}}, 对外{{student.age.sAge}}</h2>
        <h2>朋友</h2>
        <ul>
            <li v-for="(f,index) in student.friends" :key="index">
            {{f.name}}----{{f.age}}
            </li>
        </ul>
    </div>
    <script>
        const vm = new Vue({
            el:'#root',
            data:{
                name:'椰果',
                address:'郑州',
                student:{
                    name:'cgp',
                    age:{
                        rAge:'18',
                        sAge:'19'
                    },
                    friends:[
                        {name:'yhg',age:'20'},
                        {name:'zym',age:'21'}
                    ]
                }
            },
            methods:{
                add(){
                    // Vue.set(this.student,'sex','男')
                    this.$set(this.student,'sex','男')
                }
            }
        })
    </script>

四、Vue监测数组中的数据 

1.如何监测数组中的数据?


其实是Vue里面定义了一个非常牛逼的方法,目的只有一个:实现响应式。

方法里包装的代码本质就是做了两件事:
(1)调用原生对应的方法对数组进行更新。然后第二步在这个基础上再加点料
(2)重新解析模板,生成虚拟DOM,diff算法……进而更新页面,实现响应式。

所以在Vue修改数组中的某个元素实现响应式一定要用如下方法:
(1)使用这些API:push()、pop()、shift()、unshift()、splice()、sort()、reverse()
(2)Vue.set() 或 vm.$set()也可以实现响应式,第二个参数写索引,第三个写元素,可以改也可以加

2、例如

<div id="root">
    <h2>我的名字:{{name}}</h2>
    <h2>我的年龄:{{age}}</h2>
    <hr>

    <h2>他的名字:{{boyfriend.name}}</h2>

    <button @click="addHobby">点击替换'跳'的爱好</button>
    <h2>爱好</h2>
    <ul>
        <li v-for="(h,index) in girlfriend.hobby" :key="index">
            {{h}}
        </li>
    </ul>
</div>
<script>
    const vm = new Vue({
        el: '#root',
        data: {
            name: 'cgp',
            age: 18,
            girlfriend: {
                name: 'ht',
                hobby: ['唱', '跳', 'rap']
            }
        },
        methods: {
            addHobby() {
                //除了那7个方法外,set方法也可以改变数组实现响应式
                Vue.set(this.girlfriend.hobby, 1, '打游戏');
            }
        },
    })  
</script>

五、回头看看第一个问题

说了这么多,再回头看第一个问题,就恍然大悟了
1、单独改每个属性可以奏效,是因为Vue可以监测到对象里的每个属性,不管它藏的多深,只要是个对象,就有相应的getter和setter
2、将数组中的元素单独替换,数据修改成功,但是Vue没有监测到,页面不显示,这是因为数组中的每个元素是没有getter和setter的,如果修改数组中的元素无法实现响应式.
3、要想让更改数组中的元素实现响应式,就得调用数组的七个api或者用Vue.set()方法

<!-- 准备好一个容器 -->
<div id="root">
    <h1>人员列表</h1>
    <button @click="updateNing">更新冯万宁儿</button>
    <ul>
        <li v-for="(p,index) in persons" :key="p.id">
            {{p.name}}----{{p.age}}----{{p.sex}}
        </li>
    </ul>
</div>

<script>
    const vm = new Vue({
        el: '#root',
        data: {
            persons: [
                { id: 001, name: '冯万宁儿', age: 23, sex: '男' },
                { id: 002, name: '屁及万儿', age: 18, sex: '男' },
                { id: 003, name: '及丽热巴', age: 10, sex: '女' },
                { id: 004, name: '冯小刚儿', age: 60, sex: '男' }
            ],
        },
        methods: {
            updateNing() {
                // this.persons[0].name = '冯千宁儿';     //奏效
                // this.persons[0].age = 66;        //奏效
                // this.persons[0].sex = '女';      //奏效

                // this.persons[0] = { id: 001, name: '冯千宁儿', age: 66, sex: '女' };
                //上面这么写,也奏效,数据实际上已经改了,但是Vue监测不到所以没更新到页面,为啥捏?

                //实际上是因为Vue监测数组时,数组没有相应的setter和getter,直接改Vue监测不到
                //但是数组中的每个对象中的属性都有自己的getter,setter,所以单独改属性时可以监测到的
                //要想让更改数组中的元素实现响应式,就得调用数组的七个api或者用set
                this.persons.splice(0, 1, { id: 001, name: '冯千宁儿', age: 66, sex: '女' });
            }
        }
    })
</script>

六、练习 

<div id="root">
    <button @click="addSex">添加一个性别属性,默认为女</button>
    <button @click="addHeight">添加一个身高属性,默认为170</button><br>
    <button @click="girlfriend.age.realAge--">年龄-1</button>
    <button @click="addFriend">在列表前加一个朋友</button><br>
    <button @click="updateFriend">修改第一个朋友的名字为张三</button>
    <button @click="addHobby">添加一个爱好</button><br>
    <button @click="updateHobby">修改第一个爱好为散步</button>
    <button @click="removeHobby">过滤掉爱好中的跳</button>

    <h2>名字:{{girlfriend.name}}</h2>
    <h2>年龄:对外{{girlfriend.age.fakeAge}},真实{{girlfriend.age.realAge}}</h2>
    <h2 v-if="girlfriend.sex">性别:{{girlfriend.sex}}</h2>
    <h2 v-if="girlfriend.height">身高:{{girlfriend.height}}</h2>

    <h2>朋友们</h2>
    <ul>
        <li v-for="p in girlfriend.friends" :key="p.id">
            {{p.name}}----{{p.age}}
        </li>
    </ul>

    <h2>爱好</h2>
    <ul>
        <li v-for="(h,index) in girlfriend.hobby" :key="index">
            {{h}}
        </li>
    </ul>
</div>
<script>
    const vm = new Vue({
        el: '#root',
        data: {
            name: 'zzy',
            age: 18,
            girlfriend: {
                name: 'ht',
                // sex: '女',
                age: {
                    realAge: 23,
                    fakeAge: 18
                },
                friends: [
                    { id: 001, name: 'jack', age: 10 },
                    { id: 002, name: 'rose', age: 8 },
                ],
                hobby: ['唱', '跳', 'rap']
            }
        },
        methods: {
            addSex() {
                Vue.set(this.girlfriend, 'sex', '女');
            },
            addHeight() {
                this.$set(this.girlfriend, 'height', 170);
            },
            addFriend() {
                this.girlfriend.friends.unshift({ id: 003, name: 'alice', age: 5 });  //有效写法
            },
            updateFriend() {
                this.girlfriend.friends[0].name = '张三';
            },
            addHobby() {
                this.girlfriend.hobby.push('打游戏');
            },
            updateHobby() {
                // this.girlfriend.hobby[0] = '散步';  //无效写法
                // this.girlfriend.hobby.splice(0, 1, '散步');   //有效写法
                Vue.set(this.girlfriend.hobby, 0, '散步');   //有效写法
            },
            removeHobby() {
                // 变更方法,顾名思义,会变更调用了这些方法的原始数组。相比之下,也有非变更方法
                // 例如 filter()、concat() 和 slice()。它们不会变更原始数组,而总是返回一个新数组。
                // 当使用非变更方法时,可以用新数组替换旧数组:
                this.girlfriend.hobby = this.girlfriend.hobby.filter((ele) => {
                    return ele !== '跳';
                })
            }
        },
    })
</script>

(ps:今天一个好消息补考过了!听懂了 也要再练习一下哦)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值