Search K
Appearance
Appearance
📊 SEO元描述:2024年最新JavaScript响应式编程教程,详解响应式原理、依赖收集机制、变化通知系统。包含完整Vue.js响应式实现,适合前端开发者快速掌握现代响应式编程技术。
核心关键词:JavaScript响应式编程2024、响应式原理、依赖收集、变化通知、Vue响应式、观察者模式
长尾关键词:响应式编程怎么实现、依赖收集原理、JavaScript数据绑定、Vue响应式原理、观察者模式应用
通过本节JavaScript响应式编程教程,你将系统性掌握:
响应式编程是什么?这是现代前端框架的核心技术。响应式编程是一种编程范式,通过建立数据之间的依赖关系,当数据发生变化时自动触发相关的更新操作,也是Vue.js、MobX等框架的核心实现原理。
💡 响应式理解建议:响应式编程的核心是建立数据之间的依赖关系图,当数据变化时沿着依赖图传播更新。
响应式系统由三个核心组件组成:可观察对象、依赖收集器、更新调度器
// 🎉 响应式系统完整实现
// 1. 依赖收集器 - Dep类
class Dep {
constructor() {
this.subscribers = new Set(); // 使用Set避免重复订阅
}
// 添加订阅者
addSub(watcher) {
this.subscribers.add(watcher);
}
// 移除订阅者
removeSub(watcher) {
this.subscribers.delete(watcher);
}
// 依赖收集
depend() {
if (Dep.target) {
this.addSub(Dep.target);
Dep.target.addDep(this);
}
}
// 通知所有订阅者
notify() {
const subs = Array.from(this.subscribers);
subs.forEach(watcher => {
watcher.update();
});
}
}
// 当前正在收集依赖的Watcher
Dep.target = null;
// 目标栈,支持嵌套依赖收集
const targetStack = [];
// 推入目标
function pushTarget(target) {
targetStack.push(target);
Dep.target = target;
}
// 弹出目标
function popTarget() {
targetStack.pop();
Dep.target = targetStack[targetStack.length - 1];
}
// 2. 观察者 - Watcher类
class Watcher {
constructor(vm, expOrFn, cb, options = {}) {
this.vm = vm;
this.cb = cb;
this.options = options;
this.deps = new Set();
this.newDeps = new Set();
this.depIds = new Set();
this.newDepIds = new Set();
// 解析表达式或函数
if (typeof expOrFn === 'function') {
this.getter = expOrFn;
} else {
this.getter = this.parsePath(expOrFn);
}
// 立即求值
this.value = this.get();
}
// 获取值并收集依赖
get() {
pushTarget(this);
let value;
try {
value = this.getter.call(this.vm, this.vm);
} catch (e) {
console.error('Watcher getter error:', e);
} finally {
popTarget();
this.cleanupDeps();
}
return value;
}
// 添加依赖
addDep(dep) {
const id = dep.id || dep;
if (!this.newDepIds.has(id)) {
this.newDepIds.add(id);
this.newDeps.add(dep);
if (!this.depIds.has(id)) {
dep.addSub(this);
}
}
}
// 清理依赖
cleanupDeps() {
// 移除旧的依赖
this.deps.forEach(dep => {
if (!this.newDeps.has(dep)) {
dep.removeSub(this);
}
});
// 更新依赖集合
[this.depIds, this.newDepIds] = [this.newDepIds, this.depIds];
[this.deps, this.newDeps] = [this.newDeps, this.deps];
this.newDepIds.clear();
this.newDeps.clear();
}
// 更新
update() {
if (this.options.lazy) {
this.dirty = true;
} else if (this.options.sync) {
this.run();
} else {
queueWatcher(this);
}
}
// 执行更新
run() {
const value = this.get();
if (value !== this.value ||
(typeof value === 'object' && value !== null) ||
this.options.deep) {
const oldValue = this.value;
this.value = value;
if (this.cb) {
this.cb.call(this.vm, value, oldValue);
}
}
}
// 求值(用于计算属性)
evaluate() {
this.value = this.get();
this.dirty = false;
}
// 解析路径
parsePath(path) {
const segments = path.split('.');
return function(obj) {
for (let segment of segments) {
if (!obj) return;
obj = obj[segment];
}
return obj;
};
}
// 销毁
teardown() {
this.deps.forEach(dep => {
dep.removeSub(this);
});
}
}
// 3. 更新队列调度器
let queue = [];
let has = {};
let waiting = false;
let flushing = false;
let index = 0;
// 将watcher加入队列
function queueWatcher(watcher) {
const id = watcher.id || watcher;
if (has[id] == null) {
has[id] = true;
if (!flushing) {
queue.push(watcher);
} else {
// 如果正在刷新,插入到正确位置
let i = queue.length - 1;
while (i > index && queue[i].id > watcher.id) {
i--;
}
queue.splice(i + 1, 0, watcher);
}
if (!waiting) {
waiting = true;
nextTick(flushSchedulerQueue);
}
}
}
// 刷新调度队列
function flushSchedulerQueue() {
flushing = true;
let watcher, id;
// 按id排序,确保父组件在子组件之前更新
queue.sort((a, b) => a.id - b.id);
for (index = 0; index < queue.length; index++) {
watcher = queue[index];
id = watcher.id;
has[id] = null;
watcher.run();
}
// 重置状态
resetSchedulerState();
}
// 重置调度器状态
function resetSchedulerState() {
index = queue.length = 0;
has = {};
waiting = flushing = false;
}
// 下一个tick执行
function nextTick(cb) {
if (typeof Promise !== 'undefined') {
Promise.resolve().then(cb);
} else if (typeof MutationObserver !== 'undefined') {
const observer = new MutationObserver(cb);
const textNode = document.createTextNode('1');
observer.observe(textNode, { characterData: true });
textNode.data = '2';
} else {
setTimeout(cb, 0);
}
}
// 4. 响应式对象创建器
function observe(value) {
if (!value || typeof value !== 'object') {
return;
}
let ob;
if (value.__ob__ && value.__ob__ instanceof Observer) {
ob = value.__ob__;
} else {
ob = new Observer(value);
}
return ob;
}
// Observer类
class Observer {
constructor(value) {
this.value = value;
this.dep = new Dep();
// 添加__ob__属性
Object.defineProperty(value, '__ob__', {
value: this,
enumerable: false,
writable: true,
configurable: true
});
if (Array.isArray(value)) {
this.observeArray(value);
} else {
this.walk(value);
}
}
// 遍历对象属性
walk(obj) {
const keys = Object.keys(obj);
for (let key of keys) {
defineReactive(obj, key);
}
}
// 观察数组
observeArray(items) {
for (let item of items) {
observe(item);
}
}
}
// 定义响应式属性
function defineReactive(obj, key, val) {
const dep = new Dep();
// 获取属性描述符
const property = Object.getOwnPropertyDescriptor(obj, key);
if (property && property.configurable === false) {
return;
}
// 保存原有的getter和setter
const getter = property && property.get;
const setter = property && property.set;
if ((!getter || setter) && arguments.length === 2) {
val = obj[key];
}
// 递归观察子对象
let childOb = 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;
// 值没有变化则不触发更新
if (newVal === value || (newVal !== newVal && value !== value)) {
return;
}
if (setter) {
setter.call(obj, newVal);
} else {
val = newVal;
}
// 观察新值
childOb = observe(newVal);
// 通知依赖更新
dep.notify();
}
});
}
// 数组依赖收集
function dependArray(value) {
for (let item of value) {
if (item && item.__ob__) {
item.__ob__.dep.depend();
}
if (Array.isArray(item)) {
dependArray(item);
}
}
}响应式编程的核心优势:
计算属性是响应式系统的重要特性,它基于依赖的响应式数据进行计算,并且具有缓存特性:
// 🎉 计算属性实现
class ComputedWatcher extends Watcher {
constructor(vm, getter, cb, options) {
super(vm, getter, cb, { ...options, lazy: true });
this.dirty = true; // 标记是否需要重新计算
}
// 求值
evaluate() {
if (this.dirty) {
this.value = this.get();
this.dirty = false;
}
return this.value;
}
// 更新时标记为脏数据
update() {
if (this.lazy) {
this.dirty = true;
} else {
super.update();
}
}
// 依赖收集
depend() {
if (this.deps) {
this.deps.forEach(dep => {
dep.depend();
});
}
}
}
// 创建计算属性
function createComputedProperty(vm, key, userDef) {
const getter = typeof userDef === 'function' ? userDef : userDef.get;
const setter = userDef.set || (() => {
console.warn(`Computed property "${key}" was assigned to but it has no setter.`);
});
// 创建计算属性的Watcher
const computedWatcher = new ComputedWatcher(vm, getter, () => {}, {
lazy: true
});
// 在vm上存储watcher
if (!vm._computedWatchers) {
vm._computedWatchers = {};
}
vm._computedWatchers[key] = computedWatcher;
// 定义计算属性
Object.defineProperty(vm, key, {
enumerable: true,
configurable: true,
get: function computedGetter() {
const watcher = vm._computedWatchers[key];
if (watcher) {
if (watcher.dirty) {
watcher.evaluate();
}
if (Dep.target) {
watcher.depend();
}
return watcher.value;
}
},
set: setter
});
}
// 使用示例
class ReactiveVM {
constructor(options = {}) {
this._data = options.data || {};
this._computed = options.computed || {};
this._watch = options.watch || {};
this._watchers = [];
this._computedWatchers = {};
// 初始化数据
this.initData();
// 初始化计算属性
this.initComputed();
// 初始化侦听器
this.initWatch();
}
initData() {
const data = this._data;
// 代理data到vm实例
Object.keys(data).forEach(key => {
this.proxy(key);
});
// 观察data
observe(data);
}
initComputed() {
const computed = this._computed;
Object.keys(computed).forEach(key => {
createComputedProperty(this, key, computed[key]);
});
}
initWatch() {
const watch = this._watch;
Object.keys(watch).forEach(key => {
const handler = watch[key];
this.$watch(key, handler);
});
}
// 代理属性到vm实例
proxy(key) {
Object.defineProperty(this, key, {
enumerable: true,
configurable: true,
get: function proxyGetter() {
return this._data[key];
},
set: function proxySetter(val) {
this._data[key] = val;
}
});
}
// 侦听器方法
$watch(expOrFn, cb, options = {}) {
const watcher = new Watcher(this, expOrFn, cb, options);
this._watchers.push(watcher);
if (options.immediate) {
cb.call(this, watcher.value);
}
// 返回取消侦听的函数
return function unwatchFn() {
watcher.teardown();
const index = this._watchers.indexOf(watcher);
if (index > -1) {
this._watchers.splice(index, 1);
}
};
}
// 销毁实例
$destroy() {
this._watchers.forEach(watcher => {
watcher.teardown();
});
this._watchers = [];
Object.keys(this._computedWatchers).forEach(key => {
this._computedWatchers[key].teardown();
});
this._computedWatchers = {};
}
}
// 完整使用示例
const vm = new ReactiveVM({
data: {
firstName: 'John',
lastName: 'Doe',
age: 25,
items: [
{ name: 'Apple', price: 1.2, quantity: 10 },
{ name: 'Banana', price: 0.8, quantity: 15 },
{ name: 'Orange', price: 1.5, quantity: 8 }
]
},
computed: {
// 全名计算属性
fullName: {
get() {
console.log('计算fullName');
return `${this.firstName} ${this.lastName}`;
},
set(value) {
const names = value.split(' ');
this.firstName = names[0];
this.lastName = names[names.length - 1];
}
},
// 总价计算属性
totalPrice() {
console.log('计算totalPrice');
return this.items.reduce((total, item) => {
return total + (item.price * item.quantity);
}, 0);
},
// 平均价格计算属性
averagePrice() {
console.log('计算averagePrice');
if (this.items.length === 0) return 0;
return this.totalPrice / this.items.reduce((sum, item) => sum + item.quantity, 0);
},
// 成年状态
isAdult() {
return this.age >= 18;
}
},
watch: {
// 侦听firstName变化
firstName(newVal, oldVal) {
console.log(`firstName changed from ${oldVal} to ${newVal}`);
},
// 深度侦听items数组
items: {
handler(newVal, oldVal) {
console.log('Items changed:', newVal);
},
deep: true
},
// 侦听计算属性
totalPrice: {
handler(newVal, oldVal) {
console.log(`Total price changed from ${oldVal} to ${newVal}`);
},
immediate: true
}
}
});
// 测试响应式系统
console.log('=== 初始状态 ===');
console.log('Full Name:', vm.fullName); // 触发计算
console.log('Total Price:', vm.totalPrice); // 触发计算
console.log('Average Price:', vm.averagePrice); // 触发计算
console.log('\n=== 再次访问(缓存) ===');
console.log('Full Name:', vm.fullName); // 使用缓存
console.log('Total Price:', vm.totalPrice); // 使用缓存
console.log('\n=== 修改数据 ===');
vm.firstName = 'Jane'; // 触发firstName侦听器,fullName重新计算
console.log('\n=== 修改数组数据 ===');
vm.items[0].price = 1.5; // 触发items侦听器,totalPrice重新计算
console.log('\n=== 通过setter修改计算属性 ===');
vm.fullName = 'Alice Smith'; // 通过setter修改firstName和lastName// 🎉 数组变化检测实现
const arrayProto = Array.prototype;
const arrayMethods = Object.create(arrayProto);
// 需要拦截的数组方法
const methodsToPatch = [
'push', 'pop', 'shift', 'unshift',
'splice', 'sort', 'reverse'
];
methodsToPatch.forEach(method => {
const original = arrayProto[method];
Object.defineProperty(arrayMethods, method, {
value: function mutator(...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;
},
enumerable: false,
writable: true,
configurable: true
});
});
// 增强Observer类以支持数组
class EnhancedObserver extends Observer {
constructor(value) {
super(value);
if (Array.isArray(value)) {
// 替换数组原型
if (value.__proto__) {
value.__proto__ = arrayMethods;
} else {
// 不支持__proto__的环境
methodsToPatch.forEach(method => {
Object.defineProperty(value, method, {
value: arrayMethods[method],
enumerable: false,
writable: true,
configurable: true
});
});
}
}
}
}
// 深度侦听实现
function createDeepWatcher(vm, expOrFn, cb, options = {}) {
options.deep = true;
const watcher = new Watcher(vm, expOrFn, cb, options);
// 深度遍历收集依赖
const originalGet = watcher.get;
watcher.get = function() {
const value = originalGet.call(this);
if (options.deep && value) {
traverse(value);
}
return value;
};
return watcher;
}
// 遍历对象收集依赖
function traverse(val) {
const seen = new Set();
function _traverse(val) {
if (seen.has(val)) return;
seen.add(val);
if (Array.isArray(val)) {
val.forEach(item => _traverse(item));
} else if (val && typeof val === 'object') {
Object.keys(val).forEach(key => {
_traverse(val[key]);
});
}
}
_traverse(val);
}// 🎉 实际应用:购物车系统
class ShoppingCart extends ReactiveVM {
constructor() {
super({
data: {
items: [],
discountRate: 0,
taxRate: 0.1,
shippingFee: 10
},
computed: {
// 商品总数
totalItems() {
return this.items.reduce((sum, item) => sum + item.quantity, 0);
},
// 小计
subtotal() {
return this.items.reduce((sum, item) => {
return sum + (item.price * item.quantity);
}, 0);
},
// 折扣金额
discountAmount() {
return this.subtotal * this.discountRate;
},
// 税费
taxAmount() {
return (this.subtotal - this.discountAmount) * this.taxRate;
},
// 总计
total() {
return this.subtotal - this.discountAmount + this.taxAmount + this.shippingFee;
},
// 是否为空购物车
isEmpty() {
return this.items.length === 0;
},
// 购物车摘要
summary() {
return {
itemCount: this.totalItems,
subtotal: this.subtotal,
discount: this.discountAmount,
tax: this.taxAmount,
shipping: this.shippingFee,
total: this.total
};
}
},
watch: {
// 监听总计变化
total: {
handler(newTotal, oldTotal) {
console.log(`购物车总计从 $${oldTotal?.toFixed(2)} 变更为 $${newTotal.toFixed(2)}`);
this.onTotalChange(newTotal, oldTotal);
},
immediate: true
},
// 监听商品数量变化
totalItems(newCount, oldCount) {
console.log(`商品数量从 ${oldCount} 变更为 ${newCount}`);
this.onItemCountChange(newCount, oldCount);
},
// 深度监听商品列表
items: {
handler(newItems, oldItems) {
console.log('购物车商品发生变化');
this.onItemsChange(newItems, oldItems);
},
deep: true
}
}
});
// 初始化事件处理
this.initEventHandlers();
}
// 添加商品
addItem(product, quantity = 1) {
const existingItem = this.items.find(item => item.id === product.id);
if (existingItem) {
existingItem.quantity += quantity;
} else {
this.items.push({
id: product.id,
name: product.name,
price: product.price,
quantity: quantity,
image: product.image
});
}
}
// 移除商品
removeItem(productId) {
const index = this.items.findIndex(item => item.id === productId);
if (index > -1) {
this.items.splice(index, 1);
}
}
// 更新商品数量
updateQuantity(productId, quantity) {
const item = this.items.find(item => item.id === productId);
if (item) {
if (quantity <= 0) {
this.removeItem(productId);
} else {
item.quantity = quantity;
}
}
}
// 清空购物车
clear() {
this.items.splice(0, this.items.length);
}
// 应用折扣
applyDiscount(rate) {
this.discountRate = Math.max(0, Math.min(1, rate));
}
// 事件处理器初始化
initEventHandlers() {
// 可以在这里添加更多事件处理逻辑
}
// 总计变化处理
onTotalChange(newTotal, oldTotal) {
// 可以触发UI更新、发送分析事件等
if (newTotal > 100) {
console.log('🎉 恭喜!您享受免费配送!');
this.shippingFee = 0;
} else if (this.shippingFee === 0 && newTotal <= 100) {
this.shippingFee = 10;
}
}
// 商品数量变化处理
onItemCountChange(newCount, oldCount) {
if (newCount === 0) {
console.log('购物车已清空');
} else if (newCount > oldCount) {
console.log('添加了商品到购物车');
} else {
console.log('从购物车移除了商品');
}
}
// 商品列表变化处理
onItemsChange(newItems, oldItems) {
// 可以保存到本地存储
localStorage.setItem('cart', JSON.stringify(newItems));
}
}
// 使用购物车系统
const cart = new ShoppingCart();
// 添加商品
cart.addItem({ id: 1, name: 'iPhone 15', price: 999 }, 1);
cart.addItem({ id: 2, name: 'AirPods Pro', price: 249 }, 2);
console.log('购物车摘要:', cart.summary);
// 应用折扣
cart.applyDiscount(0.1); // 10%折扣
// 更新数量
cart.updateQuantity(1, 2);
// 查看最终摘要
console.log('最终摘要:', cart.summary);💼 实际应用价值:响应式编程大大简化了复杂数据关系的管理,特别适合电商购物车、表单验证、数据可视化等场景。
通过本节JavaScript响应式编程的学习,你已经掌握:
A: 响应式编程是更高层次的编程范式,观察者模式是其底层实现机制之一。响应式编程强调数据流和自动传播变化,而观察者模式只是定义了对象间的一对多依赖关系。
A: 依赖收集能够精确追踪哪些数据被实际使用,避免不必要的更新。如果监听所有数据变化,会导致大量无效更新,严重影响性能,特别是在大型应用中。
A: 计算属性通过dirty标记来控制缓存。当依赖数据未变化时,dirty为false,直接返回缓存值;当依赖数据变化时,dirty设为true,重新计算并缓存结果。
A: 深度侦听需要遍历整个对象树,确实会影响性能。优化方法包括:使用不可变数据结构、精确指定侦听路径、使用浅比较、合理使用计算属性替代深度侦听。
A: 不是。响应式编程适合数据驱动的场景,如UI组件、表单处理、数据可视化等。对于简单的工具函数、算法实现等场景,传统的命令式编程可能更合适。
// ❌ 避免:将所有数据都设为响应式
const data = reactive({
userList: [], // 大量数据
config: {}, // 静态配置
constants: {} // 常量数据
});
// ✅ 推荐:只对需要响应的数据使用响应式
const data = {
userList: reactive([]), // 只有用户列表需要响应式
config: Object.freeze({}), // 静态配置冻结
constants: Object.freeze({}) // 常量数据冻结
};// ❌ 避免:在模板中进行复杂计算
// template: {{ users.filter(u => u.active).map(u => u.name).join(', ') }}
// ✅ 推荐:使用计算属性
computed: {
activeUserNames() {
return this.users
.filter(user => user.active)
.map(user => user.name)
.join(', ');
}
}// ❌ 避免:频繁的数组重新赋值
this.items = this.items.filter(item => item.id !== targetId);
// ✅ 推荐:使用数组变异方法
const index = this.items.findIndex(item => item.id === targetId);
if (index > -1) {
this.items.splice(index, 1);
}// 添加依赖追踪日志
const originalDepend = Dep.prototype.depend;
Dep.prototype.depend = function() {
if (Dep.target) {
console.log(`收集依赖: ${Dep.target.expression || 'anonymous'}`);
}
return originalDepend.call(this);
};// 添加更新日志
const originalNotify = Dep.prototype.notify;
Dep.prototype.notify = function() {
console.log(`触发更新,影响 ${this.subscribers.size} 个订阅者`);
return originalNotify.call(this);
};"响应式编程是现代前端开发的核心技术之一,它让我们能够以声明式的方式处理复杂的数据关系。掌握响应式编程的原理,不仅能帮你更好地使用Vue.js等框架,更能让你在设计自己的应用架构时游刃有余。"