赞
踩
<1> 概括
首先来看一张图片
当你通过npm下载vue时,模块化开发时的源码在src目录下,dist是通过rollup打包后,运行在生产环境下的js文件。
找到 dist 下的 vue.js,将其提取出来,新建一个html文件引入这个vue.js用于测试可调试代码,就像下面这样。
当你一旦引入这个文件,文件中的js代码就会执行,可以简单看一看vue.js中的内容。
看到这个匿名函数自调回立马执行,如果是在浏览器环境下就会在window上挂载一个Vue的变量,这个变量是一个factory执行的结构,执行后其实返回的就是Vue(一个巨大个的构造函数).
这个factory工厂函数在执行的时候,不仅创建了Vue的构造函数,而且还在Vue的构造函数身上 (注意不是原型对象上)
挂载了很多的方法和属性,我们来看一下。(这时候最好去src中去看,更加友好)
在src 下的 core 下的 index.js 中
点进那个initGlobalAPI中去看一下。
这里就一个一个展示了对响应式帮助不大,总之这个文件会在Vue上添加很多的方法和属性,也就是全局的api
所以此时Vue既是一个构造函数也是一个对象,身上有很多的全局api和属性。诸如Vue.version Vue.component Vue.mixin等等,这就是执行环境在加载到Vue源码立即就会做的事情。
下面当我们作为程序员去写出 new Vue({ ... })
类似这样的代码的时候,就是去调用Vue构造函数,并且会把options传进去。
但Vue的构造函数却异常单调就执行了个this._init()的方法,并把参数传了进去。而vue实例上此时是没有_init方法的,就会去原型上面找,找啊找?
发现还真有,为什么会有这么一个方法呢,什么时候添加的呀?
原来在初始化Vue的构造函数的时候在initMixin(Vue)时就做了这件事情
点进initMixin中去看一下!
瞧,这不是么!_init就在初始化时添加在了Vue的原型对象上。然后在new Vue的调用,那就分析一下呗!
这里面初始化了生命周期,state ,render ,事件模型等,但最重要的还是对我们传入的data进行响应式化,后期我的博客中会对这些代码进行一一分析,但今天我们先来认识响应式化。
点进这个里面进去看一下。
如果存在data ,一般情况下都是存在的,就去初始化Data.点进去。
这里面好多东西,但observe(data)才是重点,请深深的记住,这个的意思是 将data进行关注,或者观察。既然已经到这里了就不得不用到observer模块了。走到这里前面都能说通,但是我们还是欠缺一个observe的方法,不知道这个到底做了什么,我们在下面的章节中看一下。
从上面的分析当中我们其实留了一个悬念,就是最终代码会执行到observe(data)
这里来,那这个函数到底做了什么呢?这就是observer模块做的事情了。
找到core 中的 observer 文件夹,打开它,可以看到这几个文件
可以看到这几个文件,首先找到index.js文件。因为这个里面就有observe()
方法
这里面做的事情就一个,就是去观察传进来的数据,并且生成一个Observer的实例。返回出去
我划线的部分就都是核心代码了。每当一个数据传进来进行实例化的时候,我可以直接告诉你,它一定是一个对象类型(普通对象或者数组),他会给这个对象类型添加一个__ob__
的属性,指向当前的observer实例对象。(所以各位有没有发现只要是响应式对象的数据,都有的__ob__的字样)
其次数组我们暂时不考虑,如果是 对象,那么就会指向walk()
方法,walk方法会循环这个对象的每个属性,为每个属性进行defineReactive
的处理。下面是重点!!!
这是整个响应式的核心部分。首先会创建一个实例化一个Dep对象,我们暂且先不管这个是干嘛的,但你一定要知道这里实例化了一个对象。
其次它截取了原生的getter和setter,并且执行了它,也就是正常的获取和修改是不会有问题的,除此之外它扩展了setter和setter,增加了一部分其他的逻辑,增加的getter中的这部分逻辑我们称之为依赖收集
,setter中的我们称之为派发更新
上面我们提到过,每一次调用defineReactive
,都会创建一个Dep实例对象,但我还想说的是,每一个响应式属性都拥有一个唯一属于自己的dep对象。并且将永久的保存在内存中,直到实例销毁。为什么呢?
我刚开始会觉得,这里的dep是一个局部变量,那么函数调用完之后不是会释放调么,那么为什么会永久的保存呢,这里的技巧就是因为dep在Object.defineProperty(,{})中进行了引用,因此形成了一个闭包,并不会被释放,这就是原因,而所谓的依赖收集:
当有人来访问响应式数据的属性的时候,就会触发相应的getter,然后在条件符合的时候,便会调用一个方法,将所有关心这个属性变化的所有关心这,收集到dep中,因此dep其实本质上就是一个收集盒子,收集着所有关心这个属性变化的人。
所以派发更新其实就很好理解了,因为这个dep已经存在于响应式数据中了,当某个变量变化了,就会触发setter,在setter当中它要想尽办法去造成影响呀
怎么办呢?想了半天,它发现,原来我身上有个dep对象呀,dep对象上,保存了一堆关心着我的人呀,我知道告诉他们我变了,剩下了他们自己就会知道怎么办了。
以上便是响应式原理的一部分真正的原理。
但是看到这里可能我们还是比较懵,dep是啥?那些关心他们的人是啥?
其实就是Dep类,和Watcher类,一一来认识一下。
其实所谓了Dep类代码并不复杂,但是理解起来会比较抽象,但请记住,this.subs = []
,它就是一个盒子,往盒子里面放的元素必须是Watcher实例,但我们需要关注一个方法depend()
,为什么要关注它,因为在我们刚刚说到的那个defineReactive
方法中的getter中它调用了这个方法,这个方法简单来讲,就一个作用,将自己(某个dep实例对象)添加到全局watcher中保存,
仅此而已,但请深深记住这句话。
一句话概括notify方法做了什么,循环遍历自己保存的每个watcher,依次调用他们的update()
方法
这就是Dep类,先不着急和之前对接走通,我们直接把watcher也直接来认识一下。
这个watcher实在太长了,我一下截不完,我们直接看核心的构造器部分吧。其实每次实例化一个watcher我们至少都需要传入这么3个参数。vue实例 ,路径表达式或者渲染函数,回调函数
也就是上面的 vm , expOrFn , cb
这里我要解释一下,watcher分为两种,一种是掌控DOM的渲染watche
r,一种是执行回调函数的,我将其称为 回调watcher
。
我们在Vue中可以这样使用
{
watch:{
'a.b.c'(newVal){
console.log('c发生了变化',newVal)
}
}
}
这个过程会触发响应式的getter
this.getter
一定是一个函数,要么是渲染函数,要么是一个取值函数。pushTarge(this)
,你可以点进去看一下。this.getter
,我们讨论渲染watcher的场景,此时全局会有一个渲染watcher,由于执行渲染函数的过程中,它一定会访问到响应式数据,进而会执行getter。我再把这个代码贴出来。全局watcher的addDep方法
,它做了两件事情,第一将当前的dep添加到了全局watcher当中,第二,调用当前dep实例的addSub方法,当前dep对象就把全局watcher保存到了自己身上,因此形成了一个互相引用的过程。完成了依赖收集的使命。将全局watcher置为null,以便下次的依赖收集。
一、函数劫持
vue2并不支持使用数组下标的方式的响应式处理,原因是因为,vue2确实只劫持了部分数组的方法,我把代码贴上
在上面的代码当中。vue劫持了数组的这几个方法,在这几个方法当中。他会执行默认的数组行为,并且会对新增的元素做响应式的处理。一旦有数组数据的变化,同样的会通知相应的watcher执行变化的操作。
这里面有个小几千就是那个__ob__
.
还记得么,之前在进行new Observer()的时候,我们为每一个响应式对象都添加了一个__ob__的属性指向当前的observer实例对象。为了就是在这里访问的到observer的observerArray方法
,如果不这么做,会发现还真的不好拿到实例里面的方法去用。
一、Vue.set ,Vue.delete
二、Vue.nextTick()
三、Vue.use
在Vue中我们可以使用各种各样的插件比如
Vue.use(ElementUI) 等等。它的原理是什么呢?
这里可以看出,插件如果是一个对象的话,那么一定会有一个install方法,然后这个方法会将剩余的参数传进这个Install方法。
如果是函数的会直接调用这个函数。
因此插件要么是个带install方法的对象,要么是一个函数。
四、Vue.mixin
有没有觉得mixin是个很好用的方法,可以在vue中混入一段独立的逻辑和状态。非常有利于代码复用。它复杂吗?
答案是就这么一段代码。原来,一旦我们定义好了一个mixin,它就会将我们的mixin混入的配置项与真正的实例组件配置项进行一个合并,那么这样,真正的组件其实就拥有了所有的状态和逻辑,并且可以复用,因为,Vue实例的一切行为都是根据这些配置项去完成的。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。