第1章 Vue设计哲学(深度解析版)
1.1 声明式编程 vs 命令式编程
案例对比分析
传统命令式实现:
// 创建元素
const div = document.createElement('div')
// 设置属性(命令式操作)
div.id = 'counter'
div.textContent = '0'
// 挂载到DOM
document.body.appendChild(div)
// 更新逻辑(必须手动操作DOM)
setTimeout(() => {
div.textContent = '1' // 显式指定如何更新
}, 1000)
Vue声明式实现:
<div id="app">{{ count }}</div>
<script>
new Vue({
el: '#app',
data: { count: 0 },
mounted() {
setTimeout(() => {
this.count = 1 // 只需关心数据变化
}, 1000)
}
})
</script>
关键差异解析
维度 | 命令式 | 声明式 |
---|---|---|
关注点 | 如何做(How) | 做什么(What) |
DOM操作 | 开发者直接操作 | 框架自动处理 |
代码量 | 与复杂度正相关 | 保持简洁 |
维护性 | 需要跟踪每个状态变化 | 数据驱动自动更新 |
设计哲学体现
// Vue内部处理流程示意图
数据变化 → 触发setter → 通知Watcher →
生成新VDOM → Diff算法 → 精准更新DOM
1.2 响应式系统核心实现
1.2.1 数据劫持原理
代码实现:
function defineReactive(obj, key, val) {
const dep = new Dep() // 每个属性对应一个依赖管理器
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter() {
if (Dep.target) { // 存在当前Watcher时
dep.depend() // 建立依赖关系
}
return val
},
set: function reactiveSetter(newVal) {
if (newVal === val) return
val = newVal // 更新内部值
dep.notify() // 通知所有订阅者
}
})
}
关键代码解析:
-
Dep
类:依赖管理器- 每个响应式属性对应一个 Dep 实例
- 负责收集依赖(Watcher)和通知更新
-
defineProperty
的作用:- 拦截操作:在属性被访问和修改时执行自定义逻辑
- 内存管理:通过闭包维护内部值
val
-
Dep.target
机制:- 全局唯一变量,指向当前正在计算的 Watcher
- 保证依赖收集的正确性
1.2.2 完整的依赖收集流程
Watcher类核心代码:
class Watcher {
constructor(vm, expOrFn, cb) {
this.vm = vm // Vue实例
this.cb = cb // 回调函数
this.getter = parsePath(expOrFn) // 路径解析器
this.value = this.get() // 初始获取值
}
get() {
Dep.target = this // 标记当前Watcher
let value
try {
value = this.getter.call(this.vm, this.vm) // 触发getter
} finally {
Dep.target = null // 收集完成后重置
}
return value
}
update() {
this.run() // 触发回调
}
run() {
const value = this.get()
if (value !== this.value) {
const oldValue = this.value
this.value = value
this.cb.call(this.vm, value, oldValue) // 执行回调
}
}
}
执行流程解析:
-
初始化阶段:
- 创建 Watcher 实例时立即调用
get()
- 设置
Dep.target = 当前Watcher
- 执行
getter
函数触发数据属性的get
拦截器
- 创建 Watcher 实例时立即调用
-
依赖收集阶段:
- 在数据属性的
get
中调用dep.depend()
- 将当前 Watcher 添加到 Dep 的订阅列表
- 在数据属性的
-
更新阶段:
- 数据变化触发
set
拦截器 - 调用
dep.notify()
遍历执行所有 Watcher 的update()
- 执行回调函数完成视图更新
- 数据变化触发
1.2.3 数组的特殊处理
代码示例:
const arrayProto = Array.prototype
const arrayMethods = Object.create(arrayProto)
;['push', 'pop', 'shift'].forEach(method => {
const original = arrayProto[method]
def(arrayMethods, method, function mutator(...args) {
const result = original.apply(this, args) // 调用原生方法
const ob = this.__ob__ // 获取Observer实例
ob.dep.notify() // 手动触发通知
return result
})
})
关键点说明:
- 原型链继承:创建继承自Array原型的对象
- 方法重写:对变异方法进行拦截
- 手动通知:在数组变化后主动触发依赖更新
- 实现原理:
arr.__proto__ = arrayMethods // 修改数组原型链
1.3 模板编译深度解析
1.3.1 编译过程三个阶段
示例模板:
<div v-if="show">
<span>{{ message }}</span>
</div>
阶段一:解析(Parse)
输出AST结构:
{
type: 1,
tag: 'div',
attrsList: [{ name: 'v-if', value: 'show' }],
directives: [{ name: 'if', rawName: 'v-if', value: 'show' }],
children: [{
type: 1,
tag: 'span',
children: [{
type: 2,
expression: '_s(message)',
text: '{{ message }}'
}]
}]
}
解析过程关键点:
- 使用正则表达式匹配标签/属性/指令
- 构建树形结构的AST节点
- 记录标签之间的父子关系
阶段二:优化(Optimize)
静态标记结果:
{
// ...其他属性
static: false,
children: [{
static: false,
children: [{
static: false // 包含动态插值表达式
}]
}]
}
优化策略:
- 标记静态节点(无动态绑定)
- 标记静态根节点(子树全静态)
- 提升静态节点(复用VNode)
阶段三:生成(Generate)
生成的Render函数:
function render() {
with(this){
return (show) ?
_c('div', [_c('span', [_v(_s(message))])])
: _e()
}
}
关键函数说明:
_c
: createElement(创建VNode)_v
: createTextVNode(创建文本节点)_s
: toString(转换为字符串)_e
: createEmptyVNode(创建空节点)
1.4 初始化流程全解析
1.4.1 new Vue() 的执行轨迹
function Vue(options) {
this._init(options) // 入口方法
}
Vue.prototype._init = function(options) {
// 合并选项
vm.$options = mergeOptions(...)
initLifecycle(vm) // 初始化生命周期
initEvents(vm) // 初始化事件系统
initRender(vm) // 初始化渲染函数
callHook(vm, 'beforeCreate')
initInjections(vm) // 处理inject
initState(vm) // 核心:初始化响应式系统
initProvide(vm) // 处理provide
callHook(vm, 'created')
if (vm.$options.el) {
vm.$mount(vm.$options.el) // 开始挂载
}
}
关键初始化步骤说明:
-
选项合并:
- 合并全局配置和实例配置
- 处理组件继承关系
-
initState 核心作用:
function initState(vm) { if (opts.props) initProps(vm, opts.props) // 初始化Props if (opts.methods) initMethods(vm, opts.methods) // 处理methods if (opts.data) initData(vm) // 响应式处理data if (opts.computed) initComputed(vm, opts.computed) // 处理计算属性 if (opts.watch) initWatch(vm, opts.watch) // 处理侦听器 }
-
生命周期钩子调用时机:
钩子阶段 触发时机 beforeCreate 初始化inject/provide之前 created 完成响应式处理,未挂载DOM
1.5 常见问题解析
Q1:为什么data必须是函数?
错误示例:
// 组件选项
data: { count: 0 } // 多个实例会共享同一数据对象
正确写法:
data() {
return { count: 0 } // 每次实例化返回新对象
}
原理说明:
- 组件可能被多次实例化
- 函数形式保证每个实例都有独立的数据副本
Q2:Vue如何检测数组变化?
-
无法检测 的情况:
vm.items[0] = newItem // 索引直接赋值 vm.items.length = 2 // 修改length
-
正确方式:
Vue.set(vm.items, 0, newItem) vm.items.splice(0, 1, newItem)
实现原理:
- 重写数组变异方法
- 对新增元素进行响应式处理
- 手动触发依赖通知