从vue源码解析Vue.set()和this.$set()
前端之家收集整理的这篇文章主要介绍了
从vue源码解析Vue.set()和this.$set(),
前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。
@H_4030@前言
@H
403_0@最近死磕了一段时间vue源码,想想觉得还是要
输出点东西,我们先来从Vue提供的Vue.set()和this.$set()这两个api看看它内部是怎么实现的。
@H_
4030@Vue.set()和this.$set()应用的场景
@H403_0@平时做项目的时候难免不会对 数组或者对象 进行这样的骚操作操作,结果发现,咦~~,他喵的,怎么
页面没有重新渲染。
<div class="jb51code">
<pre class="brush:js;">
const vueInstance = new Vue({
data: {
arr: [1,2],obj1: {
a: 3
}
}
});
vueInstance.$data.arr[0] = 3; // 这种骚操作
页面不会重新渲染
vueInstance.$data.obj1.b = 3; // 这种骚操作
页面不会重新渲染
@H_
403_0@查了一下官方文档,发现人家早就说过这种情况
@H_
403_0@Vue.set()向响应式对象中
添加一个
属性,并确保这个新
属性同样是响应式的,且触发视图更新。它必须用于向响应式对象上
添加新
属性,因为 Vue 无法探测普通的新增
属性 (比如 this.myObject.newProperty = 'hi')
@H_
403_0@所以按照官网的写法,我们应该使用下面这种写法:
页面重新渲染
vueInstance.$set(vueInstance.$data.arr,3); // 这样操作数组也可以让
页面重新渲染
Vue.set(vueInstance.$data.obj1,b,3); // 这样操作对象可以让
页面重新渲染
vueInstance.$set(vueInstance.$data.obj1,3); // 这样操作对象也可以让
页面重新渲染
@H_
403_0@
Vue.set()和this.$set()实现原理
@H_
403_0@是时候看一波这两个api的源码了,我们先来看看Vue.set()的源码:
@H_
403_0@再来看看
this.$set()
的源码:
@H_
403_0@结果我们发现
Vue.set()和this.$set()
这两个api的实现原理基本一模一样,都是使用了set
函数。set
函数是从 ../observer/index
文件中导出的,区别在于Vue.set()是将set
函数绑定在Vue构造
函数上,this.$set()是将set
函数绑定在Vue原型上。
@H_
403_0@接下来我们根据 ../observer/index 中找出set
函数:
| 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
}
@H_
403_0@我们发现set
函数接收三个参数分别为 target、key、val,其中target的值为数组或者对象,这正好和官网给出的
调用Vue.set()
方法时传入的参数参数对应上。如下图所示:

@H_
403_0@我们接着往下看:
@H_
403_0@我们先看isUndef和isPrimitive
方法,从名字就可以看出,isUndef是判断target是不是等于undefined或者null。isPrimitive是判断target的数据类型是不是string、number、symbol、boolean中的一种。所以这里的意思是如果当前环境不是生产环境并且
isUndef(target) || isPrimitive(target)
为真的时候,那么就抛出
错误警告。
@H_
403_0@
数组的实现原理
@H_
403_0@接着向下看:
@H_
403_0@这里实际就是
修改数组时
调用set
方法时让我们能够触发响应的
代码,不过在分析这段
代码之前我们来看看vue中的数组实际上是长什么样的。 下图分别是vue中的数组和普通的js数组:


@H_
403_0@vue中的数组我们命名为arrVue,js中的普通数组命名为arrJs。其实我们平时
调用普通数组的push、pop等
方法是
调用的Array原型上面定义的
方法, 从图中我们可以看出arrJs的原型是指向Array.prototype,也就是说 arrJs.__proto__ == Array.prototype 。
@H_
403_0@但是在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; 时不会导致
页面出现渲染。
@H_
403_0@搞清楚vue中的数组具体是怎么实现了之后,我们再来看上面的
代码:
@H_
403_0@首先if判断当前target是不是数组,并且key的值是有效的数组索引。然后将target数组的长度设置为target.length和key中的最大值,这里为什么要这样做呢?是因为我们可能会进行下面这种骚操作:
@H_
403_0@接着向下看,我们发现这里直接
调用了target.splice(key,val),在前面我们说过
调用arrayMethods提供的push、pop等7个
方法可以导致
页面重新渲染,刚好splice也是
属性arrayMethods提供的7个
方法中的一种。
@H_
403_0@总结一下Vue.set数组实现的原理:其实Vue.set()对于数组的处理其实就是
调用了splice
方法,是不是发现其实很简单~~
@H_
403_0@
对象的实现原理
@H_
403_0@我们接着向下看
代码:
@H_
403_0@这里先判断如果key本来就是对象中的一个
属性,并且key不是Object原型上的
属性。说明这个key本来就在对象上面已经定义过了的,直接
修改值就可以了,可以
自动触发响应。
@H_
403_0@关于对象的依赖收集和触发原理我们本文也不做详细解释,你可以暂时先这样理解。vue是使用的Object.defineProperty给对象做了一层
拦截,当触发get的时候就会进行依赖收集(这里收集的依赖还是像数组那样,理解成渲染
函数),当触发set的时候就会触发依赖,导致渲染
函数执行
页面重新渲染。那么第一次是在哪里触发get的呢?其实是在首次加载
页面渲染的时候触发的,这里会进行递归将对象的
属性都依赖收集,所以我们
修改对象已有
属性值得时候会导致
页面重新渲染。这也刚好解释了我们使用 vueInstance.$data.obj1.b = 3; 的时候为什么
页面不会重新渲染,因为这里的
属性b不是对象的已有
属性,也就是说
属性b没有进行过依赖收集,所以才会导致
修改属性b的值
页面不会重新渲染。
@H_
403_0@我们接着向下看
代码:
@H_
403_0@首先定义变量ob的值为 target.__ob__ ,这个 __ob__
属性到底是什么对象呢?vue给响应式对象都加了一个 __ob__
属性,如果一个对象有这个 __ob__
属性,那么就说明这个对象是响应式对象,我们
修改对象已有
属性的时候就会触发
页面渲染。
@H_
403_0@
target._isVue || (ob && ob.vmCount)
的意思是:当前的target对象是vue实例对象或者是根数据对象,那么就会抛出
错误警告。
@H_
403_0@if (!ob) 为真说明当前的target对象不是响应式对象,那么直接赋值返回即可。
@H_
403_0@接着向下看:
@H_
403_0@这里其实才是vue.set()真正处理对象的地方。 defineReactive(ob.value,val) 的意思是给新加的
属性添加依赖,以后再直接
修改这个新的
属性的时候就会触发
页面渲染。
@H_
403_0@ob.dep.notify() 这句
代码的意思是触发当前的依赖(这里的依赖依然可以理解成渲染
函数),所以
页面就会进行重新渲染。
@H_
403_0@总结
@H_
403_0@从源码层次看vue提供的vue.set()和this.$set()这两个api还是很简单的,由于本文没有详细解释vue依赖收集和触发,所以有的地方说的还是很模糊。
@H_
403_0@以上所述是小编给大家介绍的vue源码解析Vue.set()和this.$set()。编程之家 jb51.cc 收集整理的教程希望能对你有所帮助,如果觉得编程之家不错,可
分享给好友!感谢
支持。