源码:点击跳转
认识defineReactive 数据->响应式
let aa = { a: 1 };
// defineReactive 形成闭包环境
function defineReactive(data, key, value) {
// value 闭包
Object.defineProperty(data, key, {
// value: 2,
get() {
console.log("访问了2");
return value;
},
set(newValue) {
console.log("设置新值");
value = newValue;
}
})
}
// 在这里对当前的对象属性 进行一个初始化 将其设置为响历式数据
defineReactive(aa, "b", 2)
console.log(aa.b); //打印: 访问了2
这样的话, 只能检测到对象中的第一层属性. 下面继续
observe (obj) 将obj所有属性彻底变为响应式
这里设计到observe函数 Observer类 defineReactive函数
手绘关系图:

observe函数
import Observer from "./Observer";
/**
* 将对象所有属性变为响应式
* @param {object} obj 要监测的对象
* @returns {void}
*/
export default function observe(obj) {
// 如果obj不是对象 什么都不做
if (typeof obj !== 'object' || obj === null) {
return;
}
let ob;
// obj身上有没有__ob__属性(observer类的实例)
// 有的话就赋值给ob
if (typeof obj.__ob__ !== 'undefined') {
ob = obj.__ob__;
}
// 没有new一个Observer实例 赋值给ob
else {
ob = new Observer(obj);
}
return ob;
}
在这里, 首先检查对象有没有__ob__属性, 第一次肯定是没有了,
直接new一个Observer实例,赋值给ob返回
Observer实例是什么 ?做了什么?
在里面将对象每一级都转换为响应式(通过defineReactive)
在里面给当前对象通过Object.defineProperty 将__ob__属性添加到当前属性上, (def函数) , 然后调用walk函数,将当前属性的每一个子级属性传入defineReactive()
Observer类
import { def } from "./utils"
import defineReactive from "./defineReactive"
/**
* 将一个正常的obj 转换为每个层级的属性都是响应式的obj
*/
export default class Observer {
/**
* 将对象传进来,返回一个每个层级都被设置setter和getter的对象
* @param {object} value 要劫持属性的对象
*/
constructor(value) {
console.log("传进来的对象是:\n", value);
// 给传进来的value添加__ob__属性
// 构造函数中的this --> 是实例
def(value, '__ob__', this, false);
// 给实例添加了__ob__属性, 值是this(new的结果)
this.walk(value)
}
/**
* 遍历属性中的obj
* @param {object} value 进行遍历的obj
*/
walk(value) {
for (let k in value) {
defineReactive(value, k);
}
}
}
defineReactive函数
import observe from "./observe";
// defineReactive 形成闭包环境
/**
* 将传进来的obj上的第一层属性, 添加getter和setter
* @author 李哈哈 <lwb010522@126.com>
* @param {Object} data 要变为响应式的对象
* @param {string} key 对象的属性
* @param {any|undefined} value 属性的值(不传即为key值)
* @returns {void}
*/
export default function defineReactive(data, key, value) {
// 如果只传进来了2个参数 说明是子属性
if (arguments.length === 2) {
value = data[key];
}
// 因为在Observer的walk中 会传进来obj的后代属性 所以这些后代属性也要监听
let childObj = observe(value);
// value 闭包
Object.defineProperty(data, key, {
get() {
console.log("访问了", value);
return value;
},
set(newValue) {
console.log("设置新值", newValue);
value = newValue;
// 设置新值的时候 还要再监听 因为设置新值也可能被设置为了对象
childObj = observe(value);
}
})
}
数组的响应式处理


Object.setPrototypeOf(o,ArrayMethods)
定义某个对象的原型是ArrayMethods
就等于 : o.__proto__=ArrayMethods
对象属性是数组
专门创建一个对象, 将数组的prototype指向这个新对象
对这个新对象的处理如下:
import { def } from './utils'
// 得到Array的prototype
const arrayPrototype = Array.prototype;
// 然后以Array的prototype为原型 创建一个对象arrayMethods 然后数组的原型改到这个创建的对象上
// 这个对象要暴露出去 在Observer中 用来将对象的原型指向此对象
export const arrayMethods = Object.create(arrayPrototype);
const methodsNeedChange = [
"push",
"pop",
"shift",
"unshift",
"splice",
"sort",
"resolve"
]
// 遍历 重写方法
methodsNeedChange.forEach(method => {
// 先备份原来的方法
const original = arrayMethods[method];
// 再重写方法
// 给这个对象 通过definePrototype添加属性
// 添加的属性为上面那7个方法
def(arrayMethods, method, function () {
// 这里确保方法执行有原本的功能
original.apply(this, arguments);
}, false)
})
此时的Observer类
constructor(obj) {
console.log("传进来的对象是:\n", obj);
// 给传进来的obj添加__ob__属性
// 构造函数中的this --> 是实例
def(obj, '__ob__', this, false);
// 给实例添加了__ob__属性, 值是this(new的结果)
if (Array.isArray(obj)) {
// 属性是数组,需要单独处理 将数组原型 指向自己定义的对象arrayMethods
Object.setPrototypeOf(obj, arrayMethods);
console.log("数组原型已经被修改为了:", arrayMethods);
} else {
this.walk(obj)
}
}
到此 实现通过自定义的数组的原型, 实现了调用数组7个方法, 走的是自己定义的原型中的方法.
但是,新增元素依然不是响应式
对数组进一步处理
数组中的3个方法 push unshift splice 是插入数据, 所以插入的数据也有可能是数组, 所以,这3个方法,要单独处理一下,
其中,push和unshift一样,都是直接插入, 直接整合,得到插入的数据, 传入observe()就好.
而splice不一样—> splice(开始位置, 删除长度, 插入的值),要得到arguments中的slice(2) 序号为2的参数 才是插入的值, 将得到的插入值传入observe()
此时的数组处理方法
import { def } from './utils'
// 得到Array的prototype
const arrayPrototype = Array.prototype;
// 然后以Array的prototype为原型 创建一个对象arrayMethods 然后数组的原型改到这个创建的对象上
// 这个对象要暴露出去 在Observer中 用来将对象的原型指向此对象
export const arrayMethods = Object.create(arrayPrototype);
const methodsNeedChange = [
"push",
"pop",
"shift",
"unshift",
"splice",
"sort",
"reverse"
]
// 遍历 重写方法
methodsNeedChange.forEach(method => {
// 先备份原来的方法
const original = arrayMethods[method];
// 再重写方法
// 给这个对象 通过definePrototype添加属性
// 添加的属性为上面那7个方法
def(arrayMethods, method, function () {
// 把数组上的ob(Observer实例)拿出来
const ob = this.__ob__;
// 因为数组方法中有3个方法可以插入新项 要确保插入的新项也要被监测到
let inserted = [];
switch (method) {
case "push":
inserted = arguments;
console.log("监测到push方法插入元素~");
break;
case "unshift":
inserted = arguments;
console.log("监测到unshift方法插入元素~");
break;
case "splice":
// splice(下标,数量,插入值)
inserted = Array.from(arguments).slice(2);
console.log("监测到splice方法插入元素~");
break;
case "pop":
console.log("监测到pop方法移除元素~");
break;
case "shift":
console.log("监测到shift方法移除元素~");
break;
case "sort":
console.log("监测到sort方法排序元素~");
break;
case "reverse":
console.log("监测到reverse方法反转元素~");
break;
}
// 判断有没有要插入的新值 让新项也变为响应的
if (inserted) {
ob.observerArray(inserted);
}
// 这里确保方法执行有原本的功能
let res = original.apply(this, arguments);
console.log(res);
return res;
}, false)
})
此时的Observer类
增加了对数组元素的处理
import { def } from "./utils"
import defineReactive from "./defineReactive"
import { arrayMethods } from "./array"
import observe from "./observe";
/**
* 将一个正常的obj 转换为每个层级的属性都是响应式的obj
*/
export default class Observer {
/**
* 将对象传进来,返回一个每个层级都被设置setter和getter的对象
* @param {object} obj 要劫持属性的对象
*/
constructor(obj) {
console.log("传进来的对象是:\n", obj);
// 给传进来的obj添加__ob__属性
// 构造函数中的this --> 是实例
def(obj, '__ob__', this, false);
// 给实例添加了__ob__属性, 值是this(new的结果)
if (Array.isArray(obj)) {
// 属性是数组,需要单独处理 将数组原型 指向自己定义的对象arrayMethods
Object.setPrototypeOf(obj, arrayMethods);
// console.log("数组原型已经被修改为了:", arrayMethods);
// 执行数组的observer
this.observerArray(obj);
} else {
this.walk(obj)
}
}
/**
* 遍历属性中的obj
* @param {object} obj 进行遍历的obj
*/
walk(obj) {
for (let k in obj) {
defineReactive(obj, k);
}
}
observerArray(arr) {
for (let i = 0; i < arr.length; i++) {
// 数组每一项都要被监听到
observe(arr[i]);
}
}
}
数据劫持原理 流程图

双向绑定
在getter中收集依赖,
在setter中触发依赖


Dep类
let uid = 0;
// 完成依赖收集
export default class Dep {
constructor() {
// console.log("**********************************依赖");
// 用数组来存储自己的订阅者(watcher的实例)
this.subs = [];
}
/**
* 添加订阅者到订阅者数组
* @param {Object} sub watcher实例
*/
addSub(sub) {
this.id = uid++;
this.subs.push(sub);
}
/**
* 添加依赖 如果有订阅者实例需要被添加到订阅者数组 (触发添加订阅者数组方法)
*/
depend() {
// Dep.target是一个全局变量, 里面存放的是Watcher实例,
if (Dep.target) {
// 有watcher实例就push到订阅者数组里面
this.addSub(Dep.target);
}
}
/**
* 通知订阅者更新
*/
notify() {
console.log("收到了更新通知~~~");
// 浅拷贝一份
let subs = this.subs.slice();
// 遍历,执行update方法
for (let i = 0, l = subs.length; i < l; i++) {
subs[i].update();
}
}
}
Watcher类
import Dep from "./Dep";
let uid;
export default class Watcher {
constructor(target, expression, callback) {
// watcher 构造的时候, 就要传进来
// 要监听哪个对象(target)
// 监听对象中的哪个属性(expression)
this.id = uid++;
this.target = target;
this.getter = parsePath(expression);//getter就是一个获得器 因为得到了一个函数
this.callback = callback;
this.value = this.get();
}
update() {
// 更新视图
console.log("更新视图!!!!!!!!!!!!!更新视图!!!!!!!!!!!更新视图!!!!!!!!!!!!!!更新视图!!!!!!!!!!!!更新视图!!!!!!!!");
this.run();
}
get() {
// 进入依赖收集阶段
// 这样 全局变量就有了watcher实例作为值 可以触发Dep的depend()方法,然后通过addSubs方法push进入订阅者数组中
Dep.target = this;
const obj = this.target;
let value;
try {
value = this.getter(obj) //通过执行器函数拿到最终得到的值所要监听的属性值
} catch {
} finally {
// 收集完毕 就把这个变量置为null
Dep.target = null;
}
return value;
}
run() {
// update一执行 调用这个方法
this.getAndInvoke(this.callback);
}
// 唤起回调函数
getAndInvoke(cb) {
// 首先调用get得到监听的值
const value = this.get();
// 判断这个值是否发生了变化
if (value !== this.value || typeof value === 'object') {
// 保存并替换旧值
const oldValue = this.value;
this.value = value;
// 执行回调 并且将旧值和新值传入
cb.call(this.target, value, oldValue);
};
}
}
/**
* 将打点形式的字符串 如:a.b.c 改为打点运算符
* @param {string} str 要监听哪个属性 a.b.c
* @returns {function} 返回可以读取a.b.c属性的函数
*/
function parsePath(str) {
let segment = str.split('.');
return (obj) => {
for (let i = 0; i < segment.length; i++) {
if (!obj) return;
obj = obj[segment[i]];
}
return obj;
}
}
收集触发依赖的getter和setter
import Dep from "./Dep";
import observe from "./observe";
// defineReactive 形成闭包环境
/**
* 将传进来的obj上的第一层属性, 添加getter和setter
* @author 李哈哈 <lwb010522@126.com>
* @param {Object} data 要变为响应式的对象
* @param {string} key 对象的属性
* @param {any|undefined} value 属性的值(不传即为key值)
* @returns {void}
*/
export default function defineReactive(data, key, value) {
const dep = new Dep() //这里的dep是给闭包内使用的dep 因为要使用它上面的notify方法
// 如果只传进来了2个参数
if (arguments.length === 2) {
value = data[key];
}
// 因为在Observer的walk中 会传进来obj的后代属性 所以这些后代属性也要监听
let childObj = observe(value);
// value 闭包
Object.defineProperty(data, key, {
get() {
console.log("访问了", value);
// 如果现在处于依赖的收集阶段
if (Dep.target) {
// 就触发收集依赖 将订阅者加入数组
dep.depend();
// 如果子元素存在, 子元素也需要收集
if (childObj) {
childObj.dep.depend();
}
}
return value;
},
set(newValue) {
console.log("设置新值", newValue);
value = newValue;
// 设置新值的时候 还要再监听 因为设置新值也可能被设置为了对象
childObj = observe(value);
// 发布订阅模式 通知dep
dep.notify();
}
})
}
使用watcher 数据改变时页面更新


文章详细阐述了Vue.js中数据响应式的工作原理,包括`defineReactive`函数用于创建响应式数据,`Observer`类遍历并转化对象属性为响应式,以及`Watcher`类负责依赖收集和更新。同时,文章提到了数组的特殊处理,如何通过重写数组方法实现响应式,并介绍了Dep类在依赖管理和通知更新中的作用。
1760

被折叠的 条评论
为什么被折叠?



