Vue2基础 10:Vue监视数据的原理

案例:点击按钮修改数组中的某元素----存在数据改变, 但是Vue没有检测到变化的问题

<body>
    <div id="root">
        <h2>人员列表</h2>
        <button @click='updateMei'>更新马冬梅的信息</button>
        <ul>
            <li v-for="(p,index) of persons" :key="p.id">{{p.name}}-{{p.age}}-{{p.sex}}</li>
        </ul>
    </div>
</body>

<script type="text/javascript">
    new Vue({
        el:'#root',  
        data:{ 
            persons:[
                {id:'001',name:'马冬梅',age:36,sex:'女'},
                {id:'002',name:'周冬雨',age:25,sex:'女'},
                {id:'003',name:'周杰伦',age:45,sex:'男'},
                {id:'004',name:'温兆伦',age:18,sex:'男'}
            ],
        },
        methods:{
            updateMei(){
                //奏效
                this.persons[0].name = '马老师'
                this.persons[0].age = 50
                this.persons[0].sex = '男'
                
                //不奏效
                this.persons[0] = {id:'001',name:'马老师',age:50,sex:'男'}
                //数据其实改了, 但是Vue没有检测到变化
            }
        }
    })
</script>

1 探寻Vue监测对象的原理

由数据代理可知,data被赋给了Vue实例的_data,但是打开控制台查看vm._data发现与原data不同的是,配置项不是直接给的,而是由getter给的,修改也是由setter完成的。

<body>
    <div id="root">
        <h2>学校名称:{{name}}</h2>
        <h2>学校地址:{{address}}</h2>
        <ul>
            <li v-for="(p,index) of persons" :key="p.id">{{p.name}}-{{p.age}}-{{p.sex}}</li>
        </ul>
    </div>
</body>

<script type="text/javascript">
    const vm = new Vue({
        el:'#root',  
        data:{ 
            name:'武大',
            address:'武汉'
        }
    })
</script>

控制台日志
也就是每一组key: value都形成了getter、setter的写法。

2 数据改变引起页面改变的流程

传入data
加工data
变响应式
data
reactiveGetter
reactiveSetter
vm_data=data
修改_data
引起setter调用重新解析模板
生成虚拟DOM
新旧DOM对比
更新页面

3 模拟数据监测(代码中不引入Vue)

3.1 错误写法

<script type="text/javascript">
    let data = { 
        name:'武大',
        address:'武汉'
    }
    
    Object.defineProperty(data,'name',{
        get(){
            return data.name
        },
        set(val){
            data.name = val
        }
    })
</script>

在控制台输出data,结果报"溢出"如下:

读取的流程:

读取name
调get
返回data.name

修改name报错(超出最大调用堆栈大小)如下:

修改的流程:

修改name
调set
修改data.name

3.2 实际的大概写法

<script type="text/javascript">
    let data = { 
        name:'武大',
        address:'武汉'
    }
    
    //拿到data之后,先创造一个监视的实例对象,用于监视data中属性的变化
    const obs = new Observer(data)
    
    let vm = {}
    vm._data = data = obs
    
    //Observer是构造函数,参数为对象
    function Observer(obj){
        //1.汇总对象中所有属性形成一个数组
        const keys = Object.keys(obj)
        //2.遍历对象中所有的键,并添加属性
        keys.forEach((k)=>{
            Object.defineProperty(this,k,{
                get(){
                    return obj[k]//返回传入对象对应的属性值
                },
                set(val){ //收到修改的值
                    obj[k] = val //把传入对象的k所对应的值改掉
                },
            })
        })
    }
    
    
</script>

与真实不同(这段代码不够完善)的是:

描述这段代码真实Vue
修改数据vm._data.namevm.data
data对象里有对象对象里的属性没有getter、setter有递归来实现每一个属性都有
data里数组的属性没有getter、setter有getter、setter

Object.keys(obj):获取对象中所有可枚举的键,并返回一个数组。适用于需要遍历随想键的场景。
补充:Object常见API Object.defineProperty回顾

4 补充API:Vue.set( )

4.1 给data中的对象添加属性配置

<body>
    <div id="root">
        <h2>学校名称:{{name}}</h2>
        <h2>学校地址:{{address}}</h2>
        <hr/>
        <button @click='addsex'>添加性别属性</button>
        <h2>学生姓名:{{student.name}}</h2>
        <h2>学生性别:{{student.sex}}</h2>
        <h2>学生年龄:真实{{student.age.rage}},对外:{{student.age.sage}}</h2>
        <h2>朋友们:</h2>
        <ul>
            <li v-for="(f,index) of student.friends" :key="index">{{f.name}}-{{f.age}}</li>
        </ul>
    </div>
</body>

<script type="text/javascript">
    const vm = new Vue({
        el:'#root',  
        data:{ 
            name:'武大',
            address:'武汉',
            student:{
                name:'fx',
                //sex:'女',
                //注释后不会报错但是不显示,因为没有的话是undefined,但是vue不会显示undefined到页面上
                //即{{student.sex}}不报错,{{sex}}会报错
                age:{
                    rage: 24,
                    sage: 18,
                },
            
                friends:[
                    {name:'a',age:26},
                    {name:'b',age:25}
                ]
            },
        },
        methods:{
            adddsex(){
                //第一种:利用Vue的API
                Vue.set(this.student,'sex','女')
                    //Vue.set(对象名,属性,值)
                    //官网:Vue.set(target,propertyName/index,value)
                
                //第二种:Vue实例的自带方法
                this.$set(this.student,'sex','女')
            }
        }
    })
</script>

注意:只能给Vue实例的data中的某个对象添加属性,而不能直接给Vue实例或者data直接添加

5 探寻Vue监测数组的原理

案例:

<body>
    <div id="root">
        <h2>朋友们:</h2>
        <ul>
            <li v-for="(f,index) of student.friends" :key="index">{{f.name}}-{{f.age}}</li>
        </ul>
        <h2>爱好:</h2>
        <ul>
            <li v-for="(h,index) of student.hobby" :key="index">{{h}}</li>
        </ul>
    </div>
</body>

<script type="text/javascript">
    const vm = new Vue({
        el:'#root',  
        data:{ 
            student:{
                friends:[
                    {name:'a',age:26},
                    {name:'b',age:25}
                ],
                hobby:['抽烟','喝酒','烫头']
            }
        }
    })
</script>

控制台输出该实例:
students.hobby数组的每个元素无对应的getter和setter
此时直接通过索引值修改值是无法监测到的,也就是之前的那个问题

程序员通常会使用JS数组的一些常用方法,其中修改原数组的几个方法可以被Vue监测到:

方法描述
push()最后面添加元素,返回新数组长度
unshift()最前面添加元素,返回新数组长度
pop()删除最后一个元素,返回删除的元素
shift()删除最前面的元素,返回删除的元素
splice()方法(开始删除元素的索引,删除元素总数,插入的元素)元素插入的位置就是删除元素开始的位置
sort()将元素排序,并返回排序好的数组
降序:sort(function(a,b){return a-b})
升序:sort(function(a,b){return b-a})
reverse()将元素进行倒叙,返回倒叙的数组

之前那个问题也就要写成:
this.person.splice(0,1,{id:'001',name:'马老师',age:50,sex:'男'})

Vue能监测到数组的改变,其实是包装了数组这些常用方法。

对于普通数组来说,比如这里arr调用了push( ),这个方法调的是Array原型对象上的push方法(Array.prototype.push)。

let arr = [1,3,5,7,9]
arr.push(10)
arr.push === Array.prototype.push
//返回true

而Vue实例中data的student配置对象的hobby数组调用的已经不是Array原型对象的方法了(Array.prototype.push)

Vue管理的arr调用方法
沿着Array原型对象proto:Array找
找到了里面的push
vm._data.student.hobby.push === Array.prototype.push
//返回false

此时调用的push其实是Vue写的push,也就是说沿着hobby的原型找的是Vue写的push。

Vue写的push:

  1. 调用Array.prototype.push
  2. 重新解析模板–>生成虚拟DOM–>新旧DOM比较–>更新页面

所以也就能监测到这些方法的调用,监测到数组的改变。

除了这些数组方法,使用Vue.set,也可以改变数组元素。

//将原来第二个元素的喝酒变成打台球

//第一种写法
Vue.set(vm.student.hooby,1,'打台球')

//第二种写法
vm.$set(vm.student.hooby,1,'打台球')

6 总结Vue监视数据

<body>
    <div id="root">
        <h1>学生信息:</h1>
        
        <button @click='student.age++'>年龄+1</button><br/>
        <button @click='addsex'>添加性别属性</button><br/>
        <button @click='addfriend'>在首位添加一位朋友</button><br/>
        <button @click='updatefirstfriendname'>修改第一个朋友名字</button><br/>
        <button @click='addhobby'>添加一个爱好</button><br/>
        <button @click='updatefirsthobby'>修改第一个爱好为开车</button><br/>
        <button @click='removehooby'>过滤爱好</button><br/>
        
        
        <h3>姓名:{{student.name}}</h3>
        <h3>年龄:{{student.age}}</h 3>
        <h3 v-if="student.sex">性别:{{student.sex}}</h3>
        <h2>朋友们:</h2>
        <ul>
            <li v-for="(f,index) of student.friends" :key="index">{{f.name}}-{{f.age}}</li>
        </ul>
        <h2>爱好:</h2>
        <ul>
            <li v-for="(h,index) of student.hobby" :key="index">{{h}}</li>
        </ul>
    </div>
</body>

<script type="text/javascript">
    const vm = new Vue({
        el:'#root',  
        data:{ 
            student:{
                name:'fx',
                age:18,
                friends:[
                    {name:'a',age:26},
                    {name:'b',age:25}
                ],
                hobby:['抽烟','喝酒','烫头']
            }
        },
        methods:{
            addsex(){
                Vue.set(this.student,'sex','女')
                //或
                this.$set(this.student,'sex','女')
            },
            addfirend(){
                this.student.friends.unshift({name:'crisp',age:25})
                //添加的数组里的对象是响应式的,即有getter和setter
            },
            updatefirstfriendname(){
                this.student.friends[0].name = '张三'
                //this.student.friends[0]为对象,这里是修改对象的属性,有对应的getter和setter
                //若直接修改数组元素 this.student.friends[0] = '张三', Vue是不承认的
            },
            addhobby(){
                this.student.hobby.push('学习')
            },
            updatefirsthobby(){
                this.student.hobby.splice(0,1,'开车')
                //或
                Vue.set(this.student.hobby,0,'开车')
                //或
                this.$set(this.student.hobby,0,'开车')
            },
            removehooby(){
                this.student.hobby = this.student.hobby.filter((h)=>{
                    return h !== '抽烟'
                })
            }//也是要变更数组,但是不会变更原始数组
        }
    })
</script>
  1. vue会监视data中所有层次的数据
  2. 对象的数据监测:通过setter监视,且要在new Vue时就传入要检测的数据(指一开始就写好)
    (1)后追加的属性,默认不做响应式处理
    (2)如需将添加的做响应式处理,需用Vue.set的API
  3. 数据的数据监测:通过包裹数组更新元素的方法
  4. 修改数组元素的方法:
    (1)使用数组方法API
    (2)Vue.set( )或 vm.$set( )

==注意==:Vue.set( )和 vm.$set( )不能用于为 Vue实例 和 实例的根数据对象data 添加属性!!

7 数据劫持

将代码中的data改成了控制台输出实例.data,即遍历了所有的属性并变成getter和setter

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值