前言
前言
响应式原理作为 Vue 的核心,使用数据劫持实现数据驱动视图。在面试中是经常考查的知识点,也是面试加分项。本文将会循序渐进的解析响应式原理的工作流程,主要以下面结构进行:

分析主要成员,了解它们有助于理解流程

将流程拆分,理解其中的作用

结合以上的点,理解整体流程


分析主要成员,了解它们有助于理解流程将流程拆分,理解其中的作用结合以上的点,理解整体流程

文章稍长,但大部分是代码实现,还请耐心观看。为了方便理解原理,文中的代码会进行简化,如果可以请对照源码学习。主要成员主要成员响应式原理中,Observe、Watcher、Dep这三个类是构成完整原理的主要成员。

Observe,响应式原理的入口,根据数据类型处理观测逻辑

Watcher,用于执行更新渲染,组件会拥有一个渲染Watcher,我们常说的收集依赖,就是收集 Watcher

Dep,依赖收集器,属性都会有一个Dep,方便发生变化时能够找到对应的依赖触发更新


Observe,响应式原理的入口,根据数据类型处理观测逻辑Watcher,用于执行更新渲染,组件会拥有一个渲染Watcher,我们常说的收集依赖,就是收集 WatcherDep,依赖收集器,属性都会有一个Dep,方便发生变化时能够找到对应的依赖触发更新

下面来看看这些类的实现,包含哪些主要属性和方法。Observe:我会对数据进行观测Observe:我会对数据进行观测温馨提示:代码里的序号对应代码块下面序号的讲解
// 源码位置:/src/core/observer/index.js
class Observe {
constructor(data) {

this.dep = new Dep()

// 1

def(data, '__ob__', this)

if (Array.isArray(data)) {

// 2

protoAugment(data, arrayMethods)

// 3

this.observeArray(data)

} else {

// 4

this.walk(data)

}
}
walk(data) {

Object.keys(data).forEach(key => {

defineReactive(data, key, data[key])

})
}
observeArray(data) {

data.forEach(item => {

observe(item)

})
}
}
// 源码位置:/src/core/observer/index.js
class Observe {
constructor(data) {

this.dep = new Dep()

// 1

def(data, '__ob__', this)

if (Array.isArray(data)) {

// 2

protoAugment(data, arrayMethods)

// 3

this.observeArray(data)

} else {

// 4

this.walk(data)

}
}
walk(data) {

Object.keys(data).forEach(key => {

defineReactive(data, key, data[key])

})
}
observeArray(data) {

data.forEach(item => {

observe(item)

})
}
}

为观测的属性添加 __ob__ 属性,它的值等于 this,即当前 Observe 的实例

为数组添加重写的数组方法,比如:push、unshift、splice 等方法,重写目的是在调用这些方法时,进行更新渲染

观测数组内的数据,observe 内部会调用 new Observe,形成递归观测

观测对象数据,defineReactive 为数据定义 get 和 set ,即数据劫持
为观测的属性添加 __ob__ 属性,它的值等于 this,即当前 Observe 的实例为数组添加重写的数组方法,比如:push、unshift、splice 等方法,重写目的是在调用这些方法时,进行更新渲染观测数组内的数据,observe 内部会调用 new Observe,形成递归观测观测对象数据,defineReactive 为数据定义 get 和 set ,即数据劫持Dep:我会为数据收集依赖Dep:我会为数据收集依赖
// 源码位置:/src/core/observer/dep.js
let id = 0
class Dep{
constructor() {

this.id = ++id // dep 唯一标识

this.subs = [] // 存储 Watcher
}
// 1
depend() {

Dep.target.addDep(this)
}
// 2
addSub(watcher) {

this.subs.push(watcher)
}
// 3
notify() {

this.subs.forEach(watcher => watcher.update())
}
}

// 4
Dep.target = null

export function pushTarget(watcher) {
Dep.target = watcher
}

export function popTarget(){
Dep.target = null
}

export default Dep
// 源码位置:/src/core/observer/dep.js
let id = 0
class Dep{
constructor() {

this.id = ++id // dep 唯一标识

this.subs = [] // 存储 Watcher
}
// 1
depend() {

Dep.target.addDep(this)
}
// 2
addSub(watcher) {

this.subs.push(watcher)
}
// 3
notify() {

this.subs.forEach(watcher => watcher.update())
}
}

// 4
Dep.target = null

export function pushTarget(watcher) {
Dep.target = watcher
}

export function popTarget(){
Dep.target = null
}

export default Dep

据收集依赖的主要方法,Dep.target 是一个 watcher 实例

添加 watcher 到数组中,也就是添加依赖

属性在变化时会调用 notify 方法,通知每一个依赖进行更新

Dep.target 用来记录 watcher 实例,是全局唯一的,主要作用是为了在收集依赖的过程中找到相应的 watcher


据收集依赖的主要方法,Dep.target 是一个 watcher 实例添加 watcher 到数组中,也就是添加依赖属性在变化时会调用 notify 方法,通知每一个依赖进行更新Dep.target 用来记录 watcher 实例,是全局唯一的,主要作用是为了在收集依赖的过程中找到相应的 watcher

pushTarget 和 popTarget 这两个方法显而易见是用来设置 Dep.target的。Dep.target 也是一个关键点,这个概念可能初次查看源码会有些难以理解,在后面的流程中,会详细讲解它的作用,需要注意这部分的内容。Watcher:我会触发视图更新Watcher:我会触发视图更新
// 源码位置:/src/core/observer/watcher.js
let id = 0
export class Watcher {
constructor(vm, exprOrFn, cb, options){

this.id = ++id // watcher 唯一标识

this.vm = vm

this.cb = cb

this.options = options

// 1

this.getter = exprOrFn

this.deps = []

this.depIds = new Set()


this.get()
}
run() {

this.get()
}
get() {

pushTarget(this)

this.getter()

popTarget(this)
}
// 2
addDep(dep) {

// 防止重复添加 dep

if (!this.depIds.has(dep.id)) {

this.depIds.add(dep.id)

this.deps.push(dep)

dep.addSub(this)

}
}
// 3
update() {

queueWatcher(this)
}
}
// 源码位置:/src/core/observer/watcher.js
let id = 0
export class Watcher {
constructor(vm, exprOrFn, cb, options){

this.id = ++id // watcher 唯一标识

this.vm = vm

this.cb = cb

this.options = options

// 1

this.getter = exprOrFn

this.deps = []

this.depIds = new Set()


this.get()
}
run() {

this.get()
}
get() {

pushTarget(this)

this.getter()

popTarget(this)
}
// 2
addDep(dep) {

// 防止重复添加 dep

if (!this.depIds.has(dep.id)) {

this.depIds.add(dep.id)

this.deps.push(dep)

dep.addSub(this)

}
}
// 3
update() {

queueWatcher(this)
}
}

this.getter 存储的是更新视图的函数

watcher 存储 dep,同时 dep 也存储 watcher,进行双向记录

触发更新,queueWatcher 是为了进行异步更新,异步更新会调用 run 方法进行更新页面


this.getter 存储的是更新视图的函数watcher 存储 dep,同时 dep 也存储 watcher,进行双向记录触发更新,queueWatcher 是为了进行异步更新,异步更新会调用 run 方法进行更新页面

响应式原理流程响应式原理流程对于以上这些成员具有的功能,我们都有大概的了解。下面结合它们,来看看这些功能是如何在响应式原理流程中工作的。数据观测数据观测数据在初始化时会通过 observe 方法来创建 Observe 类
// 源码位置:/src/core/observer/index.js
export function observe(data) {
// 1
if (!isObject(data)) {

return
}
let ob;
// 2
if (data.hasOwnProperty('__ob__') && data.__ob__ instanceof Observe) {

ob = data.__ob__
} else {

// 3

ob = new Observe(data)
}
return ob
}
// 源码位置:/src/core/observer/index.js
export function observe(data) {
// 1
if (!isObject(data)) {

return
}
let ob;
// 2
if (data.hasOwnProperty('__ob__') && data.__ob__ instanceof Observe) {

ob = data.__ob__
} else {

// 3

ob = new Observe(data)
}
return ob
}在初始化时,observe 拿到的 data 就是我们在 data 函数内返回的对象。

observe 函数只对 object 类型数据进行观测

观测过的数据都会被添加上 __ob__ 属性,通过判断该属性是否存在,防止重复观测

创建 Observe 类,开始处理观测逻辑


observe 函数只对 object 类型数据进行观测观测过的数据都会被添加上 __ob__ 属性,通过判断该属性是否存在,防止重复观测创建 Observe 类,开始处理观测逻辑

对象观测对象观测进入 Observe 内部,由于初始化的数据是一个对象,所以会调用 walk 方法:
walk(data) {
Object.keys(data).forEach(key => {

defineReactive(data, key, data[key])
})
}
walk(data) {
Object.keys(data).forEach(key => {

defineReactive(data, key, data[key])
})
}defineReactive 方法内部使用 Object.defineProperty 对数据进行劫持,是实现响应式原理最核心的地方。
function defineReactive(obj, key, value) {
// 1
let childOb = observe(value)
// 2
const dep = new Dep()
Object.defineProperty(obj, key, {

get() {

if (Dep.target) {

// 3

dep.depend()

if (childOb) {

childOb.dep.depend()

}

}

return value

},

set(newVal) {

if (newVal === value) {

return

}

value = newVal

// 4

childOb = observe(newVal)

// 5

dep.notify()

return value

}
})
}
function defineReactive(obj, key, value) {
// 1
let childOb = observe(value)
// 2
const dep = new Dep()
Object.defineProperty(obj, key, {

get() {

if (Dep.target) {

// 3

dep.depend()

if (childOb) {

childOb.dep.depend()

}

}

return value

},

set(newVal) {

if (newVal === value) {

return

}

value = newVal

// 4

childOb = observe(newVal)

// 5

dep.notify()

return value

}
})
}

由于值可能是对象类型,这里需要调用 observe 进行递归观测

这里的 dep 就是上面讲到的每一个属性都会有一个 dep,它是作为一个闭包的存在,负责收集依赖和通知更新

在初始化时,Dep.target 是组件的渲染 watcher,这里 dep.depend 收集的依赖就是这个 watcher,childOb.dep.depend 主要是为数组收集依赖

设置的新值可能是对象类型,需要对新值进行观测

值发生改变,dep.notify 通知 watcher 更新,这是我们改变数据后能够实时更新页面的触发点


由于值可能是对象类型,这里需要调用 observe 进行递归观测这里的 dep 就是上面讲到的每一个属性都会有一个 dep,它是作为一个闭包的存在,负责收集依赖和通知更新在初始化时,Dep.target 是组件的渲染 watcher,这里 dep.depend 收集的依赖就是这个 watcher,childOb.dep.depend 主要是为数组收集依赖设置的新值可能是对象类型,需要对新值进行观测值发生改变,dep.notify 通知 watcher 更新,这是我们改变数据后能够实时更新页面的触发点

通过 Object.defineProperty 对属性定义后,属性的获取触发 get 回调,属性的设置触发 set 回调,实现响应式更新。通过上面的逻辑,也能得出为什么 Vue3.0 要使用 Proxy 代替 Object.defineProperty 了。Object.defineProperty 只能对单个属性进行定义,如果属性是对象类型,还需要递归去观测,会很消耗性能。而 Proxy 是代理整个对象,只要属性发生变化就会触发回调。数组观测数组观测对于数组类型观测,会调用 observeArray 方法:
observeArray(data) {
data.forEach(item => {

observe(item)
})
}
observeArray(data) {
data.forEach(item => {

observe(item)
})
}与对象不同,它执行 observe 对数组内的对象类型进行观测,并没有对数组的每一项进行 Object.defineProperty 的定义,也就是说数组内的项是没有 dep 的。所以,我们通过数组索引对项进行修改时,是不会触发更新的。但可以通过 this.$set 来修改触发更新。那么问题来了,为什么 Vue 要这样设计?结合实际场景,数组中通常会存放多项数据,比如列表数据。这样观测起来会消耗性能。还有一点原因,一般修改数组元素很少会直接通过索引将整个元素替换掉。例如:
export default {

data() {

return {

list: [

{id: 1, name: 'Jack'},

{id: 2, name: 'Mike'}

]

}

},

cretaed() {

// 如果想要修改 name 的值,一般是这样使用

this.list[0].name = 'JOJO'

// 而不是以下这样

// this.list[0] = {id:1, name: 'JOJO'}

// 当然你可以这样更新

// this.$set(this.list, '0', {id:1, name: 'JOJO'})

}
}
export default {

data() {

return {

list: [

{id: 1, name: 'Jack'},

{id: 2, name: 'Mike'}

]

}

},

cretaed() {

// 如果想要修改 name 的值,一般是这样使用

this.list[0].name = 'JOJO'

// 而不是以下这样

// this.list[0] = {id:1, name: 'JOJO'}

// 当然你可以这样更新

// this.$set(this.list, '0', {id:1, name: 'JOJO'})

}
}数组方法重写数组方法重写当数组元素新增或删除,视图会随之更新。这并不是理所当然的,而是 Vue 内部重写了数组的方法,调用这些方法时,数组会更新检测,触发视图更新。这些方法包括:

push()

pop()

shift()

unshift()

splice()

sort()

reverse()


push()pop()shift()unshift()splice()sort()reverse()

回到 Observe 的类中,当观测的数据类型为数组时,会调用 protoAugment 方法。
if (Array.isArray(data)) {
protoAugment(data, arrayMethods)
// 观察数组
this.observeArray(data)
} else {
// 观察对象
this.walk(data)
}
if (Array.isArray(data)) {
protoAugment(data, arrayMethods)
// 观察数组
this.observeArray(data)
} else {
// 观察对象
this.walk(data)
}这个方法里把数组原型替换为 arrayMethods ,当调用改变数组的方法时,优先使用重写后的方法。
function protoAugment(data, arrayMethods) {
data.__proto__ = arrayMethods
}
function protoAugment(data, arrayMethods) {
data.__proto__ = arrayMethods
}接下来看看 arrayMethods 是如何实现的:
// 源码位置:/src/core/observer/array.js
// 1
let arrayProto = Array.prototype
// 2
export let arrayMethods = Object.create(arrayProto)

let methods = [
'push',
'pop',
'shift',
'unshift',
'reverse',
'sort',
'splice'
]

methods.forEach(method => {
arrayMethods[method] = function(...args) {

// 3

let res = arrayProto[method].apply(this, args)

let ob = this.__ob__

let inserted = ''

switch(method){

case 'push':

case 'unshift':

inserted = args

break;

case 'splice':

inserted = args.slice(2)

break;

}

// 4

inserted && ob.observeArray(inserted)

// 5

ob.dep.notify()

return res
}
})
// 源码位置:/src/core/observer/array.js
// 1
let arrayProto = Array.prototype
// 2
export let arrayMethods = Object.create(arrayProto)

let methods = [
'push',
'pop',
'shift',
'unshift',
'reverse',
'sort',
'splice'
]

methods.forEach(method => {
arrayMethods[method] = function(...args) {

// 3

let res = arrayProto[method].apply(this, args)

let ob = this.__ob__

let inserted = ''

switch(method){

case 'push':

case 'unshift':

inserted = args

break;

case 'splice':

inserted = args.slice(2)

break;

}

// 4

inserted && ob.observeArray(inserted)

// 5

ob.dep.notify()

return res
}
})

将数组的原型保存起来,因为重写的数组方法里,还是需要调用原生数组方法的

arrayMethods 是一个对象,用于保存重写的方法,这里使用 Object.create(arrayProto) 创建对象是为了使用者在调用非重写方法时,能够继承使用原生的方法

调用原生方法,存储返回值,用于设置重写函数的返回值

inserted 存储新增的值,若 inserted 存在,对新值进行观测

ob.dep.notify 触发视图更新
将数组的原型保存起来,因为重写的数组方法里,还是需要调用原生数组方法的arrayMethods 是一个对象,用于保存重写的方法,这里使用 Object.create(arrayProto) 创建对象是为了使用者在调用非重写方法时,能够继承使用原生的方法调用原生方法,存储返回值,用于设置重写函数的返回值inserted 存储新增的值,若 inserted 存在,对新值进行观测ob.dep.notify 触发视图更新依赖收集依赖收集依赖收集是视图更新的前提,也是响应式原理中至关重要的环节。伪代码流程伪代码流程为了方便理解,这里写一段伪代码,大概了解依赖收集的流程:
// data 数据
let data = {

name: 'joe'
}

// 渲染watcher
let watcher = {

run() {

dep.tagret = watcher

document.write(data.name)

}
}

// dep
let dep = [] // 存储依赖
dep.tagret = null // 记录 watcher

// 数据劫持
Object.defineProperty(data, 'name', {

get(){

// 收集依赖

dep.push(dep.tagret)

},

set(newVal){

data.name = newVal

dep.forEach(watcher => {

watcher.run()

})

}
})
// data 数据
let data = {

name: 'joe'
}

// 渲染watcher
let watcher = {

run() {

dep.tagret = watcher

document.write(data.name)

}
}

// dep
let dep = [] // 存储依赖
dep.tagret = null // 记录 watcher

// 数据劫持
Object.defineProperty(data, 'name', {

get(){

// 收集依赖

dep.push(dep.tagret)

},

set(newVal){

data.name = newVal

dep.forEach(watcher => {

watcher.run()

})

}
})初始化:

首先会对 name 属性定义 get 和 set

然后初始化会执行一次 watcher.run 渲染页面

这时候获取 data.name,触发 get 函数收集依赖。
首先会对 name 属性定义 get 和 set然后初始化会执行一次 watcher.run 渲染页面这时候获取 data.name,触发 get 函数收集依赖。更新:修改 data.name,触发 set 函数,调用 run 更新视图。真正流程
真正流程
下面来看看真正的依赖收集流程是如何进行的。
function defineReactive(obj, key, value) {
let childOb = observe(value)
const dep = new Dep()
Object.defineProperty(obj, key, {

get() {

if (Dep.target) {

dep.depend() // 收集依赖

if (childOb) {

childOb.dep.depend()

}

}

return value

},

set(newVal) {

if (newVal === value) {

return

}

value = newVal

childOb = observe(newVal)

dep.notify()

return value

}
})
}
function defineReactive(obj, key, value) {
let childOb = observe(value)
const dep = new Dep()
Object.defineProperty(obj, key, {

get() {

if (Dep.target) {

dep.depend() // 收集依赖

if (childOb) {

childOb.dep.depend()

}

}

return value

},

set(newVal) {

if (newVal === value) {

return

}

value = newVal

childOb = observe(newVal)

dep.notify()

return value

}
})
}首先初始化数据,调用 defineReactive 函数对数据进行劫持。
export class Watcher {
constructor(vm, exprOrFn, cb, options){

this.getter = exprOrFn

this.get()
}
get() {

pushTarget(this)

this.getter()

popTarget(this)
}
}
export class Watcher {
constructor(vm, exprOrFn, cb, options){

this.getter = exprOrFn

this.get()
}
get() {

pushTarget(this)

this.getter()

popTarget(this)
}
}初始化将 watcher 挂载到 Dep.target,this.getter 开始渲染页面。渲染页面需要对数据取值,触发 get 回调,dep.depend 收集依赖。
class Dep{
constructor() {

this.id = id++

this.subs = []
}
depend() {

Dep.target.addDep(this)
}
}
class Dep{
constructor() {

this.id = id++

this.subs = []
}
depend() {

Dep.target.addDep(this)
}
}Dep.target 为 watcher,调用 addDep 方法,并传入 dep 实例。
export class Watcher {
constructor(vm, exprOrFn, cb, options){

this.deps = []

this.depIds = new Set()
}
addDep(dep) {

if (!this.depIds.has(dep.id)) {

this.depIds.add(dep.id)

this.deps.push(dep)

dep.addSub(this)

}
}
}
export class Watcher {
constructor(vm, exprOrFn, cb, options){

this.deps = []

this.depIds = new Set()
}
addDep(dep) {

if (!this.depIds.has(dep.id)) {

this.depIds.add(dep.id)

this.deps.push(dep)

dep.addSub(this)

}
}
}addDep 中添加完 dep 后,调用 dep.addSub 并传入当前 watcher 实例。
class Dep{
constructor() {

this.id = id++

this.subs = []
}
addSub(watcher) {

this.subs.push(watcher)
}
}
class Dep{
constructor() {

this.id = id++

this.subs = []
}
addSub(watcher) {

this.subs.push(watcher)
}
}将传入的 watcher 收集起来,至此依赖收集流程完毕。补充一点,通常页面上会绑定很多属性变量,渲染会对属性取值,此时每个属性收集的依赖都是同一个 watcher,即组件的渲染 watcher。数组的依赖收集数组的依赖收集
methods.forEach(method => {
arrayMethods[method] = function(...args) {

let res = arrayProto[method].apply(this, args)

let ob = this.__ob__

let inserted = ''

switch(method){

case 'push':

case 'unshift':

inserted = args

break;

case 'splice':

inserted = args.slice(2)

break;

}

// 对新增的值观测

inserted && ob.observeArray(inserted)

// 更新视图

ob.dep.notify()

return res
}
})
methods.forEach(method => {
arrayMethods[method] = function(...args) {

let res = arrayProto[method].apply(this, args)

let ob = this.__ob__

let inserted = ''

switch(method){

case 'push':

case 'unshift':

inserted = args

break;

case 'splice':

inserted = args.slice(2)

break;

}

// 对新增的值观测

inserted && ob.observeArray(inserted)

// 更新视图

ob.dep.notify()

return res
}
})还记得重写的方法里,会调用 ob.dep.notify 更新视图,__ob__ 是我们在 Observe 为观测数据定义的标识,值为 Observe 实例。那么 ob.dep 的依赖是在哪里收集的?
function defineReactive(obj, key, value) {
// 1
let childOb = observe(value)
const dep = new Dep()
Object.defineProperty(obj, key, {

get() {

if (Dep.target) {

dep.depend()

// 2

if (childOb) {

childOb.dep.depend()

}

}

return value

},

set(newVal) {

if (newVal === value) {

return

}

value = newVal

childOb = observe(newVal)

dep.notify()

return value

}
})
}
function defineReactive(obj, key, value) {
// 1
let childOb = observe(value)
const dep = new Dep()
Object.defineProperty(obj, key, {

get() {

if (Dep.target) {

dep.depend()

// 2

if (childOb) {

childOb.dep.depend()

}

}

return value

},

set(newVal) {

if (newVal === value) {

return

}

value = newVal

childOb = observe(newVal)

dep.notify()

return value

}
})
}

observe 函数返回值为 Observe 实例



childOb.dep.depend 执行,为 Observe 实例的 dep 添加依赖
observe 函数返回值为 Observe 实例

childOb.dep.depend 执行,为 Observe 实例的 dep 添加依赖所以在数组更新时,ob.dep 内已经收集到依赖了。整体流程整体流程下面捋一遍初始化流程和更新流程,如果你是初次看源码,不知道从哪里看起,也可以参照以下的顺序。由于源码实现比较多,下面展示的源码会稍微删减一些代码初始化流程初始化流程入口文件:
// 源码位置:/src/core/instance/index.js
import { initMixin } from './init'
import { stateMixin } from './state'
import { renderMixin } from './render'
import { eventsMixin } from './events'
import { lifecycleMixin } from './lifecycle'
import { warn } from '../util/index'

function Vue (options) {
this._init(options)
}

initMixin(Vue)
stateMixin(Vue)
eventsMixin(Vue)
lifecycleMixin(Vue)
renderMixin(Vue)

export default Vue
// 源码位置:/src/core/instance/index.js
import { initMixin } from './init'
import { stateMixin } from './state'
import { renderMixin } from './render'
import { eventsMixin } from './events'
import { lifecycleMixin } from './lifecycle'
import { warn } from '../util/index'

function Vue (options) {
this._init(options)
}

initMixin(Vue)
stateMixin(Vue)
eventsMixin(Vue)
lifecycleMixin(Vue)
renderMixin(Vue)

export default Vue_init:
// 源码位置:/src/core/instance/init.js
export function initMixin (Vue: Class) {
Vue.prototype._init = function (options?: Object) {

const vm: Component = this

// a uid

vm._uid = uid++


// merge options

if (options && options._isComponent) {

// optimize internal component instantiation

// since dynamic options merging is pretty slow, and none of the

// internal component options needs special treatment.

initInternalComponent(vm, options)

} else {

// mergeOptions 对 mixin 选项和传入的 options 选项进行合并

// 这里的 $options 可以理解为 new Vue 时传入的对象

vm.$options = mergeOptions(

resolveConstructorOptions(vm.constructor),

options || {},

vm

)

}


// expose real self

vm._self = vm

initLifecycle(vm)

initEvents(vm)

initRender(vm)

callHook(vm, 'beforeCreate')

initInjections(vm) // resolve injections before data/props

// 初始化数据

initState(vm)

initProvide(vm) // resolve provide after data/props

callHook(vm, 'created')


if (vm.$options.el) {

// 初始化渲染页面 挂载组件

vm.$mount(vm.$options.el)

}
}
}
// 源码位置:/src/core/instance/init.js
export function initMixin (Vue: Class) {
Vue.prototype._init = function (options?: Object) {

const vm: Component = this

// a uid

vm._uid = uid++


// merge options

if (options && options._isComponent) {

// optimize internal component instantiation

// since dynamic options merging is pretty slow, and none of the

// internal component options needs special treatment.

initInternalComponent(vm, options)

} else {

// mergeOptions 对 mixin 选项和传入的 options 选项进行合并

// 这里的 $options 可以理解为 new Vue 时传入的对象

vm.$options = mergeOptions(

resolveConstructorOptions(vm.constructor),

options || {},

vm

)

}


// expose real self

vm._self = vm

initLifecycle(vm)

initEvents(vm)

initRender(vm)

callHook(vm, 'beforeCreate')

initInjections(vm) // resolve injections before data/props

// 初始化数据

initState(vm)

initProvide(vm) // resolve provide after data/props

callHook(vm, 'created')


if (vm.$options.el) {

// 初始化渲染页面 挂载组件

vm.$mount(vm.$options.el)

}
}
}上面主要关注两个函数,initState 初始化数据,vm.$mount(vm.$options.el) 初始化渲染页面。先进入 initState:
// 源码位置:/src/core/instance/state.js
export function initState (vm: Component) {
vm._watchers = []
const opts = vm.$options
if (opts.props) initProps(vm, opts.props)
if (opts.methods) initMethods(vm, opts.methods)
if (opts.data) {

// data 初始化

initData(vm)
} else {

observe(vm._data = {}, true /* asRootData */)
}
if (opts.computed) initComputed(vm, opts.computed)
if (opts.watch && opts.watch !== nativeWatch) {

initWatch(vm, opts.watch)
}
}

function initData (vm: Component) {
let data = vm.$options.data
// data 为函数时,执行 data 函数,取出返回值
data = vm._data = typeof data === 'function'

? getData(data, vm)

: data || {}
// proxy data on instance
const keys = Object.keys(data)
const props = vm.$options.props
const methods = vm.$options.methods
let i = keys.length
while (i--) {

const key = keys[i]

if (props && hasOwn(props, key)) {

process.env.NODE_ENV !== 'production' && warn(

`The data property "${key}" is already declared as a prop. ` +

`Use prop default value instead.`,

vm

)

} else if (!isReserved(key)) {

proxy(vm, `_data`, key)

}
}
// observe data
// 这里就开始走观测数据的逻辑了
observe(data, true /* asRootData */)
}
// 源码位置:/src/core/instance/state.js
export function initState (vm: Component) {
vm._watchers = []
const opts = vm.$options
if (opts.props) initProps(vm, opts.props)
if (opts.methods) initMethods(vm, opts.methods)
if (opts.data) {

// data 初始化

initData(vm)
} else {

observe(vm._data = {}, true /* asRootData */)
}
if (opts.computed) initComputed(vm, opts.computed)
if (opts.watch && opts.watch !== nativeWatch) {

initWatch(vm, opts.watch)
}
}

function initData (vm: Component) {
let data = vm.$options.data
// data 为函数时,执行 data 函数,取出返回值
data = vm._data = typeof data === 'function'

? getData(data, vm)

: data || {}
// proxy data on instance
const keys = Object.keys(data)
const props = vm.$options.props
const methods = vm.$options.methods
let i = keys.length
while (i--) {

const key = keys[i]

if (props && hasOwn(props, key)) {

process.env.NODE_ENV !== 'production' && warn(

`The data property "${key}" is already declared as a prop. ` +

`Use prop default value instead.`,

vm

)

} else if (!isReserved(key)) {

proxy(vm, `_data`, key)

}
}
// observe data
// 这里就开始走观测数据的逻辑了
observe(data, true /* asRootData */)
}observe 内部流程在上面已经讲过,这里再简单过一遍:

new Observe 观测数据

defineReactive 对数据进行劫持


new Observe 观测数据defineReactive 对数据进行劫持

initState 逻辑执行完毕,回到开头,接下来执行 vm.$mount(vm.$options.el) 渲染页面:$mount:
// 源码位置:/src/platforms/web/runtime/index.js
Vue.prototype.$mount = function (
el?: string | Element,
hydrating?: boolean
): Component {
el = el && inBrowser ? query(el) : undefined
return mountComponent(this, el, hydrating)
}
// 源码位置:/src/platforms/web/runtime/index.js
Vue.prototype.$mount = function (
el?: string | Element,
hydrating?: boolean
): Component {
el = el && inBrowser ? query(el) : undefined
return mountComponent(this, el, hydrating)
}mountComponent:
// 源码位置:/src/core/instance/lifecycle.js
export function mountComponent (
vm: Component,
el: ?Element,
hydrating?: boolean
): Component {
vm.$el = el
callHook(vm, 'beforeMount')

let updateComponent
/* istanbul ignore if */
if (process.env.NODE_ENV !== 'production' && config.performance && mark) {

updateComponent = () => {

const name = vm._name

const id = vm._uid

const startTag = `vue-perf-start:${id}`

const endTag = `vue-perf-end:${id}`


mark(startTag)

const vnode = vm._render()

mark(endTag)

measure(`vue ${name} render`, startTag, endTag)


mark(startTag)

vm._update(vnode, hydrating)

mark(endTag)

measure(`vue ${name} patch`, startTag, endTag)

}
} else {

// 数据改变时 会调用此方法

updateComponent = () => {

// vm._render() 返回 vnode,这里面会就对 data 数据进行取值

// vm._update 将 vnode 转为真实dom,渲染到页面上

vm._update(vm._render(), hydrating)

}
}

// 执行 Watcher,这个就是上面所说的渲染wacther
new Watcher(vm, updateComponent, noop, {

before () {

if (vm._isMounted && !vm._isDestroyed) {

callHook(vm, 'beforeUpdate')

}

}
}, true /* isRenderWatcher */)
hydrating = false

// manually mounted instance, call mounted on self
// mounted is called for render-created child components in its inserted hook
if (vm.$vnode == null) {

vm._isMounted = true

callHook(vm, 'mounted')
}
return vm
}
// 源码位置:/src/core/instance/lifecycle.js
export function mountComponent (
vm: Component,
el: ?Element,
hydrating?: boolean
): Component {
vm.$el = el
callHook(vm, 'beforeMount')

let updateComponent
/* istanbul ignore if */
if (process.env.NODE_ENV !== 'production' && config.performance && mark) {

updateComponent = () => {

const name = vm._name

const id = vm._uid

const startTag = `vue-perf-start:${id}`

const endTag = `vue-perf-end:${id}`


mark(startTag)

const vnode = vm._render()

mark(endTag)

measure(`vue ${name} render`, startTag, endTag)


mark(startTag)

vm._update(vnode, hydrating)

mark(endTag)

measure(`vue ${name} patch`, startTag, endTag)

}
} else {

// 数据改变时 会调用此方法

updateComponent = () => {

// vm._render() 返回 vnode,这里面会就对 data 数据进行取值

// vm._update 将 vnode 转为真实dom,渲染到页面上

vm._update(vm._render(), hydrating)

}
}

// 执行 Watcher,这个就是上面所说的渲染wacther
new Watcher(vm, updateComponent, noop, {

before () {

if (vm._isMounted && !vm._isDestroyed) {

callHook(vm, 'beforeUpdate')

}

}
}, true /* isRenderWatcher */)
hydrating = false

// manually mounted instance, call mounted on self
// mounted is called for render-created child components in its inserted hook
if (vm.$vnode == null) {

vm._isMounted = true

callHook(vm, 'mounted')
}
return vm
}Watcher:
// 源码位置:/src/core/observer/watcher.js
let uid = 0

export default class Watcher {
constructor(vm, exprOrFn, cb, options){

this.id = ++id

this.vm = vm

this.cb = cb

this.options = options

// exprOrFn 就是上面传入的 updateComponent

this.getter = exprOrFn


this.deps = []

this.depIds = new Set()


this.get()
}
get() {

// 1. pushTarget 将当前 watcher 记录到 Dep.target,Dep.target 是全局唯一的

pushTarget(this)

let value

const vm = this.vm

try {

// 2. 调用 this.getter 相当于会执行 vm._render 函数,对实例上的属性取值,

//由此触发 Object.defineProperty 的 get 方法,在 get 方法内进行依赖收集(dep.depend),这里依赖收集就需要用到 Dep.target

value = this.getter.call(vm, vm)

} catch (e) {

if (this.user) {

handleError(e, vm, `getter for watcher "${this.expression}"`)

} else {

throw e

}

} finally {

// "touch" every property so they are all tracked as

// dependencies for deep watching

if (this.deep) {

traverse(value)

}

// 3. popTarget 将 Dep.target 置空

popTarget()

this.cleanupDeps()

}

return value
}
}
// 源码位置:/src/core/observer/watcher.js
let uid = 0

export default class Watcher {
constructor(vm, exprOrFn, cb, options){

this.id = ++id

this.vm = vm

this.cb = cb

this.options = options

// exprOrFn 就是上面传入的 updateComponent

this.getter = exprOrFn


this.deps = []

this.depIds = new Set()


this.get()
}
get() {

// 1. pushTarget 将当前 watcher 记录到 Dep.target,Dep.target 是全局唯一的

pushTarget(this)

let value

const vm = this.vm

try {

// 2. 调用 this.getter 相当于会执行 vm._render 函数,对实例上的属性取值,

//由此触发 Object.defineProperty 的 get 方法,在 get 方法内进行依赖收集(dep.depend),这里依赖收集就需要用到 Dep.target

value = this.getter.call(vm, vm)

} catch (e) {

if (this.user) {

handleError(e, vm, `getter for watcher "${this.expression}"`)

} else {

throw e

}

} finally {

// "touch" every property so they are all tracked as

// dependencies for deep watching

if (this.deep) {

traverse(value)

}

// 3. popTarget 将 Dep.target 置空

popTarget()

this.cleanupDeps()

}

return value
}
}至此初始化流程完毕,初始化流程的主要工作是数据劫持、渲染页面和收集依赖。更新流程更新流程数据发生变化,触发 set ,执行 dep.notify
// 源码位置:/src/core/observer/dep.js
let uid = 0

/**
* A dep is an observable that can have multiple
* directives subscribing to it.
*/
export default class Dep {
static target: ?Watcher;
id: number;
subs: Array;

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 () {

// stabilize the su