使用的Object.defineProperty()重新定义对象,给data的每个属性都添加了getter和setter方法。这时候会为对象的每个属性创建一个Dep实例 (依赖)。Dep实例可以订阅和通知相关的Watcher实例。, 这一步叫 数据劫持 或者 依赖收集
在数据发生更新后调用 set 时会通知发布者 notify 通知对应的订阅者做出数据更新,同时将新的数据根性到视图上显示。 这一步叫 派发更新
同时,为了解决对象属性添加和删除的问题,Vue提供了全局的Vue.set和Vue.delete方法,以及实例的vm.$set和vm.$delete方法。
解释:
(
- 在创建 Observer 实例的同时还会创建 Dep 实例,用于保存依赖项。因此每个数据都有 Observer 的实例,每个 Observer 实例中又都有一个 Dep 的实例。
- 当 Vue 解析到当解析到模板字符串 {{ }} 时中数据时,就会去创建 Watcher 实例,在 constructor 时会调用自身的 get 方法,该方法不仅将当前的 Watcher 实例赋值给了 Dep.target(表示此时处于依赖收集阶段),还让这个新实例去读取一下 {{ }} 中的数据,一旦读取,就会触发这个数据的 getter 方法。因为此时正在进行收集依赖,Dep.target 一定是为 true 的,于是顺利地把当前的这个 Watcher 实例记录到了 dep 中的 subs 数组里。再然后将 Dep.target 的值重新赋值为 null,表示退出依赖收集阶段
)
总结:
在Vue2中,响应式原理是通过使用Object.defineProperty方法来实现的。当一个对象被传入Vue的observe函数中时,Vue会为对象的每个属性创建一个Dep实例。Dep实例可以订阅和通知相关的Watcher实例。
当一个属性被访问时,Watcher实例会将自己添加到该属性的Dep实例的订阅列表中。当该属性的值发生变化时,Dep实例会遍历订阅列表,通知所有相关的Watcher实例进行更新。
然而,Vue无法检测到对象属性的添加和删除。为了解决这个问题,Vue提供了全局的Vue.set方法或实例的vm.$set方法来添加属性,使用Vue.delete方法或实例的vm.$delete方法来删除属性。对于数组的变动,Vue无法检测到利用索引设置数组,但可以使用Vue.set方法或实例的vm.$set方法。此外,Vue也无法检测直接修改数组长度,但可以使用splice方法来实现。
总结起来,Vue2的响应式原理通过使用Object.defineProperty方法来实现属性的劫持和侦听,同时使用Dep和Watcher实例来建立属性和依赖之间的关系,并进行更新通知。同时,为了解决对象属性添加和删除的问题,Vue提供了全局的Vue.set和Vue.delete方法,以及实例的vm.$set和vm.$delete方法。
原理:
通过数据劫持 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重写数组
// 数组方法重写
let oldArrayPrototy = Array.prototype
// 使用Object.create 将数组原型上的方法放到newArrayPrototy.prototype上
let newArrayPrototy = Object.create(arrayFn)
// 需要重写的数组方法列表
let method = [
'push',
'pop',
'shift',
'unshift',
'reverse',
'sort',
'splice'
]
method.forEach((item) => {
//newArrayPrototy[item] 就是 arr.某一个方法
newArrayPrototy[item] = function (...args) {
// 关键部分
let result = oldArrayPrototy[item].call(this, ...args)
// 对传进来的数据做一些处理
let insterted
switch (item) {
case 'push':
case 'unshift':
insterted = args
case 'splice':
insterted = args.slice(2)
default:
break
}
return result
}
})
Vue2.0响应式原理源码解析_玛已的博客-CSDN博客文章来源:https://www.toymoban.com/news/detail-681132.html
Vue2的响应式原理_vue2响应式原理_高等数学真简单的博客-CSDN博客文章来源地址https://www.toymoban.com/news/detail-681132.html
到了这里,关于Vue2.0 的响应式原理 私的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!