当你把一个普通的 JavaScript 对象传入 Vue 实例作为 data 选项,Vue 将遍历此对象所有的 property,并使用 Object.defineProperty 把这些 property 全部转为 getter/setter。Object.defineProperty 是 ES5 中一个无法 shim 的特性,这也就是 Vue 不支持 IE8 以及更低版本浏览器的原因。
这些 getter/setter 对用户来说是不可见的,但是在内部它们让 Vue 能够追踪依赖,在 property 被访问和修改时通知变更。这里需要注意的是不同浏览器在控制台打印数据对象时对 getter/setter 的格式化并不同,所以建议安装 vue-devtools 来获取对检查数据更加友好的用户界面。
每个组件实例都对应一个 watcher 实例,它会在组件渲染的过程中把“接触”过的数据 property 记录为依赖。之后当依赖项的 setter 触发时,会通知 watcher,从而使它关联的组件重新渲染。
vue组件的三个API:prop,event,slot
1 | <template> |
判断参数是否是其中之一
1 | function oneOf(value,sizeList){ |
无依赖的组件通信方法provide/inject
1 | //A.vue |
app.vue 是整个项目第一个被渲染的组件,而且只会渲染一次(即使切换路由,app.vue 也不会被再次渲染),
利用这个特性,很适合做一次性全局的状态数据管理,例如,我们将用户的登录信息保存起来:
1 | <script> |
如果你的项目足够复杂,或需要多人协同开发时,在 app.vue 里会写非常多的代码,多到结构复杂难以维护。这时可以使用 Vue.js 的混合 mixins,将不同的逻辑分开到不同的 js 文件里。
比如上面的用户信息,就可以放到混合里:
1 | <script> |
得理解思路才行,具体代码可以不用太认真,有一个总体的概念
动态渲染.vue文件的组件 —display
运行机制
1 | new vue =>init=>$mount=>render function =>vnode |
当然这是不够的,我们需要在上面再封装一层 observer 。这个函数传入一个 value(需要「响应式」化的对象),通过遍历所有属性的方式对该对象的每一个属性都通过 defineReactive 处理。
(注:实际上 observer 会进行递归调用,为了便于理解去掉了递归的过程)
1 | function observer(obj){ |
订阅者dep (Dependency)
主要作用是存放watcher观察者对象
1 | class Dep{ |
完成两件事
1 用addSub方法可以在目前的Dep对象中增加一个watcher的订阅操作
2 用notify方法通知目前对象dep对象的subs中的所有watcher对象触发更新操作
观察者watcher
1 | class watcher{ |
修改一下 defineReactive 以及 Vue 的构造函数,来完成依赖收集。
1 | function defineReactive(obj,key,val){ |
数据状态更新时的差异diff及patch机制
1 | const nodeOps={ |
patch的diff算法,是通过同层的树节点进行比较,而非对树进行逐层遍历搜索,时间复杂度O(n)
1 | patch的简单过程代码 |
sameVnode的判断
1 | function sameVnode(){ |
nextTick原理
Vue.js 实现了一个 nextTick 函数,传入一个 cb ,这个 cb 会被存储到一个队列中,在下一个 tick 时触发队列中的所有 cb 事件。
因为目前浏览器平台并没有实现 nextTick 方法,所以 Vue.js 源码中分别用 Promise、setTimeout、setImmediate 等方式在 microtask(或是task)中创建一个事件,目的是在当前调用栈执行完毕以后(不一定立即)才会去执行这个事件。
笔者用 setTimeout 来模拟这个方法,当然,真实的源码中会更加复杂,笔者在小册中只讲原理,有兴趣了解源码中 nextTick 的具体实现的同学可以参考next-tick。
首先定义一个 callbacks 数组用来存储 nextTick,在下一个 tick 处理这些回调函数之前,所有的 cb 都会被存在这个 callbacks 数组中。pending 是一个标记位,代表一个等待的状态。
setTimeout 会在 task 中创建一个事件 flushCallbacks ,flushCallbacks 则会在执行时将 callbacks 中的所有 cb 依次执行。
1 | let callbacks=[] |
//update 方法,
1 | let uid=0; |
queueWatcher
我们使用一个叫做 has 的 map,里面存放 id -> true ( false ) 的形式,用来判断是否已经存在相同的 Watcher 对象 (这样比每次都去遍历 queue 效率上会高很多)。
1 | let has={} |
//flushSchedulerQueue
1 | function flushSchedulerQueue(){ |
store
理解 Vuex 的核心在于理解其如何与 Vue 本身结合,如何利用 Vue 的响应式机制来实现核心 Store 的「响应式化」。
1 | let Vue; |