Complite
源码解析器因为要结合 Watcher
,所以 Complite
只在这里做简单的介绍,如果有时间单独写一篇.
我们在 .vue
的文件中编写的代码,或者创建 Vue
实例后编写的代码,部分如指令、直接在 DOM
中渲染调用并计算变量等是不被浏览器直接识别和解析的,所以在代码正常渲染在浏览器并执行业务逻辑之前,Vue
要先将我们的代码进行编译。
我们编写的 Vue
实例的结构代码其实是一个字符串,这个字符串被 Vue
的 Complite
源码解析器编译成抽象语法数AST
(DOM 节点及属性以对象嵌套的形式存在),然后根据 AST
为每个节点生成对应的 render
函数,调用某个节点的 render
函数就能形成对应的 VNode
。
Complite
除了要要编译源码之外,还要捕获数据变化,也就是绑定 Watcher
,然后根据 Watcher
类的,更新视图。
Observer
类Object.defineProperty
函数 ( Object.defineProperty(obj, key, desc);
)Object
构造器直接调用,对象的实例不能调用。obj
指要操作属性的源对象,key
指要操作的这个属性,desc
指要操作的属性的描述对象。Symbol
类型的数据作为 key
。get
和 set
。Object.defineProperty(obj, 'foo', {
get() {
// 每当 obj 访问 foo 属性时,get 函数会自动执行
// 不传入参数,会传入 this,但是 this 不一定指想 obj
// 该函数的返回值会被当做是 foo 的属性值
},
set(newVal) {
// 当 foo 属性值被修改时,set 函数会自动执行
// 接受一个参数 newVal,是为 foo 赋的新值
// 会传入 赋值时的 this 对象
}
});
Object.defineProperty
函数和 vue-cli
的关联在 Vue
中,一个组件是一个 VueComponent
实例,每个实例有自己的 $data
对象,其中存储的是当前组件的数据列表,要让每个数据动态响应,就需要为每个数据添加 get
和 set
,来劫持数据值的改变。
// 为指定对象的每个键添加 setter 和 getter
function covert(obj) {
const keyList = Object.keys(obj);
keyList.forEach(key => {
// _val 如果该属性已经存在,就是上一次的赋值,不存在为undifined
// 每个 key 的操作都会形成一个闭包,所以这个闭包就成了单独存储对象某个属性值(_val)的位置
let _val = obj[key];
Object.defineProperty(obj, key, {
get() {
// 在这里可以捕获到属性的变化,并限制取值操作,如果 return 一个固定值,那么 obj 的某个键就永远是这个值,重新赋值也没用
return _val;
},
set(val) {
// 这里可以对比属性的新旧值,并限制赋值操作
_val = val;
}
});
});
}
// -----------------------------------------------
// 调用
const data = {
visible: true,
num: 10
};
covert(data); // 为 data 的两个属性绑定 get 和 set
data.visible = false; // 调用 set
console.log(data.visible); // 调用 get
vue-cli
源码中的 Observer
类简介(极简)class Observer {
value: any;
dep: Dep;
vmCount: number;
constructor (value: any) {
this.value = value
this.dep = new Dep()
this.vmCount = 0
def(value, '__ob__', this)
if (Array.isArray(value)) {
// 数组的另一套劫持方式
} else {
this.walk(value)
}
}
walk (obj: Object) {
// 为每个键绑定 get 和 set
const keys = Object.keys(obj)
for (let i = 0; i < keys.length; i++) {
defineReactive(obj, keys[i])
}
}
observeArray (items: Array<any>) {
// 数组的特殊操作:由于数组数据类型的特殊性,数组的整体值的变更和劫持依旧在 set 和 get 中
// 但是 vue 为 Array 这个类的原型函数们添加了劫持,也就是说当数组的值发生改变时,要调用原型函数之前,先处理我们需要的业务操作
}
}
// ----------------------------------------------------------------
function defineReactive (
obj: Object,
key: string,
val: any,
customSetter?: ?Function,
shallow?: boolean
) {
...
let childOb = !shallow && observe(val)
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter () {
...
},
set: function reactiveSetter (newVal) {
...
}
})
}
Dep
类通过 Observer
类为数据们添加了 getter
和 setter
之后,就能观察到数据的变化,我们要改变跟这个数据有关系的一系列内容,可能是其他数据,也可能是页面渲染的内容。
A
直接控制的内容,或者通过 A
计算得到的内容,都被称为依赖了 A
。A
,需要一个准确的计量,才能在 A
每次改变时,执行对应的操作,这种计量方式是一个数组,因为依赖 A
的数据很可能有多个。A
,并将这些依赖收集起来:依赖数据 A
一定要获取数据 A
,所以在 A
的 getter
中,做依赖收集。A
每次发生值的改变时,setter
会执行,所以在 A
的 setter
中通知依赖。依赖可能被添加,也有可能伴随着组件卸载、销毁而被删除,所以依赖除了要被声明之外,还要有其他操作,依赖类 Dep
就是用来为每个数据创建依赖并处理依赖的。
vue-cli
中的 Dep
类...
let uid = 0
class Dep {
static target: ?Watcher; // 静态属性,Dep 构造器访问,Dep 的实例不能访问
id: number; // 每个 Dep 实例唯一的id
subs: Array<Watcher>;
constructor () {
this.id = uid++
// 某个数据的依赖列表
this.subs = []
}
addSub (sub: Watcher) {
this.subs.push(sub)
}
removeSub (sub: Watcher) {
remove(this.subs, sub)
}
depend () {
if (Dep.target) {
Dep.target.addDep(this)
}
}
notify () {
const subs = this.subs.slice()
if (process.env.NODE_ENV !== 'production' && !config.async) {
subs.sort((a, b) => a.id - b.id)
}
// 当某个数据的值发生变化时,要循环这个数据的依赖列表,并且让他们相关的所有操作都更新一次
for (let i = 0, l = subs.length; i < l; i++) {
subs[i].update()
}
}
}
...
Observer
中调用 Dep
(简写)class Observer {
constructor() {
this.dep = new Dep();
}
...
}
function defineReactive() {
Object.defineProperty(obj, key, {
get() {
// get 被调用一次,都代表有一个数据依赖于当前数据,要向当前数据的依赖列表 subs 中添加一个依赖
dep.depend()
...
},
set() {
// set 被调用且值发生改变时,代表当前数据更新,那么依赖于当前数据的所有内容都要发生对应变化
dep.notify()
...
}
...
});
}
Watcher
类上面的 Dep
类只是对数据的依赖进行管理的一套方式,从 Dep
的源码中我们可以捕捉到,真正的被添加到 subs
数组中的依赖是 Watcher
。
Watcher
某个表达式 Express
,用到到了某个数据 A
,我们就说 Express
订阅了 A
,为了能让这个 Express
在 A
每次发生变化时,都能动态的重新计算自己,我们就为它编写一个 update
方法来更新自己并做后续的数据渲染。每次 A
变化,都通知 Express
让它 update
。
在 Vue
中,到处都是这样的订阅与响应,所以产生了 Watcher
类,专门处理数据变化之后其依赖们的响应动作。
Watcher
需要具备的功能以下三点来自小马哥源码解析及总结
dep
)里面添加自己。update()
方法。dep.notify()
通知时,能调用自身的 update()
方法,并触发Compile中绑定的回调,则功成身退。Observer
和 Dep
,整个数据双向响应流程如下:dep
属性,继承了 Dep
类的 subs
属性,来承接依赖列表;getter
和 setter
;getter
返回数据值的同时,向 subs
数组中添加了一个 Watcher
;setter
,setter
设置数据值的同时,调用 Dep
提供的 notify
函数,来通知该数据的依赖做出响应;notify
函数内部遍历依赖列表(即订阅者 Watcher
列表)subs
数组,调用每个订阅者的 update
函数,让其根绝数据新的值重新计算自己。