1.Object.defineProperty
基本使用方法:第一个参数是被拦截的对象,第二个参数是对象的具体属性,第三个是一个对象,里面有对该属性的具体配置。
const obj = {
name: 'cloudgaps',
age: '18',
info: {
a:'1',
b:'2'
}
}
Object.defineProperty(obj,'name',{
enumerable:true, //当前属性允许被循环 枚举
configurable:true, //当前属性允许被配置 删除
get(){}, //取值拦截 getter
set(newVal){} //赋值拦截 setter
})
当访问obj.name时,会先被defineProperty中的get方法拦截,
当为obj.name赋值时,会被defineProperty中的set方法拦截,set方法中可以拿到你赋的最新值。
上文说到,当vue监听到最新赋值后应该通知订阅者,而监听的时候就是在set内,此时我们就应该在set内调用通知订阅者的notify去通知订阅者。
2.vue中其实是在vue的实例对象中定义了一个数据劫持的方法,该方法会拿到data中的数据,为所有数据添加get和set方法,当vue中的数据有了变化时,每个数据对应的set内就会拿到最新的值并通过发布订阅模式去通知订阅者
const obj = {
name: 'cloudgaps',
age: '18',
info: {
a:'1',
b:'2'
}
}
// vue构造函数中定义的数据劫持的方法
function Observe() {
// 递归的终止条件
if(!obj || typeof obj !== 'object') return false
//Object.keys遍历对象中的每个key,并返回所有key组成的一个数组
Object.keys(obj).forEach(key => {
// 当前循环的属性所对应的值
let value = obj[key]
// 进行递归
Observe(value)
Object.defineProperty(obj,key,{
enumerable:true,
configurable:true,
get(){
// 当数据被访问,则return出当前的值出去给访问者拿到
return value
},
set(newValue){
// 如果值被重新设置,则将value设置为最新的value
// 那么再访问拿到该值时就时最新的value了
value = newValue
//当重新赋值后再调用observe为数据增加getter和setter
Observe(value)
}
})
})
}
注:只写出了data中是对象的情况,如果数据是数组,则另作讨论,但原理是一致的。
vue中实例对象直接访问到数据也是通过这一原理,将
// 假设vm是vue单页面的实例对象
const vm = new Vue({
// 传入了obj数据
})
class Vue {
constructor(options){
this.$data = options.data
}
// 此时我们拿到页面的数据要通过vm.$data才能拿到 十分不方便
// 于是我们通过defineProperty代理属性将数据代理到vm上
Object.keys(this.$data).forEach(key => {
// this指向的就是vm实例对象
Object.defineProperty(this,key, {
enumerable:true,
configurable:true,
get(){
return this.$data[key]
},
set(newValue){
this.$data[key] = newValue
}
}