Vue2(四):Vue监测数据的原理

Vue监测数据的原理

一、先来看一个问题

如果我要点击按钮实现更新冯万宁儿的信息,那么如果一个属性一个属性地改,可以修改成功,并且Vue也会检测到并更新到页面。但是我如果直接把整个对象改了,可以修改成功,但是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: {
            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
如果不理解obj[k],想想前边讲Object.defineProperty时那个number

	let data = {
	    name: 'zzy',
	    age: 18
	}
	
	//创建一个监视的实例对象,用来监视data中数据的变化
	const obs = new Observer(data);
	const vm = {};
	vm._data = obs;
	
	//创建一个类似vm._data的构造函数
	function Observer(obj) {
	    //1.创建一个数组接收传入对象的属性名
	    let arr = Object.keys(obj);  //['name','age']
	    //2.遍历属性名,让Observer实例对data中的每个数据进行数据代理
	    arr.forEach((k) => {
	        Object.defineProperty(this, k, {
	            get() {
	                //有人想读实例中的属性值,我就把data中对应的属性值拿过来
	                return obj[k];
	            },
	            set(val) {
	                //有人想改实例中的属性值,我就把data中对应的属性值更改(数据代理)
	                console.log(`${k}被改了,我要重新解析模板,生成虚拟DOM,开始diff算法`);
	                obj[k] = val;
	            }
	        })
	    })
	}

这只是个模拟,实际上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.girlfriend.sex = '女')为啥呢?

_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">
    <h2>我的名字:{{name}}</h2>
    <h2>我的年龄:{{age}}</h2>
    <h3 v-if="sex">我的性别:{{sex}}</h3>
    <button @click="addmySex">点击添加我的性别</button>
    <hr>
    <h2>她的名字:{{girlfriend.name}}</h2>
    <button @click="addherSex">点击添加性别,属性值为女</button>
    <h2 v-if="girlfriend.sex">她的性别:{{girlfriend.sex}}</h2> <!-- undefined不显示,也不报错 -->
    <h2>她的年龄:对外{{girlfriend.age.fakeAge}},真实{{girlfriend.age.realAge}}</h2>
    <h2>朋友们</h2>
    <ul>
        <li v-for="p in girlfriend.friends" :key="p.id">
            {{p.name}}----{{p.age}}
        </li>
    </ul>
</div>
<script>
    const vm = new Vue({
        el: '#root',
        data: {
            name: 'zzy',
            age: 18,
            girlfriend: {
                name: 'ht',
                // sex: '女',
                age: {
                    realAge: 23,
                    fakeAge: 18
                }
            }
        },
        methods: {
            addherSex() {
                // Vue.set(this.girlfriend, 'sex', '女');
                this.$set(this.girlfriend, 'sex', '女');  //vm.$set(vm.girlfriend, 'sex', '女');
            },
            addmySex() {
                Vue.set(this, '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>她的名字:{{girlfriend.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: 'zzy',
            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>

七、总结

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的根据对象 添加属性!!!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值