共计 8589 个字符,预计需要花费 22 分钟才能阅读完成。
第九期: 前端九条启发分享
作者始终忙别的事好久没写文章羞愧羞愧, 工作变动求靠谱初创公司举荐ღ(´・ᴗ・`)比心
一、浏览器 tab 间通信利器 BroadcastChannel
浏览器 tab 间通信的场景尽管不多但咱们也要会(就要就要), 我上面举几个应用场景大家品品:
- 下图是一个常见的
列表
, 点击列表里的详情按钮会跳到详情页
, 那么兴许咱们在详情页批改了数据状态
, 此时可能须要把批改后的状态间接传给列表页
从而本地间接更新列表, 这样就不必发送新的 api 申请与后端交互了。 - 与上一个例子雷同, 如果我从列表页关上了很多的
详情页
, 就会呈现很多的 tab 看着让人不难受, 那么我在列表页
就能够提供一键敞开详情页 tab 的能力。
一起看看用法吧
咱们以上述的第二个例子为例, 这里是 列表页
代码
const bt = document.getElementById('bt');
const bc = new BroadcastChannel('test_channel');
bt.onclick = function () {bc.postMessage({ close: true});
}
new BroadcastChannel('test_channe')
创立了一个名为test_channe
的频道。- 当 button 被点击时触发
bc.postMessage({close: true});
对test_channe
频道公布音讯{close: true}
。 BroadcastChannel
的长处之一就是他公布的音讯能够是对象
, 然而如果通过localStorage
传递信息的话就是字符串的模式。
这里是 详情页面
代码
const bc = new BroadcastChannel('test_channel');
bc.onmessage = function (res) {if(res.data.close){window.close()
}
}
bc.onmessageerror = function () {// 错误处理}
new BroadcastChannel('test_channel')
同样须要监听test_channel
频道。bc.onmessage
是收听频道的意思。
承受到的 res 数据如下:
动效如图:
浏览器的实现各不相同
火狐与 safari 360 极速等浏览器无奈间接应用本地的 html 文件进行 BroadcastChannel
的应用, 本地调试须要启动 page-server
才行, 然而谷歌不必启动服务, 间接用 html 文件即可调试。
兼容性
办法二: 应用 localStorage 实现通信
收回信息页面代码
localStorage.setItem('key999', '要传递的值')
- 这个命令会向 localStorage 里存储一个值
接管信息页面
window.addEventListener('storage', (res) => {console.log(res.storageArea.key999)
})
- 监听 storage 的变动, 获取 key 对应的 val
- 毛病是只能传字符串
- 其余的 localStorage 变动也会导致触发, 无奈确定是否为本人想监听的
- key 的值不变, 然而调用了
localStorage.setItem
, 也会触发监听
总的来说 localStorage
就是比拟浪费资源并且不太业余, 所以举荐应用BroadcastChannel
。
二、被动插入微工作队列 window.queueMicrotask
咱们触发一个微工作大部分都是利用Promis
, 个别的办法是Promise.resolve().then(() => {console.log('promise) })
, 然而这样写不是很业余, 其实浏览器给咱们筹备了业余的办法:
Promise.resolve().then(() => {console.log(1) })
queueMicrotask(() => {console.log('执行了')
})
Promise.resolve().then(() => {console.log(2) })
上图能够看出 queueMicrotask
定义的工作在两个 Promise
两头执行了, 语法更简洁, 然而作者认为你写代码更多要思考共事的接受程度, 如果共事了解这个 api
比拟吃力的话那我举荐仍然应用 Promise.resolve().then
来写, 毕竟代码不是用来秀的。
小心混同 requestIdleCallback (浏览器闲暇执行)
大家千万不要搞混了,requestIdleCallback
是在浏览器闲暇执行, 与微工作不同的是 requestIdleCallback
的执行机会不是很确定, 例如下图:
Promise.resolve().then(() => {console.log(1)
})
requestIdleCallback(() => {console.log('执行了')
})
Promise.resolve().then(() => {console.log(2)
})
setTimeout(() => {console.log(3)
})
setTimeout(() => {console.log(4)
}, 10)
情况 1:
情况 2:
之所以这个样子是因为 requestIdleCallback
并不在事件循环的队列里, 咱们能够通过一个属性执行一个超时工夫:
Promise.resolve().then(() => {console.log(1)
})
requestIdleCallback(() => {console.log('执行了')
}, {timeout: 1}) // 留神这里
Promise.resolve().then(() => {console.log(2)
})
setTimeout(() => {console.log(3)
})
setTimeout(() => {console.log(4)
}, 10)
上述代码外面减少了 {timeout: 1}
这个选项, 它的意思是当超过 1
毫秒指标函数仍未被执行则将其退出工夫循环的队列里, 这样就能够保障 requestIdleCallback
内的函数必然会被执行, 而不是浏览器始终不闲暇导致始终不执行。
浏览器何时闲暇这个不太好判断, 比方垃圾回收机制就可能导致浏览器晚一些才闲暇, 那么 requestIdleCallback
的执行机会就不是很确定了, 然而它自身很适宜差别渲染, 比方将不重要的逻辑放在 requestIdleCallback
外面, 这样不影响次要程序的执行。
三、csp 内容安全策略
CSP 的次要指标是缩小和报告 XSS 攻打。XSS 攻打利用了浏览器对于从服务器所获取的内容的信赖。歹意脚本在受害者的浏览器中得以运行,因为浏览器信赖其内容起源,即便有的时候这些脚本并非来自于它本该来的中央。
下面提到了 csp 次要针对脚本文件, 举例说明: 当一个网页蒙受 xss 攻打时, 可能会加载到歹意的 <script src="歹意链接"> </script>
, 那么其实咱们只有阻止网站获取与执行生疏 url 加载到的脚本就解决了啊。
要配置 csp 还须要 server 大兄弟前来助力, 服务器返回 Content-Security-Policy HTTP 标头, 以及前端 html 外面能够做一些配置:
某些性能(例如发送 CSP 违规报告)仅在应用 HTTP 标头时可用。
<meta
http-equiv="Content-Security-Policy"
content="script-src'self' " />
这里 content="script-src'self' "
表明页面只加载以后域名下的脚本文件: 更多配置能够看这里
亚马逊 csp 的配置真是夸大啊
server 的返回也有配置
有空钻研下 csp 让你的网站更平安吧!
四、Window.getSelection 让选中的文字起舞
先看成果:
1: 选中文字
在做复制文案相干需要的时候我接触到了 Window.getSelection
办法, 上面的办法能够输入用户选中的文字:
<div>
1234567890
</div>
<script>
setTimeout(() => {let selObj = window.getSelection();
alert(selObj)
}, 3000)
</script>
2: 框选对象
window.alert 的参数将调用对象的 toString 办法
所以其实 selObj
对象是有很多值的, 他自身并不是字符串:
其中咱们能够通过 baseNode
与extentNode
两个属性获取到框选的初始 dom 与完结 dom。
3: 切分文本不便动画
为了不便操控每一个字符咱们将文章内容切分成一个个 span
标签, 并且从 0 开始标记序号:
let res = ''
let str = ` 咱们激励开发者大开脑洞,联合日常生活中的痛点需要、兴趣爱好和业余方向,进行 Generative AI 利用的构建,例如:艺术品、音乐、漫画头像、求职简历生成器,智能客服机器人,AI Code Review 工具,基于 AI 的数据可视化平台,医疗图像剖析利用等等 —— 以上这些场景联想由 ChatGPT 生成。当然,你也能够抉择任何你感兴趣的方向。`
for (let i = 0; i < str.length; i++) {res += `<span class="sp" data-index="${i}">${str.charAt(i)}</span>`
}
最初应用 res
作为 body
的innerHTML
, 下图是渲染后的成果, 所以不要用在文字内容多的场景下:
4: 赋予类型, 操作动画
咱们能够通过 baseNode
与extentNode
两个属性获取到框选的初始 dom 与完结 dom, 再通过获取 dom 身上的 data-index
属性求出左侧元素与右侧元素, 接下来就是从左往右一一元素进行赋予className
:
const i1 = +selObj.baseNode.parentElement.getAttribute('data-index')
const i2 = +selObj.extentNode.parentElement.getAttribute('data-index')
let max = Math.max(i1, i2)
let min = Math.min(i1, i2)
let targetdom;
if (i1 > i2) {targetdom = selObj.extentNode.parentElement;} else {targetdom = selObj.baseNode.parentElement;}
有了指标元素咱们就能够一一获取兄弟元素了:
(function task() {if (min <= max) {selectedDom.push(targetdom) // 放在数组里不便对立解决
targetdom.classList.add('动画属性')
targetdom = targetdom.nextElementSibling;
min++
timer = setTimeout(() => { // 用 timer 记录不便后续清理
task()}, 50)
}
})()
触发条件: 每次鼠标抬起触发
document.onmouseup = () => {foo() // 动画函数
}
要留神: 抉择模式, 当 selObj.type === 'Caret'
时用户并没有框选文字, 而是点击了某处使光标停在某处, 所以此时咱们应该间接 return
而不进行动画操作。
五、Window.stop() 进步性能神技
window.stop() 办法的成果相当于点击了浏览器的进行按钮。因为脚本的加载程序,该办法不能阻止曾经蕴含在加载中的文档,然而它可能阻止图片、新窗口、和一些会提早加载的对象的加载。
初见这个办法感觉也没啥太大作用, 然而当你遇到一个图片资源很大的页面时它就是神级的办法了, 比方商品的详情页里可能会有不少商品详情的清晰图, 然而当咱们从商品列表页进入商品详情页后立刻返回列表再点开其余商品时, 其实上一个页面的图片资源没必要持续加载了, 如果图片资源较大甚至会导致页面显著卡顿, 那咱们其实能够被动调用window.stop()
。
六、writing-mode 文字排布与 padding-inline 的默契配合
writing-mode 属性定义了文本程度或垂直排布以及在块级元素中文本的前进方向。为整个文档设置该属性时,应在根元素上设置它(对于 HTML 文档,应该在 html 元素上设置)
writing-mode
能够做到的性能之一就是将文字从横向书写变成纵向书写:
在父级元素设置值:
writing-mode: vertical-lr;
此时可能你曾经想到问题了, 如果改成竖着书写那么如果有 padding
的话岂不是就出 bug 了:
padding-left: 20px;
padding-right: 10px;
左右的边距还是须要改的, 因为这个边距是针对 dom 容器的而不是文本的, 那其实就要能够请出咱们的配角padding-inline:
writing-mode: vertical-lr;
padding-inline: 20px 10px;
咱们还能够应用 padding-inline-end
与padding-inline-start
这样语义化更好一些。
七、@counter-style 解决有序列表(官网演示不靠谱!)
这个属性是能够给列表减少序号的 css 属性, 尽管咱们不太可能会用它然而咱们还是要领会一下, 咱们先来看一下 MDN 官网提供的例子:
@counter-style pad-example {
system: numeric;
symbols: "0" "1" "2" "3" "4" "5";
pad: 2 "0";
}
.list {list-style: pad-example;}
<ul class="list">
<li>1</li>
<li>2</li>
<li>3</li>
<li>4</li>
<li>5</li>
</ul>
图片上看起来这个属性就是用来设置序号, 并且能够补位的, 然而此时如果咱们减少更多的li
:
<ul class="list">
<li>1</li>
<li>2</li>
<li>3</li>
<li>4</li>
<li>5</li>
<li>6</li>
<li>7</li>
<li>8</li>
</ul>
这个景象还是蛮奇怪的, 从第六个开始因为咱们没有定义 symbols
属性后果这里间接从 10 开始了计数, 这里过后看的一头雾水, 该不会 css 的计算规定写错了吧!
而且还有另一个疑难symbols: "0" "1" "2" "3" "4" "5";
这外面的 ”0″ 哪里去了?
要解开上述两个谜题就须要咱们换一个写法:
@counter-style pad-example {
system: numeric;
symbols: "xxx" "1" "2" "3";
}
<ul class="list">
<li>1</li>
<li>2</li>
<li>3</li>
<li>4</li>
<li>5</li>
<li>6</li>
<li>7</li>
<li>8</li>
<li>9</li>
<li>10</li>
<li>11</li>
</ul>
咱们做的再显著一点:
@counter-style pad-example {
system: numeric;
symbols: "&" "-" "*";
}
其实当咱们 symbols
传入 2 个值的时候它相当于是 2 进制计算, 输出 n 个值就是 n 进制。
神奇的 pad 属性
咱们能够应用 pad 属性进行序号的补全, 然而肯定留神这里说的补全是针对 symbols
属性, 如下写法是不失效的:
@counter-style pad-example {
system: numeric;
pad: 2 "0";
}
然而写成这样他就无效了:
@counter-style pad-example {
system: numeric;
symbols: "x" "y";
pad: 4 "0";
}
论断是这个属性还蛮乏味的, 咱们能感触到 css 的共性, 但真不倡议应用。
八、react 里多个申请只应用最初一个
场景是这样的咱们在一段时间内发送了多个申请, 但我只想要最初收回的申请的返回值, 假如我以后发送了一个 申请 a1
, 过了 5 秒 申请 a1
没有返回我又发送了一次雷同的申请 申请 a2
, 立即 申请 a2
返回了值, 我应用返回值更新了列表, 然而 2 秒后 申请 a1
的值返回了, 那么此时我就不应该再更新一遍列表了, 所以有了如下的 hook 办法:
同一个申请屡次触发为啥不必防抖或者节流? 因为防抖与节流不好解决工夫较长的状况, 就像例子中 申请 a1
5 秒都没有返回后果并且你不晓得 申请 a
10 秒还是 20 秒后会返回后果, 或者永远不返回。
function useGetEndPromise() {let ref = useRef(0);
return function (promise: any, cb: any) {
ref.current += 1;
let n = ref.current;
promise.then((res: any) => {if (n === ref.current) {cb(res);
}
});
};
}
- 利用 ref 计数, 每次 ref+1, 最终返回值时如果以后的 ref 不等于申请时的 ref 则示意
应用办法:
export default function Home() {const getEndPromise = useGetEndPromise();
// ....
return <button
onClick={() => {getEndPromise(你申请的 Promise, promis 返回时执行的函数);
}}
>
}
- 要留神的是
useGetEndPromise();
生成的函数应用时接管的所有函数都会依照只有最初发动的失效, 所以某些需要外面倡议用useGetEndPromise();
生成多个函数再离开应用。
九、react 中 ref 引起的一个 bug
useref 的值的扭转不会触发 react 的从新渲染, 所以它值的扭转也是同步执行的不必异步获取。
这是我的一个实在案例, 共事封装的一个插件须要我传入一些生命周期 hooks, 然而呈现了 bug 我传入的函数外面无奈执行 hook 操作, 咱们间接看例子:
function Demo(props: any) {const { fn, n} = props;
const ref = useRef(fn);
return <button onClick={ref.current}> 点击展现 {n}</button>;
}
export default function StateDemo() {const [n, setN] = useState(1);
const fn = () => {setN(n + 1);
};
return (
<div>
<Demo fn={fn} n={n} />
</div>
);
}
很奇怪吧, button 点击后 n 变成 2 之后就不再变动了, 这里其实好了解因为 const ref = useRef(fn);
这一步其实只会执行一次, 所以 ref.current
始终是同一个函数, 当初咱们加大难度:
function Demo(props: any) {const { fn, n} = props;
const ref = useRef(fn);
useEffect(() => {
ref.current = fn;
console.log(ref.current === fn)
}, [fn]);
return <button onClick={ref.current}> 点击展现 {n} {ref.current === fn ? 'true' : "flase"}</button>;
}
这里咱们新加了 useEffect
当fn
变动的时候咱们更新ref.current
, 然而此时点击 button 后的成果依然与下面雷同:
咱们再降级一下代码, 奇怪的景象又来了:
function Demo(props: any) {const { fn, n} = props;
const ref = useRef(fn);
useEffect(() => {
ref.current = fn;
console.log(ref.current === fn)
}, [fn]);
return <button onClick={ref.current}> 点击展现 {n} {ref.current === fn ? 'true' : "flase"}</button>;
}
export default function StateDemo() {const [n, setN] = useState(1);
const fn = () => {setN(n + 1);
};
return (
<div>
<button onClick={() => { // 新增了外层触发 n 的变动
setN(n + 1)
}}> 点击 n +1 </button>
<Demo fn={fn} n={n} />
</div>
);
}
尽管咱们在 useEffect
中ref.current = fn;
然而别忘了, ref 值的变动不会触发 react 从新 render, useEffect
在 react 的所有 render 执行完结后才执行, 所以就是说 dom 上 onClick 办法挂载的 fn 永远是上一个, 所以导致了点击看似有效, 实则是执行了上一个 fn:
被坑了后我扒了下组件库的源码才找到这个问题, 过后真是被坑哭了, 但愿大家别采坑吧。
end
这次就是这样, 心愿与你一起提高。