Vue 源码解析(Reflect and Proxy)

SegmentFault

共 5447字,需浏览 11分钟

 ·

2021-03-08 21:59

作者:夏灬影
来源:SegmentFault 思否社区




最近研究 vue3 源码, 知道使用了 Proxy 和 Reflect, 但是不了解它们之间的关系


这篇文章主要是让大家了解 vue3 为什么使用 Proxy 和 Reflect 以及响应式的部分原理




为什么使用 Proxy(Proxy 和 Object.defineproperty)


Object.defineproperty实现对象监听


先解析一下vue2中使用的Object.defineproperty


let obj = {
    a: 10
}
Object.keys(obj).forEach(key => {
    let value = obj[key]
    Object.defineProperty(obj, key, {
        set(newValue) {
            console.log(`监听${key}改变: ${newValue}`);
            value = newValue
        },
        get() {
            console.log(`获取${key}对应的值: ${value}`);
            return value
        }
    })
})
obj.a = 100 // 监听a改变: 100
obj.b = 10 // 不会被监听到


通过上面的例子我们可以看到obj新添加的属性b, 并不会被监听到


vue2中使用中我们也会遇到这样的问题


# template
<p @click="adda(obj)">{{ obj.a }}</p>
<p @click="addb(obj)">{{ obj.b }}</p>

# srcript
data () {
    return {
        obj:{
            a:1
        }
    }
},
mounted () {
    this.obj.b = 1;
},
methods: {
    addb(item){
        item.b += 1;
        console.log(this.obj.b)
    },
    adda(item){
        item.a += 1;
    }
}


我们发现点击obj.a是响应式, 页面也会更新


而新增的obj.b点击则不会


因为vue2使用的Object.defineproperty无法监听到新增的对象属性


针对这个问题vue2提供了$set方法来解决


mounted () {
    this.$set(this.obj, "b", 1)
}


Proxy实现对象监听


let obj = {
    a: 10
}
const handler = {
    get(target, prop) {
        console.log(`获取${prop}对应的值: ${target[prop]}`);
        return target[prop];
    },
    set(target, prop, val) {
        target[prop] = val;
        console.log(`监听${prop}改变: ${val}`);
        return true
    }
}

let obj2 = new Proxy(obj, handler)
obj2.b = 100 // 监听b改变: 100


我们可以看到通过Proxy实例可以对新添加的属性进行监听


当然Proxy还可以做许多的其他功能, 这里就不多介绍了


我查看Vue3的源码的时候一直对Proxy中使用的Reflect感到不解,为什么要使用Reflect.get和Reflect.set, 我查询了一些文章, 大概了一下思路


Reflect


我将通过一些问题, 来指明Reflect中Proxy中的用处


我们有一个user带有_name属性的对象和一个吸气剂。


这是围绕它的代理:


let user = {
  _name: "Guest",
  get name() {
    return this._name;
  }
};

let userProxy = new Proxy(user, {
  get(target, prop, receiver) {
    return target[prop];
  }
});

console.log(userProxy.name); // Guest


对于我们的示例而言,这就足够了。


一切似乎都还好。但是,让我们将示例变得更加复杂。


继承另一个对象后admin从user,我们可以观察到不正确的行为:


llet user = {
  _name: "Guest",
  get name() {
    return this._name;
  }
};

let userProxy = new Proxy(user, {
  get(target, prop, receiver) {
    console.log(target) // user对象{_name: "Guest"}
    return target[prop];
  }
});

let admin = {
  __proto__: userProxy,
  _name: "Admin"
};

console.log(admin.name); // Guest


阅读admin.name应该返回"Admin",而不是"Guest"!


怎么了?也许我们在继承方面做错了什么?


问题实际上出在代理所在的行中:


  1. 当我们阅读时admin.name,由于admin对象没有自己的属性,搜索将转到其原型。
  2. 原型是userProxy
  3. name从代理读取属性时,其get将触发并从原始对象中返回该属性,它在上下文中运行其代码this=target。因此,结果this._name来自原始对象target,即:from user。

而这个时候就是Reflect.get就派上用场了

如果我们使用它,一切都会正常运行。

let user = {
  _name: "Guest",
  get name() {
    return this._name;
  }
};

let userProxy = new Proxy(user, {
  get(target, prop, receiver) { // receiver = admin
    return Reflect.get(target, prop, receiver);
  }
});


let admin = {
  __proto__: userProxy,
  _name: "Admin"
};

console.log(admin.name); // Admin

Reflect.get中receiver参数,保留了对正确引用this(即admin)的引用,该引用将Reflect.get中正确的对象使用传递给get。



点击左下角阅读原文,到 SegmentFault 思否社区 和文章作者展开更多互动和交流,扫描下方”二维码“或在“公众号后台回复“ 入群 ”即可加入我们的技术交流群,收获更多的技术文章~

- END -


浏览 32
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报