第九期:前端九条启发分享

作者始终忙别的事好久没写文章羞愧羞愧, 工作变动求靠谱初创公司举荐( ´・・` )比心

一、浏览器tab间通信利器 BroadcastChannel


    浏览器tab间通信的场景尽管不多但咱们也要会(就要就要), 我上面举几个应用场景大家品品:

  1. 下图是一个常见的列表, 点击列表里的详情按钮会跳到详情页, 那么兴许咱们在详情页批改了数据状态, 此时可能须要把批改后的状态间接传给列表页从而本地间接更新列表, 这样就不必发送新的api申请与后端交互了。
  2. 与上一个例子雷同, 如果我从列表页关上了很多的详情页, 就会呈现很多的tab看着让人不难受, 那么我在列表页就能够提供一键敞开详情页tab的能力。
一起看看用法吧

咱们以上述的第二个例子为例, 这里是列表页代码

    const bt = document.getElementById('bt');    const bc = new BroadcastChannel('test_channel');    bt.onclick = function () {        bc.postMessage({ close: true });    }
  1. new BroadcastChannel('test_channe') 创立了一个名为test_channe的频道。
  2. 当button被点击时触发bc.postMessage({ close: true });test_channe频道公布音讯{ close: true }
  3. BroadcastChannel 的长处之一就是他公布的音讯能够是对象, 然而如果通过localStorage传递信息的话就是字符串的模式。

这里是详情页面代码

    const bc = new BroadcastChannel('test_channel');    bc.onmessage = function (res) {        if(res.data.close){            window.close()        }    }    bc.onmessageerror = function () {        // 错误处理    }
  1. new BroadcastChannel('test_channel') 同样须要监听test_channel频道。
  2. bc.onmessage 是收听频道的意思。

承受到的res数据如下:

动效如图:

浏览器的实现各不相同

   火狐与 safari 360极速等浏览器无奈间接应用本地的html文件进行BroadcastChannel的应用, 本地调试须要启动page-server才行, 然而谷歌不必启动服务, 间接用html文件即可调试。

兼容性

办法二: 应用localStorage实现通信

收回信息页面代码

 localStorage.setItem('key999', '要传递的值')
  1. 这个命令会向localStorage里存储一个值

接管信息页面

 window.addEventListener('storage', (res) => {    console.log(res.storageArea.key999) })
  1. 监听storage的变动, 获取key对应的val
  2. 毛病是只能传字符串
  3. 其余的localStorage变动也会导致触发, 无奈确定是否为本人想监听的
  4. 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对象是有很多值的, 他自身并不是字符串:

   其中咱们能够通过baseNodeextentNode两个属性获取到框选的初始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作为bodyinnerHTML, 下图是渲染后的成果, 所以不要用在文字内容多的场景下:

4: 赋予类型, 操作动画

    咱们能够通过baseNodeextentNode两个属性获取到框选的初始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-endpadding-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办法:

    同一个申请屡次触发为啥不必防抖或者节流? 因为防抖与节流不好解决工夫较长的状况, 就像例子中申请a15秒都没有返回后果并且你不晓得申请a10秒还是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);      }    });  };}
  1. 利用ref计数, 每次ref+1, 最终返回值时如果以后的ref不等于申请时的ref则示意

应用办法:

export default function Home() {  const getEndPromise = useGetEndPromise();  // .... return <button  onClick={() => {    getEndPromise(你申请的Promise, promis返回时执行的函数);  }}>}
  1. 要留神的是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>;}

    这里咱们新加了useEffectfn变动的时候咱们更新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>  );}

     尽管咱们在useEffectref.current = fn;然而别忘了, ref值的变动不会触发react从新render, useEffect在react的所有render执行完结后才执行, 所以就是说dom上onClick办法挂载的fn永远是上一个, 所以导致了点击看似有效, 实则是执行了上一个fn:

     被坑了后我扒了下组件库的源码才找到这个问题, 过后真是被坑哭了, 但愿大家别采坑吧。

end

     这次就是这样, 心愿与你一起提高。