原理:
通过数据劫持 defineProperty + 发布订阅者模式,当 vue 实例初始化后 observer 会针对实例中的 data 中的每一个属性进行劫持并通过 defineProperty() 设置值后在 get() 中向发布者添加该属性的订阅者,
使用的Object.defineProperty()重新定义对象,给data的每个属性都添加了getter和setter方法。
在get的时候会调用dep.depend;
如果是数组,则调用dependArray(对数组里的每个元素进行递归调用dep.depend);
在set的时候会先判断数据是否更新,未更新不做操作,更新则observe(),且dep.notify()
1 在initState()方法里会对组件的props, methods, data,computed, watch等进行编译初始化=>
2 initData()会先获取组件内部的data数据,然后判断data里的数据和props,或者和methods里的名称重复,则抛出错误提示,然后就会去监听data,执行observe方法=>
3 Observer() => defineReactive, dependArray => defineProperty() => Observer() 递归
使用的Object.defineProperty()重新定义对象,给data的每个属性都添加了getter和setter方法。
在get的时候会调用dep.depend;如果是数组,则调用dependArray(对数组里的每个元素进行递归调用dep.depend);
在set的时候会先判断数据是否更新,未更新不做操作,更新则observe(),且dep.notify()
源码解析:
不同版本的vue的源码实现可能会有些不同,我这里的版本是2.6.14
首先我们要知道定义响应式是在哪个时间段实现的,从源码中我们可以看到,是在执行beforeCreate生命周期函数之后,Created之前。也就是说,这也就是我们在beforeCreate无法拿到Data中的数据的原因。
首先我们要知道定义响应式是在哪个时间段实现的,从源码中我们可以看到,是在执行beforeCreate生命周期函数之后,Created之前。也就是说,这也就是我们在beforeCreate无法拿到Data中的数据的原因。
第一步: 在initState方法里会对组件的props, methods, data,computed, watch等进行编译初始化
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) {
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)
}
}
第二步: initData()会先获取组件内部的data数据,然后判断data里的数据和props,或者和methods里的名称重复,则抛出错误提示,然后就会去监听data,执行observe方法
function initData (vm: Component) {
let data = vm.$options.data
data = vm._data = typeof data === 'function'
? getData(data, vm)
: data || {}
if (!isPlainObject(data)) {
data = {}
process.env.NODE_ENV !== 'production' && warn(
'data functions should return an object:\n' +
'https://vuejs.org/v2/guide/components.html#data-Must-Be-a-Function',
vm
)
}
// 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 (process.env.NODE_ENV !== 'production') {
if (methods && hasOwn(methods, key)) {
warn(
`Method "${key}" has already been defined as a data property.`,
vm
)
}
}
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) //将data上的属性代理到vm实例上。
}
}
// observe data
observe(data, true /* asRootData */)
}
第三步:在这里observe()中,会先判断data中的数据是否是对象,然后判断data中是否已经有了ob(也就是Observer实例)最后判断是否满足监听的条件。才会创建一个新的Observer对象
/**
* Attempt to create an observer instance for a value,
* returns the new observer if successfully observed,
* or the existing observer if the value already has one.
*/
export function observe (value: any, asRootData: ?boolean): Observer | void {
if (!isObject(value) || value instanceof VNode) {
return
}
let ob: Observer | void
if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
ob = value.__ob__
} else if (
shouldObserve &&
!isServerRendering() &&
(Array.isArray(value) || isPlainObject(value)) &&
Object.isExtensible(value) &&
!value._isVue
) {
ob = new Observer(value)
}
if (asRootData && ob) {
ob.vmCount++
}
return ob
}
第四步:
每一个observer实例都有自己的一个Dep, 在new Oberver后,会判断传入的value也就是vm.data是不是数组。
如果是数组,会采用函数劫持的方法重写数组的方法,先判断数组支不支持原型链,支持就将当前数组的原型指向已经重写了Array里的7种方法的arrayMethod,当数组里的方法被调用时,Dep会notify通知视图更新,然后执行ObserveArray方法,如果数组里的数据是对象,则继续回调observe();
如果是对象,则调用this.walk(),在walk()中,会遍历data的属性执行defineReactive()定义响应式
/**
* Observer class that is attached to each observed
* object. Once attached, the observer converts the target
* object's property keys into getter/setters that
* collect dependencies and dispatch updates.
*/
export class Observer {
value: any;
dep: Dep;
vmCount: number; // number of vms that have this object as root $data
constructor (value: any) {
this.value = value
this.dep = new Dep()
this.vmCount = 0
def(value, '__ob__', this)
if (Array.isArray(value)) {
if (hasProto) {
protoAugment(value, arrayMethods)
} else {
copyAugment(value, arrayMethods, arrayKeys)
}
this.observeArray(value)
} else {
this.walk(value)
}
}
/**
* Walk through all properties and convert them into
* getter/setters. This method should only be called when
* value type is Object.
*/
walk (obj: Object) {
const keys = Object.keys(obj)
for (let i = 0; i < keys.length; i++) {
defineReactive(obj, keys[i])
}
}
/**
* Observe a list of Array items.
*/
observeArray (items: Array<any>) {
for (let i = 0, l = items.length; i < l; i++) {
observe(items[i])
}
}
}
第五层:
使用的Object.defineProperty()重新定义对象,给data的每个属性都添加了getter和setter方法。
在get的时候会调用dep.depend;如果是数组,则调用dependArray(对数组里的每个元素进行递归调用dep.depend);
在set的时候会先判断数据是否更新,未更新不做操作,更新则observe(),且dep.notify()
以下是Dep的代码,我们可以将Dep看作一个观察者。
/**
* Define a reactive property on an Object.
*/
export function defineReactive (
obj: Object,
key: string,
val: any,
customSetter?: ?Function,
shallow?: boolean
) {
const dep = new Dep()
const property = Object.getOwnPropertyDescriptor(obj, key)
if (property && property.configurable === false) {
return
}
// cater for pre-defined getter/setters
const getter = property && property.get
const setter = property && property.set
if ((!getter || setter) && arguments.length === 2) {
val = obj[key]
}
let childOb = !shallow && observe(val)
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter () {
const value = getter ? getter.call(obj) : val
if (Dep.target) {
dep.depend()
if (childOb) {
childOb.dep.depend()
if (Array.isArray(value)) {
dependArray(value)
}
}
}
return value
},
set: function reactiveSetter (newVal) {
const value = getter ? getter.call(obj) : val
/* eslint-disable no-self-compare */
if (newVal === value || (newVal !== newVal && value !== value)) {
return
}
/* eslint-enable no-self-compare */
if (process.env.NODE_ENV !== 'production' && customSetter) {
customSetter()
}
// #7981: for accessor properties without setter
if (getter && !setter) return
if (setter) {
setter.call(obj, newVal)
} else {
val = newVal
}
childOb = !shallow && observe(newVal)
dep.notify()
}
})
}
第六层 解释 第五层:
depend方法就是将当前dep的实例添加到对应的Watcher中,
notify方法就是通知所有收集的Wacher进行更新,subs[i].update()
/* @flow */
import type Watcher from './watcher'
import { remove } from '../util/index'
import config from '../config'
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<Watcher>;
constructor () {
this.id = uid++
this.subs = [] //存储所有订阅的Watcher
}
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 subscriber list first
const subs = this.subs.slice()
if (process.env.NODE_ENV !== 'production' && !config.async) {
// subs aren't sorted in scheduler if not running async
// we need to sort them now to make sure they fire in correct
// order
subs.sort((a, b) => a.id - b.id)
}
for (let i = 0, l = subs.length; i < l; i++) {
subs[i].update()
}
}
}
// The current target watcher being evaluated.
// This is globally unique because only one watcher
// can be evaluated at a time.
Dep.target = null
const targetStack = []
export function pushTarget (target: ?Watcher) {
targetStack.push(target)
Dep.target = target
}
export function popTarget () {
targetStack.pop()
Dep.target = targetStack[targetStack.length - 1]
}
第七层.Watcher.js
当解析到模板字符串 {{ }} 时,会默认去 new Watcher 实例。
/**
* 每一次的 new Watcher 都是独立的,因此构造器接收的三个参数,虽然名字一样但确实不同的数据,就像是 vm.$watch() 接收的参数一样,
* @param {*} target 需要监视的对象,当做修改时,他就是
* @param {*} expression 这个对象中的某个属性,它是一个表达式 比如 obj.a.b.c
* @param {*} callback 回调函数,需要执行的操作
*/
import Dep from "./Dep";
// 这个 uid 用于对每一个的 Watcher 实例添加唯一的 id
var uid = 0
// 在这里哪一步算是调用了 get 方法???????,解析到模板的时候
export default class Watcher {
constructor(target, expression, callback) {
console.log('我是 Watcher 构造器');
this.id = uid++;
// 模板字符串中的整个表达式
this.target = target;
// 通过拆分表达式(对象中的对象...),获得需要 Watch 的那个数据。比如传入的是 a.b.c.d 我们需要监视属性 d,就需要拆分
this.getter = parsePath(expression) // 有两种方法供使用 parsePath 会返回一个函数;如果用 reduce 方法,那么 getter 就会是一个具体的值,此时一定要修改下边的 get 方法!!!
this.callback = callback
// 调用该方法,进入依赖收集阶段
this.value = this.get()
}
// 当更新 dep 中的依赖项时,会调用每一个 Watcher 实例身上的 update 方法
update() {
console.log('我是Watcher实例身上的update方法');
this.run()
}
// 进入依赖收集阶段,让全局的 Dep.target 设置为 Watcher 本身
get(){
// Webpack 在打包的时候 Dep 是全局唯一的,不管多少个JS 文件在用 dep 的时候,都是这一个文件
// 因此执行到这里
console.log(this); // Watcher 实例
Dep.target = this;
// debugger;
const obj = this.target;
var value;
// 防止找不到,用try catch一下,只要能找,就一直找
try {
value = this.getter(obj) // 获取需要监视的那个值。这里因为constructor 的时候 this.get() 返回的是一个函数
} finally {
Dep.target = null // 清空全局 target 的指向,同时也表示退出依赖收集阶段
}
return value
}
// 其实可以直接 getAndInvoke,但是 Vue 源码时这样写的
run(){
this.getAndInvoke(this.callback)
}
//
getAndInvoke(callback){
// 获取到修改后的新值 旧值是 this.value
const value = this.get()
if(value !== this.value || typeof value == 'object'){
const oldValue = this.value;
this.value = value;
callback.call(this.target, value, oldValue)
}
}
}
// 拆分表达式:
// 方法一:将 str 用 . 分割成数组 segments,然后循环数组,一层一层去读取数据,最后拿到的 obj 就是 str 中想要读的数据
// 假设 let o = {a:{b:{c:{d:55}}}},我想要取得 d 的值,经过拆分后的 segments 数组的值为 ['a', 'b', 'c', 'd']
// 第一次循环后 obj = {b:{c:{d:55}}}, 第二次 obj = {c:{d:55}}, 第三次 obj = {d:55}, 第四次 obj = 55
function parsePath(str) {
let segments = str.split(".");
return function (obj) {
for (let key of segments) {
if (!obj) return; // 当没有传入 obj 时,直接 return
obj = obj[key];
}
return obj;
};
}
// 方法二 用 reduce 方法实现
// function parsePathReduce(str) {
// let segments = str.split(".");
// let result = segments.reduce((total, item) => {
// total = total[item]
// return total
// }, str)
// return result
// }
前置知识:
首先要了解三个最重要的对象:
Observer 对象:将 Vue 中的数据对象在初始化过程中转换为 Observer 对象。
Watcher 对象:将模板和 Observer 对象结合在一起生成 Watcher 实例,Watcher 是订阅者中的订阅者。
Dep对象:Watcher 对象和 Observer 对象之间纽带,每一个 Observe r都有一个 Dep 实例,用来存储订阅者 Watcher。
过程:
- 在生命周期的 initState 方法中将 data,prop,method,computed,watch等所有数据全部进行数据劫持,将所有数据变为 Observer 实例,并且每个数据身上还有 Dep 实例。
- 然后在 initRender 方法中也就是模板编译过程,遇到的指令和数据绑定都会生成 Watcher 实例,并且把这个实例存入对应数据的 Dep 实例中的 subs 数组里。这样每一个数据的 Dep 实例里就都存放了依赖关系。
- 当数据变化时,数据的 setter 方法被调用,触发 dep.notify 方法,就会通知 Dep 实例依赖列表,执行 update 方法通知 Watcher,Watcher 会执行 run 方法去更新视图。
- 更新视图的过程,我猜是 Vue 接下来要进行 diff 算法,对比新旧模板,然后重新渲染页面。
缺陷:只能够监听初始化实例中的 data 数据,动态添加值不能响应,要使用对应的 Vue.set()。
- Vue 是无法检测到对象属性的添加和删除,但是可以使用全局 Vue.set 方法(或 vm.$set 实例方法)。
- Vue 无法检测利用索引设置数组,但是可以使用全局 Vue.set方法(或 vm.$set 实例方法)。
- 无法检测直接修改数组长度,但是可以使用 splice。
Vue2.0响应式原理源码解析_玛已的博客-CSDN博客
Vue2的响应式原理_vue2响应式原理_高等数学真简单的博客-CSDN博客