找到
295
篇与
易航
相关的结果
- 第 13 页
-
一个兼容 OneBot 协议的非官方 QQ 客户端实现,支持网页及 electron 客户端 Stapxs-QQ-Lite-2.0 一个兼容 OneBot 协议的非官方 QQ 客户端实现,支持网页及 electron 客户端Github地址:隐藏内容,请前往内页查看详情 在线体验:隐藏内容,请前往内页查看详情 图片 ✨ 特性支持 ✅ 使用 Vue.js 全家桶开发,快乐前后端分离 🎨 自适应布局,竖版也能使用 🖥️ 支持 PWA(都有 Electron 了(小声)) 🌚 Light/Dark Mode 自动切换 🍱 该有的都有(虽然比不过官方端) 复杂消息显示、转发、回复、撤回 群文件、群公告、群设置(一小部分)、精华消息 图片、收藏表情、文件发送 📦️ 支持多种 bot,我就是要用! 🔥 水深火热但是更好看的 Electron 客户端 🥚 彩蛋!来更多的彩蛋! 🛠 更多特性开发中 💬 提醒和问题 > 关于不安全连接 当使用 https 页面连接 ws 服务(反之相同)的情况下,连接将会失败;这是由于其中某一者是不安全的。在这种情况下,你可以选择将 ws 提升为 wss 或者将 https 降级为 http(不安全)来解决问题,此处不提供解决方案。>> Stapxs-QQ-Lite#32 > 我能使用其他 QQ Http Bot 吗 如果它兼容 OneBot 11 协议, 你可以尝试连接它, 但是由于消息体格式和接口扩展的差异,大部分情况下都不能完全正常使用。 已经兼容的 Bot 都写在了文档里,可以去 这里 查看。 > 使用 Bot 是否有风险 如果你使用的是 oicq-http, 可以查看此处了解 如果你尝试使用其他 QQ Bot (参见上一条问题), 请自行参考它的文档。 使用风险:https://github.com/takayama-lily/oicq/wiki/98.%E5%85%B3%E4%BA%8E%E8%B4%A6%E5%8F%B7%E5%86%BB%E7%BB%93%E5%92%8C%E9%A3%8E%E6%8E%A7
-
不要过度使用 console.log 啦!我们有更好的调试方式! Hello,大家好,我是 Sunday。 在日常的工作中,我经常会帮同学【远程调试代码】。在远程时,我发现 很多同学会大量使用 console.log 进行打印调试。打印的数量之多,让同学自己都摸不清哪个打印对应哪个内容了😂。 毫无疑问,console.log 是一个很好的调试方式。但是 如果我们滥用它,效果反而会适得其反!大量打印信息堆积如山,反倒使得我们难以理清各条输出的对应逻辑! 因此,我们可以寻找可好的调试方案,来解决 console.log 过多而导致的混乱问题。 console 不止 log 没错!console 不止 log ,console 对象内部提供了很多的方法。在之前的文章中,我们提到过这一点:调试只会console.log?来看一看这 6 种惊艳的调试技巧! 使用更多的 console 方法配合可以帮助我们大幅提升调试的效率。 1. 使用 console.dir() 打印对象 console.dir() 是一个专门打印 对象 的 API。 console.dir() 方法可以显示指定 JavaScript 对象的属性列表,并以交互式的形式展现。输出结果呈现为分层列表,包含展开/折叠的三角形图标,可用于查看子对象的内容。因此,当我们打印对象时,可以使用 dir 代替 log 2. 使用 console.table() 打印数组 如果想要打印数组的话,那么 table() 是首选方法: const users = [ { id: 1, name: '张三', age: 25 }, { id: 2, name: '李四', age: 30 }, { id: 3, name: '王五', age: 35 }, ]; console.table(users);一句话:贼清晰! 图片 3. 使用 console.clear() 清理控制台 这个 API 可以帮我们解决控制台打印过多的问题。 如果我们要开启一轮新的调试,那么可以在开始前执行 console.clear(),清空之前的控制台打印。 这样,可以帮我们更加专注的针对本次的调试,而不需要被之前的打印信息所误导! 4. 使用 console.group() 控制打印组 这在 嵌套函数、递归 中非常有用,配合 console.groupEnd() 可以帮助我们完成分组打印。 比如下面的场景: function factorial(n) { console.group(`方法开始,长度(${n})`); if (n <= 1) { console.log("执行 1"); console.groupEnd(); return 1; } else { let result = n * factorial(n - 1); console.log(`执行 ${result}`); console.groupEnd(); return result; } } factorial(3);最终,打印结果如下: 图片 5. 使用 console.time() 完成计时 console.time() 配合 console.timeEnd() 经常用来处理 计时操作。 比如,我们想要测试一个函数的执行耗时,那么就可以通过这种方式完成: function processLargeData() { console.time("time"); // 模拟耗时操作 for (let i = 0; i < 1000000; i++) { Math.sqrt(i); } console.timeEnd("time"); } processLargeData();计时结果如下: 图片
-
免费的轻量级代码编辑器 HBuilderX HBuilderX 是由DCloud开发的一款面向Web前端开发者的集成开发环境(IDE),它是HBuilder的下一代产品,继承了HBuilder的诸多优点,并在此基础上进行了全面的升级。HBuilderX旨在提供更加强大、高效的开发体验,支持多种前端技术栈,包括但不限于HTML5、CSS3、JavaScript、Vue.js、React、Angular等。 功能特点 快速启动:启动速度快,能够在几秒内启动,这对于频繁切换任务的开发者来说是非常有用的特性。 智能提示:提供了丰富的代码补全和提示功能,能够提高编写代码的速度和准确性。 实时预览:支持实时预览功能,开发者可以直接在编辑器中看到代码修改的效果,无需频繁切换浏览器。 多页面编辑:支持多页面同时编辑,可以在同一个窗口中打开多个文件,方便进行对比和同步修改。 多光标操作:支持多光标编辑,允许用户一次编辑多处相同代码,提高编码效率。 HTML5/CSS3支持:内置对HTML5和CSS3的支持,包括新特性的语法高亮和代码提示。 Git集成:内置Git支持,方便开发者进行版本控制和团队协作。 代码美化:提供代码格式化功能,帮助开发者保持代码整洁。 文件压缩打包:支持JavaScript、CSS文件的压缩合并,减少HTTP请求次数,提高网页加载速度。 调试工具:拥有内置的调试工具,便于开发者进行前端代码的调试。 扩展插件:支持插件扩展,用户可以根据需要安装额外的插件来增强IDE的功能。 模板支持:内置了多种模板,包括常用的框架模板,方便快速搭建项目。 下载及安装 HBuilderX官网下载地址:https://www.dcloud.io/hbuilderx.html,可以在下载页面选择Windows或MacOS平台,Windows平台压缩包下载后解压即可运行。 HBuilderX首次运行时会出现主题和快捷键选择窗口,可以选择喜欢的主题和习惯的操作方式。点击【开始体验】按钮后,可以在HBuilderX自述文件中查看HBuilderX的特性。 图片 基本操作 语法提示:工具提供多种框架语法提示库,可以在窗口右下角进行选择。 图片 代码助手:工具提供智能的代码提示助手,可按【ctrl+I】打开代码助手,代码提示后可以按【alt+数字】选择直接选择某个项目。 图片 语法帮助:在编辑区域鼠标悬停会自动弹出语法或说明提示,按下【F1】键可以跳转到官方手册(2.5.7以上版本会在外部浏览器打开说明文档)。 图片 多光标操作:在编辑区域按【ctrl+鼠标左键】可增加一个光标,【ctrl+鼠标右键】可取消一个光标或选区。 图片 相同词操作:在编辑区域按【ctrl+shift+L】可以选择和光标位置相同的所有词同时进行编辑。 列选择:在编辑区域使用【alt+鼠标拖选】进入列选择编辑方式。 图片 Emmet支持:Emmet语法可以用缩写来动态生成所需要的结构及样式,从而极大改善HTML和CSS的编写流程。 图片 智能双击: 双击引号/括号内侧,选中引号/括号内的内容 双击逗号两侧,选择逗号前一段或后一段 双击行尾,选中该行(不含回车符) 双击连词符(-_)选中整个词 双击折叠行首内容开头,选择折叠段落 双击行首缩进,选择相同缩进的段落 双击列表符号,选择列表段落 双击Tag开头或结尾,选择整段Tag 双击属性赋值等号=,选择Html属性 双击if、function等关键字,选择整段包围区域 双击分号,选择js等语言的;分号前段落 双击css类名左侧,选择Css类 双击注释符选择注释区域 双击#选择markdown标题段落 双击语法定义符开头选择markdown图片、超链接、加粗、倾斜、代码等语法区 还有更多的操作和编辑技巧可以查看官方文档:https://hx.dcloud.net.cn 另外,软件对vue开发做了大量优化,新版本仅支持uni-app、uni-app cli、vue2-cli、vue3-cli项目中可以切换vue语法提示库;对Markdown文件编辑提供了强大支持,支持Emmet、快捷键编辑、智能粘贴、路径提示、Mermaid流程图等;对Json编辑进行了优化;支持开发小程序;可以创建uni-app项目;支持java插件、nodejs插件,并兼容了很多vscode的插件及代码块。 总之,HBuilderX非常适用于Web前端开发人员,特别是在进行HTML5相关的项目开发时非常高效。此外,由于其对多种语言的支持,也可以作为通用的轻量级开发工具使用。
-
你不知道的 7 项 CSS 新功能 CSS 一直在进步,不断推出新的功能,让开发更加强大、简单、有趣。以下是一些最新的 CSS 更新,它们不仅能优化你的工作流程,还会改变你对 CSS 的使用方式。我们也会通过一些示例帮助你理解这些功能的应用场景。 1. 无需 Flexbox 或 Grid 的快速居中对齐 还记得以前用 CSS 居中元素的“痛苦”吗? 现在,有了 align-content 属性,居中对齐变得轻松无比,无需额外的容器。 .my-element { display: block; align-content: center; }再也不用纠结是选 flexbox 还是 grid 了。 2. 更智能的 CSS 变量:@property CSS 变量非常灵活,但也存在缺陷,比如缺乏类型约束和默认值支持。 现在,@property 规则让你可以定义具有特定类型、继承规则和初始值的自定义属性。 传统做法: :root { --rotation: 45deg; } div { transform: rotate(var(--rotation)); }如果意外赋值 --rotation: blue;,代码可能会报错或行为异常。 新方法: @property --rotation { syntax: '<angle>'; inherits: false; initial-value: 0deg; } div { transform: rotate(var(--rotation)); } syntax: 限定变量值类型,比如 <angle> 表示角度。 inherits: 决定变量是否继承父级值。 initial-value: 定义默认值(这里是 0deg)。 这不仅提升了代码的健壮性,也减少了意外错误。 3. @starting-style:解决页面加载时样式闪烁问题 开发者经常遇到页面加载时内容闪烁的问题(FOUC)。 @starting-style 提供了解决方案,可以为元素设置初始样式,避免布局错乱。 @starting-style { .modal { opacity: 0; visibility: hidden; } } .modal { opacity: 1; visibility: visible; transition: opacity 0.3s ease, visibility 0.3s ease; }这样,即使在页面加载时,也能保证模态框的显示效果流畅自然,无需繁琐的内联样式。 4. 数学功能升级:更多强大的计算方法 以前,calc() 是 CSS 唯一的数学函数,但功能有限。 现在,新增了 round()、mod() 和 rem() 等强大函数,大幅扩展了计算能力。 传统方式: .element { width: calc(100% - 50px); }新功能: .box { /* 四舍五入为 3px */ margin: round(2.5px); } .stripe:nth-child(odd) { left: calc(var(--index) * 50px mod 200px); } .circle { /* 输出 1px */ width: rem(10px, 3px); }这些新增函数让复杂的布局计算变得更加灵活且直观。 5. 光明与黑暗的完美切换:light-dark() 定义明暗主题的样式曾经需要依赖媒体查询,容易导致代码重复。 现在,light-dark() 函数简化了这一过程。 传统方法: body { background-color: white; color: black; } @media (prefers-color-scheme: dark) { body { background-color: black; color: white; } }新方法: body { background-color: light-dark(white, black); color: light-dark(black, white); } 第一个值是浅色模式的样式。 第二个值是深色模式的样式。 再也不用重复代码,主题切换更加高效。 6. 表单验证新伪类::user-valid 和 :user-invalid 以前,:valid 和 :invalid 的表单验证伪类会在页面加载时触发,导致样式误判。 新伪类 :user-valid 和 :user-invalid 专注于用户交互后的状态,大幅提升体验。 新方法: input:user-valid { border-color: green; } input:user-invalid { border-color: red; }这避免了未交互前就出现错误提示的问题,让表单验证更贴合实际需求。 7. interpolate-size:更顺滑的尺寸动画 CSS 一直以来对高度、宽度等尺寸的动画支持不够友好。 现在,有了 interpolate-size 属性,尺寸动画变得更加平滑自然。 传统方法: .collapsible { overflow: hidden; max-height: 0; transition: max-height 0.3s ease-out; } .collapsible.open { max-height: 500px; }新方法: .panel { interpolate-size: height 0.5s ease; } .panel.open { height: auto; }浏览器会动态计算起始和结束尺寸,无论内容多少,动画都能流畅衔接。 总结 这些 CSS 新功能都得到了主流浏览器的支持,无论是 Chrome、Firefox 还是 Safari,都可以立即开始使用。掌握这些特性不仅能提升你的工作效率,还能让代码更加简洁优雅。
-
npm最新国内镜像源设置 国内源 淘宝:https://registry.npmmirror.com/ 腾讯云:https://mirrors.cloud.tencent.com/npm/ CNPM:https://r.cnpmjs.org/ 设置 #查询当前使用的镜像源 npm get registry #设置为淘宝镜像源 npm config set registry https://registry.npmmirror.com/ #验证设置 npm get registry #还原为官方源 npm config set registry https://registry.npmjs.org/
-
PHP 实现 Wget 在线仿站工具 引言 项目的核心是使用PHP处理用户请求,通过SSH连接服务器执行爬取命令,并将结果发送到用户邮箱。 功能概述 该工具具备以下功能: 输入有效的URL和邮箱。 验证URL格式。 通过SSH连接执行Wget命令抓取网页。 生成ZIP文件并通过邮箱通知用户。 页面结构 页面使用Bootstrap框架实现响应式设计。以下是页面的基本HTML结构示例: <!DOCTYPE html> <html lang="zh-cn"> <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1" /> <title>在线仿站工具</title> <link rel="stylesheet" href="https://cdn.staticfile.org/twitter-bootstrap/4.3.1/css/bootstrap.min.css"> </head> <body> <nav class="navbar sticky-top navbar-expand-lg navbar-light bg-white border-bottom"> <div class="container"> <a class="navbar-brand" href="./">在线仿站工具</a> </div> </nav> <div class="container mt-5"> <div class="row"> <div class="col-md-8 offset-md-2"> <input type="text" id="url" class="form-control" placeholder="请输入有效的网址" required /> <input type="text" id="email" class="form-control mt-2" placeholder="请输入有效的邮箱" required /> <input id="submit" type="button" value="提交任务" class="btn btn-primary btn-block mt-3" /> </div> </div> </div> <script src="./assets/js/common.js"></script> </body> </html>图片 后端逻辑 后端使用PHP实现,主要功能集中在api.php文件中。以下是该文件的核心代码示例: 1. 请求处理 首先,我们检查请求方法是否为POST,并获取URL和邮箱: <?php if ($_SERVER['REQUEST_METHOD'] !== 'POST') { exit(json_encode(array('code' => '-1', 'msg' => '仅支持POST请求'), JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT)); } $url = $_POST['url']; $email = $_POST['email'];2. URL验证 使用正则表达式验证输入的URL格式,确保用户输入的是有效的URL: $preg = "/^http(s)?:\\/\\/.+/"; if (!preg_match($preg, $url)) { log_error("Invalid URL format: $url"); exit(json_encode(array('code' => '-1', 'msg' => '域名请带上协议头!如(http:// 或 https://)'), JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT)); }3. 生成文件名 生成唯一的文件名以便于后续的下载: $timestamp = time(); $file = "website_$timestamp.zip";4. 执行SSH命令 通过自定义的SSH类连接到服务器并执行Wget命令: $ssh = new Components_Ssh($host, $user, $pass, $port, $log); $command = escapeshellcmd("bash /www/wwwroot/{$site_url}/wget_site.sh {$url} {$file} >/dev/null && echo \"success\""); $result = $ssh->cmd($command);5. 检查文件生成状态 检查文件是否成功生成,若未生成,则记录错误信息: if (!file_exists('./down/' . $file)) { log_error("File generation failed for: $url"); exit(json_encode(array('code' => '-1', 'msg' => '爬取失败,请稍后再试。'), JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT)); }6. 发送邮件通知 发送爬取结果到用户邮箱的代码如下: $content = '爬取成功,下载链接:' . $site_url . '/down/' . $file; $wz = $smtpapi . "?adress=" . urlencode($email) . "&isHTML=false&title={$site_url}爬取成功&content=" . urlencode($content); $response = @file_get_contents($wz);7. 错误日志记录 使用自定义的log_error函数记录所有错误信息,确保系统的可维护性: function log_error($message) { error_log(date('Y-m-d H:i:s') . " - " . $message . "\n", 3, '/path/to/error.log'); }8. SSH连接类 以下是一个简单的SSH连接类示例,供后续使用: class Components_Ssh { private $connection; public function __construct($host, $user, $pass, $port = 22) { $this->connection = ssh2_connect($host, $port); ssh2_auth_password($this->connection, $user, $pass); } public function cmd($command) { $stream = ssh2_exec($this->connection, $command); stream_set_blocking($stream, true); return stream_get_contents($stream); } }9. Wget脚本示例 wget_site.sh脚本负责执行实际的爬取操作,代码示例如下: #!/bin/bash url=$1 file=$2 wget --mirror --convert-links --adjust-extension --page-requisites --no-parent $url -P /www/wwwroot/your_site/down/ zip -r /www/wwwroot/your_site/down/$file /www/wwwroot/your_site/down/*10. 处理返回结果 处理执行命令后的返回结果以便后续使用: if ($result === 'success') { exit(json_encode(array('code' => '1', 'msg' => '爬取任务已提交,请查看邮箱。'), JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT)); } else { log_error("Command execution failed: $command"); exit(json_encode(array('code' => '-1', 'msg' => '命令执行失败,请重试。'), JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT)); }11. 提交任务前的提示 在前端添加任务提交前的提示,增强用户体验: document.getElementById('submit').onclick = function() { const url = document.getElementById('url').value; const email = document.getElementById('email').value; if (url === '' || email === '') { alert('请填写所有字段!'); return; } fetch('api.php', { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body: `url=${encodeURIComponent(url)}&email=${encodeURIComponent(email)}` }) .then(response => response.json()) .then(data => { if (data.code === '-1') { alert(data.msg); } else { alert('任务已提交,请查看您的邮箱!'); } }); };12. 邮件发送状态检查 检查邮件发送状态以确保用户能够及时收到通知: if ($response === false) { log_error("Email failed to send to: $email"); exit(json_encode(array('code' => '-1', 'msg' => '邮件发送失败,请检查邮箱地址。'), JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT)); }核心技术 PHP:后端逻辑处理,包括请求处理和SSH命令执行。 SSH:使用SSH连接到服务器进行远程操作。 Wget:爬取网页及其资源的工具。 Bootstrap:用于简化前端布局和样式。 总结 这个在线仿站工具允许用户快速爬取并下载网站资源。关键在于安全地处理用户输入、稳定地执行后端爬取操作,并确保系统的可维护性。
-
CSS奇思妙想:动态点亮文本下划线 一、简介 在现代网页中,为用户提供流畅且具有吸引力的交互体验至关重要。本文分享了一种纯CSS实现的动态文本下划线效果。当鼠标悬停在文本上时,无论文本的宽度如何、有多少行、行中有多少字,下划线都会以从左到右、从上到下的顺序,自适应的在文本底部动态、平滑地显示出来。而当鼠标移出时,下划线则按照相反的方向,逐行动态、平滑地隐藏,直到恢复至最初状态。具体效果如下: 案例效果 图片 该动画效果看起来很简单,具体实现也确实不难,难的是实现思路,需要扎实的前端CSS基础和很大的脑洞。该动画效果中利用到了:行内元素的基础特性、元素背景特性、trasition过渡等CSS的基础知识。 该动画可用于资料列表、新闻列表等页面场景中,可以突出显示鼠标所聚焦的文本内容,提升用户的视觉交互体验。 二、具体实现 1、实现思路 ① 看到文本下划线,我们首先会考虑通过text-decoration属性来实现,将文本的该属性默认状态设置为none,然后在hover的时候将其设置为underline,使文本出现下划线。但仔细想想,会发现这个方案并不可取,因为text-decoration是一个离散属性,只能在不同属性值状态之间切换,无法通过trasition过渡实现平滑动画。 ② 排除掉text-decoration属性之后,我们继续思考还有什么样式属性可以实现类似下划线的效果。此时应该想到两个属性:border和background。然后进一步思考,发现border只能控制宽度或者颜色,而且每一行的border只能同时显示,或者同时隐藏,无法实现逐行按序显示/隐藏下划线的效果。排除掉border后,最终确定需要依赖background属性来实现目标效果。 ③ 确定属性之后,首先我们要使用行内元素(或设置display: inline;的其他元素)来承载文本内容。因为行内元素具有一个重要的特性:元素的宽度和高度由其内容决定,当文本内容换行时,从布局角度看,每个换行后的文本片段会被视为单独的内联内容部分,而不是一个整体的块级元素那样有明确的整体宽度和高度概念。这一点特性,在后面通过背景实现下划线效果时将会起到关键作用。 ④ 接下来思考应该如何设置背景属性。目标效果中下划线是纯色的,首先想到的应该是设置背景颜色,但这种想法是错误的。因为设置背景颜色会直接应用到整个行内元素内容区域,而不是对每行的内联文本片段进行单独设置,且无法控制尺寸和位置。但如果给行内元素设置的背景图片,则会基于每行的内联文本片段来进行处理,而非整体内容区域,并且可以通过 background-size 和 background-position 属性进行精确控制。简单来说:我们给换行的行内元素设置背景图片时,其实际效果并非给整个内容块设置背景,而是给每行的内联文本片段设置背景。 ⑤ 经过前面四步的思考,我们已经明确了实现目标效果的关键,接下来只需按部就班设置相关样式即可。首先通过linear-gradient生成一张纯色图片作为元素的背景图片,并设置为不允许重复;其次设置background-size属性,设置初始宽高,使其看起来为一条线;然后通过background-position将背景定位在元素底部,变为下划线。此时查看页面样式,发现每行文本底部的下划线效果已经实现。 ⑥ 最后我们实现平滑动画效果。首先设置background-size属性,将初始宽度设置为0,将背景隐藏;然后在元素hover的时候,将背景宽度恢复为100%,显示背景;再然后借助transition属性,实现平滑的过渡效果,使得背景能逐行动态平滑的显示。 2、具体代码 HTML <div class="title-box"> <span> 有志者,事竟成。破釜沉舟,百二秦关终属楚。苦心人,天不负。卧薪尝胆,三千越甲可吞吴。有志者,事竟成。破釜沉舟,百二秦关终属楚。苦心人,天不负。卧薪尝胆,三千越甲可吞吴。 </span> </div>CSS .title-box { /* 父级盒子设置宽度 限制内部文本宽度 使其换行 */ width: 336px; margin: 50px auto; } .title-box span { color: #333; /* 通过渐变设置图片背景 且不允许重复 */ background: linear-gradient(to right, #333, #333) no-repeat; /* 背景初始宽度为0 高度为2px */ background-size: 0 2px; /* 背景位置在文字底部 */ background-position: left bottom; /* 设置平滑过渡效果 */ transition: background-size 1.5s ease-in-out; cursor: pointer; } /* hover时触发动画 */ .title-box span:hover { /* 背景宽度变为100% 高度不变 */ background-size: 100% 2px; /* 可以通过改变背景位置 改变下划线出现和消失的方向 */ /* background-position: right bottom; */ }三、相似扩展 1、效果1 图片 2、效果2 图片
-
揭开迷雾:全面解析 JS 中的 this 指向问题 一、简介 身为一个使用JavaScript的开发者,我们或多或少在开发中都遇到过因 this 指向不明导致的代码错误。函数中this 关键字的指向是一个复杂但又关键的概念,大多数时候它的指向不是在编写时确定的,而是在代码执行时根据函数的调用方式来决定,简单来说,this 的指向取决于函数的调用者(箭头函数除外)。 本文将全面详细的讲解this的指向规则,让你能够清晰理解并正确使用 this,避免由于错误的指向导致的程序异常,并提升对JavaScript的整体理解。 二、详解 1、 全局作用域 在全局作用域下(非严格模式),也就是最外层的JS脚本中,this指向全局对象(浏览器中为window对象)。 <script> console.log(this === window); // 浏览器中输出 true </script>2、普通函数 在调用一个独立的普通函数时(非严格模式),此时相当于通过全局对象来调用函数,this会进行默认绑定,指向全局对象(浏览器中为window对象)。 重点:普通函数中的 this 指向是动态的,取决于函数的调用方式,会因为调用方式的不同而发生改变。 // 非严格模式下 // 两个普通函数 function showThis() { console.log(this); } const showThis2 = function() { console.log(this); } // 调用普通话函数 showThis(); // 输出 window 对象 showThis2(); // 输出 window 对象 // 相当于通过window来调用函数 因此this指向window window.showThis(); // 输出 window window.showThis2(); // 输出 window如果让一个对象内部的方法指向普通函数,并通过该对象来调用函数,则this指向发生改变,指向调用函数的对象,谁进行调用,this就指向谁。 // 普通函数 function showThis() { console.log(this); } // 创建对象 并让对象的方法指向函数 const obj = { getThis: showThis }; // 通过对象调用函数 obj.getThis(); // 输出 obj3、箭头函数 箭头函数不会创建自己的this,其内部的this会继承函数定义时其外层最近的非箭头函数的 this 指向。如果是在全局作用域内使用箭头函数,则this指向全局对象;如果是在构造函数中使用箭头函数,则this指向new出来的新对象... 重点:箭头函数中的 this 指向是静态的,在函数定义时确定,后面不会因为调用方式的不同而发生改变,也无法被call/apply/bind等方法改变。 // 非严格模式下 // 箭头函数 this指向window const showThis3 = () => { console.log(this); } // 调用箭头函数 showThis3(); // 输出 window 对象如果让一个对象内部的方法指向箭头函数,并通过该对象来调用函数,此时虽然函数的调用者发生变化,但this指向不会改变,依旧指向全局对象。 // 箭头函数 const showThis3 = () => { console.log(this); } // 创建对象 并让对象的方法指向函数 const obj = { getThis: showThis3 }; // 通过对象调用函数 但this指向不会改变 obj.getThis(); // 输出 window4、调用对象的方法 在通过对象直接调用其内部的方法时,方法中的this 会进行隐式绑定,指向当前对象。 const obj = { getThis: function() { // 对象的方法中的this 指向当前对象 console.log(this) } }; // 通过对象调用方法 this指向当前对象 obj.getThis(); // 输出 obj如果先用一个变量指向对象的方法,然后再通过变量来调用对应的方法时,this的指向将不再指向对象,而是指向全局对象(浏览器中为window对象)。因为此时相当于进行独立函数调用,遵守独立函数的默认绑定规则。 <script> const obj = { getThis: function () { console.log(this) } }; // 通过对象调用方法 this指向对象本身 obj.getThis(); // 输出 obj // 使用变量指向对象的方法 但不调用 const fun = obj.getThis; // 通过变量调用方法 此时相当于独立函数调用 所以this指向window fun(); // 输出 window </script>如果使用一个对象的方法指向另一个对象的方法,此时两个对象都可以调用该方法,那么具体的this指向取决于调用者,谁进行调用, this就指向谁。 <script> const obj = { getThis: function () { console.log(this) } }; // 通过obj调用方法 this指向obj本身 obj.getName(); // 输出 obj // 在另一个对象中使用属性指向前面对象的方法 const obj2 = { // 指向obj的方法 get2: obj.getThis } // 通过obj2调用方法 此时this指向obj2 obj2.get2(); // 输出 obj2 </script>还存在一种特殊情况:如果对象的方法使用的是箭头函数,那么方法中的 this 将继承定义对象时所在作用域的this指向,而非对象本身,并且后续不会因为调用者改变而改变this指向。 <script> // 如果对象的方法使用的是箭头函数 那么this将继承定义对象时所在作用域的this指向 // 在全局作用域下定义对象 const obj = { getThis2: () => { // 此时箭头函数中的this 指向全局对象window console.log(this) } } // 虽然是通过obj调用的 但是其this还是会指向全局对象window obj.getThis2(); // 输出 window // 使用变量指向对象的方法 但不调用 const fun = obj.getThis2; // 通过变量调用方法 this依旧指向window fun(); // 输出 window // 在另一个对象中使用属性指向对象的方法 const obj2 = { // 指向obj的方法 get2: obj.getThis2 } // 通过obj2调用方法 此时this依旧指向window obj2.get2(); // 输出 window </script>5、调用构造函数 在使用new关键字调用构造函数时,会创建一个新的对象,此时this 会指向被创建的新对象。内部的普通函数和箭头函数内的 this 也都指向被创建的新对象,但后续普通函数的this指向是会根据调用者不同而发生改变,箭头函数则不会改变。 // 构造函数 function Person(name) { // 构造函数中的this指向通过new创建的新对象 this.name = name; // 普通函数 this.getThis = function () { console.log(this) } // 箭头函数 this.getThis2 = () => { console.log(this) } } // 调用构造函数创建新对象 const person = new Person('Bob'); // 通过新对象调用方法 此时this指向新对象 person.getThis(); // 输出 person person.getThis2(); // 输出 person // 在另一个对象中使用属性指向new出来的新对象的方法 const obj = { getThis: person.getThis, getThis2: person.getThis2 } // 通过obj调用方法 此时this指向会根据函数类型不同而不同 obj.getThis(); // 输出 obj obj.getThis2(); // 输出 person 还存在一种特殊情况,就是Function()构造函数,在使用该构造函数创建新函数时,其内部的this指向并非指向新函数本身,而是根据函数被调用的方式来决定this指向,谁调用this就指向谁。 // 非严格模式 // 在全局作用域下 const func = new Function('console.log(this)'); // 相当于通过window来调用函数 因此this指向window func(); // 输出 window 对象 // 在对象中 const p = { name: 'Alice', get: func } // 通过对象中调用函数 对象内的this指向对象本身 p.get(); // 输出 Person { name: 'Alice', get: [Function] }6、事件处理函数 在事件处理函数中,如果绑定的函数是普通函数,则相当于由DOM元素调用函数,则函数内的this指向触发事件的DOM元素;如果绑定的是箭头函数,则函数内的this会继承绑定事件处理函数所在作用域的this指向。 <button id="btn">Click me</button> <!-- 内联的事件处理器也一样 --> <button onclick="console.log(this)">Click me</button> <script> const btn = document.getElementById('btn'); // 绑定普通函数 相当于dom调用函数 所以this指向button元素 btn.addEventListener('click', function () { console.log('普通函数this--', this); // 输出 button 元素 }); // 绑定箭头函数 继承绑定事件处理函数所在作用域的this 此时为全局作用域 所以this指向window btn.addEventListener('click', () => { console.log('箭头函数this--', this); // 输出 window }); </script>7、回调函数 当把一个函数作为回调函数传递后,其this指向取决于回调函数的调用方式和函数的类型两者: 如果函数类型为普通函数且被传递后是直接独立调用的,此时相当于独立函数调用,函数内部this指向全局对象(浏览器中为window对象)。 如果函数类型为普通函数且被传递后是通过别的对象进行调用的,此时相当于通过对象调用函数,函数内部的this指向函数的调用者。 如果函数类型为箭头函数,则无论被传递后是如何进行调用的,都不会修改函数内部的this指向,箭头函数内部的this指向在函数定义后确定就不会再被改变。 const obj = { // 普通函数 showThis: function () { console.log(this); }, // 箭头函数 showThis2: () => { console.log(this); } } function fun(fn) { // 直接独立调用回调函数 fn(); } function fun2(fn) { // 通过对象调用回调函数 const a = { getThis: fn, } a.getThis(); } // 对象普通函数中的this指向对象本身 obj.showThis(); // 输出 obj // 对象箭头函数中的this继承定义对象时所在作用域的this指向 此时为window obj.showThis2(); // 输出 window // 传入一个普通函数 并且直接调用函数 相当于独立函数调用 所以this指向window fun(obj.showThis); // 输出 window // 传入一个箭头函数 并且直接调用函数 this指向不受影响 依旧为window fun(obj.showThis2); // 输出 window // 传入一个普通函数 并且通过对象调用函数 this被改变 指向其调用者 fun2(obj.showThis); // 输出调用者对象 a // 传入一个箭头函数 并且通过对象调用函数 this指向不受影响 依旧为window fun2(obj.showThis2); // 输出 window数组提供的内置方法中,除了传递回调函数之外,还可以通过第二个参数指定回调函数的this指向。如果未指定this的指向,则取决于函数的调用环境内的this指向。 // 创建对象 const obj = { multiplier: 2 }; // 创建数组 const numbers = [1, 2, 3]; // 使用数组提供的map方法 第一个参数是回调函数 第二个参数是指定回调函数的this指向 const doubled = numbers.map(function (number) { // 回调函数中的this指向obj 所以this.multiplier指向obj.multiplier 值为2 return number * this.multiplier; }, obj); // 最终运算结果为[2, 4, 6] console.log(doubled); // 未指定this指向 回调函数中的this指向window const doubled2 = numbers.map(function (number) { return this; }); // 最终运算结果为[window, window, window] console.log(doubled2);8、call/apply/bind 显式指定 call()、apply()、bind()三个方法都可以显式的指定函数的this指向,只是在一些具体细节上有所不同。例如:call() 和 apply() 方法会在改变函数内部this指向的同时,调用函数执行一次。而bind() 不会调用函数执行,仅改变函数的this指向;在向函数传递参数时,call() 和 bind() 传递的参数都是用逗号隔开的独立参数,而apply()是以数组的形式来传递参数。 // call方法 function greet(text) { console.log(`${text}, ${this.name}`); } const user = { name: 'Charlie' }; // 使函数的this指向user 且会调用执行一次 greet.call(user, 'Hello'); // 输出 'Hello, Charlie' // apply方法 function introduce(age, gender) { console.log(`Name: ${this.name}, Age: ${age}, Gender: ${gender}`); } const user2 = { name: 'Dana' }; // 使函数的this指向user 且会调用执行一次 introduce.apply(user2, [25, 'Female']); // 输出 'Name: Dana, Age: 25, Gender: Female' // bind方法 const module = { x: 42, getX: function () { return this.x; } }; // 此时指向对象的方法 但不调用执行 const retrieveX = module.getX; // 调用执行 此时的 this 指向window 因为相当于独立函数调用 console.log(retrieveX()); // 输出 undefined // 修改this指向 使函数的this指向module 且不会调用执行 const boundGetX = retrieveX.bind(module); // 手动调用执行一次 console.log(boundGetX()); // 输出 42三、课后练习 1、下面代码的执行结果是什么? // 全局作用域 且非严格模式 [1, 2, 3].forEach(function (item) { console.log(this); });2、下面代码的执行结果是什么? // 全局作用域 且非严格模式 const obj = { a: () => { console.log(this); }, b: function () { console.log(this); } }; obj.a(); obj.b();3、下面代码的执行结果是什么? // 全局作用域 且非严格模式 function Person(name) { this.name = name; this.getName = () => { return this.name; }; } const person1 = new Person('Alice'); const person2 = { name: 'Bob' }; person2.getName = person1.getName; console.log(person2.getName());欢迎大家在评论区揭晓答案