前言
开发过程中,总免不了遇坑,遇坑不要紧,填坑是紧要. 文中不会对问题有过多的解释,这里会先列出对应的解决方案,如果要解释其中缘由必定篇幅过长,且当这是一个简单的填坑指南.
填坑
Number.prototype.toFixed()
方法对保留对应小数位不正确问题?
原因可看这篇文章 toFixed四舍五入的不准确性.
注意:js 中的 Number 类型计算时存在精度缺失问题Number 类型使用 IEEE 754 格式表示 整数 和 浮点值(在某些语言中也叫双精度值),通常在
js
中通过计算正常都会使用十进制,但实际上计算过程是十进制 --> 转成二进制 --> 二进制进行计算 --> 转成十进制
,这时候就会有精度问题.解决方法既然
Number.prototype.toFixed()
方法既然存在四舍五入问题,那么最简单的就是重写这个方法:
// 重写方法 Number.prototype.toFixed = function (d) { var s = this + '' // number -> string if (!d) d = 0 // 参数默认值 if (s.indexOf('.') == -1) s += '.' // 格式化数字字符串,如 '100' -> '100.') s += new Array(d + 1).join('0') //若 d = 2, 则 '100.' -> '100.00', '100.123' -> '100.12300' if ( new RegExp('^(-|\\+)?(\\d+(\\.\\d{0,' + (d + 1) + '})?)\\d*$').test(s) ) { var s = '0' + RegExp.$2, // $2 为匹配到完整数字,如 '100.666' -> '0100.666' pm = RegExp.$1, // $1 正负符号 a = RegExp.$3.length, // $3 获取包含 . 即后面需要 d + 1 长度的数值 b = true if (a == d + 2) { // a 长度包含了 . 和 对应 d 长度的后一位 a = s.match(/\d/g) // 获取数字部分字符串 // 判断最后完整数字字符最后一位是否 > 4, 即是否需要四舍五入 if (parseInt(a[a.length - 1]) > 4) { for (var i = a.length - 2; i >= 0; i--) { // 倒序遍历 a[i] = parseInt(a[i]) + 1 // 进一 if (a[i] == 10) { // 满 10 进制,本位置 0 ,前一位进一 a[i] = 0 b = i != 1 // 当前遍历是到第二位时,b = false,第一位为 0 } else break // 没满 10 直接跳出循环 } } // 只获取小数点前后的数字,通过 . 拼接 s = a .join('') .replace(new RegExp('(\\d+)(\\d{' + d + '})\\d$'), '$1.$2') } if (b) s = s.substr(1) // 删除首位的 0 return (pm + s).replace(/\.$/, '') // 以 . 结尾替换成空字符 } // 不符合正则,直接转 string 返回 return this + '' } 复制代码
window.open()
方法被浏览器拦截?
原因:浏览器出于安全考虑,不允许非用户操作情况下打开新的页面.示例代码
<button onclick="getUrlAndOpen()">点我</button> <script> async function getUrlAndOpen() { let res = await new Promise((resolve, reject) => { setTimeout(() => { resolve('http://www.baidu.com') }, 500) }) console.log("异步请求结果:", res) window.open(res) } </script> 复制代码
解决方法:在调用异步请求之前先通过
window.open()
打开页面,获取这个窗口的引用,等待异步响应之后去设置新开窗口的 url
示例代码
<button onclick="getUrlAndOpen()">点我</button> <script> async function getUrlAndOpen() { let newWindowReference = window.open(); let res = await new Promise((resolve, reject) => { setTimeout(() => { resolve('http://www.baidu.com') }, 500) }) console.log("异步请求结果:", res) newWindowReference.location.href = res; } </script> 复制代码
<img draggable="true" />
时,在浏览器中拖拽图片时会自动下载或新开页面预览?
直接通过效果来解释:
<img id="img1" src="./img/xm.jpg" alt="xm"> 复制代码
如上图所示,在不同浏览器下,对于图片、链接等元素的 draggable
属性的默认值是不同的,这就导致了它们在浏览器上进行拖拽后产生了差异,下面就以 谷歌浏览器 和 QQ 浏览器进行对比:
- QQ 浏览器:
draggable = true
时拖拽图片,鼠标下会有图片缩影,放开鼠标后,浏览器会打开新页面并访问对应资源的url
地址 - Google 浏览器:
draggable = true || false
都不会有上述的行为产生
问题描述QQ 浏览器中对图片进行拖拽时,若
img
标签中是预览地址,则打开新页面后会访问这个预览地址,若当前img
标签中是下载地址,则会询问是否需要下载这个图片资源.解决方法
- 给普通的
img
或a
标签设置draggable="false"
,禁止拖拽行为产生 - 若当前
img
元素需要进行拖拽,即draggable="true"
,此时只能通过ondragover
事件处理程序中使用ev.preventDefault();
阻止浏览器的默认行为
下面是示例代码:
<div class="box" ondrop="drop(event)" ondragover="dragover(event)"></div> <div class="box" ondrop="drop(event)" ondragover="dragover(event)"> <img id="img1" src="./img/xm.jpg" ondragstart="dragstart(event)" draggable="true" alt="xm"> </div> <script> function dragover(ev) { ev.preventDefault(); } function dragstart(ev) { ev.dataTransfer.setData("text", ev.target.id); } function drop(ev) { var data = ev.dataTransfer.getData("text"); ev.target.appendChild(document.getElementById(data)); } </script> 复制代码
项目地址支持 http
和 https
,怎么保证项目中的请求和用户访问页面时的协议保持一致?
问题描述: 根据用户在浏览器地址栏中访问时,使用的协议决定项目中静态资源和请求的协议要一致,如果不一致会导致请求产生跨域问题.解决方法: 要动态修改协议,还得考虑项目中不同位置的请求和外链:
- 对于
<script src="http://xxx">
的形式,改写为<script src="//xxx">
让浏览器自己去匹配当前协议 - 对于
api
接口中的协议
- 若使用的是
axios
则指定baseURL
为空字符''
或者不进行设置即可
// 创建axios实例 const service = axios.create({ baseURL: '', // api的base_url // `responseType` 表示服务器响应的数据类型,可以是 'arraybuffer', 'blob', 'document', 'json', 'text', 'stream' responseType: reqConf.responseType, }); 复制代码
- 若项目中的
api
是使用完整路径的形式,那么只能通过js
进行动态替换协议头
在 Google 浏览器使用 Google 翻译插件翻译页面导致原有页面文字显示异常?
解决方法方法就是通过 head 标签禁止 Google 翻译插件翻译页面文字:
<html translate="no" lang="en"> <head> <meta charset="utf-8" /> ... <meta content="telephone=no" name="format-detection" /> <meta name="google" content="notranslate" /> <meta http-equiv="Expires" content="0" /> <meta http-equiv="Pragma" content="no-cache" /> <meta http-equiv="Cache-control" content="no-cache" /> <meta http-equiv="Cache" content="no-cache" /> <link rel="icon" href="./favicon.ico" /> <title>Tencent</title> </head> 复制代码
什么时候需要使用 HTML crossorigin 属性?
问题描述:由于最近在做的项目需要显示页面加载的进度条,所以使用了下面的代码引入这个库:
<script src="https://cdn.bootcss.com/nprogress/0.2.0/nprogress.min.js" crossorigin="anonymous"></script> 复制代码
其中的 crossorigin="anonymous"
属性是不是很显眼?
事情起因就是项目中进行前端架监控,即 aegis.js
,它存放在公司的另一个 cdn 服务下,并且对应的开发人员建议不要使用 npm 包,于是就通过 script
进行全局引入。 根据往常的开发,就正常的复制了上面的 nprogress.min.js
的 script
标签,并将其 src 属性直接进行了替换,于是报了跨域 的错误.
解决方案:正常情况下
其实没有必要使用这个属性,所以以上问题直接删除掉这个 crossorigin
就好了.
特殊情况下
也就是当我们需要收集或显示外部第三方包中的具体错误时,就需要使用这个 crossorigin
HTML 属性,正常情况下,如果外部的第三方包中出现异常时,在全局监听的 window.addEventListener('error', function(msg, url, lineno, colno, error) {xxx})
中得到的错误信息是不详细的,比如可能只会显示 script error
。
通过使用 crossorigin
属性可以使跨域 js 暴露出跟同域 js 同样的报错信息。但资源服务器必须返回一个 Access-Control-Allow-Origin 的 header,否则资源无法访问。
拓展
crossorigin
属性的作用:
crossorigin
会让浏览器启用CORS访问检查,检查 http 响应头的 Access-Control-Allow-Origin- 对于传统 script 需要跨域获取的 js 资源,控制暴露出其报错的详细信息
- 对于
module script
,控制用于跨域请求的凭据模式
crossorigin
的属性值可以是anonymous
、use-credentials
,如果没有属性值或者非法属性值,会被浏览器默认做anonymous
.
@Component({...}) 和 keep-alive 一起使用出现的 bug?
问题描述:由于项目中主框架使用的
vue-property-decorator + vue2 + typeScript
,因此在定义组件的时候需要使用@Component
进行修饰,主要也是为了让vue2
更好的支持TS
.
@Component({ ... }) export default class MyComponent extends Vue {...} 复制代码
某天的某需求导致某同事需要使用 keep-alive + router-view
实现缓存页面的功能,但是在这个被 keep-alive
包裹下的 router-view
中的其他几个页面并不需要被缓存,因此我们使用了 keep-alive
上的 exclude
排除掉对应的其他不需要被缓存的组件.
<keep-alive :exclude="['MyComponent', ...]"> <router-view></router-view> </keep-alive> 复制代码
开发环境:exclude
属性按预期达到最终效果
测试环境 & 生成环境:exclude
不生效,该 router-view
对应的所有页面被缓存
问题原因:首先
exclude
中使用的是在定义组件时class
关键字后面的MyComponent
,由于开发环境(本地环境)并不会将代码按生产模式进行打包(比如压缩、混淆代码等),所以这个MyComponent
是不会变化的,因此和exclude
中的字符串是对应的,但是按生产模式进行打包以后,类似于MyComponent
这样的命名会被构建工具进行代码压缩、代码混淆等操作,简单来说就是这个MyComponent
真正打包后就一定叫MyComponent
可能会变成类似x
之类的形式。
此时,组件默认名称不再是原来的 MyComponent
,因此也就会和 exclude
中的字符名称对应不上,导致页面被缓存.
解决方案:既然默认定义的组件名称会被改变,那么我们就定义一个不会被改变的组件名称,也就是定义组件时定义
name: xxx
,即:
@Component({ name: 'MyComponent', ... }) export default class MyComponent extends Vue {...} 复制代码
【移动端】在真机上 Calendar 日历组件展示时为空白内容?
问题描述:需要在 H5 移动端实现日期违反选择,于是使用了 vant 中的日历组件,但在真机上在展示日期内容时可能出现空白(偶现),必须要手动滚动才会展示原本内容.PS:出现空白可能是某些场景下,设置的默认日期格式有误,导致组件报错,因此复现问题时最好确认是否控制台是否有异常信息。
解决方案:目前暂时没有发现原因,但按测试描述:需要手动滑动一下才能展示内容,那么解决方法就是打开日历组件后,通过 js 模拟滚动的动作,即通过设置 scrollTop 的值即可.
this.$nextTick(() => { const dom = document.querySelector('.van-calendar__body') // 模拟滑动,避免白屏 if (dom) dom.scrollTop = dom.scrollTop - 4 }) 复制代码
【移动端】IOS/Android 下软键盘弹出并将页面顶上去,收起时无法复原?
问题描述:直接上图(网图)
解决方案:但是在调试过程中发现当点击其他输入框时,整个视图自动复原了.
于是,解决方法就是在页面上顶部放置一个隐藏的 input 标签,然后当其他视图内的输入框失焦时,让这个隐藏的 input 聚焦.
【移动端】在真机上 sessionStorage 无法获取对应数据内容?
最近在开发移动端项目时,需要用到的本地存储的地方不少,都是一些只要记住当前打开窗口的用户数据就行,所以我选择用的 sessionStorage(关闭当前窗口或标签可会被删除)
使用场景:
A.html 页面需要记录一条数据: {a:1, b:2}
sessionStorage.setItem("data","{a:1, b:2}"); 复制代码
B.html 页面取出使用:
sessionStorage.getItem("data"); // 获取结果为 null 复制代码
问题描述:
如果项目不是单页面复应用,A 和 B 是两个 html 文件,需要跳转 href 的
- 发现有些 Andiron 系统的浏览器在 B 页获取是到的结果是 null (如:vivo 手机自带的世界之窗浏览器)
- 直接在 微信 中访问对应页面时,由于拉取微信卡包需要打开一个新的 webview ,选择数据并通过 sessionStorage 保存数据,接着返回上一个页面时,无法获取保存的数据
产生原因:
其实这并不是 某些手机浏览器 或 微信 不支持 sessionStorage,因为仍然可以获取到 sessionStorage 对象.
其实,是因为 sessionStorage 是基于当前窗口的会话级的数据存储,移动端浏览器 或 微信 中在跳转新页面的时候,可能打开的是一个新的 webView,这就相当于我们在浏览器一个新窗口中进行的存储,是没办法在之前的窗口中读取的
解决方案:可以直接使用 localStorage 进行代替,但是要在离开页面时,是否需要清空本次的存储内容,因为 sessionStorage 本身是支持自动清除的,但 localStorage 是持久存储,需要手动清除数据.
Safari 浏览器中 antd@2.12.1
的表单出现样式错乱?
问题描述:
Safari 16.0/16.1
版本中出现如下布局:
产生原因:
Safari 浏览器
中格式错乱是因为给 label
设置了 text-align-last: justify
,Safari 16.0/16.1
版本对这个属性的支持有问题(16.2
不清楚,16.3
正常),会让 display: inline-block
的元素的宽度变成 block
的宽度。
解决方案:
给这些错乱的 label
添加一下 固定宽度 即可,不过需要注意的是项目涉及不同语言环境,所以需要特别处理一下。