1. 对象访问器属性 将键与两个访问器函数(get
和 set
)相关联,以获取或者存储值。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 let user = { firstName : "John" , lastName : "Doe" , get fullName () { return `${this .firstName} ${this .lastName} ` ; }, set fullName (value ) { [this .firstName , this .lastName ] = value.split (" " ); }, }; console .log (user.fullName ); user.fullName = "Alice White" ; console .log (user.fullName );
一个访问器属性,有着以下得特性:
get
: 该函数使用一个空的参数列表,以便有权对值执行访问时,获取属性值。参见 getter 。
set
: 使用包含分配值的参数调用的函数。每当尝试更改指定属性时执行。参见 setter 。
enumerable
: 一个布尔值,表示是否可以通过 for...in
循环来枚举属性。另请参阅枚举性和属性所有权 ,以了解枚举属性如何与其他函数和语法交互。
configurable
: 一个布尔值,表示该属性是否可以删除,是否可以更改为访问器属性,并可以更改其特性。
2. Ref 在之前得章节中,我们聊过了 vue3 的响应式核心track
、trigger
如果还不了解的朋友,建议看一下前面的文章 🥬 建立一个响应式系统 。闲言少叙,我们直接开始。
简单回顾一下我们的核心内容:
track
track
的主要作用是帮助我们创建对应的映射关系,最后帮助我们添加依赖,用weakMap
的 key 存储对象地址,weakMap 的 value 是一个Map
。Map
的 key 存储的是对象的属性,Map
的 value 存储的是一个Set
集合,Set
集合内部存储的就是我们该对象依赖的方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 const targetMap = new WeakMap ();function track (target, key ) { let depsMap = targetMap (target); if (!depsMap) { targetMap.set (target, (depsMap = new Map ())); } let dep = depsMap.get (key); if (!dep) { depsMap.set (key, (dep = new Set ())); } dep.add (effect); }
trigger
trigger
的主要作用根据对象和 key 在存储的映射关系中,找到并运行相关的依赖。
1 2 3 4 5 6 7 8 function trigger (target, key ) { let depsMap = targetMap.get (target); if (!depsMap) return ; let dep = depsMap.get (key); if (dep) { dep.forEach ((effect ) => effect ()); } }
2.1 Ref 原理实现 Vue3 的做法是借助 ref
将基础数据类型值进行一层封装,如果想读取数据的话,通过 refReturn.value,如果想设置新值的话,也通过 refReturn.value。接下来直接看伪代码,很简单。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 function ref (raw ) { let oldVal; const r = { get value () { track (r, "value" ); return raw; }, set value (newVal ) { oldVal = raw; raw = newVal; if (oldVal !== raw) { trigger (r, "value" ); } }, }; return r; }
2.2 Ref 源码解读
第一步简简单单调用createRef
函数,见名知意,创建一个Ref
1 2 3 function ref (value ) { return createRef (value, false ); }
第二步简简单单,先判断是不是 ref,如果是直接返回,不是则new RefImpl
1 2 3 4 5 6 7 function createRef (rawValue, shallow ) { if (isRef (rawValue)) { return rawValue; } return new RefImpl (rawValue, shallow); }
第三步简简单单,检查对象是否带有__v_raw
属性,最后返回普通对象
1 2 3 4 5 function toRaw (observed ) { const raw = observed && observed["__v_raw" ]; return raw ? toRaw (raw) : observed; }
第四步简简单单,如果传递过来的是对象,直接使用reactive
变成响应式对象
1 2 const toReactive = (value ) => shared.isObject (value) ? reactive (value) : value;
第五步简简单单,判断是否是shallowRef
浅层的 ref
1 2 3 4 function isShallow (value ) { return !!(value && value["__v_isShallow" ]); }
有人会问,为什么这些东西就是标识符的呢?你是怎么知道的呢?下面给大家扩展一下,我们就用isReadonly
举例子:
从上图中我们可以看出来,value
被断言成Target
类型。
然后我们在继续看Target
对象类型
这样就很明显了把,我们要从 value 对象中取到ReactiveFlages.IS_READONLY
类型的属性对应的boolean
值,而ReactiveFlages.IS_READONLY
类型是一个枚举:
所以__v_isReadonly
是readonly
的标识符。如果想看具体简洁的可以去看isRef
判断是否是readonly
只读的
1 2 3 4 function isReadonly (value ) { return !!(value && value["__v_isReadonly" ]); }
上面的代码(3-6)其实都是一些辅助函数,不过不要担心,下面的主要内容也是简简单单,一看就会:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 class RefImpl { constructor (value, __v_isShallow ) { this .__v_isShallow = __v_isShallow; this .dep = void 0 ; this .__v_isRef = true ; this ._rawValue = __v_isShallow ? value : toRaw (value); this ._value = __v_isShallow ? value : toReactive (value); } get value () { trackRefValue (this ); return this ._value ; } set value (newVal ) { const useDirectValue = this .__v_isShallow || isShallow (newVal) || isReadonly (newVal); newVal = useDirectValue ? newVal : toRaw (newVal); if (shared.hasChanged (newVal, this ._rawValue )) { this ._rawValue = newVal; this ._value = useDirectValue ? newVal : toReactive (newVal); triggerRefValue (this , newVal); } } }