Vue3 相比于 Vue2 的有哪些“与众不同”( 二 )

搬运 Vue2 核心源码 , 略删减 。
function defineReactive(obj, key, val) {// 一 key 一个 depconst dep = new Dep()// 获取 key 的属性描述符 , 发现它是不可配置对象的话直接 returnconst property = Object.getOwnPropertyDescriptor(obj, key)if (property && property.configurable === false) { return }// 获取 getter 和 setter , 并获取 val 值const getter = property && property.getconst setter = property && property.setif((!getter || setter) && arguments.length === 2) { val = obj[key] }// 递归处理 , 保证对象中所有 key 被观察let childOb = observe(val)Object.defineProperty(obj, key, {enumerable: true,configurable: true,// get 劫持 obj[key] 的 进行依赖收集get: function reactiveGetter() {const value = https://www.isolves.com/it/cxkf/qd/2022-02-25/getter ? getter.call(obj) : valif(Dep.target) {// 依赖收集dep.depend()if(childOb) {// 针对嵌套对象 , 依赖收集childOb.dep.depend()// 触发数组响应式if(Array.isArray(value)) {dependArray(value)}}}}return value})// set 派发更新 obj[key]set: function reactiveSetter(newVal) {...if(setter) {setter.call(obj, newVal)} else {val = newVal}// 新值设置响应式childOb = observe(val)// 依赖通知更新dep.notify()}}那 Vue3 为何会抛弃它呢?那肯定是有一些缺陷的 。
主要原因:无法监听对象或数组新增、删除的元素 。
Vue2 方案:针对常用数组原型方法push、pop、shift、unshift、splice、sort、reverse进行了hack处理;提供Vue.set监听对象/数组新增属性 。对象的新增/删除响应 , 还可以new个新对象 , 新增则合并新属性和旧对象;删除则将删除属性后的对象深拷贝给新对象 。
Tips: Object.defineOProperty是可以监听数组已有元素 , 但 Vue2 没有提供的原因是性能问题 , 具体可看见参考第二篇 ~ 。
ProxyProxy是ES6新特性 , 通过第2个参数handler拦截目标对象的行为 。相较于Object.defineProperty提供语言全范围的响应能力 , 消除了局限性 。但在兼容性上放弃了(IE11以下)
局限性

  1. 对象/数组的新增、删除 。
  2. 监测.length修改 。
  3. Map、Set、WeakMap、WeakSet的支持 。
基本用法:创建对象的代理 , 从而实现基本操作的拦截和自定义操作 。
const handler = {get: function(obj, prop) {return prop in obj ? obj[prop] : ''},set: function() {},...}搬运 Vue3 的源码 reactive.ts 文件
function createReactiveObject(target, isReadOnly, baseHandlers, collectionHandlers, proxyMap) {...// collectionHandlers: 处理Map、Set、WeakMap、WeakSet// baseHandlers: 处理数组、对象const proxy = new Proxy(target,targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers)proxyMap.set(target, proxy)return proxy}以 baseHandlers.ts 为例 , 使用Reflect.get而不是target[key]的原因是receiver参数可以把this指向getter调用时 , 而非Proxy构造时的对象 。
// 依赖收集function createGetter(isReadonly = false, shallow = false) {return function get(target: Target, key: string | symbol, receiver: object) {...// 数组类型const targetIsArray = isArray(target)if (!isReadonly && targetIsArray && hasOwn(arrayInstrumentations, key)) {return Reflect.get(arrayInstrumentations, key, receiver)}// 非数组类型const res = Reflect.get(target, key, receiver);// 对象递归调用if (isObject(res)) {return isReadonly ? readonly(res) : reactive(res)}return res}}// 派发更新function createSetter() {return function set(target: Target, key: string | symbol, value: unknown, receiver: Object) {value = https://www.isolves.com/it/cxkf/qd/2022-02-25/toRaw(value)oldValue = target[key]// 因 ref 数据在 set value 时就已 trigger 依赖了 , 所以直接赋值 return 即可if (!isArray(target) && isRef(oldValue) && !isRef(value)) {oldValue.value = valuereturn true}// 对象是否有 key 有 key set , 无 key addconst hadKey = hasOwn(target, key)const result = Reflect.set(target, key, value, receiver)if (target === toRaw(receiver)) {if (!hadKey) {trigger(target, TriggerOpTypes.ADD, key, value)} else if (hasChanged(value, oldValue)) {trigger(target, TriggerOpTypes.SET, key, value, oldValue)}}return result}}虚拟DOMVue3 相比于 Vue2 虚拟DOM 上增加patchFlag字段 。我们借助Vue3 Template Explorer来看 。
<div id="app"><h1>技术摸鱼</h1><p>今天天气真不错</p><div>{{name}}</div></div>渲染函数如下 。
import { createElementVNode as _createElementVNode, toDisplayString as _toDisplayString, openBlock as _openBlock, createElementBlock as _createElementBlock, pushScopeId as _pushScopeId, popScopeId as _popScopeId } from "vue"const _withScopeId = n => (_pushScopeId("scope-id"),n=n(),_popScopeId(),n)const _hoisted_1 = { id: "app" }const _hoisted_2 = /*#__PURE__*/ _withScopeId(() => /*#__PURE__*/_createElementVNode("h1", null, "技术摸鱼", -1 /* HOISTED */))const _hoisted_3 = /*#__PURE__*/ _withScopeId(() => /*#__PURE__*/_createElementVNode("p", null, "今天天气真不错", -1 /* HOISTED */))export function render(_ctx, _cache, $props, $setup, $data, $options) {return (_openBlock(), _createElementBlock("div", _hoisted_1, [_hoisted_2,_hoisted_3,_createElementVNode("div", null, _toDisplayString(_ctx.name), 1 /* TEXT */)]))}


推荐阅读