Vue.set()和this.$set()源码解析

摘要:
如果我们在看文档有这样一个api,以下内容:Vue.set()和this.$set()实现原理Vue.set()的源码:...这里是省略的代码import{set}from'../observer/index'...Vue.set=set...this.$set()的源码:import{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原型上。arrayMethods上面只有7个push、pop等方法,并且arrayMethods的原型才是指向的Array.prototype。Step2if{target.length=Math.maxtarget.splicereturnval}if判断当前target是不是数组,并且key的值是有效的数组索引。

前言

我们在日常项目开发过程中,有时候我们对数组或者对象进行了一些操作后,发现页面数据没有更新到。这个时候就会有疑问,why?

如果我们在看文档有这样一个api,以下内容:

Vue.set()和this.$set()源码解析第1张

Vue.set()和this.$set()实现原理

Vue.set()的源码:... 这里是省略的代码

import { set } from '../observer/index'
...
Vue.set = set...

this.$set()的源码:

import { 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函数:

function set (target: Array<any> |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) returnval }
// 对象
if (key in target && !(key inObject.prototype)) { target[key] =val returnval } 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.') returnval } if (!ob) { target[key] =val returnval } defineReactive(ob.value, key, val) ob.dep.notify() returnval }

set函数接收三个参数分别为 target、key、val,其中target的值为数组或者对象。

具体代码分析:

Step1

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) 为真的时候,那么就抛出错误警告。

分析数组这块代码前,我们先来看下vue数组与普通js数组的区别:

Vue.set()和this.$set()源码解析第2张

Vue.set()和this.$set()源码解析第3张

vue中的数组我们命名为arrVue,js中的普通数组命名为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;时不会导致页面出现渲染。

Step2


if (Array.isArray(target) &&isValidArrayIndex(key)) { target.length =Math.max(target.length, key) target.splice(key, 1, val) returnval }

if判断当前target是不是数组,并且key的值是有效的数组索引。

然后将target数组的长度设置为target.length和key中的最大值,为了防止我们传入key下标超过数组长度导致报错。

调用arrayMethods提供的push、pop等7个方法可以导致页面重新渲染,这里使用splice是arrayMethods提供的7个方法中的一种。

这块代码意思是在修改数组时调用set方法时让我们能够触发响应的代码

Step3

if (key in target && !(key inObject.prototype)) {
    target[key] =val
    returnval
  }

如果key本来就是对象中的一个属性,并且key不是Object原型上的属性。说明这个key本来就在对象上面已经定义过了的,直接修改值就可以了,可以自动触发响应。

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.')
    returnval
  }
  if (!ob) {
    target[key] =val
    returnval
  }

定义变量ob的值为 ```target.__ob__```,vue给响应式对象都加了一个__ob__属性,如果一个对象有这个__ob__属性,那么就说明这个对象是响应式对象,我们修改对象已有属性的时候就会触发页面渲染。

```target._isVue || (ob && ob.vmCount)```的意思是:当前的target对象是vue实例对象或者是根数据对象,那么就会抛出错误警告。

```if (!ob)```为真说明当前的target对象不是响应式对象,不需要响应,那么直接赋值返回即可。比如:

```let obj = {o: 3}; this.$set(obj1, 'o', 2);```

defineReactive(ob.value, key, val)
  ob.dep.notify()
  return val

这里才是vue.set()真正处理对象的地方。```defineReactive(ob.value, key, val)```的意思是给新加的属性添加依赖,以后再直接修改这个新的属性的时候就会触发页面渲染。 ```ob.dep.notify()```这句代码的意思是触发当前的依赖(这里的依赖依然可以理解成渲染函数),所以页面就会进行重新渲染。


本文分享到这里,给朋友们推荐一个前端公众号

Vue.set()和this.$set()源码解析第4张

免责声明:文章转载自《Vue.set()和this.$set()源码解析》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇什么是遗传方差(Genetic variance)、加性遗传方差(Additive genetic variance)、显性遗传方差(Dominance genetic variance)、上位遗传方差(Epistatic genetic variance)Jmeter系列(27)- 详解正则提取器下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

redhat7 升级openssh openssl

部署telnet,防止ssh启动失败  1、关闭防火墙或者开放23端口  2、安装启动服务,并开启root访问 yum install -y telnet-server.x86_64 yum install -y telnet.x86_64 yum install -y xinetd.x86_64 systemctl enable xinetd.ser...

vue中如何生成组件的文档说明

针对vue组件,编写对应的组件文档 使用vuepress直接通过markdown文件去动态生成对应的组件演示和代码预览以及说明。 准备工作 先安装vuepress,npm i vuepress -D。 接着安装vuepress-plugin-demo-container,npm i vuepress-plugin-demo-container -D。 整...

uni-app(一)环境准备,项目架构

安装 HBuilderX 安装插件,结合自己的场景,酌情安装 创建项目   配置开发者工具路径(我这里是微信,请酌情处理) 在这里有一个小妙招,项目在开发者工具运行起来后,隐藏掉 HBuilderX,用vs code打开项目,修改代码,保存后,一样是实时更新视图的,就可以继续使用 习惯已久的 vs code了 由于uni-app使用vue框架开发的...

适用于 Vue 的播放器组件Vue-Video-Player。

如果h5的标签<vedio>不能满足你的需求,那就用这个组件Vue-Video-Player吧,也许可以覆盖到你的需求。 <video-player ref="videoPlayer" :playsinline="true" :options="playerOptions" >&l...

[转]OpenTK学习笔记(1)-源码、官网地址

OpenTK源码下载地址:https://github.com/opentk/opentk OpenTK使用Nuget安装命令:OpenTK:Install-Package OpenTK -Version 3.0.1  OpenTK.GLControl:Install-Package OpenTK.GLControl -Version 3.0.1 Open...

ZYNQ:提取PetaLinux中Linux和UBoot配置、源码

说明 默认情况下,PetaLinux在编译完成后会删除源代码,以节省硬盘空间。 在project-spec/meta-user/conf/petalinuxbsp.conf里,添加如下内容,可以保留Linux和UBoot源代码。 RM_WORK_EXCLUDE += "linux-xlnx" RM_WORK_EXCLUDE += "u-boot-xlnx"...