第 4 卷
| 24 | lastTime = Date.now(); //执行后更新上次时间 |
|---|---|
| 25 | timer = null; //清空定时器 |
| 26 | }, interval - (now - lastTime)); //计算剩余延迟时间 |
| 27 | } |
| 28 }; |
29 30// 取消节流:手动清除定时器
31throttled.cancel = function() {
| 32 | if (timer) clearTimeout(timer); |
|---|---|
| 33 | timer = null; |
| 34 | lastTime = 0; |
| 35 | }; |
36 37return throttled;
38} 39
40// 测试示例 41function handleScroll() {
42console.log(`滚动触发时间:${new Date().toLocaleTimeString()}`);
43} 44
45// 节流函数(间隔1000ms执行一次)
46const throttledScroll = throttle(handleScroll, 1000);
47 48// 模拟频繁滚动(1秒内多次触发,仅1秒执行一次)
49window.addEventListener('scroll', throttledScroll);
50 51// 取消节流(不需要时移除)
52// window.removeEventListener('scroll', throttledScroll);
53// throttledScroll.cancel();
54
八、手写Vue响应式原理的代码实现(简易版)
核心原理(Vue2)
- 核心是“数据劫持+依赖收集”:通过Object.defineProperty()劫持数据的getter和setter方法
- getter:当数据被访问时,收集依赖(记录使用该数据的组件/函数)
- setter:当数据被修改时,触发依赖更新(通知所有使用该数据的组件/函数重新渲染)
实现代码
Code block
1// 1. 依赖管理类:收集依赖、触发更新 2class Dep {
3constructor() { 4this.subs = []; // 存储所有依赖(Watcher实例)
5} 6
7// 添加依赖 8addSub(sub) {
| 9 | //确保依赖是Watcher实例,且不重复添加 |
|---|---|
| 10 | if (sub && sub.update && !this.subs.includes(sub)) { |
| 11 | this.subs.push(sub); |
| 12 | } |
| 13 | } |
14 15// 触发所有依赖更新
16notify() { 17this.subs.forEach(sub => {
18sub.update(); // 调用Watcher的更新方法 19});
20} 21}
22 23// 2. 观察者类:关联数据和更新函数
24class Watcher { 25/**
| 26 | * @param {Object} vm -组件实例(此处简化为响应式数据对象) |
|---|---|
| 27 | * @param {string} exp -数据属性名(如:'name'、'user.age') |
| 28 | * @param {Function} cb -数据变化时的更新函数 |
| 29 | */ |
| 30 | constructor(vm, exp, cb) { |
| 31 | this.vm = vm; |
| 32 | this.exp = exp; |
| 33 | this.cb = cb; |
| 34 | this.value = this.get(); //初始化时获取数据值,触发getter收集依赖 |
| 35 | } |
| 37 | //获取数据值,触发getter以收集依赖 |
| 38 | get() { |
| 39 | Dep.target = this; //标记当前Watcher为待收集的依赖 |
| 45 | //解析属性路径,获取数据值(支持嵌套属性,如'user.age') |
|---|---|
| 46 | getVal(vm, exp) { |
| 47 | return exp.split('.').reduce((data, key) => { |
| 48 | return data[key]; |
| 49 | }, vm); |
| 50 | } |
51 52// 数据变化时执行的更新方法
53update() {
| 54 | const newValue = this.getVal(this.vm, this.exp); |
|---|---|
| 55 | const oldValue = this.value; |
| 56 | if (newValue !== oldValue) { |
| 57 | this.value = newValue; |
| 58 | this.cb(newValue, oldValue); //执行更新回调(如重新渲染视图) |
| 59 | } |
| 60 | } |
| 61 } |
62 63// 3. 响应式处理类:递归劫持对象属性
64class Observer { 65constructor(data) {
66this.observe(data); // 开始劫持数据 67}
68 69// 劫持数据主方法
70observe(data) { 71// 只处理对象/数组(基本类型无需劫持)
72if (!data || typeof data !== 'object') return;
73 74// 遍历对象所有属性,进行劫持
| 75 | Object.keys(data).forEach(key => { |
|---|---|
| 76 | this.defineReactive(data, key, data[key]); |
| 77 | }); |
| 78 | } |
79 80// 定义响应式属性(核心:Object.defineProperty)
81defineReactive(obj, key, value) {
| 82 | const dep = new Dep(); //每个属性对应一个Dep实例 |
|---|---|
| 83 | this.observe(value); //递归劫持子属性(支持嵌套对象) |
| 85 | Object.defineProperty(obj, key, { |
| 86 | enumerable: true, //可枚举 |