注意:示例图中的防抖事件/节流事件有
delay: 50ms
的延迟调用,示例图中仅表示普通事件进行防抖/节流处理后可以触发的对应事件,而非执行的时间点。
定义:函数防抖就是指触发事件后一定时间内函数只能执行一次,如果在这段时间内再次触发事件,则会重新计算函数执行时间。
举例:乘坐公交车时,司机需要等待最后一个人进入后才能关门。每次进入一个人,司机就会多等待几秒再关门。
1 | // immediate 首次是否立即执行 |
定义:函数节流就是指连续触发事件在一定时间内(周期)只执行一次,函数节流会稀释函数执行的频率。
举例:乘坐地铁过闸机时,每个人进入后 3 秒后门关闭,等待下一个人进入。
1 | // immediate 首次是否立即执行 |
相同点
函数防抖与函数节流都是用来控制某个函数在一定事件内触发的次数,降低事件触发频率,从而提高性能避免资源浪费。
不同点
函数防抖仅触发最后一次事件,其余事件被屏蔽;而函数节流保留第一次触发的事件,其余事件被屏蔽。
这里提醒一下,使用 COS 部署静态网站一定要使用 私有读写 的访问权限,并且一定要搭配 CDN 使用,否则网站遭受攻击会产生 大量的流量费用。
需要准备一个已经完成 ICP 备案的域名。如果没有域名可以在域名服务商处购买,如果没有完成域名备案,则可以使用 Serverless 备案,价格为 110 元 / 5 年 (这里以腾讯云为例)。
不同的云服务器服务商操作可能会有不同,这里仅以腾讯云为例进行介绍。
通过 pnpm 安装最新版本的 Serverless Cloud Framework,详情见 安装 Serverless Cloud Framework。
如果由于 网络问题 导致安装失败,请将 pnpm 切换为 淘宝的镜像源 后,重新进行安装。
打开 Git Bash 并定位到项目根目录。在项目根目录下,创建 serverless.yml 文件:
1 | touch serverless.yml |
在 serverless.yml 文件中进行如下配置:
1 | # serverless.yml |
通过 scf deploy
命令进行部署,并可以添加 --debug
参数查看部署过程中的信息。如您的账号未 登录 或 注册 腾讯云,您可以直接通过 微信 扫描命令行中的二维码进行授权登录和注册。
1 | scf deploy |
访问命令行输出的 Website URL,即可查看您的 Serverless Web 站点。
如果希望更新静态网站,需要在本地重新生成静态网站,再运行 Serverless Cloud Framework 更新静态网站。
通过以下命令移除 Hexo 网站:
1 | scf remove --debug |
当前默认支持 CLI 扫描二维码登录,如您希望配置持久的环境变量/密钥信息,也可以本地创建 .env
文件:
1 | touch .env # 腾讯云的配置信息 |
在 .env
文件中配置腾讯云的 SecretId 和 SecretKey 信息并保存:
1 | # .env |
COS 存储桶配置仅需要注意以下两点:
建议将防盗链设置开启,将静态网站域名设置为白名单即可,详情参见 设置防盗链。存储桶访问权限切记一定要修改为 私有读写,否则 不安全。
在 域名管理 中,点击 添加域名 进行 CDN 配置。
注意:每次修改静态网站后,需要 刷新 CDN,否则会出现 静态网站未刷新 的情况。
按照图片中进行配置即可。
baidu.com
以及 www.baidu.com
提升资源命中率
按照个人需求配置即可,如果是静态网站为 个人博客,且发布文章比较频繁,则建议将 html
设置为 不缓存。
提升访问性能
防止费用超额
用量封顶配置:必须开启,防止产生 高额账单。
可以参照图片进行配置,具体封顶流量参照个人静态网站实际情况进行配置。
提升访问安全
HTTPS 服务:推荐开启,但需要注意会产生 额外的流量费用
HTTPS 配置:如果开启 HTTPS, 则推荐配置。可以在 SSL 证书 申请免费证书
IP 访问限频配置:推荐开启,可一定程度上防范单一用户对资源、接口的恶意访问,需要根据个人静态网站实际情况进行配置
说明:IP访问阈值 并非单纯指单位时间访问的次数,而是包含一次访问 全部资源请求的次数。
如果域名已经托管在 DNSPod,则直接 一键配置 即可,否则需要在域名服务商处进行配置。
在域名管理中成功添加域名后,需要对相应域名进行补充配置。
防盗链配置:建议开启。通过对用户 HTTP Request Header 中 referer
字段的值设置访问控制策略,从而限制访问来源,避免恶意用户盗刷
1 | ## 推荐白名单 |
注意:别忘记添加当前静态网站域名进入白名单。
这里并非教学如何配置 DNS 解析记录,而是说明一下如何配置境内/外分别解析。
通过配置不同的 线路类型,以达到境内/外分别解析的效果。
对于境外的静态网站,这里推荐使用 Vercel 部署项目。
又拍云推出的开发者帮助计划,为开发者提供专业、免费、稳定的 CDN 及云存储服务,加速个人网站及 APP 等项目。
加入又拍云联盟,按步骤完成申请即可获得 10GB 免费存储空间 以及 15GB 免费 CDN 流量 (HTTP/HTTPS 均可用)。
详情参见 又拍云联盟。
通过查看标签的样式发现,自动填充的 input 标签新增了样式,并且该样式无法被覆盖。
1 | input:-internal-autofill-selected { |
目前(当前:2023/03/20),解决方式有如下几种:
因为自动填充功能产生的问题,那么直接关闭即可解决问题。
1 | <input autocomplete="off"> |
适合信息敏感的情况(例如:密码),可以使用这种方法。
通过使用 CSS 动画延迟,使自动填充样式延迟生效,只要时间足够长,页面停留足够短,相当于样式没变化。
1 | input:-webkit-autofill, |
适合用户停留短暂页面,例如登录页面。
通过阴影将背景覆盖掉。
1 | input { |
适合非透明背景的情况。
利用 background-clip 属性,将背景裁剪掉。
背景颜色默认渲染到 padding-box 中,可以设置 background-clip: centent-box
渲染到 content-box 中。
1 | input { |
Chrome 自动填充样式除了背景以外,还有字体相关样式,可以通过 ::first-line
伪类解决。
1 | input:first-line { |
除此以外,当鼠标悬浮在自动输入列表元素时,还会产生字体样式的变化,可以通过 CSS 动画解决。
1 | input:-webkit-autofill, |
这种方法相对而言比较通用,可以比较好地应用各种场景。
Waline 服务端常用的环境变量,详细情况参见 Waline 文档 - 服务端环境变量。
Key | Note |
---|---|
SITE_NAME | 网站名称 |
SITE_URL | 网站地址 |
LEAN_ID | LeanCloud 应用的 App ID |
LEAN_KEY | LeanCloud 应用的 App Key |
LEAN_MASTER_KEY | LeanCloud 应用的 Master Key 用于后台修改数据 |
LEAN_SERVER | LeanCloud 服务地址,国内版用户需要配置此项 |
DISABLE_REGION | 是否隐藏评论者的归属地 |
DISABLE_USERAGENT | 是否隐藏评论者的 UA,默认为否 |
AVATAR_PROXY | 头像的代理地址,设置 false 关闭代理 |
GRAVATAR_STR | Gravatar 头像的地址,基于 nunjucks 语法 |
SECURE_DOMAINS | 安全域名配置,支持逗号分隔配置多个域名 |
MAIL_SUBJECT | 评论回复邮件标题自定义 |
MAIL_SUBJECT_ADMIN | 新评论通知邮件标题自定义 |
MAIL_TEMPLATE | 评论回复邮件内容自定义 |
MAIL_TEMPLATE_ADMIN | 新评论通知邮件内容自定义 |
AUTHOR_EMAIL | 作者邮箱,用来接收新评论通知 |
SENDER_NAME | 自定义发送邮件的发件人 |
SENDER_EMAIL | 自定义发送邮件的发件地址 |
SMTP_SERVICE | SMTP 邮件发送服务提供商 |
SMTP_USER | SMTP 用户名 |
SMTP_PASS | SMTP 密码 |
提示:
LEAN_SERVER
:LeanCloud 服务地址,使用国内版本 LeanCloud 需要配置,且需要 域名已完成备案AVATAR_PROXY
:头像的代理地址,推荐配置false
GRAVATAR_STR
:Gravatar 头像的地址,推荐配置https://cravatar.cn/avatar/{{mail|lower|trim|md5}}
SECURE_DOMAINS
:安全域名配置,配置时需要同时添加网站地址和 Waline 服务端地址,切记不包含传输协议(http://
或https://
)SMTP_SERVICE
:邮件发送服务提供商,从 此处 查看支持的服务商
记录一下邮件模板,其中模板变量以 Waline 为例。文章中提供的是压缩后的模板,方便直接使用。
在 Valine-Admin 项目中提供的邮件模板,具体出处不知。
1 | {{parent.nick}},您在『{{site.name}}』上的留言有新的回复 |
1 | 您的『{{site.name}}』上有新的留言 |
1 | <body><div style="margin: 50px auto;width: 666px;max-width: 100%;border: 1px solid #EEE;border-radius: 10px 10px 10px 10px;background: #FFFFFF repeating-linear-gradient(-45deg, #FFF, #FFF 1.125rem, transparent 1.125rem, transparent 2.25rem);box-shadow: 0 1px 5px rgba(0, 0, 0, 0.15);color: #555555;font-size: 14px;font-family: 'Century Gothic', 'Trebuchet MS', 'Hiragino Sans GB', 微软雅黑, 'Microsoft Yahei', Tahoma, Helvetica, Arial, 'SimSun', sans-serif;"><div style="width: 100%;background: #49BDAD;background-image: -moz-linear-gradient(0deg, rgb(67, 198, 184), rgb(255, 209, 244));background-image: -webkit-linear-gradient(0deg, rgb(67, 198, 184), rgb(255, 209, 244));border-radius: 10px 10px 0 0;color: #FFFFFF;"><p style="margin: 0;padding: 23px calc(5%);background-color: hsla(0, 0%, 100%, .4);border-radius: 10px 10px 0 0;font-size: 15px;word-break: break-all;">您在<a style="color: #FFFFFF;text-decoration: none;"href="{{site.url}}"target="_blank">{{site.name}}</a>上的留言有新的回复啦!</p></div><div style="margin: 40px auto;width: 90%;"><p>Hi,{{parent.nick}},您曾在文章上发表评论:</p><div style="margin: 20px 0px;padding: 15px;background: #FAFAFA repeating-linear-gradient(-45deg, #FFF, #FFF 1.125rem, transparent 1.125rem, transparent 2.25rem);box-shadow: 0 2px 5px rgba(0, 0, 0, 0.15);border-radius: 5px;color: #555555;font-size: 14px;">{{parent.comment|safe}}</div><p><strong>{{self.nick}}</strong>给您的回复如下:</p><div style="margin: 20px 0px;padding: 15px;background: #FAFAFA repeating-linear-gradient(-45deg, #FFF, #FFF 1.125rem, transparent 1.125rem, transparent 2.25rem);box-shadow: 0 2px 5px rgba(0, 0, 0, 0.15);border-radius: 5px;color: #555555;font-size: 14px;">{{self.comment|safe}}</div><p>您可以点击<a style="color: #12ADDB;text-decoration: none;"href="{{site.postUrl}}"target="_blank">查看回复的完整內容</a>。</p><div style="border-bottom: 1px solid #EEEEEE;"></div><p style="font-size: 12px;color: #B7ADAD;">本邮件为系统自动发送,请勿直接回复邮件。</p></div></div></body> |
1 | <body><div style="margin: 50px auto;width: 666px;max-width: 100%;border: 1px solid #EEE;border-radius: 10px 10px 10px 10px;background: #FFFFFF repeating-linear-gradient(-45deg, #FFF, #FFF 1.125rem, transparent 1.125rem, transparent 2.25rem);box-shadow: 0 1px 5px rgba(0, 0, 0, 0.15);color: #555555;font-size: 14px;font-family: 'Century Gothic', 'Trebuchet MS', 'Hiragino Sans GB', 微软雅黑, 'Microsoft Yahei', Tahoma, Helvetica, Arial, 'SimSun', sans-serif;"><div style="width: 100%;background: #49BDAD;background-image: -moz-linear-gradient(0deg, rgb(67, 198, 184), rgb(255, 209, 244));background-image: -webkit-linear-gradient(0deg, rgb(67, 198, 184), rgb(255, 209, 244));border-radius: 10px 10px 0 0;color: #FFFFFF;"><p style="margin: 0;padding: 23px calc(5%);background-color: hsla(0, 0%, 100%, .4);border-radius: 10px 10px 0 0;font-size: 15px;word-break: break-all;">您的<a style="color: #FFFFFF;text-decoration: none;"href="{{site.url}}"target="_blank">{{site.name}}</a>上有新的留言啦!</p></div><div style="margin: 40px auto;width: 90%;"><p><strong>{{self.nick}}</strong>给您的留言如下:</p><div style="margin: 20px 0px;padding: 15px;background: #FAFAFA repeating-linear-gradient(-45deg, #FFF, #FFF 1.125rem, transparent 1.125rem, transparent 2.25rem);box-shadow: 0 2px 5px rgba(0, 0, 0, 0.15);border-radius: 5px;color: #555555;font-size: 14px;">{{self.comment|safe}}</div><p>您可以点击<a style="color: #12ADDB;text-decoration: none;"href="{{site.postUrl}}"target="_blank">查看留言的完整內容</a>。</p><div style="border-bottom: 1px solid #EEEEEE;"></div><p style="font-size: 12px;color: #B7ADAD;">本邮件为系统自动发送,请勿直接回复邮件。</p></div></div></body> |
给定一个由 0
和 1
组成的数组 arr
,将数组分成 3
个非空的部分 ,使得所有这些部分表示相同的二进制值。
如果可以做到,请返回 任何 [i, j]
,其中 i + 1 < j
,这样一来:
arr[0], arr[1], ..., arr[i]
为第一部分;arr[i + 1], arr[i + 2], ..., arr[j - 1]
为第二部分;arr[j], arr[j + 1], ..., arr[arr.length - 1]
为第三部分。如果无法做到,就返回 [-1, -1]
。
注意,在考虑每个部分所表示的二进制时,应当将其看作一个整体。例如,[1,1,0]
表示十进制中的 6
,而不会是 3
。此外,前导零也是 被允许的,所以 [0,1,1]
和 [1,1]
表示相同的值。
示例 1:
1 | 输入:arr = [1,0,1,0,1] |
示例 2:
1 | 输入:arr = [1,1,0,1,1] |
示例 3:
1 | 输入:arr = [1,1,0,0,1] |
提示:
3 <= arr.length <= 3 * 10^4
arr[i]
是0
或1
1 | public int[] threeEqualParts(int[] arr) { |
网站域名 "discuss.leetcode.com"
由多个子域名组成。顶级域名为 "com"
,二级域名为 "leetcode.com"
,最低一级为 "discuss.leetcode.com"
。当访问域名 "discuss.leetcode.com"
时,同时也会隐式访问其父域名 "leetcode.com"
以及 "com"
。
计数配对域名 是遵循 "rep d1.d2.d3"
或 "rep d1.d2"
格式的一个域名表示,其中 rep
表示访问域名的次数,d1.d2.d3
为域名本身。
"9001 discuss.leetcode.com"
就是一个 计数配对域名 ,表示 discuss.leetcode.com
被访问了 9001
次。给你一个 计数配对域名 组成的数组 cpdomains
,解析得到输入中每个子域名对应的 计数配对域名 ,并以数组形式返回。可以按 任意顺序 返回答案。
示例 1:
1 | 输入:cpdomains = ["9001 discuss.leetcode.com"] |
示例 2:
1 | 输入:cpdomains = ["900 google.mail.com", "50 yahoo.com", "1 intel.mail.com", "5 wiki.org"] |
提示:
1 <= cpdomain.length <= 100
1 <= cpdomain[i].length <= 100
cpdomain[i]
会遵循"repi d1i.d2i.d3i"
或"repi d1i.d2i"
格式repi
是范围[1, 10^4]
内的一个整数d1i
、d2i
和d3i
由小写英文字母组成
1 | public List<String> subdomainVisits(String[] cpdomains) { |
时间复杂度: O(L)*,其中 *L 是数组 cpdomains 中的所有字符串长度之和。遍历数组中所有的计数配对域名计算每个子域名的计数需要 O(L) 的时间,遍历哈希表也需要 O(L) 的时间。
空间复杂度: O(L)*,其中 *L 是数组 cpdomains 中的所有字符串长度之和。哈希表需要 O(L) 的空间。
只有满足下面几点之一,括号字符串才是有效的:
AB
(A
与 B
连接), 其中 A
和 B
都是有效字符串,或者(A)
,其中 A
是有效字符串。给定一个括号字符串 s
,移动N次,你就可以在字符串的任何位置插入一个括号。
s = "()))"
,你可以插入一个开始括号为 "(()))"
或结束括号为 "())))"
。返回 为使结果字符串 s
有效而必须添加的最少括号数。
示例 1:
1 | 输入:s = "())" |
示例 2:
1 | 输入:s = "(((" |
提示:
1 <= s.length <= 1000
s
只包含'('
和')'
字符。
题目描述有点 高深莫测,总结一下:添加 最少 的括号,使字符串 s
中的左右括号可以相互匹配。
1 | public int minAddToMakeValid(String s) { |
charAt()
替换 toCharArray()
,时间复杂度为 *O(1)*。给你一个二进制字符串 s
,该字符串 不含前导零 。
如果 s
包含 零个或一个由连续的 '1'
组成的字段 ,返回 true
。否则,返回 false
。
如果 s
中 由连续若干个 '1'
组成的字段 数量不超过 1,返回 true
。否则,返回 false
。
示例 1:
1 | 输入:s = "1001" |
示例 2:
1 | 输入:s = "110" |
提示:
1 <= s.length <= 100
s[i]
为'0'
或'1'
s[0]
为'1'
1 | public boolean checkOnesSegment(String s) { |
1 | public boolean checkOnesSegment(String s) { |
1 | public boolean checkOnesSegment(String s) { |
在一个由 'L'
, 'R'
和 'X'
三个字符组成的字符串(例如 "RXXLRXRXL"
)中进行移动操作。一次移动操作指用一个 "LX"
替换一个 "XL"
,或者用一个 "XR"
替换一个 "RX"
。现给定起始字符串 start
和结束字符串 end
,请编写代码,当且仅当存在一系列移动操作使得 start
可以转换成 end
时, 返回 True
。
示例 :
1 | 输入: start = "RXXLRXRXL", end = "XRLXXRRLX" |
提示:
1 <= len(start) = len(end) <= 10000。
start
和end
中的字符串仅限于'L'
,'R'
和'X'
。
通过题目描述可知,start
字符串中的 "XL"
换成 "LX"
,"RX"
换成 "XR"
,也就是 'L'
左移,'R'
右移,使得 start
转换成为 end
。仔细思考将会发现,如果可以转换,两个字符串中的 'L'
与 'R'
的相对位置不会放生变化,并且顺序和数量一致。
1 | public boolean canTransform(String start, String end) { |
1 | public boolean canTransform(String start, String end) { |
Q:为什么操作
charAt()
方法效率低于操作toCharArray()
方法获得的字符数组?A: 虽然
charAt()
方法也是操作String
对象的内置字符数组,但是中间夹杂了其他的一些判断,所以导致效率低。
给定两个字符串 s1
和 s2
,请编写一个程序,确定其中一个字符串的字符重新排列后,能否变成另一个字符串。
示例 1:
1 | 输入: s1 = "abc", s2 = "bca" |
示例 2:
1 | 输入: s1 = "abc", s2 = "bad" |
说明:
0 <= len(s1) <= 100
0 <= len(s2) <= 100
1 | public boolean CheckPermutation(String s1, String s2) { |
1 | public boolean CheckPermutation(String s1, String s2) { |