VUE原理(一):observe和Object.defineProperty()数据劫持

        众所周知,vue2使用Object.defineProperty()劫持数据的get/set,并在数据初始化的时候递归data属性,该方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象。

        感兴趣可以继续看原理解析:

VUE原理(二):依赖收集:Dep属性订阅器、Watcher订阅者
VUE原理(三):Compile模板解析指令和vue原理概括

笔者今天的目标就是记录下这个方法是如何驱动视图的。首先看一下这个方法:

语法: Object.defineProperty(obj, prop, descriptor)

第一个参数是要定义的对象;第二个参数是属性名,第三个参数是一个对象,可以在里面写属性。

一、一个简单的例子

        首先,先用个简单的例子介绍一下Object.defineProperty()

let person = {
    name:'zoie',
    age:'18',
    address:'江苏'
}
Object.defineProperty(person,'sex',{
    value: 'f'
})

        此时输出person,笔者截图如下:

        这样就给person添加了新的属性sex,且值为f,但是这个值是不可枚举,不可修改,不可删除的,可以使用enumerable来设置为可枚举,使用writable设置为可修改,使用configurable设置为可删除,这里笔者就不去一个个尝试了,感兴趣的可以自己试一下。

let person = {
    name:'zoie',
    age:'18',
    address:'江苏'
}
Object.defineProperty(person,'sex',{
    value: 'f',
    enumerable:true,
    writable:true,
    configurable:true
})

        还是这个例子,这次我们把sex的值通过get返回,且这个值用变量存起来:

let person = {
    name:'zoie',
    age:'18',
    address:'江苏'
}
let sex = 'f'
Object.defineProperty(person,'sex',{
    get(){
        return sex
    }
})

        此时输出person,sex的值是通过get取到的:

        既然如此,不妨修改一下sex值为'm':

        可以看到sex值已经变了。

        与get相同,该方法还有一个存取描述符:set,两者默认值都为undefined。set接收一个值,我们可以把这个值当做新值,那么就像上面的例子一样,只是把手动给sex赋值为'm'这件事通过set来做而已。

let person = {
    name:'zoie',
    age:'18',
    address:'江苏'
}
let sex = 'f'
Object.defineProperty(person,'sex',{
    get(){
        return sex
    },
    set(v){
        sex = v;
    }
})

        看一下输出的person:

        触发set:

二、Object.defineProperty()操作DOM

        通过上面的例子,不难解决这个问题,只需要修改某个属性,然后在set中修改dom内容,在get中返回dom内容即可:

    let obj = {};
    Object.defineProperty(obj,'name',{
        get(){
            return document.querySelector('#root').innerText;
        },
        set(v) {
            document.querySelector('#root').innerText = v;
        }
    });
    obj.name = 'zoie'

        当执行了obj.name赋值语句,就触发了set,由此将页面上id为root的元素内容改写。

三、Vue中的应用(简洁版)

        首先来看一个vue的组件写法:

    const app = new vue({
        el:'#root',
        data:{
            name:'zoie',
            favourite:{
                food:'meat'
            }
        },
        created(){
        },
        methods:{
        }
    });

        给vue类传入一个对象,这里叫它options好了,option对象挂载了几个属性,这里监听的是data里面的内容,因此,给data的所有属性都进行数据劫持,这样每次修改都会触发各自的set,从而被感知。

class vue {
    constructor(options){
        this.$options = options;
        this.$data = options.data;
        this.observe(this.$data);
    }
    observe(obj){
        if(!obj || typeof obj !== 'object'){
            return
        }
        Object.keys(obj).forEach((val) => {
            this.defineReactive(obj,val,obj[val]);
        })

    }
    defineReactive(obj,key,val){
        Object.defineProperty(obj,key,{
            get(){
                return val
            },
            set(v) {
                if(v === val){
                    return;
                }
                val = v;
                console.log('更新:'+ JSON.stringify(v))
            }
        });
    }
}

        经过一次数据劫持后,此时修改data下的属性,可以在控制台看到有更新:

 app.$data.name = 'zzz';
 app.$data.favourite = {food:'apple'};

        但是!当修改app.$data.favourite.food = 'orange'时,却没有触发更新,这是因为food在favourite对象下,因此,vue需要层层递归,给每一个属性都加上数据劫持:

    defineReactive(obj,key,val){
        this.observe(val);
        //...
    }

        现在,修改app.$data.favourite.food = 'orange',也可以在控制台看到更新了。

总结

        本篇主要介绍了Object.defineProperty()方法的使用和在vue中的简洁版使用,从中可以看出,vue的原理是采用数据劫持结合观察者模式,在observe()中,对data中所有属性进行劫持,又递归对所有属性是对象的再次进行数据劫持,确保每一个属性更新后都能触发set。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值