# Vue基础知识
# 什么是MVVM?
M
:Model层,代表 模型、数据。负责数据修改、操作的业务逻辑
V
:View层,代表 视图、模板。负责将数据模型转化UI展现出来
VM
:ViewModel层,将 View
和 Model
连接起来
- 将 Model层 的数据
同步
到 View层 ,进行渲染 - 将 View层 的修改
同步
到 Model层 ,进行存储
Vue框架充当了MVVM开发模式中的 ViewModel
层,负责 View
和 Model
之间通信的桥梁。
# MVVM的实现原理
如何监听JS对象属性发生了变更?
Vue.js
通过 数据劫持、发布订阅。
当数据变动时,数据会触发劫持时绑定的方法,对视图进行更新。
Angular.js
/RegularJS
通过 脏检查。
在各种可能引发状态变更的事件后,启动一次脏检查,来决定是否更新视图。(数据模型本身无状态)
数据劫持、脏检查 的相同点:
- 解析模板
- 解析数据
- 绑定模板与数据
# 数据双向绑定
Vue
是通过 数据劫持 + 发布订阅 来实现 数据双向绑定 的。
在 初始化Vue实例 的 initState
过程中,会对传入的 data
进行属性遍历。为每个属性,都通过 Object.defineProperty
改造它们的 getter
、 setter
方法(数据劫持 )。对于每一个响应式属性,会绑定一个 dep
订阅器(发布订阅 ),负责:
- 在
getter
时的 依赖收集 - 在
setter
时的 发布通知
1、如果
data[key]
是对象,进行深度劫持)2、其中的 订阅者 指的是一个个
watcher
实例。
# 订阅者watcher
模板中每个指令(v-model="name"
)、数据绑定({ { name } }
)都会对应一个 watcher
对象。在模板的编译计算过程中,响应式属性(这里为 name
)会通过 getter依赖收集 将它(这里为watcher
)记录为依赖。
当响应式属性(这里为name
)的 setter发布更新 被调用时,会触发它依赖列表中的 watcher
的重新计算(watcher.update()
),也就会导致它的关联指令去更新DOM。
# 依赖收集
依赖收集
发生在响应式属性的 getter
阶段,但不是每次 getter
都会收集依赖,会通过 Dep.target
来区分。
若
Dep.target
有值,说明是Vue内部依赖收集过程触发的,需先收集依赖this.dep.depend()
,再返回value;否则直接返回value即可。
为什么要用Dep.target代表Watcher实例?答:因为getter函数不能传参
Dep.prototype.depend = function () {
Dep.target.addDep(this);
}
2
3
# 异步更新队列
Vue在更新 DOM 时是 异步 执行的。只要监听到数据变化,Vue将开启一个队列,并缓冲 在本次事件循环 中发生的所有数据变更。目的是为了避免不必要的计算和DOM操作。
然后在 下次事件循环(tick) 中,Vue
会刷新队列,并执行更新。
Vue.nextTick(callback)
的回调函数,会 在 Vue
完成DOM更新后 立即执行。
# Vue VS React
相同:
- Virtual DOM
- 组件化
- 组件内部执行
render
-> VNode树 -> Diff -> 真实DOM
- 组件内部执行
- 推崇“单向数据流”
- 父组件传入数据给子组件,子组件不能直接修改父组件传过来的props
不同:
- 核心思想
- React:函数式编程、推崇JSX、组件复用HOC/Hooks
- Vue:双向绑定、推崇template、组件复用Mixin、单文件.vue
- 组件的实现方式
- React:React.Component类、函数式组件返回的React元素;传入
ReactDOM.render()
去渲染 - Vue:options对象声明,实例化
new Vue()
,内部方法initState
等
- React:React.Component类、函数式组件返回的React元素;传入
- Diff算法
- React:三个策略、三种细粒度Diff;从根组件重新渲染整棵树
- Vue:五种情况、四次比较、根据Key值查找匹配对象;更细粒度更新
# 参考链接
# Observer、Dep、Watcher源码
# Observer监听器
class Observer {
constructor(data){
this.data = data
this.walk(data)
}
// 遍历walk中所有的数据,劫持 set 和 get方法
walk(data) {
// 判断data 不存在或者不是对象的情况
if(!data || typeof data !=='object') return
// 拿到data中所有的属性
Object.keys(data).forEach(key => {
// 给data中的属性添加 getter和 setter方法
this.defineReactive(data,key,data[key])
// 如果data[key]是对象,深度劫持
this.walk(data[key])
})
}
// 定义响应式数据
defineReactive(obj,key,value) {
let _this = this
let dep = new Dep() // Dep函数请看下节实现
Object.defineProperty(obj,key,{
enumerable:true,
configurable: true,
get(){
// 如果Dep.target 中有watcher 对象,则存储到订阅者数组中
Dep.target && dep.addSub(Dep.target)
return value
},
set(newVal){
if(value === newVal) return
value = newVal
// 如果设置的值是一个对象,那么这个对象也应该是响应式的
_this.walk(newVal)
// 发布通知,让所有订阅者更新内容
dep.notify()
}
})
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
# Dep订阅器
class Dep {
constructor() {
this.subs = [] // 用来存储订阅者
}
// 添加订阅者
addSub(watcher) {
this.subs.push(watcher)
}
//通知订阅者
notify() {
// 通知所有订阅者更新视图
this.subs.forEach(sub => {
sub.update()
})
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# Watcher订阅者
class Watcher {
/**
*
* @param {*} vm 当前的vue实例
* @param {*} expr data中数据的名字
* @param {*} callback 一旦数据改变,则需要调用callback
*/
constructor(vm,expr,callback){
this.vm = vm
this.expr = expr
this.callback = callback
Dep.target = this // 全局变量 订阅者 赋值
this.oldValue = this.getVMData(vm,expr) // 强制执行监听器Observer里的get函数
Dep.target = null //全局变量 订阅者 释放
}
// 对外暴露的方法,用于更新页面
update() {
// 对比expr是否发生改变,如果改变则调用callback
let oldValue = this.oldValue
let newValue = this.getVMData(this.vm,this.expr)
// 变化的时候调用callback
if(oldValue !== newValue) {
this.callback(newValue,oldValue)
}
}
getVMData(vm,expr) {
let data = vm.$data
expr.split('.').forEach(key => {
data = data[key]
})
return data
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
# 稍微了解MVC
M
指的是Model层
- 用于封装和业务逻辑相关的数据、以及对数据处理的方法
V
指的是View层
- 监听模型层上的数据改变,并实时更新页面
C
指的是Controller层
- 负责接收用户的操作,然后调用模型或视图去完成用户的操作
本质: 将 数据展示 和 数据 进行隔离,提高代码的复用性和扩展性;
特点: 职责明确、相互分离;
和 MVVM 的区别: MCV耦合度较高。因为MVVM简化了View、Model的依赖,也解决了数据频繁更新的问题,不必再用选择器去操作DOM。
生命周期 →