原理:
通过数据劫持 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.$optionsif (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.datadata = 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 instanceconst keys = Object.keys(data)const props = vm.$options.propsconst methods = vm.$options.methodslet i = keys.lengthwhile (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 dataobserve(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 | voidif (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 $dataconstructor (value: any) {this.value = valuethis.dep = new Dep()this.vmCount = 0def(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/settersconst getter = property && property.getconst setter = property && property.setif ((!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) : valif (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 setterif (getter && !setter) returnif (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 firstconst 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// ordersubs.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.valueconst 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 时,直接 returnobj = 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博客