找到
171
篇与
技术教程
相关的结果
- 第 9 页
-
PHP自建MD5解密平台 使用 PHP 构建 MD5 彩虹表生成器的完整过程 在信息安全领域,彩虹表(Rainbow Table) 是一种通过预计算哈希值和对应原始值的方式来破解密码的工具。为了实现一个简单高效的 MD5 彩虹表生成器,我使用了 PHP 和 MySQL,本文将详细介绍整个实现过程,并深入探讨如何解决开发中遇到的问题。 项目背景和需求 在项目的初始阶段,我们需要一个自动化生成 MD5 彩虹表的工具,并将生成的数据存储在 MySQL 数据库中。主要功能包括: 批量生成 MD5 哈希和对应的原始字符串 去重处理,避免重复存储相同的 MD5 哈希 可扩展性,支持任意字符集和字符串长度。 高效插入数据,避免性能瓶颈。 然而,在实现的过程中遇到了两个关键问题: 每次刷新页面时,因没有去重导致重复数据大量生成。 设置批量生成的目标条数(如 10,000 条),但数据量却无法稳定在目标值。 接下来,我们将详细拆解整个解决方案和代码实现。 环境准备 确保开发环境的搭建,包括: PHP 7.x或更高版本 MySQL 5.x或更高版本 Apache或Nginx服务器 数据库设计 在生成彩虹表之前,我们需要设计存储数据的数据库表。 数据表结构 表名为 rainbow_table,包含以下字段: id: 自增主键,用于标识每条记录。s hash: 存储 MD5 哈希值。 original: 存储原始字符串。 unique_hash: 创建唯一约束,确保哈希值不重复。 SQL 表的创建代码如下: CREATE TABLE rainbow_table ( id INT(6) UNSIGNED AUTO_INCREMENT PRIMARY KEY, hash VARCHAR(32) NOT NULL, original VARCHAR(255) NOT NULL, UNIQUE KEY unique_hash (hash) );如果表不存在,我们会在 PHP 脚本中动态创建它,确保代码的鲁棒性。 PHP 脚本实现 核心逻辑概述 字符集配置:可以自由定义生成字符串所使用的字符集,包括大小写字母、数字和特殊符号。 随机生成字符串:通过随机组合字符,生成指定长度的字符串。 MD5 哈希计算:将随机字符串计算为 MD5 哈希值。 数据插入与去重:使用 INSERT IGNORE 避免重复插入。 批量生成与页面自动刷新:每次生成一定数量的数据后自动刷新页面,持续生成。 完整 PHP 代码如下: <?php $servername = "localhost"; $username = "md5"; $password = "123456"; $dbname = "md5"; $tableName = "rainbow_table"; // 创建数据库连接 $conn = new mysqli($servername, $username, $password, $dbname); // 检查连接是否成功 if ($conn->connect_error) { die("连接失败: " . $conn->connect_error); } // 检查表是否存在,如果不存在则创建 $tableExists = $conn->query("SHOW TABLES LIKE '$tableName'")->num_rows > 0; if (!$tableExists) { $createTableSql = "CREATE TABLE $tableName ( id INT(6) UNSIGNED AUTO_INCREMENT PRIMARY KEY, hash VARCHAR(32) NOT NULL, original VARCHAR(255) NOT NULL, UNIQUE KEY unique_hash (hash) )"; if ($conn->query($createTableSql) === TRUE) { echo "表 $tableName 创建成功<br>"; } else { die("创建表错误: " . $conn->error); } } // 彩虹表生成函数 function generateRainbowTable($charset, $batchSize, $conn, $tableName) { $charsetLength = strlen($charset); $count = 0; $maxLength = 18; // 设置随机字符串最大长度 while ($count < $batchSize) { // 随机生成字符串长度 $length = rand(1, $maxLength); $count += generateRandomCombination($charset, $length, $charsetLength, $conn, $batchSize - $count, $tableName); } echo "生成了 $count 条记录\n"; } function generateRandomCombination($charset, $length, $charsetLength, $conn, $remaining, $tableName) { if ($remaining <= 0) return 0; $currentString = ''; for ($i = 0; $i < $length; $i++) { $currentString .= $charset[rand(0, $charsetLength - 1)]; } $hash = md5($currentString); // 使用 INSERT IGNORE 避免重复插入 $stmt = $conn->prepare("INSERT IGNORE INTO $tableName (hash, original) VALUES (?, ?)"); $stmt->bind_param("ss", $hash, $currentString); $stmt->execute(); return $stmt->affected_rows > 0 ? 1 : 0; } // 配置 $charset = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789.@#*?!$%^&()-_=+[]{}|;:,.<>~'; // 定义字符集 $batchSize = 2000; // 每次生成 2000 条 generateRainbowTable($charset, $batchSize, $conn, $tableName); $conn->close(); ?>关键点解析 1. 随机生成字符串 $length = rand(1, $maxLength); $currentString .= $charset[rand(0, $charsetLength - 1)];使用 rand() 随机生成字符串长度,再通过字符集随机组合字符,生成目标字符串。 2. 避免重复插入 $stmt = $conn->prepare("INSERT IGNORE INTO $tableName (hash, original) VALUES (?, ?)");使用 INSERT IGNORE 确保插入数据时,遇到重复哈希值会自动跳过,避免浪费资源。 3. 批量生成与计数 $count += generateRandomCombination($charset, $length, $charsetLength, $conn, $batchSize - $count, $tableName);每次生成一定数量的数据,直到达到目标批量数量。 前端自动刷新页面 为了实现自动化生成,我们在前端页面使用 JavaScript 设置定时刷新,每次刷新生成一批数据: <script> // 页面加载完成后自动刷新 window.onload = function() { setTimeout(function() { location.reload(); }, 1000); // 1秒后刷新页面 }; </script>通过这种方式,可以在页面刷新时持续调用生成函数,直到数据库存储满足预期需求。 问题与优化方案 问题 1:重复数据导致效率低下 最初代码未去重,每次刷新生成的大量数据包含重复项,浪费了存储和计算资源。通过添加 INSERT IGNORE 和数据库唯一索引,解决了此问题。 问题 2:生成数据量不足 由于生成逻辑中可能因重复数据导致插入失败,数据总量无法达到目标条数。通过调整随机生成逻辑,确保生成字符串更加分散,提高了数据覆盖率。 结论 本文通过详细介绍一个 PHP 实现的 MD5 彩虹表生成器,展示了从数据库设计到代码优化的完整流程。通过随机生成字符串、去重插入以及前端自动刷新机制,实现了高效的批量生成工具。 该工具不仅适用于 MD5,还可拓展为其他哈希算法生成器(如 SHA-1 或 SHA-256)。未来可进一步优化数据生成算法,提高生成速度和覆盖范围。 最后我的MD5解密平台:隐藏内容,请前往内页查看详情 图片 项目地址 隐藏内容,请前往内页查看详情
-
解决 Windows 宝塔面板报错 KeyError: ‘list’ Windows 宝塔面板 7.7.0 以上版本报错 KeyError: 'list' 是因为 7.7.0 以上的版本不再支持 Windows 家庭版的使用,并且根据我当时修复报错文件 panelPlugin.py 时发现 7.7.0 以上的版本时没有这个文件的,只有被编译好的不能反编译的 panelPlugin.pyd 文件,也就是说 Windows 宝塔 7.7.0 以上版本不再是开源面板,而宝塔官方给出的解决方案是安装 Windows 服务器版本虚拟机,但是这样不仅麻烦而且会占用电脑的性能,目前根据我在网上搜集到的信息结合自己的摸索总结出两个解决方案 图片 方案一 先使用 Shell 命令对宝塔面板进行降级到 7.7.0 版本 隐藏内容,请前往内页查看详情 降级到 7.7.0 版本后可以再使用 Shell 命令对宝塔面板升级到 8.1.0 版本,而后将不再会提示报错内容 隐藏内容,请前往内页查看详情 当然也可以只使用 7.7.0 版本 方案二 手动对宝塔面板进行降级到 7.7.0 版本,将文件 panel_7.7.0.zip 解压到宝塔安装目录的 panel 目录下 官方下载链接:隐藏内容,请前往内页查看详情 我自己备份的包:隐藏内容,请前往内页查看详情 然后再手动下载 8.1.0 版本的 panel_8.1.0.zip 文件解压到宝塔安装目录的 panel 目录下 官方下载链接:隐藏内容,请前往内页查看详情 我自己备份的包:隐藏内容,请前往内页查看详情
-
不要过度使用 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();计时结果如下: 图片
-
你不知道的 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());欢迎大家在评论区揭晓答案
-
PHP8.1 推出了 fiber,fiber可以实现什么样的操作 ? PHP 8.1 引入了新的特性 fiber,也称为协程(Coroutines),它为 PHP 提供了一种轻量级的并发编程方式,使得开发者可以更方便地实现异步和并发操作。 Fiber 可以实现以下一些新的操作和用途: 轻量级线程:Fiber 允许在一个单独的 PHP 进程中创建多个轻量级线程,这些线程可以独立地执行不同的任务,实现并发处理,而不需要创建额外的进程或线程。 非阻塞 IO:通过使用 Fiber,可以实现非阻塞的 IO 操作,例如异步读写文件、网络请求等,从而提高 IO 操作的效率,避免程序在 IO 等待时被阻塞。 并发任务处理:Fiber 可以用于处理并发任务,例如在同时进行多个网络请求、数据库查询等任务时,可以使用 Fiber 来管理和协调这些任务的执行,提高处理效率。 轻量级同步:Fiber 可以用于实现轻量级的同步操作,例如在多个 Fiber 之间进行数据交换或共享数据,而不需要使用传统的锁机制。 嵌套调用:Fiber 可以进行嵌套调用,允许在一个 Fiber 中调用另一个 Fiber,从而实现更复杂的异步调用和逻辑。 异步编程:Fiber 可以与异步编程模式结合,例如配合事件循环库,实现更高效的异步编程。 通过引入 Fiber,PHP 可以在单个进程中实现高效的并发处理,而不需要依赖于多线程或多进程,从而减少资源开销和复杂性。这使得 PHP 能够更好地应对高并发和IO密集型的应用场景,例如网络编程、Web服务等。 以下是几个示例代码,展示了 Fiber 可以实现的一些新操作: 1 异步任务处理: 使用 Fiber 可以方便地实现异步任务处理,允许在一个单独的 PHP 进程中并发执行多个任务,提高处理效率。在下面的示例中,我们创建了两个 Fiber 来同时处理两个耗时任务,并在结束后输出结果。 <?php use Fiber\Fiber; function task1() { echo "Task 1 started.\n"; Fiber::suspend(); echo "Task 1 resumed.\n"; Fiber::suspend(); echo "Task 1 finished.\n"; } function task2() { echo "Task 2 started.\n"; Fiber::suspend(); echo "Task 2 resumed.\n"; Fiber::suspend(); echo "Task 2 finished.\n"; } $fiber1 = new Fiber('task1'); $fiber2 = new Fiber('task2'); $fiber1->start(); $fiber2->start(); $fiber1->resume(); $fiber2->resume(); $fiber1->resume(); $fiber2->resume(); ?>2 协程嵌套: Fiber 允许在一个 Fiber 中调用另一个 Fiber,实现协程的嵌套调用。在下面的示例中,我们创建了两个 Fiber,其中一个 Fiber 内部调用了另一个 Fiber。 <?php use Fiber\Fiber; function task1() { echo "Task 1 started.\n"; Fiber::suspend(); echo "Task 1 resumed.\n"; Fiber::suspend(); echo "Task 1 finished.\n"; } function task2() { echo "Task 2 started.\n"; $fiber = new Fiber('task1'); $fiber->start(); $fiber->resume(); echo "Task 2 resumed.\n"; $fiber->resume(); echo "Task 2 finished.\n"; } $fiber2 = new Fiber('task2'); $fiber2->start(); $fiber2->resume(); $fiber2->resume(); ?>3 非阻塞 IO: 使用 Fiber 可以实现非阻塞的 IO 操作,例如异步读写文件。在下面的示例中,我们使用 Fiber 来实现异步读取文件的操作。 <?php use Fiber\Fiber; function readFileAsync() { $file = fopen('example.txt', 'r'); echo "Start reading file.\n"; while (!feof($file)) { echo "Read: " . fgets($file); Fiber::suspend(); } fclose($file); echo "Finish reading file.\n"; } $fiber = new Fiber('readFileAsync'); $fiber->start(); while ($fiber->status() !== Fiber::STATUS_FINISHED) { echo "Resuming fiber...\n"; $fiber->resume(); } echo "Fiber finished.\n"; ?>在上述示例中,我们使用 Fiber 来异步读取文件,每次读取一行数据后就暂停 Fiber,等待下一次恢复。这样可以实现在文件读取过程中不阻塞其他操作的效果。 这些示例只是 Fiber 的一部分用法,实际上 Fiber 提供了更多灵活的协程编程方式,可以用于解决并发问题和异步编程等场景。由于 Fiber 是一个相对较新的特性,它在 PHP 中的使用需要根据具体需求和场景进行谨慎考虑。