最新更新时间:2019年05月21日17:51:48
《猛戳-查看我的博客地图-总有你意想不到的惊喜》
本文内容:日常bug汇总
概述
在我的工作经历中,作为一名基层开发工程师,开发业务过程中,会遇到大大小大的问题,这里主要记录耗时较长的问题
数组filter错误的用法
问题时间:2019年05月17日
问题描述:用户管理系统,用户类型分为,一级用户,二级用户,三级用户,每个级别用户看到的左侧树目录不一样,管理权限越高,看到的目录越多,因此需要过滤处理。但是如下方案一,出现的错误是权限越低的用户登录过,高级别权限的用户看到的是较少的目录。原因:引用类型问题,返回的结果没有切断和原数组的引用
//不应该对原始数组userPermissionArr进行操作
function filterArr(userPermissionArr, roles) {
//这条语句没有问题,filter方法不对原始数组进行操作,并且返回新数组
const res = userPermissionArr.filter(item => {
if (allow(roles, item)) {
if (item.children && item.children.length) {
//下面这句递归语句有问题,虽然filter方法返回新数组,不对原数组进行操作,但是route.children还是原始数组的引用,因此原数据被改变了,导致后续逻辑出错
item.children = filterArr(item.children, roles)
}
return true
}
return false
})
return res
}
//如下双层forEach方法,完全切断了引用
function filterArr(userPermissionArr, roles) {
const res = []
userPermissionArr.forEach((item, idx1) => {
if (allow(roles, item)) {
if (item.children && item.children.length) {
res[res.length] = { ...item }
res[res.length - 1].children = []
item.children.forEach((child, idx2) => {
if (allow(roles, child)) {
res[res.length - 1].children.push({ ...child })
}
})
} else {
res.push({ ...item })
}
}
})
return res
}
- 注意:api好用,但是弊端需要掌握
react异步dom问题
问题时间:2019年05月21日
问题描述:react移动端的项目,图片预览功能,使用react-cropper组件,点击图片打开预览,点击预览区域任意地方可以关闭预览,但是第三方组件在iOS设备上不支持关闭,猜测react-cropper组件dragMode='move’的属性,阻止了click事件,综合考虑后,在预览界面右上角加关闭icon,解决了iOS不兼容的问题。
//场景一
componentDidMount() {
let _this = this;
let dom = document.getElementsByClassName('cropper-drag-box')
console.log(dom);
//控制台打印如下,看上去是一个空数组,即没有拿到'cropper-drag-box'元素
//HTMLCollection []
//展开之后,如下,确实存在元素,可以得出react页面在异步渲染,'cropper-drag-box'元素是异步生成的,首次打印的时候还没有生成真实dom
//HTMLCollection []
//0: div.cropper-drag-box.cropper-move
//length: 1
//__proto__: HTMLCollection
}
//场景二:如下报错,也是异步渲染原因
componentDidMount() {
let _this = this;
let dom = document.getElementsByClassName('cropper-drag-box')[0]
console.log(dom);//undefined
dom.addEventListener('click',function(){},false)
//Uncaught TypeError: Cannot read property 'addEventListener' of undefined
}
//上面两种错误的解决方案
componentDidMount() {
let dom = null
setTimeout(function(){
dom = document.getElementsByClassName('cropper-drag-box')[0]
console.log(dom);
dom.addEventListener('click',function(){},false)
},1000)
}
- 扩展:vue中处理异步dom的方案,this.$nextTick(function(){console.log(1)})
上传文件剔除非法格式文件算法出错
问题时间:2019年05月25日
问题描述:本地上传文件,需要校验文件格式,以读取到的文件名来剔除非法文件
//方案一:当文件名中包含格式名的时候会出错
let isFormat = false
const arrList = ['pdf', 'ppt', 'pptx', 'doc', 'docx']
for (let i = 0; i < arrList.length; i++) {
//如果文件名包含 pdf 则判断失效:比如'这不是一个pdf文件.rmvb'
if (file.name.toLowerCase().indexOf(arrList[i]) !== -1) {
isFormat = true
}
}
//方案二:取文件名末尾,不会出错
let isFormat = false
const arrList = ['pdf', 'ppt', 'pptx', 'doc', 'docx']
for (let i = 0; i < arrList.length; i++) {
if (file.name.split('.').pop() === arrList[i]) {
isFormat = true
}
}
react项目在移动端iOS 9 以下打开白屏&&空白页面
问题时间:2019年06月13日
问题描述:用目前市面上的所有react脚手架构建项目,都会出现在iOS 9的手机上无法打开页面,出现白屏,包括官方的create-react-app脚手架。这个问题耗时半个月,最终定位到核心问题是,babel 7没有配置好。
//.babelrc文件 失败的配置
{
"presets": [
"@babel/preset-env",
"@babel/preset-react"
],
"plugins": [
"@babel/plugin-proposal-function-bind",
"@babel/plugin-proposal-class-properties"
]
}
//.babelrc文件 成功的配置
{
"presets": [
[
"@babel/preset-env",
{
"targets": {
"browsers": ["safari >= 8"]
}
}
],
"@babel/preset-react"
],
"plugins": [
"@babel/plugin-proposal-function-bind",
"@babel/plugin-proposal-class-properties",
"@babel/plugin-transform-runtime"
]
}
Object.assign is not a function
移动端项目开发过程中,低版本手机系统(iOS 9 以下)的浏览器不兼容Object.assign语法,因此需要做兼容处理
//场景一
let a = {a:2,b:2};
let b = {c:3,d:3,a:5};
let c = Object.assign(a,b);//将b对象合并到a对象上,并返回a对象
console.log(c);//{ a: 5, b: 2, c: 3, d: 3 }
console.log(c === b);//false
console.log(c === a);//true
console.log(a === b);//false
console.log(c == b);//false
console.log(c == a);//true
console.log(a == b);//false
//场景二
let a = {a:2,b:2};
let b = {c:3,d:3,a:5};
let c = Object.assign({},a,b);//将b对象合并到a对象上,再将a对象合并到新对象上,返回新对象
console.log(c);//{ a: 5, b: 2, c: 3, d: 3 }
console.log(c == b);//false
console.log(c == a);//false
//场景三 Object.assign is not a function 兼容处理
//全局定义 Object.assign
if(typeof Object.assign != 'function'){
Object.assign = function(target) {//target 最终输出的结果
'use strict';
if (target == null) {//undefined == null true null == null true
throw new TypeError('Cannot convert undefined or null to object');
}
console.log(target == Object(target))
target = Object(target);//target == Object(target) true
for(var i = 1;i<arguments.length;i++) {
var source = arguments[i];
if (source != null) {
for (var key in source) {
if (Object.prototype.hasOwnProperty.call(source, key)) {
target[key] = source[key];
}
}
}
}
return target;
};
}
控制台报错“SyntaxError: Unexpected end of input”
前端代码本地实时编译打包,webpack启动本地服务没有问题;通过命令
scp -r /Users/wanshaobo/project/xdf-middle-school-webview/old/ root@172.22.31.45:/root/project/old
将包发布到服务器,此时服务器报错磁盘已满,看上去包已经发布成功,但是通过域名访问这个网站报错SyntaxError: Unexpected end of input
。经过排查,重启nginx修复了这个报错。
经过排查,导致这个报错不是nginx的问题,是因为本地重新安装了node包,导致语法格式变换;
//修复前
//a.js
export const options = []
export const subjectInfo = arr[2]
export const requestTips = {
timeout: '哎呀,加载超时了,请刷新重试~'
}
//在b.js中使用a.js
import { requestTips } from '../../common/dic'
//修复后
//a.js
const options = []
const subjectInfo = arr[2]
const requestTips = {
timeout: '哎呀,加载超时了,请刷新重试~'
}
module.exports = {
options,
subjectInfo,
requestTips
}
react项目控制台报错“Can’t perform a React state update on an unmounted component”
A路由跳转到B路由,执行如下路由跳转方法,
this.props.history.replace({pathname:’/B’,search: ‘a=1&type=1’})
报错如下:
react-dom.development.js:506
Warning: Can’t perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in the componentWillUnmount method.
可以看出,错误的原因是A路由的页面在执行跳转方法之后,又执行setState方法了,经过排查,A路由的页面中有一个setInterval定时器没有卸载
解决方案:在A路由页面生命周期方法componentWillUnmount中卸载定时器
componentWillUnmount() {
clearInterval(this.Timer);
this.allTimer = null;
}
URL传参的两个问题
- 参数中包含汉字、数组等格式的数据,导致浏览器无法解析报错
此时需要用到 encodeURI() 函数 和 decodeURI() 函数,该函数把字符串作为 URI 进行编码和解码
对以下在 URI 中具有特殊含义的 ASCII 标点符号,encodeURI() 函数是不会进行转义的: , / ? : @ & = + $ #
(可以使用 encodeURIComponent() 方法分别对特殊含义的 ASCII 标点符号进行编码)
//没有做处理的场景
//URL中将要包含如下的数组和汉字
let params1 = [1,2,3]
let params2 = '万少博'
//如果不用JSON.stringify()对数组进行转化,在拼接URL的时候,会调用数组的toString方法
let url = 'www.baidu.com?params1=' + params1 + '¶ms2=' + params2;//www.baidu.com?params1=1,2,3¶ms2=万少博
//做过处理之后的场景
let url = 'www.baidu.com?params1=' + JSON.stringify(params1) + '¶ms2=' + params2;//www.baidu.com?params1=[1,2,3]¶ms2=万少博
//用 encodeURI 函数对URL进行编码
let encode = encodeURI(url);//www.baidu.com?params1=%5B1,2,3%5D¶ms2=%E4%B8%87%E5%B0%91%E5%8D%9A
//用 decodeURI 函数对URL进行解码
let decode = decodeURI(encode);//www.baidu.com?params1=[1,2,3]¶ms2=万少博
- 参数中包含 & 导致浏览器无法解析报错,或导致接收端解析异常
此时需要用到 escape() 函数 和 unescape() 函数,该函数可对字符串进行编码和解码
该方法不会对 ASCII 字母和数字进行编码,也不会对下面这些 ASCII 标点符号进行编码: * @ - _ + . /
(其他所有的字符都会被转义序列替换)
//没有做处理的场景
//传递对象的值虽然是字符串类型的数据,但是包含了&
let params = {name: 'a&b',age:18}
let url = 'www.baidu.com?params1=' + JSON.stringify(params) + '¶ms2=zhongguo'
let kv = url.split('?')[1];//params1={"name":"a&b","age":18}¶ms2=zhongguo
//解析期望得到的数据是有两个元素的数组[ 'params1={"name":"a&b","age":18}', 'params2=zhongguo' ],但是实际得到了三个
let value = kv.split('&');//[ 'params1={"name":"a', 'b","age":18}', 'params2=zhongguo' ]
//做过处理之后的场景
let params = {name: 'a&b',age:18}
//用 escape 函数对字符串进行编码
let url = 'www.baidu.com?params1=' + escape(JSON.stringify(params)) + '¶ms2=zhongguo'
let kv = url.split('?')[1];//params1={"name":"a&b","age":18}¶ms2=zhongguo
let value = kv.split('&');//['params1=%7B%22name%22%3A%22a%26b%22%2C%22age%22%3A18%7D','params2=zhongguo']
let p1 = value[0];//params1=%7B%22name%22%3A%22a%26b%22%2C%22age%22%3A18%7D
//用 unescape 函数对字符串进行解码
let v1 = unescape(p1.split('=')[1]);//{"name":"a&b","age":18}
let obj = JSON.parse(v1);//{ name: 'a&b', age: 18 }
参考资料
- 无
感谢阅读,欢迎评论^-^