Study Notes
本博主會持續(xù)更新各種前端的技術(shù)意狠,如果各位道友喜歡,可以關(guān)注泵琳、收藏摄职、點(diǎn)贊下本博主的文章誊役。
Vue.js 源碼剖析-響應(yīng)式原理
響應(yīng)式處理的入口
-
src/core/instance/init.js
- initState(vm) vm 狀態(tài)的初始化
- 初始化了 _data获列、_props、methods 等
- src/core/instance/state.js
/* @flow */
import ...;
const sharedPropertyDefinition = {...};
export function proxy(target: Object, sourceKey: string, key: string) {...}
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);
// 判斷options中是否存在data屬性
if (opts.data) {
// 如果存在data屬性蛔垢,則給其添加響應(yīng)式
initData(vm);
} else {
// 如果不存在data屬性击孩,則給給vue對象,添加_data屬性鹏漆,初始化為空對象巩梢,并給其添加響應(yīng)式
observe((vm._data = {}), true /* asRootData */);
}
if (opts.computed) initComputed(vm, opts.computed);
if (opts.watch && opts.watch !== nativeWatch) {
initWatch(vm, opts.watch);
}
}
function initProps(vm: Component, propsOptions: Object) {...}
function initData(vm: Component) {
let data = vm.$options.data;
// 判斷data是否是函數(shù)创泄,如果是一個函數(shù),則是組件中的data括蝠,將其this指向vue
// 不是函數(shù)鞠抑,則判斷是否存在。不存在則賦值為空對象
data = vm._data = typeof data === 'function' ? getData(data, vm) : data || {};
// 判斷data是否是[object Object]忌警,如果不是搁拙,則將data賦值為空對象
// 并在非production環(huán)境下,拋出警告
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
// 獲取data的所有key值
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];
// 在非production環(huán)境下法绵,判斷是否與methods里的方法存在同名箕速,存在則拋出警告
if (process.env.NODE_ENV !== 'production') {
if (methods && hasOwn(methods, key)) {
warn(
`Method "${key}" has already been defined as a data property.`,
vm,
);
}
}
// 在非production環(huán)境下,判斷是否與props里的屬性存在同名朋譬,存在則拋出警告
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)) {
// 如果key的第一個字符不是$或者_(dá)盐茎,為其設(shè)置代理
proxy(vm, `_data`, key);
}
}
// observe data
// 將data轉(zhuǎn)換為響應(yīng)式數(shù)據(jù)
// true:告訴observe函數(shù),其為根屬性data
observe(data, true /* asRootData */);
}
function getData(data: Function, vm: Component): any {...}
const computedWatcherOptions = { lazy: true };
function initComputed(vm: Component, computed: Object) {...}
export function defineComputed(
target: any,
key: string,
userDef: Object | Function,
) {...}
function createComputedGetter(key) {...}
function initMethods(vm: Component, methods: Object) {...}
function initWatch(vm: Component, watch: Object) {...}
function createWatcher(
vm: Component,
keyOrFn: string | Function,
handler: any,
options?: Object,
) {...}
export function stateMixin(Vue: Class<Component>) {...}
- observe(value, asRootData)
- 負(fù)責(zé)為每一個 Object 類型的 value 創(chuàng)建一個 observer 實(shí)例
/* @flow */
import ...;
const arrayKeys = Object.getOwnPropertyNames(arrayMethods);
export const observerState = {
shouldConvert: true,
};
export class Observer {...}
function protoAugment(target, src: Object, keys: any) {...}
/* istanbul ignore next */
function copyAugment(target: Object, src: Object, keys: Array<string>) {...}
/**
* 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.
*/
// 如果不存在觀察者實(shí)例徙赢,則創(chuàng)建觀察者實(shí)例字柠,并返回
// 存在,則直接返回現(xiàn)有的觀察者實(shí)例
export function observe(value: any, asRootData: ?boolean): Observer | void {
// 如果value不是一個對象或者value是一個虛擬dom狡赐,則不往下執(zhí)行募谎,即不需為其轉(zhuǎn)換為響應(yīng)式數(shù)據(jù)
if (!isObject(value) || value instanceof VNode) {
return;
}
let ob: Observer | void;
// 如果value中存在__ob__屬性,并且__ob__是Observer阴汇,則直接將value.__ob__賦值給ob
if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
ob = value.__ob__;
} else if (
observerState.shouldConvert &&
!isServerRendering() &&
(Array.isArray(value) || isPlainObject(value)) &&
Object.isExtensible(value) &&
!value._isVue
) {
// observerState.shouldConvert為true
// 并且當(dāng)前是瀏覽器環(huán)境
// 并且value是一個數(shù)組或者是'[object Object]'
// 并且value是可擴(kuò)展的(可以在它上面添加新的屬性)
// 并且value不是Vue實(shí)例
// 創(chuàng)建一個Observer對象数冬,并賦值給ob
ob = new Observer(value);
}
// 如果處理的是根數(shù)據(jù)并且存在ob,則ob.vmCount++
if (asRootData && ob) {
ob.vmCount++;
}
return ob;
}
export function defineReactive(
obj: Object,
key: string,
val: any,
customSetter?: ?Function,
shallow?: boolean,
) {...}
export function set(target: Array<any> | Object, key: any, val: any): any {...}
export function del(target: Array<any> | Object, key: any) {...}
function dependArray(value: Array<any>) {...}
Observer
- Observer 類
- constructor()方法搀庶,初始化數(shù)據(jù)拐纱,并改變數(shù)組當(dāng)前對象的原型屬性
- walk()方法為對象做響應(yīng)式處理
- observeArray()方法為數(shù)組做響應(yīng)式處理
export class Observer {
// 數(shù)據(jù)對象
value: any;
// 依賴對象
dep: Dep;
// 實(shí)例計(jì)數(shù)器
// 以該對象在根$data的vms數(shù)
vmCount: number; // number of vms that has this object as root $data
// 構(gòu)造函數(shù),初始化數(shù)據(jù)
constructor(value: any) {
this.value = value;
this.dep = new Dep();
this.vmCount = 0;
// 將實(shí)例掛載到觀察對象的__ob__屬性
def(value, '__ob__', this);
// 如果觀察對象是一個數(shù)組哥倔,將數(shù)組轉(zhuǎn)換為響應(yīng)式數(shù)據(jù)
if (Array.isArray(value)) {
const augment = hasProto
? protoAugment // 改變數(shù)組當(dāng)前對象的原型屬性
: copyAugment; // 改變數(shù)組當(dāng)前對象的原型屬性
// arrayKeys是對象的所有自身屬性的屬性名組成的數(shù)組
augment(value, arrayMethods, arrayKeys);
// 為數(shù)組中的每一個對象元素創(chuàng)建一個observer實(shí)例
this.observeArray(value);
} else {
// 遍歷對象中的每一個屬性秸架,轉(zhuǎn)換成getter/setter
this.walk(value);
}
}
/**
* Walk through each property 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);
// 遍歷每一個屬性,設(shè)置為響應(yīng)式數(shù)據(jù)
for (let i = 0; i < keys.length; i++) {
defineReactive(obj, keys[i], 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]);
}
}
}
對象響應(yīng)式處理
- defineReactive
- 為一個對象定義一個響應(yīng)式的屬性咆蒿,每一個屬性對應(yīng)一個 dep 對象
- 如果該屬性的值是對象东抹,繼續(xù)調(diào)用 observe
- 如果給屬性賦的新值是一個對象,繼續(xù)調(diào)用 observe
- 如果數(shù)據(jù)更新發(fā)送通知
// 在對象上定義響應(yīng)式屬性
export function defineReactive(
obj: Object,
key: string,
val: any,
customSetter?: ?Function,
shallow?: boolean,
) {
// 創(chuàng)建依賴對象實(shí)例沃测,其作用是為當(dāng)前屬性(key)收集依賴
const dep = new Dep();
// 獲取obj對象的屬性描述符
const property = Object.getOwnPropertyDescriptor(obj, key);
// 當(dāng)且僅當(dāng)該屬性的 configurable 鍵值為 true 時
// 該屬性的描述符才能夠被改變缭黔,同時該屬性也能從對應(yīng)的對象上被刪除。
// 如果存在屬性描述符蒂破,并且configurable為false馏谨,即不可被轉(zhuǎn)換為響應(yīng)式數(shù)據(jù),則不繼續(xù)往下執(zhí)行
if (property && property.configurable === false) {
return;
}
// cater for pre-defined getter/setters
// 提供預(yù)定義的存取器函數(shù)
const getter = property && property.get;
const setter = property && property.set;
// 當(dāng)shallow為false或者不存在時
// observe會判斷val是否是遞歸觀察子對象
// 并將對象屬性都轉(zhuǎn)換為getter/setter附迷,返回子觀察對象
let childOb = !shallow && observe(val);
// 當(dāng)且僅當(dāng)該屬性的 enumerable 鍵值為 true 時惧互,該屬性才會出現(xiàn)在對象的枚舉屬性中哎媚。
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter() {
// 如果存在用戶設(shè)置的getter,將getter的this指向obj喊儡,并賦值給value
// 否則直接將傳入的val賦值給value
const value = getter ? getter.call(obj) : val;
// 如果存在當(dāng)前依賴目標(biāo)拨与,即watcher對象,則建立依賴
// 在src/core/observer/index.js中的mountComponent方法艾猜,創(chuàng)建了watcher對象
// 在watcher對象的構(gòu)造函數(shù)中截珍,創(chuàng)建了Dep.target
if (Dep.target) {
// 為當(dāng)前屬性收集依賴
dep.depend();
// 如果存在子觀察對象,則建立子對象的依賴關(guān)系
if (childOb) {
// 為當(dāng)前子對象收集依賴
childOb.dep.depend();
// 如果value是數(shù)組箩朴,則特殊處理收集數(shù)組對象依賴
if (Array.isArray(value)) {
dependArray(value);
}
}
}
// 返回屬性值
return value;
},
set: function reactiveSetter(newVal) {
const value = getter ? getter.call(obj) : val;
// 如果新值等于舊值或者新值和舊值都為NaN岗喉,則不繼續(xù)往下執(zhí)行
/* eslint-disable no-self-compare */
if (newVal === value || (newVal !== newVal && value !== value)) {
return;
}
// 如果傳入customSetter參數(shù)時,并且在非production環(huán)境時炸庞,調(diào)用customSetter方法
/* eslint-enable no-self-compare */
if (process.env.NODE_ENV !== 'production' && customSetter) {
customSetter();
}
// 如果存在用戶設(shè)置的setter钱床,則將setter的this指向obj,并將newVal傳入
// 否則直接newVal賦值給val
if (setter) {
setter.call(obj, newVal);
} else {
val = newVal;
}
// 當(dāng)shallow為false或者不存在時
// observe會判斷newVal是否是是遞歸觀察子對象埠居,返回子觀察對象
childOb = !shallow && observe(newVal);
// 派發(fā)更新(發(fā)布更新通知)
dep.notify();
},
});
}
數(shù)組響應(yīng)式處理
在 observer 構(gòu)造函數(shù)中
// 如果觀察對象是一個數(shù)組查牌,將數(shù)組轉(zhuǎn)換為響應(yīng)式數(shù)據(jù)
if (Array.isArray(value)) {
const augment = hasProto
? protoAugment // 改變數(shù)組當(dāng)前對象的原型屬性
: copyAugment; // 改變數(shù)組當(dāng)前對象的原型屬性
// arrayKeys是對象的所有自身屬性的屬性名組成的數(shù)組
augment(value, arrayMethods, arrayKeys);
// 為數(shù)組中的每一個對象元素創(chuàng)建一個observer實(shí)例
this.observeArray(value);
} else {
// 遍歷對象中的每一個屬性,轉(zhuǎn)換成getter/setter
this.walk(value);
}
- 改變數(shù)組當(dāng)前對象的原型屬性
/*
* not type checking this file because flow doesn't play well with
* dynamically accessing methods on Array prototype
*/
import { def } from '../util/index';
const arrayProto = Array.prototype;
// 使用數(shù)組的原型創(chuàng)建一個新的對象
export const arrayMethods = Object.create(arrayProto);
/**
* Intercept mutating methods and emit events
*/
// 修改數(shù)組元素的方法
['push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse'].forEach(
function (method) {
// cache original method
// 緩存數(shù)組的原方法
const original = arrayProto[method];
// 調(diào)用Object.defineProperty()方法滥壕,重寫數(shù)組的方法
def(arrayMethods, method, function mutator(...args) {
// 執(zhí)行原數(shù)組的原方法纸颜,并改變其this指向
const result = original.apply(this, args);
// 獲取數(shù)組對象的__ob__屬性
const ob = this.__ob__;
// 獲取數(shù)組新增的元素
let inserted;
switch (method) {
case 'push':
case 'unshift':
inserted = args;
break;
case 'splice':
inserted = args.slice(2);
break;
}
// 如果存在新增元素,重新遍歷數(shù)組元素設(shè)置為響應(yīng)式數(shù)據(jù)
if (inserted) ob.observeArray(inserted);
// notify change
// 調(diào)用數(shù)組的ob對象發(fā)送更新通知
ob.dep.notify();
return result;
});
},
);
Dep 類
- 在 defineReactive() 的 getter 中創(chuàng)建 dep 對象绎橘,當(dāng)存在 Dep.target , 調(diào)用 dep.depend()
- dep.depend() 內(nèi)部調(diào)用 Dep.target.addDep(this)胁孙,也就是 watcher 的 addDep() 方法,它內(nèi)部最終調(diào)用 dep.addSub(this)称鳞,把訂閱者(Watcher)添加到 dep 的 subs 數(shù)組中涮较,當(dāng)數(shù)據(jù)變化的時候調(diào)用 watcher 對象的 update() 方法
- 什么時候設(shè)置的 Dep.target? 通過簡單的案例調(diào)試觀察。調(diào)用 mountComponent() 方法的時候冈止,創(chuàng)建了渲染 watcher 對象狂票,執(zhí)行 watcher 中的 get() 方法
- get() 方法內(nèi)部調(diào)用 pushTarget(this),把當(dāng)前 Dep.target = watcher熙暴,同時把當(dāng)前 watcher 入棧闺属,因?yàn)橛懈缸咏M件嵌套的時候先把父組件對應(yīng)的 watcher 入棧,再去處理子組件的 watcher周霉,子組件的處理完畢后掂器,再把父組件對應(yīng)的 watcher 出棧,繼續(xù)操作
- Dep.target 用來存放目前正在使用的 watcher诗眨。全局唯一唉匾,并且一次也只能有一個 watcher 被使用
/* @flow */
import type Watcher from './watcher';
import { remove } from '../util/index';
let uid = 0;
/**
* A dep is an observable that can have multiple
* directives subscribing to it.
*/
export default class Dep {
// 靜態(tài)屬性,Watcher對象
static target: ?Watcher;
// dep實(shí)例id
id: number;
// dep實(shí)例對應(yīng)的Watcher對象/訂閱者數(shù)組
subs: Array<Watcher>;
// 構(gòu)造函數(shù)匠楚,初始化數(shù)據(jù)
constructor() {
this.id = uid++;
this.subs = [];
}
// 添加新的訂閱者Watcher對象
addSub(sub: Watcher) {
this.subs.push(sub);
}
// 移除訂閱者Watcher對象
removeSub(sub: Watcher) {
remove(this.subs, sub);
}
// 將觀察對象和Watcher對象建立依賴
depend() {
// 如果存在target巍膘,則把dep對象添加到watcher的依賴中
if (Dep.target) {
Dep.target.addDep(this);
}
}
// 發(fā)布通知
notify() {
// stabilize the subscriber list first
// 克隆數(shù)組
const subs = this.subs.slice();
// 調(diào)用每個訂閱者的update方法實(shí)現(xiàn)更新
for (let i = 0, l = subs.length; i < l; i++) {
subs[i].update();
}
}
}
// Dep.target 用來存放目前正在使用的watcher
// 全局唯一,并且一次也只能有一個watcher被使用
// the current target watcher being evaluated.
// this is globally unique because there could be only one
// watcher being evaluated at any time.
Dep.target = null;
const targetStack = [];
// 入棧并將當(dāng)前 watcher 賦值給Dep.target
export function pushTarget(_target: Watcher) {
// 每一個組件都有一個mountComponent函數(shù)
// mountComponent函數(shù)創(chuàng)建了watcher對象
// 所以每一個組件對應(yīng)一個watcher對象
// 如果A組件嵌套B組件芋簿,當(dāng)我們渲染A組件時峡懈,發(fā)現(xiàn)存在子組件,則先渲染子組件与斤,將A組件渲染掛起
// 所以這里需要將A組件的Dep.target入棧
// 當(dāng)子組件渲染結(jié)束后肪康,將其彈出棧,并繼續(xù)執(zhí)行A組件渲染
// 總結(jié):
// 父子組件嵌套的時候先把父組件對應(yīng)的watcher入棧撩穿,再去處理子組件的watcher磷支。
// 子組件處理完畢后,再把父組件對應(yīng)的watcher出棧食寡,繼續(xù)操作
if (Dep.target) targetStack.push(Dep.target);
Dep.target = _target;
}
// 出棧操作
export function popTarget() {
Dep.target = targetStack.pop();
}
Watcher 類
- Watcher 分為三種雾狈,Computed Watcher、用戶 Watcher (偵聽器)抵皱、渲染 Watcher
- 什么時候渲染 Watcher善榛?在 src/core/instance/lifecycle.js 中的 mountComponent 方法中創(chuàng)建
- Watcher 的構(gòu)造函數(shù)初始化,處理 expOrFn (渲染 watcher 和偵聽器處理不同)
- 調(diào)用 this.get() 呻畸,它里面調(diào)用 pushTarget() 方法移盆,接下來執(zhí)行 this.getter.call(vm, vm) (對于渲染 watcher 調(diào)用 updateComponent),如果是用戶 watcher 會獲取屬性的值(觸發(fā) get 操作)
- 當(dāng)數(shù)據(jù)更新的時候伤为,dep 中調(diào)用 notify() 方法咒循,notify() 中調(diào)用 watcher 的 update() 方法
- update() 中調(diào)用 queueWatcher()
- queueWatcher() 是一個核心方法,去除重復(fù)操作绞愚,調(diào)用 flushSchedulerQueue() 刷新隊(duì)列并執(zhí)行 watcher
- flushSchedulerQueue() 中對 watcher 排序剑鞍,遍歷所有 watcher ,如果存在 before爽醋,觸發(fā)生命周期的鉤子函數(shù) beforeUpdate蚁署,執(zhí)行 watcher.run(),它內(nèi)部調(diào)用 this.get()蚂四,然后調(diào)用 this.cb() (渲染 watcher 的 cb 是 noop)
哪些數(shù)組操作可以觸發(fā)視圖更新
const vm = new Vue({
el: '#app',
data: {
arr: [2, 3, 5],
},
});
// 在瀏覽器控制臺分別執(zhí)行下面的代碼
// 會更新視圖
// vm.arr.push(8)
// 數(shù)據(jù)已改變光戈,但不會更新視圖
// vm.arr[0] = 100
// 將數(shù)組清空,也不會更新視圖
// vm.arr.length = 0
// 將數(shù)組第一位刪除遂赠,并重新賦值為100久妆,這時會更新視圖
// vm.arr.splice(0,1,100)
總結(jié)
實(shí)例方法/數(shù)據(jù)
Vue.set(target, propertyName/index, value)
-
參數(shù):
- {Object | Array} target
- {string | number} propertyName/index
- {any} value
返回值:設(shè)置的值。
-
用法:
向響應(yīng)式對象中添加一個 property跷睦,并確保這個新 property 同樣是響應(yīng)式的筷弦,且觸發(fā)視圖更新。它必須用于向響應(yīng)式對象上添加新 property,因?yàn)?Vue 無法探測普通的新增 property (比如 this.myObject.newProperty = 'hi')
注意對象不能是 Vue 實(shí)例,或者 Vue 實(shí)例的根數(shù)據(jù)對象勉盅。
- 示例
Vue.set(obj, 'foo', 'test');
vm.$set(target, propertyName/index, value)
-
參數(shù):
- {Object | Array} target
- {string | number} propertyName/index
- {any} value
返回值:設(shè)置的值譬挚。
-
用法:
這是全局 Vue.set 的別名。
示例
vm.$set(obj, 'foo', 'test');
源碼分析
-
Vue.set()
-
vm.$set()
在 src/core/instance/index.js 中的引用了在 src/core/instance/state.js 中的 stateMixin 方法梗夸,該方法定義了$set
上訴兩個方法都指向 src/core/observer/index.js 中的 set()方法
export function set(target: Array<any> | Object, key: any, val: any): any {
// 如果目標(biāo)是數(shù)組,并且key 是合法的索引
if (Array.isArray(target) && isValidArrayIndex(key)) {
// 通過Math.max返回最大值号醉,并賦值給target.length
target.length = Math.max(target.length, key);
// 通過 splice 對key位置的元素進(jìn)行替換
// splice 在 src/core/observer/array.js中進(jìn)行了響應(yīng)式的處理
target.splice(key, 1, val);
return val;
}
// 如果target中已存在key屬性反症,則直接賦值
if (hasOwn(target, key)) {
target[key] = val;
return val;
}
// 獲取 target 中的 observer 對象
const ob = (target: any).__ob__;
// 如果target是vue實(shí)例或者是$data,則直接返回
if (target._isVue || (ob && ob.vmCount)) {
process.env.NODE_ENV !== 'production' &&
warn(
'Avoid adding reactive properties to a Vue instance or its root $data ' +
'at runtime - declare it upfront in the data option.',
);
return val;
}
// 如果不存在ob畔派,那么target 不是一個響應(yīng)式對象铅碍,則直接賦值并返回
if (!ob) {
target[key] = val;
return val;
}
// 把 key 設(shè)置為響應(yīng)式屬性
defineReactive(ob.value, key, val);
// 發(fā)送更新通知
ob.dep.notify();
return val;
}
Vue.delete(target, propertyName/index)
-
參數(shù):
- {Object | Array} target
- {string | number} propertyName/index
僅在 2.2.0+ 版本中支持 Array + index 用法。
-
用法:
刪除對象的 property线椰。如果對象是響應(yīng)式的胞谈,確保刪除能觸發(fā)更新視圖。這個方法主要用于避開 Vue 不能檢測到 property 被刪除的限制士嚎,但是你應(yīng)該很少會使用它呜魄。
在 2.2.0+ 中同樣支持在數(shù)組上工作。
目標(biāo)對象不能是一個 Vue 實(shí)例或 Vue 實(shí)例的根數(shù)據(jù)對象莱衩。
vm.$delete(target, propertyName/index)
-
參數(shù):
- {Object | Array} target
- {string | number} propertyName/index
-
用法:
這是全局 Vue.delete 的別名爵嗅。
源碼分析
-
Vue.delete()
-
vm.$delete()
在 src/core/instance/index.js 中的引用了在 src/core/instance/state.js 中的 stateMixin 方法,該方法定義了$delete
上訴兩個方法都指向 src/core/observer/index.js 中的 del()方法
export function del(target: Array<any> | Object, key: any) {
// 如果目標(biāo)是數(shù)組笨蚁,并且key 是合法的索引
if (Array.isArray(target) && isValidArrayIndex(key)) {
// 通過 splice 對key位置的元素進(jìn)行刪除
// splice 在 src/core/observer/array.js中進(jìn)行了響應(yīng)式的處理
target.splice(key, 1);
return;
}
// 獲取 target 中的 observer 對象
const ob = (target: any).__ob__;
// 如果target是vue實(shí)例或者是$data睹晒,則直接返回
if (target._isVue || (ob && ob.vmCount)) {
process.env.NODE_ENV !== 'production' &&
warn(
'Avoid deleting properties on a Vue instance or its root $data ' +
'- just set it to null.',
);
return;
}
// 如果target不存在key屬性,則直接返回
if (!hasOwn(target, key)) {
return;
}
// 刪除target的key屬性
delete target[key];
// 如果不存在ob括细,那么target 不是一個響應(yīng)式對象伪很,則直接返回
if (!ob) {
return;
}
// 發(fā)送更新通知
ob.dep.notify();
}
vm.$watch(expOrFn, callback, [options])
-
功能
- 觀察 Vue 實(shí)例變化的一個表達(dá)式或計(jì)算屬性函數(shù)》艿ィ回調(diào)函數(shù)得到的參數(shù)為新值和舊值锉试。表達(dá)式只接受監(jiān)督的鍵路徑。對于更復(fù)雜的表達(dá)式览濒,用一個函數(shù)取代呆盖。
-
參數(shù)
{string | Function} expOrFn:要監(jiān)視的 $data 中的屬性,可以是表達(dá)式或函數(shù)
-
{Function | Object} callback:數(shù)據(jù)變化后執(zhí)行的函數(shù)
函數(shù):回調(diào)函數(shù)
對象:具有 handler 屬性(字符串或者函數(shù))贷笛,如果該屬性為字符串則 methods 中相應(yīng)的定義
-
{Object} [options]:可選的選項(xiàng)
{boolean} deep:布爾類型应又,深度監(jiān)聽
{boolean} immediate:布爾類型,是否立即執(zhí)行一次回調(diào)函數(shù)
返回值:{Function} unwatch
注意:在變更 (不是替換) 對象或數(shù)組時乏苦,舊值將與新值相同株扛,因?yàn)樗鼈兊囊弥赶蛲粋€對象/數(shù)組。Vue 不會保留變更之前值的副本。
示例
const vm = new Vue({
el: '#app',
data: {
a: '1',
b: '2',
msg: 'Hello Vue',
user: {
firstName: '諸葛',
lastName: '亮',
},
},
});
// expOrFn 是表達(dá)式
vm.$watch('msg', function (newVal, oldVal) {
console.log(newVal, oldVal);
});
vm.$watch('user.firstName', function (newVal, oldVal) {
console.log(newVal);
});
// expOrFn 是函數(shù)
vm.$watch(
function () {
return this.a + this.b;
},
function (newVal, oldVal) {
console.log(newVal);
},
);
// deep 是 true洞就,消耗性能
vm.$watch(
'user',
function (newVal, oldVal) {
// 此時的 newVal 是 user 對象
console.log(newVal === vm.user);
},
{
deep: true,
},
);
// immediate 是 true
vm.$watch(
'msg',
function (newVal, oldVal) {
console.log(newVal);
},
{
immediate: true,
},
);
三種類型的 Watcher 對象
沒有靜態(tài)方法盆繁,因?yàn)?$watch 方法中要使用 Vue 的實(shí)例
Watcher 分三種:計(jì)算屬性 Watcher、用戶 Watcher (偵聽器)奖磁、渲染 Watcher
創(chuàng)建順序:計(jì)算屬性 Watcher改基、用戶 Watcher (偵聽器)繁疤、渲染 Watcher
-
源碼分析
Vue.prototype.$watch = function (
expOrFn: string | Function,
cb: any,
options?: Object
): Function {
// 獲取 Vue 實(shí)例 this
const vm: Component = this
if (isPlainObject(cb)) {
// 如果 cb 是對象咖为,則執(zhí)行 createWatcher
return createWatcher(vm, expOrFn, cb, options)
}
options = options || {}
// 標(biāo)記為用戶 watcher
options.user = true
// 創(chuàng)建用戶 watcher 對象
const watcher = new Watcher(vm, expOrFn, cb, options)
if (options.immediate) {
// immediate 如果為 true,則立即執(zhí)行一次 cb 回調(diào)稠腊,把this指向vue躁染,并且當(dāng)前值傳入
cb.call(vm, watcher.value)
}
// 返回取消監(jiān)聽的方法
return function unwatchFn () {
watcher.teardown()
}
}
}
Vue.nextTick([callback, context])
參數(shù):
{Function} [callback]
{Object} [context]
-
用法:
在下次 DOM 更新循環(huán)結(jié)束之后執(zhí)行延遲回調(diào)。在修改數(shù)據(jù)之后立即使用這個方法架忌,獲取更新后的 DOM吞彤。
// 修改數(shù)據(jù)
vm.msg = 'Hello';
// DOM 還沒有更新
Vue.nextTick(function () {
// DOM 更新了
});
// 作為一個 Promise 使用 (2.1.0 起新增,詳見接下來的提示)
Vue.nextTick().then(function () {
// DOM 更新了
});
2.1.0 起新增:如果沒有提供回調(diào)且在支持 Promise 的環(huán)境中叹放,則返回一個 Promise饰恕。請注意 Vue 不自帶 Promise 的 polyfill,所以如果你的目標(biāo)瀏覽器不原生支持 Promise (IE:你們都看我干嘛)井仰,你得自己提供 polyfill埋嵌。
- 參考:異步更新隊(duì)列
vm.$nextTick([callback])
-
參數(shù):
- {Function} [callback]
-
用法:
將回調(diào)延遲到下次 DOM 更新循環(huán)之后執(zhí)行。在修改數(shù)據(jù)之后立即使用它俱恶,然后等待 DOM 更新雹嗦。它跟全局方法 Vue.nextTick 一樣,不同的是回調(diào)的 this 自動綁定到調(diào)用它的實(shí)例上合是。
2.1.0 起新增:如果沒有提供回調(diào)且在支持 Promise 的環(huán)境中了罪,則返回一個 Promise。請注意 Vue 不自帶 Promise 的 polyfill聪全,所以如果你的目標(biāo)瀏覽器不是原生支持 Promise (IE:你們都看我干嘛)泊藕,你得自行 polyfill。
示例
new Vue({
// ...
methods: {
// ...
example: function () {
// 修改數(shù)據(jù)
this.message = 'changed';
// DOM 還沒有更新
this.$nextTick(function () {
// DOM 現(xiàn)在更新了
// `this` 綁定到當(dāng)前實(shí)例
this.doSomethingElse();
});
},
},
});
- 參考:異步更新隊(duì)列
源碼分析
在 src/core/instance/index.js 中調(diào)用 src/core/instance/render.js 中的 renderMixin 方法
- 手動調(diào)用 vm.$nextTick()
- 在 Watcher 的 queueWatcher 中執(zhí)行 nextTick()
- src/core/util/env.js
export const nextTick = (function () {
const callbacks = [];
let pending = false;
let timerFunc;
function nextTickHandler() {
pending = false;
// 克隆回調(diào)函數(shù)數(shù)組
const copies = callbacks.slice(0);
// 將callbacks數(shù)組置為空數(shù)組
callbacks.length = 0;
// 遍歷執(zhí)行回調(diào)函數(shù)數(shù)組中的每一個回調(diào)函數(shù)
for (let i = 0; i < copies.length; i++) {
copies[i]();
}
}
// An asynchronous deferring mechanism.
// In pre 2.4, we used to use microtasks (Promise/MutationObserver)
// but microtasks actually has too high a priority and fires in between
// supposedly sequential events (e.g. #4521, #6690) or even between
// bubbling of the same event (#6566). Technically setImmediate should be
// the ideal choice, but it's not available everywhere; and the only polyfill
// that consistently queues the callback after all DOM events triggered in the
// same loop is by using MessageChannel.
/* istanbul ignore if */
if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) {
timerFunc = () => {
setImmediate(nextTickHandler);
};
} else if (
typeof MessageChannel !== 'undefined' &&
(isNative(MessageChannel) ||
// PhantomJS
MessageChannel.toString() === '[object MessageChannelConstructor]')
) {
const channel = new MessageChannel();
const port = channel.port2;
channel.port1.onmessage = nextTickHandler;
timerFunc = () => {
port.postMessage(1);
};
} else if (typeof Promise !== 'undefined' && isNative(Promise)) {
/* istanbul ignore next */
// use microtask in non-DOM environments, e.g. Weex
const p = Promise.resolve();
timerFunc = () => {
p.then(nextTickHandler);
};
} else {
// fallback to setTimeout
timerFunc = () => {
setTimeout(nextTickHandler, 0);
};
}
return function queueNextTick(cb?: Function, ctx?: Object) {
let _resolve;
// 把 cb 加上異常處理存入 callbacks 數(shù)組中
callbacks.push(() => {
if (cb) {
try {
// 如果存在cb
// 將cb的this指向ctx难礼,并調(diào)用 cb()
cb.call(ctx);
} catch (e) {
handleError(e, ctx, 'nextTick');
}
} else if (_resolve) {
_resolve(ctx);
}
});
if (!pending) {
pending = true;
timerFunc();
}
// $flow-disable-line
if (!cb && typeof Promise !== 'undefined') {
// 返回 promise 對象
return new Promise((resolve, reject) => {
_resolve = resolve;
});
}
};
})();