当前位置: 首页 > java >正文

常见问题三

在前端开发中,Vue 的数据响应机制、脚本加载策略以及函数式编程技巧是高频考点和日常开发的核心基础。本文将围绕这几个关键点展开详细解析,帮助开发者深入理解其原理与应用。

一、Vue2 与 Vue3 的数据响应原理对比

Vue 的核心特性之一是数据响应式—— 当数据变化时,视图自动更新。但 Vue2 和 Vue3 实现这一特性的底层原理存在显著差异。

1. Vue2 的数据响应原理:Object.defineProperty

Vue2 通过 **Object.defineProperty劫持对象的 getter 和 setter** 实现响应式。其核心逻辑是:
初始化时遍历data中的属性,为每个属性设置getter(获取值时收集依赖)和setter(修改值时触发更新)。

实现核心步骤:
  • 递归遍历data中的所有属性(包括嵌套对象);
  • 对每个属性调用Object.defineProperty,重写getset方法;
  • 当属性被访问时(get),收集当前依赖(如组件渲染函数);
  • 当属性被修改时(set),通知所有依赖更新(触发视图重新渲染)。
代码示例(简化版):
function defineReactive(obj, key, value) {// 递归处理嵌套对象observe(value);Object.defineProperty(obj, key, {get() {console.log(`获取${key}的值: ${value}`);// 收集依赖(实际中会关联Dep和Watcher)Dep.target && dep.addSub(Dep.target);return value;},set(newVal) {if (newVal !== value) {console.log(`更新${key}的值: ${newVal}`);value = newVal;// 通知依赖更新dep.notify();}}});
}// 遍历对象属性,批量设置响应式
function observe(obj) {if (typeof obj !== 'object' || obj === null) return;Object.keys(obj).forEach(key => {defineReactive(obj, key, obj[key]);});
}
局限性:
  • 无法监听数组索引变化(如arr[0] = 1)和对象新增属性(如obj.newKey = 1);
  • 需通过Vue.setthis.$set手动触发响应式(本质是为新增属性重新设置getter/setter);
  • 对数组的监听通过重写push/pop/splice等 7 个方法实现(修改数组时触发更新)。

2. Vue3 的数据响应原理:Proxy

Vue3 改用 **Proxy代理对象 ** 实现响应式,解决了 Vue2 的局限性。Proxy可以直接代理整个对象,而非单个属性,支持监听更多场景。

实现核心优势:
  • 代理整个对象:无需递归遍历属性,初始化性能更好;
  • 支持监听新增属性 / 删除属性:如obj.newKey = 1delete obj.key
  • 原生支持数组索引修改:如arr[0] = 1可直接被监听;
  • 支持MapSet等复杂数据结构的响应式。
代码示例(简化版):
function reactive(obj) {return new Proxy(obj, {get(target, key) {console.log(`获取${key}的值: ${target[key]}`);// 收集依赖(类似Vue2的Dep)track(target, key);return target[key];},set(target, key, value) {if (target[key] !== value) {console.log(`更新${key}的值: ${value}`);target[key] = value;// 通知更新(类似Vue2的Watcher)trigger(target, key);}},deleteProperty(target, key) {console.log(`删除${key}`);delete target[key];trigger(target, key); // 删除也触发更新}});
}
总结:
特性Vue2(Object.defineProperty)Vue3(Proxy)
监听范围仅已定义的属性整个对象(含新增 / 删除)
数组支持需重写方法,不支持索引修改原生支持索引修改和方法调用
初始化性能递归遍历属性,性能较差懒代理,性能更优
复杂数据结构(Map)不支持支持

二、Vue2 如何监听 data 里的数据变化

Vue2 对data的监听是一个递归劫持 + 依赖收集的过程,核心通过ObserverDepWatcher三个类协同实现。

1. 核心流程:

  1. 初始化data:组件初始化时,data函数返回的对象会被传入observe函数;
  2. 创建Observer实例observe函数会为对象创建Observer实例,负责劫持属性;
  3. 劫持属性Observer通过Object.defineProperty重写对象的getter/setter
  4. 收集依赖(Dep:当属性被访问时(如渲染时),getter会将当前Watcher(依赖)添加到Dep(依赖管理器)中;
  5. 触发更新:当属性被修改时,setter会通知DepDep再通知所有Watcher执行更新(如重新渲染组件)。

2. 数组的特殊处理:

由于Object.defineProperty无法监听数组索引变化,Vue2 通过重写数组原型方法实现监听:

// 重写数组的7个变更方法
const arrayMethods = Object.create(Array.prototype);
['push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse'].forEach(method => {arrayMethods[method] = function(...args) {// 执行原数组方法const result = Array.prototype[method].apply(this, args);// 通知更新(触发Observer的dep)this.__ob__.dep.notify();return result;};
});// 为数组设置新原型
function observeArray(arr) {arr.__proto__ = arrayMethods; // 覆盖数组原型for (let i = 0; i < arr.length; i++) {observe(arr[i]); // 递归监听数组元素}
}

三、watch 与 computed 的区别

watchcomputed都是 Vue 中监听数据变化的工具,但应用场景截然不同。

1. 核心差异对比:

特性computed(计算属性)watch(监听器)
本质基于依赖的衍生数据(类似 “变量”)数据变化后的回调函数(类似 “事件”)
缓存机制有缓存,依赖不变则不重新计算无缓存,数据变化即触发回调
返回值必须有返回值(用于页面渲染)无返回值(用于执行副作用,如异步操作)
异步支持不支持(不能包含异步逻辑,否则缓存失效)支持(可执行异步操作,如接口请求)
适用场景简单的衍生数据计算(如拼接字符串、计算总价)复杂的副作用处理(如数据变化后请求接口)

2. 代码示例:

// computed示例:计算全名(有缓存)
computed: {fullName() {// 依赖firstName和lastName,只有它们变化时才重新计算return `${this.firstName} ${this.lastName}`;}
}// watch示例:监听name变化,执行异步操作
watch: {name(newVal, oldVal) {// 支持异步(如请求接口)this.$axios.get(`/user?name=${newVal}`).then(res => {this.userInfo = res.data;});}
}

3. 总结:

  • 当需要根据已有数据生成新数据时,用computed(利用缓存提升性能);
  • 当需要在数据变化时执行异步操作或复杂逻辑时,用watch

四、script 标签的 defer 和 async 区别

script标签的deferasync属性用于控制脚本的加载与执行时机,解决默认加载阻塞 HTML 解析的问题。

1. 默认行为:

不添加deferasync时,脚本加载会阻塞 HTML 解析:浏览器遇到script标签时,会暂停 HTML 解析,下载脚本并立即执行,执行完成后再继续解析 HTML。

2. defer 与 async 的差异:

特性asyncdefer
加载时机并行下载脚本(不阻塞 HTML 解析)并行下载脚本(不阻塞 HTML 解析)
执行时机下载完成后立即执行(可能打断 HTML 解析)下载完成后等待 HTML 解析完毕再执行
执行顺序不保证顺序(哪个先下载完就先执行)严格按照 HTML 中脚本的顺序执行
适用场景独立脚本(如统计脚本、广告脚本)依赖 DOM 或顺序执行的脚本(如 jQuery 插件)

3. 执行流程图示:

  • 默认(无属性):加载阻塞解析 → 执行阻塞解析;
  • async:加载不阻塞解析 → 执行阻塞解析(顺序不确定);
  • defer:加载不阻塞解析 → 执行在 HTML 解析完成后(顺序与标签一致)。

4. 总结:

  • 若脚本无需依赖 DOM 且无顺序要求(如独立工具库),用async
  • 若脚本依赖 DOM 或需要按顺序执行(如 jQuery 后加载插件),用defer

五、函数柯里化及常见应用场景

函数柯里化(Currying) 是将多参数函数转化为一系列单参数函数的技术,形如f(a,b,c) → f(a)(b)(c)

1. 核心原理:

通过闭包收集参数,当参数数量满足原函数需求时,执行原函数;否则返回一个新函数继续收集参数。

实现示例:
// 将多参数函数柯里化
function curry(fn) {const argsLength = fn.length; // 原函数的参数数量return function curried(...args) {// 若收集的参数足够,执行原函数if (args.length >= argsLength) {return fn.apply(this, args);}// 否则返回新函数,继续收集参数return function(...nextArgs) {return curried.apply(this, args.concat(nextArgs));};};
}// 测试:柯里化add函数
const add = (a, b, c) => a + b + c;
const curriedAdd = curry(add);curriedAdd(1)(2)(3); // 6
curriedAdd(1, 2)(3); // 6
curriedAdd(1)(2, 3); // 6

2. 常见应用场景:

  • 参数复用:固定部分参数,简化函数调用。
    例:计算商品税后价格(固定税率):

    const calculateTax = (taxRate, price) => price * (1 + taxRate);
    const withTax = curry(calculateTax)(0.1); // 固定税率10%
    withTax(100); // 110(无需重复传税率)
    
  • 延迟执行:分步传递参数,直到条件满足再执行。
    例:事件绑定中分步传参:

    // 柯里化事件处理函数
    const handleClick = curry((id, event) => {console.log(`点击了ID为${id}的元素,事件:`, event);
    });// 绑定事件时先传id,事件触发时传event
    document.getElementById('btn1').onclick = handleClick(1);
    document.getElementById('btn2').onclick = handleClick(2);
    
  • 函数式编程:配合composepipe等工具实现函数组合。

总结

本文解析了前端开发中的 5 个核心知识点:

  • Vue2 通过Object.defineProperty劫持属性,Vue3 通过Proxy代理对象,后者更全面;
  • Vue2 对data的监听是递归劫持 + 依赖收集的过程,数组需特殊处理;
  • computed适用于衍生数据计算(有缓存),watch适用于异步副作用(无缓存);
  • async脚本加载完立即执行(乱序),defer等待 HTML 解析后执行(顺序);
  • 柯里化通过闭包分步收集参数,适用于参数复用、延迟执行等场景。
http://www.xdnf.cn/news/16308.html

相关文章:

  • linux 进程信号
  • 佳能iR-ADV C5560复印机如何扫描文件到电脑
  • Gorm教程 - 关联
  • 电厂液压执行器自动化升级:Modbus TCP与DeviceNet的协议贯通实践
  • 微观低代码
  • SpringBoot实战指南:从快速入门到生产级部署(2025最新版)
  • 【运维】ubuntu 安装图形化界面
  • Vue2下
  • SQLFluff
  • Hive-vscode-snippets
  • [特殊字符] 第9篇:《SQL高阶 SELECT 技巧:DISTINCT、ORDER BY、LIMIT 全家桶》
  • CN3798-2A 降压型单节锂电池充电芯片
  • Androidstudio 上传当前module 或本地jar包到maven服务器。
  • 二分查找----6.寻找两个正序数组的中位数
  • Python 数据分析(一):NumPy 基础知识
  • PI 思维升级 PI设计的典范转移:从阻抗思维到谐振控制
  • 【办公类-107-03】20250725通义万相2.1“动物拟人化”视频,优化关键词(图片转视频MP4转gif))
  • 我的世界之战争星球 暮色苍茫篇 第二十三章、出发!暮色森林!
  • 【硬件-笔试面试题】硬件/电子工程师,笔试面试题-26,(知识点:硬件电路的调试方法:信号追踪,替换,分段调试)
  • 恋爱时间倒计时网页设计与实现方案
  • 数据仓库深度探索系列 | 开篇:开启数仓建设新征程
  • Homebrew 更换镜像源加速软件安装:详细操作指南
  • NVM踩坑实录:配置了npm的阿里云cdn之后,下载nodejs老版本(如:12.18.4)时,报404异常,下载失败的问题解决
  • 壁纸管理 API 文档
  • PPIO上线阿里旗舰推理模型Qwen3-235B-A22B-Thinking-2507
  • [特殊字符] VLA 如何“绕过”手眼标定?—— 当机器人学会了“看一眼就动手”
  • Qt 与 SQLite 嵌入式数据库开发
  • ✨ 使用 Flask 实现头像文件上传与加载功能
  • 工业缺陷检测的计算机视觉方法总结
  • 【C++ python cython】C++如何调用python,python 运行速度如何提高?