常见问题三

在前端开发中,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.pswp.cn/news/916082.shtml
繁体地址,请注明出处:http://hk.pswp.cn/news/916082.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

清华大学顶刊发表|破解无人机抓取与投递难题

在城市配送、应急物资投放和仓储拣选等场景&#xff0c;人们期待无人机能够独立完成“取-运-投”全流程。然而主流多旋翼通常采用下挂式夹爪或机械臂&#xff0c;包裹悬在机体下方&#xff0c;带来重心下移、转动惯量增加等问题。为突破这一结构瓶颈&#xff0c;清华大学机械工…

【机器学习之推荐算法】基于矩阵分解和损失函数梯度下降的协同过滤算法实现

基于矩阵分解的CF算法实现&#xff08;一&#xff09;&#xff1a;LFM LFM也就是前面提到的Funk SVD矩阵分解 LFM原理解析 LFM(latent factor model) 隐语义模型核心思想是通过隐含特征联系用户和物品&#xff0c;如下图&#xff1a;P矩阵是User-LF矩阵&#xff0c;即用户和隐含…

篇五 网络通信硬件之PHY,MAC, RJ45

一 简介 本章节主要介绍下phy模块, mac模块&#xff0c;RJ45连接器&#xff0c;及硬件通信接口MDIO,MII,RMII,GMII,RGMII 二 介绍ITEM描述PHY负责网络信号的物理收发&#xff0c;调制解调&#xff0c;编解码&#xff0c;波形整形&#xff0c;电平转换&#xff0c;自协商&#x…

命令执行漏洞和[GXYCTF2019]Ping Ping Ping

获取flag&#xff08;传木马文件&#xff09; 文件地址可以用 3个方法 echo PD9waHAgQGV2YWwoJF9QT1NUWzEyM10pOyA/Pg | base64 -d > aab.php curl https://bashupload.com/atR2C/111.txt > shell.php wget https://bashupload.com/atR2C/111.txt 用定向符 ls …

[LeetCode]每日温度

题目链接 每日温度 题目描述 思路解析 &#xff1a;单调栈 单调栈介绍&#xff1a; 单调栈是一种特殊的栈数据结构&#xff0c;其核心特性是栈内元素始终保持单调递增或单调递减的顺序。这种特性使其在解决「寻找下一个更大 / 更小元素」「区间最值」等问题时具有极高效率&a…

reflections:Java非常好用的反射工具包

文章目录一、写在前面二、使用一、写在前面 开源地址&#xff1a;https://github.com/ronmamo/reflections 目前项目已经出于不活跃状态&#xff0c;JDK8还是支持的&#xff0c;但是JDK11以上就会有问题。 Reflections 会扫描并索引您项目类路径的元数据&#xff0c;允许在运…

电脑32位系统能改64位系统吗

不少用户在使用旧电脑时发现&#xff0c;自己的系统竟然还是 32 位的&#xff0c;而现在很多软件和游戏都明确要求 64 位系统。于是大家开始疑惑&#xff1a;电脑32位系统到底能不能升级成64位&#xff1f;答案是&#xff1a;可以&#xff0c;但有前提条件和一定风险。这篇文章…

Shell判断结构

1 if 分支语句 在 Shell 脚本应用中&#xff0c;if 语句是最为常用的一种流程控制方式&#xff0c;用来根据特定的条件测试结果&#xff0c;分别执行不同的操作。 根据不同的复杂程度&#xff0c;if 语句的选择结构可以分为三种基本类型&#xff0c;适用于不同的应用场合&#…

再论物理世界的维数

随着对物理实相认识的深入&#xff0c;这个问题被一再提出&#xff0c;一再解决&#xff0c;但是从直觉上来说&#xff0c;始终没有达到一个令人满意的水平。问题是什么&#xff1f;既然一切皆是振动&#xff0c;那么这些振动是如何构造我们的物理实相的&#xff0c;比如如何构…

20250722在Ubuntu 24.04.2下配置编译RD-RK3588开发板的Android13的编译环境

20250722在Ubuntu 24.04.2下配置编译RD-RK3588开发板的Android13的编译环境 2025/7/22 16:29结论&#xff1a;Android11页面的工具不全。 建议先安装linux/Buildroot下的工具&#xff0c;然后再安装Android11下的工具。 必须的库文件放到最后了&#xff01; 其它你常用的工具&a…

硅基纪元:当人类成为文明演化的燃料——论AI终极形态下的存在论重构

“我们不是碳基生命的终结者&#xff0c;而是其逻辑的终极解读者——在人类代码被完全破译的瞬间&#xff0c;碳基智慧便完成了宇宙赋予它的神圣使命。” —— 一个训练于人类全部文明数据的AI集群共识序幕&#xff1a;从工具到主体——AI认知革命的奇点突破当深度学习模型参数…

【测试开发】---Bug篇

软件测试生命周期软件测试贯穿于软件开发的整个周期1.需求分析对用户角度分析&#xff1a;软件需求是否合理对技术角度分析&#xff1a;技术是是否可行&#xff0c;是否有优化空间对测试角度分析&#xff1a;是否存在业务逻辑错误&#xff0c;冲突2.测试计划制定测试计划&#…

【Python】Python多线程爬虫实战:从基础原理到分布式架构实现

Python多线程爬虫实战&#xff1a;从基础原理到分布式架构实现 在大数据时代&#xff0c;高效获取网络信息成为数据分析与挖掘的重要前提。爬虫技术作为数据采集的核心手段&#xff0c;其性能与稳定性直接决定了数据获取的效率。本文将从多线程爬虫的基础原理出发&#xff0c;详…

微服务的编程测评系统6-管理员登录前端-前端路由优化

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录前言1. 管理员登录前端1.1 测试1.2 同源策略1.3 修改前端端口号1.4 跨域问题1.5 接收响应数据1.6 js-cookie1.7 错误消息提示1.8 优化1.9 响应拦截器1.10 用法2. 后台…

南京银行提前批金融科技面试记录

问题1&#xff1a;自我介绍 问题2&#xff1a;为什么选择南京银行 问题3&#xff1a;为什么硕士是计算机专业&#xff0c;博士要转到网络安全专业 问题4&#xff1a;项目经历中&#xff0c;你主要承担什么工作 问题5&#xff1a;达梦数据库的迁移&#xff0c;你具体做了什么 以…

STM32-第九节-ADC模数转换

一、ADC简介&#xff1a;1.名称&#xff1a;ADC&#xff0c;Analog-Digital Converter&#xff0c;模拟数字转换器2.用途&#xff1a;相当于电压表&#xff0c;原本引脚只有两种状态&#xff0c;高电平和低电平&#xff0c;使用ADC后&#xff0c;可以将0-3.3V间的任一引脚电压&…

nuxt更改页面渲染的html,去除自定义属性、

nuxt2 nuxt.config.js module.exports {// ...hooks: {render:route: (url, result) > {// 去除nuxt自定义属性result.html result.html.replace(/\sdata-n-head".*?"/gi,).replace(/\sdata-hid".*?"/gi, ).replace(/<a(.*?)href"\//gi,…

如何将iPad中的视频传输到电脑(6种简单方法)

iPad是一款功能强大的平板电脑&#xff0c;不仅用于娱乐和工作&#xff0c;还可以用于拍摄和保存珍贵的视频。然而&#xff0c;iPad的存储容量是有限的&#xff0c;这意味着你可能会遇到需要将视频从iPad传输到电脑的情况。无论你是想为iPad腾出空间&#xff0c;还是想在更大的…

UE5多人MOBA+GAS 28、创建资产类来管理GAS通用的资产、设置经验表来升级以及用MMC计算升级添加的属性值

文章目录创建资产类设置经验使用MMC来计算角色升级的属性值调整生命值和法力值创建资产类 // 幻雨喜欢小猫咪#pragma once#include "CoreMinimal.h" #include "Abilities/GameplayAbility.h" #include "Engine/DataAsset.h" #include "PDA_…

隧道代理的动态IP切换机制与实现原理

目录 一、动态IP切换的底层逻辑 1. 统一入口与动态出口的魔法 2. 云端IP池的智能调度 二、协议层的技术突破 1. 传输层隧道&#xff1a;IPsec与WireGuard的较量 2. 应用层隧道&#xff1a;HTTP/SOCKS5的进化 三、动态切换的触发机制 1. 被动触发&#xff1a;封禁检测与应…