深入详解Vue3 ref底层实现原理
作者:矢心
前言
随着现在vue3越来越普及,相应的面试题也多了起来
说到vue3的面试题,有一个最经典的就是ref和reactive的区别,用法上的区别很明显,大家都理解,对于实现这两个方法的底层原理网上却众说纷坛,各有说法。
其中vue3的reactive的是用Proxy实现的这一点是明确的
这里讲的就是这个ref,有说是使用Object.defineProperty实现的,有说是使用Proxy实现的,说法不一,到底哪些是正确的呢
下面说一下我自己的理解,如有误请在评论区指出!谢谢
源码解析
既然各方说法不一,那首先想到的就是直接去看vue3官方源码不就好了,看别人不如自己实际动起来
通过node_modules依赖文件找到vue中实现ref的源码
源码如下:
为方便理解,把相关涉及到的源码以及代码含义加上注释
function ref(value) { // ref方法 return createRef(value, false); } function shallowRef(value) { // 浅层ref return createRef(value, true); } function createRef(rawValue, shallow) { // 创建ref if (isRef(rawValue)) { // 判断是否为ref return rawValue; } return new RefImpl(rawValue, shallow); // 返回RefImpl实例对象 } class RefImpl { constructor(value, __v_isShallow) { // 值,是否浅层ref this.__v_isShallow = __v_isShallow; this.dep = undefined; this.__v_isRef = true; this._rawValue = __v_isShallow ? value : toRaw(value); this._value = __v_isShallow ? value : toReactive(value); // 判断是否为浅层ref,否则调用toReactive,方法在下面 } get value() { // getter方法 获取value值 trackRefValue(this); return this._value; } set value(newVal) { // setter方法 设置value值 const useDirectValue = this.__v_isShallow || isShallow(newVal) || isReadonly(newVal); newVal = useDirectValue ? newVal : toRaw(newVal); if (hasChanged(newVal, this._rawValue)) { this._rawValue = newVal; this._value = useDirectValue ? newVal : toReactive(newVal); // 在value值更新时进行判断是否为浅层ref,否则调用toReactive triggerRefValue(this, newVal); } } } const toReactive = (value) => isObject(value) ? reactive(value) : value; // 是否为对象,如果是则调用reactive
经过一番阅读理解,我们只需抓取关键信息即可
可以看到通过 ref(1)
的值在 RefImpl
类中为 _value
,然后使用class中的get
和set
语法糖对该value值进行相应的操作,获取和赋值
再判断为对象时才使用reactive()
方法
实践操作
从源码上看,我们看到了,使用了class的get和set来对这个value值进行操作,那么我们自己动手实践一下,看看怎么实现
这里把源码的_value
的口头约定私有属性形式改为es9新增加的#value
形式
class RefImpl { #value = '' // #value 私有属性 constructor(value) { this.#value = value } get value() { console.log('触发获取', this.#value) return this.#value } set value(newVal) { console.log('触发更新', newVal) this.#value = newVal } } function ref(value) { return new RefImpl(value) } const test = ref('我是小涛测试') setTimeout(() => { test.value = '我设置了值' }, 2000)
可以看到,这个简单的实例,也可以劫持数据的更新方便我们进行其他操作
class类的get和set是什么
到了这里,可以确定ref是使用class里的get/set进行数据劫持和更新的
而这个get/set实际是语法糖,本质是js的特性,是劫持property(属性)的一种方式
对象内分为数据属性
和访问器属性
,访问器属性不包含数据,是一对get和set方法
Getter 属性访问器(accessor)和 Setter 属性修改器(mutator)
结论
综上所述
所以一刚开始说的使用Object.defineProperty
说法并不正确,因为Object.defineProperty()
可以用来给修改对象属性,然后使用到了getter/setter
所以使用了class
的说法也并不正确,也是在对象内使用访问器属性,使用到了getter/setter这两个方法
Object.defineProperty() 方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象。
Vue 将遍历此对象所有的属性,并使用Object.defineProperty把这些属性全部转为 getter/setter。
附上一张官方说法 官方链接
在对象内也可以使用访问器属性
const test = { _value: '', get value() { console.log('触发获取', this._value) return this._value }, set value(newVal) { console.log('触发更新', newVal) this._value = newVal } }
最后,其实简化到最后发现,都不会难以理解,所以保持探索态度,多看多学,方是正途
到此这篇关于深入详解Vue3 ref底层实现原理的文章就介绍到这了,更多相关Vue3 ref内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!