使用的Object.defineProperty()重新定義對象,給data的每個屬性都添加了getter和setter方法。這時候會為對象的每個屬性創(chuàng)建一個Dep實例? (依賴)。Dep實例可以訂閱和通知相關(guān)的Watcher實例。,? 這一步叫? 數(shù)據(jù)劫持? 或者 依賴收集
在數(shù)據(jù)發(fā)生更新后調(diào)用 set 時會通知發(fā)布者 notify 通知對應(yīng)的訂閱者做出數(shù)據(jù)更新,同時將新的數(shù)據(jù)根性到視圖上顯示。 這一步叫 派發(fā)更新
同時,為了解決對象屬性添加和刪除的問題,Vue提供了全局的Vue.set和Vue.delete方法,以及實例的vm.$set和vm.$delete方法。
解釋:
(
- 在創(chuàng)建 Observer 實例的同時還會創(chuàng)建 Dep 實例,用于保存依賴項。因此每個數(shù)據(jù)都有 Observer 的實例,每個 Observer 實例中又都有一個 Dep 的實例。
- 當(dāng) Vue 解析到當(dāng)解析到模板字符串 {{ }} 時中數(shù)據(jù)時,就會去創(chuàng)建 Watcher 實例,在 constructor 時會調(diào)用自身的 get 方法,該方法不僅將當(dāng)前的 Watcher 實例賦值給了 Dep.target(表示此時處于依賴收集階段),還讓這個新實例去讀取一下 {{ }} 中的數(shù)據(jù),一旦讀取,就會觸發(fā)這個數(shù)據(jù)的 getter 方法。因為此時正在進(jìn)行收集依賴,Dep.target 一定是為 true 的,于是順利地把當(dāng)前的這個 Watcher 實例記錄到了 dep 中的 subs 數(shù)組里。再然后將 Dep.target 的值重新賦值為 null,表示退出依賴收集階段
)
總結(jié):
在Vue2中,響應(yīng)式原理是通過使用Object.defineProperty方法來實現(xiàn)的。當(dāng)一個對象被傳入Vue的observe函數(shù)中時,Vue會為對象的每個屬性創(chuàng)建一個Dep實例。Dep實例可以訂閱和通知相關(guān)的Watcher實例。
當(dāng)一個屬性被訪問時,Watcher實例會將自己添加到該屬性的Dep實例的訂閱列表中。當(dāng)該屬性的值發(fā)生變化時,Dep實例會遍歷訂閱列表,通知所有相關(guān)的Watcher實例進(jìn)行更新。
然而,Vue無法檢測到對象屬性的添加和刪除。為了解決這個問題,Vue提供了全局的Vue.set方法或?qū)嵗膙m.$set方法來添加屬性,使用Vue.delete方法或?qū)嵗膙m.$delete方法來刪除屬性。對于數(shù)組的變動,Vue無法檢測到利用索引設(shè)置數(shù)組,但可以使用Vue.set方法或?qū)嵗膙m.$set方法。此外,Vue也無法檢測直接修改數(shù)組長度,但可以使用splice方法來實現(xiàn)。
總結(jié)起來,Vue2的響應(yīng)式原理通過使用Object.defineProperty方法來實現(xiàn)屬性的劫持和偵聽,同時使用Dep和Watcher實例來建立屬性和依賴之間的關(guān)系,并進(jìn)行更新通知。同時,為了解決對象屬性添加和刪除的問題,Vue提供了全局的Vue.set和Vue.delete方法,以及實例的vm.$set和vm.$delete方法。
原理:
通過數(shù)據(jù)劫持 defineProperty + 發(fā)布訂閱者模式,當(dāng) vue 實例初始化后 observer 會針對實例中的 data 中的每一個屬性進(jìn)行劫持并通過 defineProperty() 設(shè)置值后在 get() 中向發(fā)布者添加該屬性的訂閱者,
使用的Object.defineProperty()重新定義對象,給data的每個屬性都添加了getter和setter方法。
在get的時候會調(diào)用dep.depend;?
如果是數(shù)組,則調(diào)用dependArray(對數(shù)組里的每個元素進(jìn)行遞歸調(diào)用dep.depend);
在set的時候會先判斷數(shù)據(jù)是否更新,未更新不做操作,更新則observe(),且dep.notify()
1 在initState()方法里會對組件的props, methods, data,computed, watch等進(jìn)行編譯初始化=>
2 initData()會先獲取組件內(nèi)部的data數(shù)據(jù),然后判斷data里的數(shù)據(jù)和props,或者和methods里的名稱重復(fù),則拋出錯誤提示,然后就會去監(jiān)聽data,執(zhí)行observe方法=>
3? ?Observer()? =>? ?defineReactive,?dependArray =>??defineProperty()? ?=>???Observer()? ? 遞歸
使用的Object.defineProperty()重新定義對象,給data的每個屬性都添加了getter和setter方法。
在get的時候會調(diào)用dep.depend;如果是數(shù)組,則調(diào)用dependArray(對數(shù)組里的每個元素進(jìn)行遞歸調(diào)用dep.depend);
在set的時候會先判斷數(shù)據(jù)是否更新,未更新不做操作,更新則observe(),且dep.notify()
源碼解析:
不同版本的vue的源碼實現(xiàn)可能會有些不同,我這里的版本是2.6.14
首先我們要知道定義響應(yīng)式是在哪個時間段實現(xiàn)的,從源碼中我們可以看到,是在執(zhí)行beforeCreate生命周期函數(shù)之后,Created之前。也就是說,這也就是我們在beforeCreate無法拿到Data中的數(shù)據(jù)的原因。
首先我們要知道定義響應(yīng)式是在哪個時間段實現(xiàn)的,從源碼中我們可以看到,是在執(zhí)行beforeCreate生命周期函數(shù)之后,Created之前。也就是說,這也就是我們在beforeCreate無法拿到Data中的數(shù)據(jù)的原因。
第一步:?在initState方法里會對組件的props, methods, data,computed, watch等進(jìn)行編譯初始化
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()會先獲取組件內(nèi)部的data數(shù)據(jù),然后判斷data里的數(shù)據(jù)和props,或者和methods里的名稱重復(fù),則拋出錯誤提示,然后就會去監(jiān)聽data,執(zhí)行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中的數(shù)據(jù)是否是對象,然后判斷data中是否已經(jīng)有了ob(也就是Observer實例)最后判斷是否滿足監(jiān)聽的條件。才會創(chuàng)建一個新的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是不是數(shù)組。
如果是數(shù)組,會采用函數(shù)劫持的方法重寫數(shù)組的方法,先判斷數(shù)組支不支持原型鏈,支持就將當(dāng)前數(shù)組的原型指向已經(jīng)重寫了Array里的7種方法的arrayMethod,當(dāng)數(shù)組里的方法被調(diào)用時,Dep會notify通知視圖更新,然后執(zhí)行ObserveArray方法,如果數(shù)組里的數(shù)據(jù)是對象,則繼續(xù)回調(diào)observe();
如果是對象,則調(diào)用this.walk(),在walk()中,會遍歷data的屬性執(zhí)行defineReactive()定義響應(yīng)式
?
/**
* 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的時候會調(diào)用dep.depend;如果是數(shù)組,則調(diào)用dependArray(對數(shù)組里的每個元素進(jìn)行遞歸調(diào)用dep.depend);
在set的時候會先判斷數(shù)據(jù)是否更新,未更新不做操作,更新則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方法就是將當(dāng)前dep的實例添加到對應(yīng)的Watcher中,
notify方法就是通知所有收集的Wacher進(jìn)行更新,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
當(dāng)解析到模板字符串 {{ }} 時,會默認(rèn)去 new Watcher 實例。
/**
* 每一次的 new Watcher 都是獨立的,因此構(gòu)造器接收的三個參數(shù),雖然名字一樣但確實不同的數(shù)據(jù),就像是 vm.$watch() 接收的參數(shù)一樣,
* @param {*} target 需要監(jiān)視的對象,當(dāng)做修改時,他就是
* @param {*} expression 這個對象中的某個屬性,它是一個表達(dá)式 比如 obj.a.b.c
* @param {*} callback 回調(diào)函數(shù),需要執(zhí)行的操作
*/
import Dep from "./Dep";
// 這個 uid 用于對每一個的 Watcher 實例添加唯一的 id
var uid = 0
// 在這里哪一步算是調(diào)用了 get 方法???????,解析到模板的時候
export default class Watcher {
constructor(target, expression, callback) {
console.log('我是 Watcher 構(gòu)造器');
this.id = uid++;
// 模板字符串中的整個表達(dá)式
this.target = target;
// 通過拆分表達(dá)式(對象中的對象...),獲得需要 Watch 的那個數(shù)據(jù)。比如傳入的是 a.b.c.d 我們需要監(jiān)視屬性 d,就需要拆分
this.getter = parsePath(expression) // 有兩種方法供使用 parsePath 會返回一個函數(shù);如果用 reduce 方法,那么 getter 就會是一個具體的值,此時一定要修改下邊的 get 方法!!!
this.callback = callback
// 調(diào)用該方法,進(jìn)入依賴收集階段
this.value = this.get()
}
// 當(dāng)更新 dep 中的依賴項時,會調(diào)用每一個 Watcher 實例身上的 update 方法
update() {
console.log('我是Watcher實例身上的update方法');
this.run()
}
// 進(jìn)入依賴收集階段,讓全局的 Dep.target 設(shè)置為 Watcher 本身
get(){
// Webpack 在打包的時候 Dep 是全局唯一的,不管多少個JS 文件在用 dep 的時候,都是這一個文件
// 因此執(zhí)行到這里
console.log(this); // Watcher 實例
Dep.target = this;
// debugger;
const obj = this.target;
var value;
// 防止找不到,用try catch一下,只要能找,就一直找
try {
value = this.getter(obj) // 獲取需要監(jiān)視的那個值。這里因為constructor 的時候 this.get() 返回的是一個函數(shù)
} 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)
}
}
}
// 拆分表達(dá)式:
// 方法一:將 str 用 . 分割成數(shù)組 segments,然后循環(huán)數(shù)組,一層一層去讀取數(shù)據(jù),最后拿到的 obj 就是 str 中想要讀的數(shù)據(jù)
// 假設(shè) let o = {a:{b:{c:{d:55}}}},我想要取得 d 的值,經(jīng)過拆分后的 segments 數(shù)組的值為 ['a', 'b', 'c', 'd']
// 第一次循環(huán)后 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; // 當(dāng)沒有傳入 obj 時,直接 return
obj = obj[key];
}
return obj;
};
}
// 方法二 用 reduce 方法實現(xiàn)
// function parsePathReduce(str) {
// let segments = str.split(".");
// let result = segments.reduce((total, item) => {
// total = total[item]
// return total
// }, str)
// return result
// }
前置知識:
首先要了解三個最重要的對象:
Observer 對象:將 Vue 中的數(shù)據(jù)對象在初始化過程中轉(zhuǎn)換為 Observer 對象。
Watcher 對象:將模板和 Observer 對象結(jié)合在一起生成 Watcher 實例,Watcher 是訂閱者中的訂閱者。
Dep對象:Watcher 對象和 Observer 對象之間紐帶,每一個 Observe r都有一個 Dep 實例,用來存儲訂閱者 Watcher。
?
過程:
- 在生命周期的 initState 方法中將 data,prop,method,computed,watch等所有數(shù)據(jù)全部進(jìn)行數(shù)據(jù)劫持,將所有數(shù)據(jù)變?yōu)?Observer 實例,并且每個數(shù)據(jù)身上還有 Dep 實例。
- 然后在 initRender 方法中也就是模板編譯過程,遇到的指令和數(shù)據(jù)綁定都會生成 Watcher 實例,并且把這個實例存入對應(yīng)數(shù)據(jù)的 Dep 實例中的 subs 數(shù)組里。這樣每一個數(shù)據(jù)的 Dep 實例里就都存放了依賴關(guān)系。
- 當(dāng)數(shù)據(jù)變化時,數(shù)據(jù)的 setter 方法被調(diào)用,觸發(fā) dep.notify 方法,就會通知 Dep 實例依賴列表,執(zhí)行 update 方法通知 Watcher,Watcher 會執(zhí)行 run 方法去更新視圖。
- 更新視圖的過程,我猜是 Vue 接下來要進(jìn)行 diff 算法,對比新舊模板,然后重新渲染頁面。
?
缺陷:只能夠監(jiān)聽初始化實例中的 data 數(shù)據(jù),動態(tài)添加值不能響應(yīng),要使用對應(yīng)的 Vue.set()。
- Vue 是無法檢測到對象屬性的添加和刪除,但是可以使用全局 Vue.set 方法(或 vm.$set 實例方法)。
- Vue 無法檢測利用索引設(shè)置數(shù)組,但是可以使用全局 Vue.set方法(或 vm.$set 實例方法)。
- 無法檢測直接修改數(shù)組長度,但是可以使用 splice。
vue2.0重寫數(shù)組
// 數(shù)組方法重寫
let oldArrayPrototy = Array.prototype
// 使用Object.create 將數(shù)組原型上的方法放到newArrayPrototy.prototype上
let newArrayPrototy = Object.create(arrayFn)
// 需要重寫的數(shù)組方法列表
let method = [
'push',
'pop',
'shift',
'unshift',
'reverse',
'sort',
'splice'
]
method.forEach((item) => {
//newArrayPrototy[item] 就是 arr.某一個方法
newArrayPrototy[item] = function (...args) {
// 關(guān)鍵部分
let result = oldArrayPrototy[item].call(this, ...args)
// 對傳進(jìn)來的數(shù)據(jù)做一些處理
let insterted
switch (item) {
case 'push':
case 'unshift':
insterted = args
case 'splice':
insterted = args.slice(2)
default:
break
}
return result
}
})
Vue2.0響應(yīng)式原理源碼解析_瑪已的博客-CSDN博客文章來源:http://www.zghlxwxcb.cn/news/detail-681132.html
Vue2的響應(yīng)式原理_vue2響應(yīng)式原理_高等數(shù)學(xué)真簡單的博客-CSDN博客文章來源地址http://www.zghlxwxcb.cn/news/detail-681132.html
到了這里,關(guān)于Vue2.0 的響應(yīng)式原理 私的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!