文章目录
- 1、基本原理
- [2、数据劫持 Object.defineProperty](#2、数据劫持 Object.defineProperty)
-
- [2.1 对象响应式](#2.1 对象响应式)
- [2.2 数组响应式处理](#2.2 数组响应式处理)
- [3、观察者 Watcher](#3、观察者 Watcher)
- [4、依赖 Dep](#4、依赖 Dep)
- 5、依赖收集、派发更新
1、基本原理
vue2 中响应式原理主要就是通过数据劫持,依赖收集,派发更新的方式来实现的
-
数据劫持: Vue 2使用Object.defineProperty函数对组件的data对象的属性进行劫持(或称为拦截)。当读取 data 中的属性时触发 get,当修改 data 中的属性时触发 set
-
依赖收集:当模板或者计算属性等引用了data 中的响应式数据时,Vue将这些消费者(观察者)收集起来,建立起数据与消费者之间的关联
-
派发更新:当响应式数据变化时,通过 dep 来执行 watcher 的 notify 方法进行通知更新
2、数据劫持 Object.defineProperty
Vue 2使用 Object.defineProperty 函数对组件的 data 对象的属性进行劫持
局限性:Object.defineProperty 只能劫持对象的属性,因此Vue 2无法自动侦测到对象属性的添加或删除,以及直接通过索引修改数组项的情况。Vue解决这个问题的方式是提供了全局方法如 Vue.set 和 Vue.delete,以及修改数组时应该使用的一系列方法(如push、splice等)
2.1 对象响应式
源码位置:src/core/observer/index.ts
ts
import { def } from "core/util/lang";
import { hasChanged, isArray, isPlainObject } from "src/shared/util";
import { arrayMethods } from "./array";
class Observer {
constructor(value: any) {
def(value, "__ob__", this);
if (isArray(value)) {
// 数组,需要特殊处理,进行劫持数组方法
(value as any).__proto__ = arrayMethods;
this.observeArray(value);
} else {
// 对象
const keys = Object.keys(value);
for (let i = 0; i < keys.length; i++) {
const key = keys[i];
defineReactive(value, key);
}
}
}
/**
* 将数组的每一项进行响应式处理
* @param value
*/
observeArray(value: any[]) {
for (let i = 0, l = value.length; i < l; i++) {
observe(value[i]);
}
}
}
// 数据响应式的入口函数
export function observe(value: any) {
if (isPlainObject(value) || isArray(value)) {
// 当值为对象或数组时,进行响应式处理
return new Observer(value);
}
}
// 核心:定义对象的响应式属性
function defineReactive(obj: any, key: string) {
let value = obj[key];
// 深度代理
observe(value);
Object.defineProperty(obj, key, {
get() {
console.log("获取", key);
// 这里进行依赖收集 dep.depend()
return value;
},
set(newVal) {
console.log("设置");
if (!hasChanged(value, newVal)) {
return;
}
// 新值进行响应式
observe(newVal);
value = newVal;
// 这里进行通知更新 dep.notify()
},
});
}
2.2 数组响应式处理
问题:因为 JavaScript 的限制使得 Vue 不能直接检测到数组索引和长度的变化
解决:通过劫持数组的方法,包括:
push
pop
shift
unshift
splice
sort
reverse
当你使用这些方法时,Vue 内部的实现会首先调用原生的数组方法来更新数组,然后执行额外的逻辑来通知变化,从而触发视图的更新。
源码位置:src/core/observer/array.ts
ts
import { def } from "core/util/lang";
const arrayProto = Array.prototype;
// 创建一个新对象,该新对象的原型指向 Array 的原型
export const arrayMethods = Object.create(arrayProto);
const methodsToPatch = [
"push",
"pop",
"shift",
"unshift",
"splice",
"sort",
"reverse",
];
methodsToPatch.forEach((method) => {
// 缓存原始方法
const original = arrayProto[method];
// 定义新方法
def(arrayMethods, method, function (...args) {
console.log("劫持数组", args);
const result = original.apply(this, args);
const ob = this.__ob__;
let inserted;
switch (method) {
case "push":
case "unshift":
inserted = args;
break;
case "splice":
inserted = args.slice(2);
break;
}
if (inserted) {
ob.observeArray(inserted);
}
// 通知更新
return result;
});
});
3、观察者 Watcher
Watcher(观察者)是一个关键的部分,它用于在数据变化时执行更新的操作。其主要作用是在依赖性收集阶段将自己添加到每个相关数据的Dependent(Dep)对象中,并在数据变化时接收到通知,从而触发回调函数。
主要职责:
(1)依赖收集: Watcher在初始化时会调用自己的get方法去读取数据,这会触发数据的getter函数从而进行依赖性收集。在getter函数中,当前Watcher实例会被添加到数据对应的Dep实例中。
(2)执行更新: 当数据发生变化,Dep实例调用notify方法时,Watcher实例会接收到通知,然后调用自己的update方法以触发回调
源码位置:src/core/observer/watcher.ts
ts
import { isFunction, noop } from "src/shared/util";
import { Component } from "src/types/component";
import Dep, { popTarget, pushTarget } from "./dep";
let uid = 0;
export default class Watcher {
vm: Component;
cb: Function;
getter: Function;
id: number;
constructor(vm: Component, expOrFn: Function, cb: Function) {
this.cb = cb;
this.vm = vm;
this.id = ++uid;
if (isFunction(expOrFn)) {
this.getter = expOrFn;
} else {
this.getter = noop;
}
this.get();
}
// 初次渲染
get() {
pushTarget(this); // 给 dep 添加 Watcher
this.getter(); // 执行 render 进行渲染页面,(src/core/instance/lifecycle.ts)
popTarget(); // 给 dep 取消 Watcher
}
/**
* 添加依赖项
* @param dep
*/
addDep(dep: Dep) {
dep.addSub(this);
}
/**
* 更新方法
*/
update() {
console.log("更新");
this.get();
}
}
说明:
调用:在 src/core/instance/lifecycle.ts 中执行 mountComponent 时
4、依赖 Dep
Dep(Dependency)是一个核心的类,它负责建立数据和观察者(Watcher)之间的关联(依赖),并提供接口触发它们的更新
主要职责:
(1)存储观察者: Dep实例内部维护了一个观察者(Watcher)对象的数组。在依赖收集阶段,观察者对象会被添加到Dep实例的数组中,而在派发更新阶段,Dep类则会遍历这个数组,通知所有的观察者。
(2)依赖收集: Dep类提供了addSub方法,用于在依赖收集阶段添加新的观察者。当数据的getter函数被调用时,Dep会把当前正在评估的观察者添加到自身的观察者列表中。
(3)派发更新: Dep类提供了notify方法,用于在数据发生变更时通知所有的观察者。当数据的setter函数被调用时,Dep会遍历自己的观察者列表,并调用它们的update方法
源码位置:src/core/observer/dep.ts
ts
interface DepTarget {
id: number;
addDep(dep: Dep): void;
update(): void;
}
export default class Dep {
static target?: DepTarget | null;
subs: Array<DepTarget | null>; // 观察者(Watcher)对象的数组
constructor() {
this.subs = [];
}
addSub(sub: DepTarget) {
// sub 是 Watcher 对象
this.subs.push(sub);
}
// 收集 Watcher
depend() {
if (Dep.target) {
// 这里是 Watcher 中的 addDep 方法
Dep.target.addDep(this);
}
}
// 通知更新
notify() {
const subs = this.subs.filter((s) => s) as DepTarget[];
for (let i = 0; i < subs.length; i++) {
const sub = subs[i];
// 这里就是 Watcher 中的 update 方法
sub.update();
}
}
}
Dep.target = null;
export function pushTarget(target: DepTarget) {
Dep.target = target;
}
export function popTarget() {
Dep.target = null;
}
5、依赖收集、派发更新
修改 src/core/observer/index.ts ,添加依赖收集和通知更新的逻辑处理:
ts
import { def } from "core/util/lang";
import { hasChanged, isArray, isPlainObject } from "src/shared/util";
import { arrayMethods } from "./array";
import Dep, { pushTarget } from "./dep";
class Observer {
dep: Dep;
constructor(value: any) {
this.dep = new Dep();
def(value, "__ob__", this);
if (isArray(value)) {
// 数组,需要特殊处理,进行劫持数组方法
(value as any).__proto__ = arrayMethods;
this.observeArray(value);
} else {
// 对象
const keys = Object.keys(value);
for (let i = 0; i < keys.length; i++) {
const key = keys[i];
defineReactive(value, key);
}
}
}
/**
* 将数组的每一项进行响应式处理
* @param value
*/
observeArray(value: any[]) {
for (let i = 0, l = value.length; i < l; i++) {
observe(value[i]);
}
}
}
// 数据响应式的入口函数
export function observe(value: any) {
if (isPlainObject(value) || isArray(value)) {
// 当值为对象或数组时,进行响应式处理
return new Observer(value);
}
}
// 核心:定义对象的响应式属性
function defineReactive(obj: any, key: string) {
let value = obj[key];
let dep = new Dep();
// 深度代理
let childOb = observe(value);
Object.defineProperty(obj, key, {
get() {
console.log("获取", key);
// 这里进行依赖收集 dep.depend()
if (Dep.target) {
dep.depend();
if (childOb) {
// value 是对象或者数组,childOb 才会有值
// 但是这里是针对数组的依赖收集
childOb.dep.depend();
}
}
return value;
},
set(newVal) {
console.log("设置");
if (!hasChanged(value, newVal)) {
return;
}
// 新值进行响应式
observe(newVal);
value = newVal;
// 这里进行通知更新 dep.notify()
dep.notify();
},
});
}
数组:src/core/observer/array.ts
ts
import { def } from "core/util/lang";
const arrayProto = Array.prototype;
// 创建一个新对象,该新对象的原型指向 Array 的原型
export const arrayMethods = Object.create(arrayProto);
const methodsToPatch = [
"push",
"pop",
"shift",
"unshift",
"splice",
"sort",
"reverse",
];
methodsToPatch.forEach((method) => {
// 缓存原始方法
const original = arrayProto[method];
// 定义新方法
def(arrayMethods, method, function (...args) {
console.log("劫持数组", args);
const result = original.apply(this, args);
const ob = this.__ob__;
let inserted;
switch (method) {
case "push":
case "unshift":
inserted = args;
break;
case "splice":
inserted = args.slice(2);
break;
}
if (inserted) {
ob.observeArray(inserted);
}
// 通知更新
ob.dep.notify();
return result;
});
});