# 16. 遇到的坑

# window.addEventListener('scroll', function)事件不会触发

scroll 事件不会冒泡,这个带来的影响就是,当我们去做事件委托的时候,其它的大部分事件可以在冒泡阶段的时候完成委托,而 scroll 事件必须在捕获阶段完成委托。

scroll 事件无法取消( 没有冒泡的基本都没法取消 ),scroll 回调中的 preventDefault 和 stopPropagation 都是无效的。

参考:JavaScript 中那些不会冒泡的事件 (opens new window)

removeEventListener事件时需要同样匹配冒泡true/捕获false标志。
参考:EventTarget.removeEventListener() (opens new window)

# 事件参考

https://developer.mozilla.org/zh-CN/docs/Web/Events (opens new window)

# input输入框浏览器保存密码后,初始会自动填充输入框

input框初始接受浏览器自动填充的值不会触发change事件,切换不同的填充值后才会触发。input,blur,focus会触发

# font-size: 0有啥用?

CSS 中使用 font-size: 0 的主要目的是为了消除元素之间的空格,通常是为了解决行内块元素之间因为空格而产生的间隔问题。 替代 font-size: 0 的方法有很多种,以下是一些可能的解决方案:

  1. 使用 display: flexdisplay: grid 布局,这些布局方式不会产生元素之间的空格。
  2. 使用 letter-spacing 属性设置字母间距为负值,例如 letter-spacing: -1em
  3. 使用 marginpadding 属性来控制元素之间的间距。
  4. 使用伪元素 ::before::after 来添加空白字符,例如 content: "",并设置其 font-size 属性为需要的值。 请注意,这些替代方案的适用性取决于具体的情况,需要根据实际情况进行选择。

# JSON.stringify路由的url参数,原始值改变的问题

var obj = {
    "custid": "28521998890000000000000000003997",
    "custName": "[H]Mybells\u0007 .●ΒuЪù!@$%^&#123\\\\r",
    "custflag": 1,
}
1
2
3
4
5

希望将obj转为字符串。

JSON.stringify(obj); // '{"custid":"28521998890000000000000000003997","custName":"[H]Mybells\\u0007 .●ΒuЪù!@$%^&#123\\\\\\\\r","custflag":1}'
1

可以看到obj.custName和原始值不同。

obj.custName = encodeURIComponent(obj.custName); // '%5BH%5DMybells%07%20.%E2%97%8F%CE%92u%D0%AA%C3%B9!%40%24%25%5E%26%23123%5C%5Cr'
JSON.stringify(obj); // '{"custid":"28521998890000000000000000003997","custName":"%5BH%5DMybells%07%20.%E2%97%8F%CE%92u%D0%AA%C3%B9!%40%24%25%5E%26%23123%5C%5Cr","custflag":1}'
decodeURIComponent('{"custid":"28521998890000000000000000003997","custName":"%5BH%5DMybells%07%20.%E2%97%8F%CE%92u%D0%AA%C3%B9!%40%24%25%5E%26%23123%5C%5Cr","custflag":1}'); // '{"custid":"28521998890000000000000000003997","custName":"[H]Mybells\x07 .●ΒuЪù!@$%^&#123\\\\r","custflag":1}'
1
2
3

浏览器访问时会自动对url后的参数执行decodeURIComponent,保证访问后参数不会改变。

# from的submit事件和button关系

在 HTML 中,<button> 标签可以被用来提交表单数据。如果 <button> 标签没有设置 type 属性或者设置为 type="submit",则在点击该按钮时,会触发包含该按钮的最近的表单的 submit 事件。 这个行为是由 HTML 规范定义的,主要是为了方便用户提交表单数据。如果想要避免 <button> 标签触发 submit 事件,可以将 type 属性设置为 "button",或者使用 <input> 标签并设置 type="button"。同时,也可以在 submit 事件处理函数中通过 event.preventDefault() 方法来阻止表单的默认提交行为。

<form @submit="submit">
    <button type="button">不提交</button>
    <button type="text">提交</button>
    <button type="submit">提交</button>
</form>
1
2
3
4
5

# css flex布局中最右边的节点如何靠最右放置

使用CSS Flex布局,可以使用justify-content: flex-end;将弹性容器中的所有项目都靠右对齐。如果只想将最右边的项目靠右对齐,可以将该项目的margin-left: auto;设置为自动,这将使项目向右移动,直到它与容器的右侧对齐。

# 给一个元素的css设置一个 超过100W PX的值后, 再获取这个值就 变成了 1+e6

如果需要正确地获取一个元素的CSS属性值,即使该值超过了100W像素,可以使用getComputedStyle方法来获取。该方法返回的是一个CSSStyleDeclaration对象,其中包含了元素的所有计算样式属性,包括超过100W像素的属性值。可以通过该对象的getPropertyValue方法来获取指定属性的值,例如:

const elem = document.getElementById('my-element');
const computedStyle = window.getComputedStyle(elem);
const widthValue = computedStyle.getPropertyValue('width');
console.log(widthValue); // 输出元素的宽度值,包含单位,例如:2000000px
1
2
3
4

使用getComputedStyle方法获取的属性值是字符串类型,如果需要进行数值计算,可以使用parseFloat方法将字符串转换为数值类型,例如:

const widthNumber = parseFloat(widthValue);
console.log(widthNumber); // 输出元素的宽度数值,例如:2000000
1
2

# 在相同域名下的其他页面(如一个新标签或 iframe)通信,通过 StorageEvent 响应存储的变化

Web Storage 包含如下两种机制:

sessionStorage 为每一个给定的源(given origin)维持一个独立的存储区域,该存储区域在页面会话期间可用(即只要浏览器处于打开状态,包括页面重新加载和恢复)。

localStorage 同样的功能,但是在浏览器关闭,然后重新打开后数据仍然存在。

这两种机制是通过 Window.sessionStorage 和 Window.localStorage 属性使用(更确切的说,在支持的浏览器中 Window 对象实现了 WindowLocalStorage 和 WindowSessionStorage 对象并挂在其 localStorage 和 sessionStorage 属性下)—— 调用其中任一对象会创建 Storage 对象,通过 Storage 对象,可以设置、获取和移除数据项。对于每个源(origin)sessionStorage 和 localStorage 使用不同的 Storage 对象——独立运行和控制。

无论何时,Storage 对象发生变化时(即创建/更新/删除数据项时,重复设置相同的键值不会触发该事件,Storage.clear() 方法至多触发一次该事件),StorageEvent 事件会触发。在同一个页面内发生的改变不会起作用——在相同域名下的其他页面(如一个新标签或 iframe)发生的改变才会起作用。在其他域名下的页面不能访问相同的 Storage 对象。

在事件结果页面中的 JavaScript 如下所示(可见 events.js):

window.addEventListener("storage", function (e) {
  document.querySelector(".my-key").textContent = e.key;
  document.querySelector(".my-old").textContent = e.oldValue;
  document.querySelector(".my-new").textContent = e.newValue;
  document.querySelector(".my-url").textContent = e.url;
  document.querySelector(".my-storage").textContent = e.storageArea;
});
1
2
3
4
5
6
7

这里,我们为 window 对象添加了一个事件监听器,在当前域名相关的 Storage 对象发生改变时该事件监听器会触发。正如你在上面看到的,此事件相关的事件对象有多个属性包含了有用的信息——改变的数据项的键,改变前的旧值,改变后的新值,改变的存储对象所在的文档的 URL,以及存储对象本身。

https://developer.mozilla.org/zh-CN/docs/Web/API/Web_Storage_API/Using_the_Web_Storage_API

效果: https://twitter.com/nonfigurativ/status/1727322594570027343

# vue中data函数和设置immediate的watch方法谁先执行?

Alt text

# new Image请求图片加了crossOrigin = 'anonymous'照样跨域的问题。

背景:点击预览图片,然后点击copy按钮,复制图片到粘贴板,报错跨域。 原因:点击预览图片会第一次请求图片,点击copy会第二次请求图片,这时浏览器会存在缓存,会走缓存header中不会携带crossOrigin = 'anonymous',导致跨域。 解决方法:加time唯一标识,不走缓存。 参考:https://juejin.cn/post/7043642128861233189

// 点击复制图片
handleCopy(imageUrl) {
    this.convertImgToBase64(imageUrl || this.currentImg, async (base64Img) => {
        try {
            const data = await fetch(base64Img);
            const blob = await data.blob();
            if (navigator.clipboard) {
                this.$message.success('已复制图片到剪贴板');
                await navigator.clipboard.write([
                    // eslint-disable-next-line no-undef
                    new ClipboardItem({
                        [blob.type]: blob,
                    }),
                ]);
            } else {
                this.$message.error('无法复制图片到剪贴板,请在https协议下操作');
            }
        } catch (error) {
            this.$message.error(`复制图片失败:${error}`);
        }
    });
},
convertImgToBase64(url, callback, outputFormat) {
    let canvas = document.createElement('CANVAS');
    const ctx = canvas.getContext('2d');
    const img = new Image;
    let retryCount = 0;
    const loadImg = () => {
        img.crossOrigin = 'anonymous';
        // 跨域请求图片不能走缓存,否则之后请求会使用缓存导致跨域报错。加唯一标识
        img.src = `${url}${url.includes('?') ? '&' : '?'}time=${new Date().valueOf()}`;
        img.onload = () => {
            canvas.height = img.height;
            canvas.width = img.width;
            ctx.drawImage(img, 0, 0);
            const dataURL = canvas.toDataURL(outputFormat || 'image/png');
            callback.call(this, dataURL);
            canvas = null;
        };
        img.onerror = () => {
            if (retryCount < 1) {
                retryCount += 1;
                loadImg(); // 调用加载图片的函数
            } else {
                // 重试一次后仍加载失败的处理
                this.$message.error('图片加载失败,请重试');
            }
        };
    };
    loadImg(); // 调用加载图片的函数
},
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51

# innerText和textContent有啥区别

innerTexttextContent 都用于获取或设置DOM节点的文本内容,但它们之间有一些区别:

  1. innerText

    • innerText 返回元素内的文本内容,不包括HTML标签。
    • innerText 会受CSS样式的影响,只返回可见文本内容。
    • 当元素包含 <script><style> 标签时, innerText 会返回空字符串。
    • innerText 是相对慢的属性,因为它会触发重排和重绘。
  2. textContent

    • textContent 返回元素内的所有文本内容,包括文本节点和注释节点,但不包括HTML标签。
    • textContent 不受CSS样式的影响,会返回所有文本内容,包括隐藏的文本。
    • textContent 会保留空白文本节点,而 innerText 会移除空白文本节点。
    • textContent 是更快的属性,因为它不会触发重排和重绘。

因此,根据需求选择使用 innerText 还是 textContent 。如果需要获取或设置可见文本内容,并受CSS样式影响,可以使用 innerText 。如果需要获取或设置所有文本内容,包括隐藏的文本和空白文本节点,可以使用 textContent