洛阳营销型网站建设,网站开发后未付维护费,厦门建网站多少钱,wordpress mip改造前言 最近死磕了一段时间vue源码#xff0c;想想觉得还是要输出点东西#xff0c;我们先来从Vue提供的Vue.set()和this.$set()这两个api看看它内部是怎么实现的。
Vue.set()和this.$set()应用的场景 平时做项目的时候难免不会对 数组或者对象 进行这样的骚操作操作#xff…前言 最近死磕了一段时间vue源码想想觉得还是要输出点东西我们先来从Vue提供的Vue.set()和this.$set()这两个api看看它内部是怎么实现的。
Vue.set()和this.$set()应用的场景 平时做项目的时候难免不会对 数组或者对象 进行这样的骚操作操作结果发现咦~~他喵的怎么页面没有重新渲染。
12345678910const vueInstance new Vue({ data: { arr: [1, 2], obj1: { a: 3 } }});vueInstance.$data.arr[0] 3; // 这种骚操作页面不会重新渲染vueInstance.$data.obj1.b 3; // 这种骚操作页面不会重新渲染
查了一下官方文档发现人家早就说过这种情况
Vue.set()向响应式对象中添加一个属性并确保这个新属性同样是响应式的且触发视图更新。它必须用于向响应式对象上添加新属性因为 Vue 无法探测普通的新增属性 (比如 this.myObject.newProperty ‘hi’)
所以按照官网的写法我们应该使用下面这种写法
1234Vue.set(vueInstance.$data.arr, 0, 3); // 这样操作数组可以让页面重新渲染vueInstance.$set(vueInstance.$data.arr, 0, 3); // 这样操作数组也可以让页面重新渲染Vue.set(vueInstance.$data.obj1, b, 3); // 这样操作对象可以让页面重新渲染vueInstance.$set(vueInstance.$data.obj1, b, 3); // 这样操作对象也可以让页面重新渲染
Vue.set()和this.$set()实现原理 是时候看一波这两个api的源码了我们先来看看Vue.set()的源码
1234import { set } from ../observer/index...Vue.set set...
再来看看this.$set()的源码
1234import { set } from ../observer/index...Vue.prototype.$set set...
结果我们发现Vue.set()和this.$set()这两个api的实现原理基本一模一样都是使用了set函数。set函数是从 …/observer/index 文件中导出的区别在于Vue.set()是将set函数绑定在Vue构造函数上this.$set()是将set函数绑定在Vue原型上。
接下来我们根据 …/observer/index 中找出set函数
| 12345678910111213141516171819202122232425262728293031 | function set (target: Arrayany | Object, key: any, val: any): any { if (process.env.NODE_ENV ! production (isUndef(target) || isPrimitive(target)) ) { warn(Cannot set reactive property on undefined, null, or primitive value: ${(target: any)}) } if (Array.isArray(target) isValidArrayIndex(key)) { target.length Math.max(target.length, key) target.splice(key, 1, val) return val } if (key in target !(key in Object.prototype)) { target[key] val return val } const ob (target: any).__ob__ if (target._isVue || (ob ob.vmCount)) { process.env.NODE_ENV ! production warn( Avoid adding reactive properties to a Vue instance or its root $data at runtime - declare it upfront in the data option. ) return val } if (!ob) { target[key] val return val } defineReactive(ob.value, key, val) ob.dep.notify() return val} | | :— | :— |
我们发现set函数接收三个参数分别为 target、key、val其中target的值为数组或者对象这正好和官网给出的调用Vue.set()方法时传入的参数参数对应上。如下图所示 我们接着往下看
| 12345 | if (process.env.NODE_ENV ! production (isUndef(target) || isPrimitive(target)) ) { warn(Cannot set reactive property on undefined, null, or primitive value: ${(target: any)}) } | | :— | :— |
我们先看isUndef和isPrimitive方法从名字就可以看出isUndef是判断target是不是等于undefined或者null。isPrimitive是判断target的数据类型是不是string、number、symbol、boolean中的一种。所以这里的意思是如果当前环境不是生产环境并且 isUndef(target) || isPrimitive(target) 为真的时候那么就抛出错误警告。 数组的实现原理 接着向下看
12345if (Array.isArray(target) isValidArrayIndex(key)) { target.length Math.max(target.length, key) target.splice(key, 1, val) return val }
这里实际就是修改数组时调用set方法时让我们能够触发响应的代码不过在分析这段代码之前我们来看看vue中的数组实际上是长什么样的。 下图分别是vue中的数组和普通的js数组 vue中的数组我们命名为arrVuejs中的普通数组命名为arrJs。其实我们平时调用普通数组的push、pop等方法是调用的Array原型上面定义的方法 从图中我们可以看出arrJs的原型是指向Array.prototype也就是说 arrJs.proto Array.prototype 。
但是在vue的数组中我们发现arrVue的原型其实不是指向的Array.prototype而是指向的一个对象我们这里给这个对象命名为arrayMethods。arrayMethods上面只有7个push、pop等方法并且arrayMethods的原型才是指向的Array.prototype。所以我们在vue中调用数组的push、pop等方法时其实不是直接调用的数组原型给我们提供的push、pop等方法而是调用的arrayMethods给我们提供的push、pop等方法。vue为什么要给数组的原型链上面加上这个arrayMethods呢这里涉及到了vue的数据响应的原理我们这篇文章暂时不谈论数据响应原理的具体实现。这里你可以理解成vue在arrayMethods对象中做过了特殊处理如果你调用了arrayMethods提供的push、pop等7个方法那么它会触发当前收集的依赖这里收集的依赖可以暂时理解成渲染函数导致页面重新渲染。换句话说对于数组的操作我们只有使用arrayMethods提供的那7个方法才会导致页面渲染这也就解释了为什么我们使用 vueInstance.$data.arr[0] 3; 时不会导致页面出现渲染。
搞清楚vue中的数组具体是怎么实现了之后我们再来看上面的代码
12345if (Array.isArray(target) isValidArrayIndex(key)) { target.length Math.max(target.length, key) target.splice(key, 1, val) return val }
首先if判断当前target是不是数组并且key的值是有效的数组索引。然后将target数组的长度设置为target.length和key中的最大值这里为什么要这样做呢?是因为我们可能会进行下面这种骚操作
12arr1 [1,3];Vue.set(arr1,10,1) // 如果不那样做这种情况就会出问题
接着向下看我们发现这里直接调用了target.splice(key, 1, val)在前面我们说过调用arrayMethods提供的push、pop等7个方法可以导致页面重新渲染刚好splice也是属性arrayMethods提供的7个方法中的一种。
总结一下Vue.set数组实现的原理其实Vue.set()对于数组的处理其实就是调用了splice方法是不是发现其实很简单~~
对象的实现原理 我们接着向下看代码
1234if (key in target !(key in Object.prototype)) { target[key] val return val }
这里先判断如果key本来就是对象中的一个属性并且key不是Object原型上的属性。说明这个key本来就在对象上面已经定义过了的直接修改值就可以了可以自动触发响应。
关于对象的依赖收集和触发原理我们本文也不做详细解释你可以暂时先这样理解。vue是使用的Object.defineProperty给对象做了一层拦截当触发get的时候就会进行依赖收集这里收集的依赖还是像数组那样理解成渲染函数当触发set的时候就会触发依赖导致渲染函数执行页面重新渲染。那么第一次是在哪里触发get的呢其实是在首次加载页面渲染的时候触发的这里会进行递归将对象的属性都依赖收集所以我们修改对象已有属性值得时候会导致页面重新渲染。这也刚好解释了我们使用 vueInstance.$data.obj1.b 3; 的时候为什么页面不会重新渲染因为这里的属性b不是对象的已有属性也就是说属性b没有进行过依赖收集所以才会导致修改属性b的值页面不会重新渲染。
我们接着向下看代码
| 123456789101112 | const ob (target: any).__ob__ if (target._isVue || (ob ob.vmCount)) { process.env.NODE_ENV ! production warn( Avoid adding reactive properties to a Vue instance or its root $data at runtime - declare it upfront in the data option. ) return val } if (!ob) { target[key] val return val } | | :— | :— |
首先定义变量ob的值为 target.ob 这个 ob 属性到底是什么对象呢vue给响应式对象都加了一个 ob 属性如果一个对象有这个 ob 属性那么就说明这个对象是响应式对象我们修改对象已有属性的时候就会触发页面渲染。
target._isVue || (ob ob.vmCount) 的意思是当前的target对象是vue实例对象或者是根数据对象那么就会抛出错误警告。
if (!ob) 为真说明当前的target对象不是响应式对象那么直接赋值返回即可。
接着向下看
123defineReactive(ob.value, key, val) ob.dep.notify() return val
这里其实才是vue.set()真正处理对象的地方。 defineReactive(ob.value, key, val) 的意思是给新加的属性添加依赖以后再直接修改这个新的属性的时候就会触发页面渲染。
ob.dep.notify() 这句代码的意思是触发当前的依赖这里的依赖依然可以理解成渲染函数所以页面就会进行重新渲染。
总结
从源码层次看vue提供的vue.set()和this.$set()这两个api还是很简单的由于本文没有详细解释vue依赖收集和触发所以有的地方说的还是很模糊。