Vue 相关原理学习笔记
2014 年 12 月 1 日
响应式原理
方案一:Object.defineProperty
基于 Object.defineProperty 通过 setter/getter 方法来监听数据的变化。 getter 进行依赖收集,setter 在数据变更的时候通知订阅者,递归调用监听对象的所有属性。
缺点:
- 无法检测到对象属性的添加或删除
- 不能监听数组的变化,需要重写数组方法
function defineReactive(obj, key, value) { Object.defineProperty(obj, key, { enumerable: true, configurable: true, get() { console.log('get', obj, key, value) return value }, set(newVal) { observe(newVal) if (newVal !== value) { console.log('set', obj, key, newVal) value = newVal } } }) } function observe(obj) { if (!obj || typeof obj !== 'object') { return } Object.keys(obj).forEach(key => { const value = obj[key] observe(value) defineReactive(obj, key, obj[key]) }) } const data = { a: { b: 1 } } observe(data) data.a.b = 2
方案二:ES6 Proxy
优点:
- 针对整个对象,而不是某个属性,所以不需要遍历,但是仍需要递归
- 支持数组变化监听
const handler = { get(target, key) { if (typeof target[key] == 'object' && target[key] !== null) { return new Proxy(target[key], handler) } console.log('set', key) return Reflect.get(target, key) }, set(target, key, value) { if (key === 'length') return true console.log('set', value) return Reflect.set(target, key, value) } } const data = { a: { b: 1 } } const proxy = new Proxy(data, handler) proxy.a.b = 2
如何收集依赖
两个关键类:
- Watcher:封装一个观察订阅的依赖,触发 getter 事件
- Dep:存放 Watcher 数组,需要通知变更的时候遍历 Watcher 发送通知
流程:
- 对于某个属性,先声明一个 Dep 对象,用来存放依赖
- Watcher 初始化的时候,会临时将 Dep.target 指向 this ,然后调用 getter 方法
- 在 getter 中,将 Dep.target 指向的 Water 存入依赖数组
- 在 setter 中,通过 dp.notify() 通知所有依赖对象
Vue Router 原理
- hash mode:根据不同 hash 值渲染不同的组件和数据,通过在 window.onhashchange 监听 hash 改变实现路由切换
- history mode:利用了 HTML5 History API 中的 pushState() 和 replaceState() 方法,虽然改变了当前的 URL ,但浏览器不会向后端发送请求。后台需要配置一个解析规则,避免 404 出现
Vuex 原理
vuex 中的 store 本质就是没有 template 的 vue 组件
Nuxt 原理
在打包之前,有两者的入口文件:
- Server entry:服务端入口文件主要是返回新创建的 Vue 实例。为了避免在一个单例对象中的多次请求对状态的污染,所以每次渲染都会重复创建 Vue 实例。在这个地方,也会进行组件状态数据的预获取,路由处理等。
- Client entry:客户端入口文件就是将 Vue 实例挂载到指定的 DOM 元素上。
在打包之后,有两个 bundle 文件:
- Server bundle:服务器按照请求通过 Node 去生成预渲染的 HTML 字符串返回到请求的客户端,完成初始的渲染。
- Client bundle:客户端拿到 HTML 之后,会使用这个 bundle 进行混合,使其变为由 Vue 管理的动态 DOM,为之后能够响应后续的数据变化。

同构渲染:同一份代码,服务端先渲染生成 HTML(Dehydrate),客户端拿到代码后运行 JS ,进行客户端激活(Client-Side Hydration,CSH)的过程。