日常踩坑
摘要
记录日常开发时踩的一些坑,从这些坑中慢慢去扩展对应的技术
# 常用的工具
- 使用
nvm
来切换 node.js 版本,使用nrm
来切换 npm 源 - 使用
dayjs
来进行时间戳的运算 - 使用
uuid/v1
来确保唯一 key 值 - 使用
lodash
来更好地运用工具函数来开发 - 使用
superstruct
简单地进行接口校验 - 使用
logline
来进行日志记录 - 使用
mathjs
来避免算数运算不符合常识的结果
# 浮点数运算
在开发的时候,总有一些计算逻辑需要我们将小数计算成百分数,但是只是这样简单的计算,在 JavaScript 中也是有坑存在的。
0.28 * 10 === 2.8; // true ? false
答案是 false
,这是由于 JavaScript 语言的一个缺陷导致的,所以,想使它为 ture,则需要 * 100
来运算并使用 toFixed 函数去解决
Number((0.28 * 10).toFixed(1)) === 2.8; // true
或者使用 Math 对象相关的方法,再使用 Number.EPSILON
来确保精度问题
/** 默认返回整数 */
function getWholeNumber(num, decimalPlace = 0) {
let digit = Math.pow(10, decimalPlace);
return Math.round((num + Number.EPSILON) * digit) / digit;
}
getWholeNumber(0.28 * 10, 1) === 2.8; // true
- Number.EPSILON 属性表示 1 与 Number 可表示的大于 1 的最小的浮点数之间的差值。
- 像这类浮点数的问题,建议使用专门的数学库来处理,比如 Numeral.js 和 mathjs
# 时间戳比较问题
日常开发中,总会限制一些时间范围,不仅是在开发中,日常生活也是,比如说国庆节,一般放假都是 10.01 开始到 10.07 结束,简单一看很简单,只要限制开始时间和结束时间就是了
这里我们直接用常用的 dayjs
来举例代码
function isOnVacation(time) {
const startTime = dayjs("2020-10-01").valueOf();
const endTime = dayjs("2020-10-07").valueOf();
return time > startTime && time < endTime;
}
乍一看好像没什么毛病,但实际上,像 dayjs
等常用的处理工具,转换日期时,只会转换到 2020-10-07 00:00:00
这个时间戳出来,相当于 7 号零点敲响,假期结束,但这不是我们想要的 7 天长假,怎么能忍受假期被吞了呢!,所以改造如下
function isOnVacation(time) {
const startTime = dayjs("2020-10-01").valueOf();
const endTime = dayjs("2020-10-08").valueOf() - 1;
return time > startTime && time < endTime;
}
只要到 2020-10-08 00:00:00
再减去 1 毫秒就是了,假期连 1 毫秒都不能少!,所以 使用时间戳进行比较时,截止时间要精确到毫秒级别,不然会导致某些业务逻辑出错
# UA 判断
npm 上有一个 mobile-detect
的库可以简单提供一些方法,另外关于 IOS 的方法只使用于移动端网页,ios13 后,ipad 的 ua 不再有 ipad 的标识符
import MobileDetect from "mobile-detect";
const ua = navigator.userAgent;
const md = new MobileDetect(ua);
export const isAndroid = ua.includes("Android") || ua.includes("Linux");
export const isWechat =
/micromessenger/i.test(ua.toLowerCase()) ||
/windows phone/i.test(ua.toLowerCase());
// export const isIOS = !!ua.match(/\(i[^;]+;( U;)? CPU.+Mac OS X/)
export const isIOS = !!ua.match(/Mac OS X/);
export const isIPhone = ua.toLowerCase().includes("iphone");
export const isIPad =
isIOS &&
!isIPhone &&
(ua.toLowerCase().includes("ipad") || "ontouchend" in document);
// 获取设备型号,不一定能全拿到,只能拿到大多数
export const getDevice = () => {
const phone = md.phone();
const version = md.version(phone);
if (isIOS) {
return `${phone} ${version}`;
} else {
const arr = ua.split(";");
const item = arr.find(i => i.indexOf("Build/") > 0);
if (item) {
return item.substring(0, item.indexOf("Build/"));
} else {
return `${phone} ${version}`;
}
}
};
// 获取设备系统
export const getSystem = () => {
const os = md.os();
const key = os === "AndroidOS" ? "Android" : os;
const version = md.version(key);
return `${key} ${version}`;
};
// 是否是全面屏幕的手机
export const judgeBigScreen = (() => {
const rate = window.screen.height / window.screen.width;
// 临界判断值
const limit = window.screen.height == window.screen.availHeight ? 1.8 : 1.65;
return rate > limit;
})();
# 图片 Exif 信息
- 图片的 Exif 信息是只有拍照得到的图片才有的信息
- 可以根据图片的 exif 信息来对获取额外信息,比如可以根据角度将图片进行摆正,以及获取拍摄的地理位置等
- 可以使用 exif.js 来获取图片的 Exif 信息
- 可以使用 blueimp-load-image 插件配合
exif.js
来对图片进行压缩,转正,格式转换等处理
adjustImgOrientation (file) {
return new Promise((resolve, reject) => {
EXIF.getData(file, function () {
let orientation = EXIF.getTag(this, 'Orientation')
if (!orientation) orientation = true
loadImage(
file,
function (canvas, data) {
if (canvas.type === 'error') {
console.error('Error loading image ' + canvas)
} else {
canvas.toBlob(blob => {
resolve(blob)
}, 'image/jpeg')
}
},
{
orientation: orientation, // 允许根据指定的Exif方向转换画布
canvas: true, // callback 返回 canvas
meta: true, // callback 返回 data
maxWidth: 750,
maxHeight: 1334
}
)
})
})
}
# 使用 nvm 安装 Node
工作以后,我们会跟着公司使用稳定的 Node 版本进行日常开发,但是如果要尝鲜 Node 的新特性怎么办?而且公司要升级 Node 版本进行调研要怎么去办?而这就需要用 nvm
来安装不同版本的 Node.js 了
以 Windows 为例,可以去 nvm-windows 上下载一个 nvm-setup.zip
进行安装,个人用的是 1.1.7
的版本
安装注意事项
如果本机已经安装了 Node.js,需要将其卸载。并卸载现有的 Npm
C:\Program Files\nodejs
C:\Users\<用户>\AppData\Roaming\npm
如果配置了 npmrc
,那最好备份一下,位置在C:\Users\<用户>\AppData\Roaming\npm\etc\npmc
下载完成后进行安装即可,然后在命令行中输入 nvm -v
,有版本号输出就表示安装成功了,最后 nvm 被安装在该目录下 C:\Users\<用户>\AppData\Roaming\nvm
接下来就可以安装 Node 了,注意 Node 在 10 版本以后才会一起安装 npm,而且由于国内网速原因,下载 Node 很慢,还好阿里提供了镜像源,所以只需先配置一下镜像即可
nvm node_mirror https://npm.taobao.org/mirrors/node/
nvm npm_mirror https://npm.taobao.org/mirrors/npm/
现在就可以安装 Node 了
nvm install v14.15.0
## ...等待安装
## 使用 use 来选择使用 14.15.0 版本的 node
nvm use 14.15.0
## 查看是否安装成功
node -v
npm -v
## 设置淘宝源
npm config set registry=https://registry.npm.taobao.org
如果安装 Node 失败,则需要重新安装一遍 nvm,还有其他错误的话可以再另行搜索,或者查看下对应的 issue 进行处理反馈
# new Array 无法使用 map 的原因与解决方法
有时候想造一些假数据来开发,但是又不想写的很麻烦,于是我写过下面这段代码
new Array(100).map(i => Math.random());
按照代码的理解,应该是会返回“长度 100 且元素为随机数的数组”,但实际执行后拿到的却是“长度 100 且元素为 empty 的数组”
注意,这里是 empty
而不是 undefined
哦,后来查了下 MDN,才发现
说明
Array 构造器会根据给定的元素创建一个 JavaScript 数组,但是当仅有一个参数且为数字时除外,这时会返回一个 length
的值等于 arrayLength
的数组对象,不能理所当然地认为它包含 arrayLength
个值为 undefined
的元素
但是明明通过下标拿元素的时候又是 undefined... 所以再仔细查了一遍资料,发现,数组还分“密集型数组”和“稀疏型数组”。并且 JS 的数组,只是一种类列表对象
,数组的索引其实也是字符串
,而不是数字
,所以算是稀疏型数组
知识点
- 密集型数组
- 数组是一片连续的存储空间,有着固定的长度,在强类型语言中常见。
- 稀疏型数组
- 稀疏型数组,表明元素之间可以有空隙,数组的长度可随时改变,并且其数据在内存中也可以不连续
最后,回到我们的需求上来,如果想创建长度 100 且元素为随机数的数组,且不用 for 循环的话,要怎么写会优雅简洁呢?,有几种方式如下
// 第一种,使用 fill 方法填充
new Array(100).fill(undefined).map(i => Math.random())
// 第二种,使用浅复制方法,可以使用 Array.from() 方法来代替
[...new Array(100)].map(i => Math.random())
// 或者使用 apply 改变指向
Array.apply(null,new Array(100)).map(i => Math.random())
// 第三种,使用 ES2017 中提供的 TypedArray,这种是密集型数组
new Float64Array(100).map(i => Math.random())
# Vue 新手犯错:vuex 中操作数组,报错 Do not mutate vuex store state outside mutation handlers
实际上是因为 vuex 开启了 strict
模式,然后没有使用 vuex 的方法来处理数组,直接对数组进行数据修改操作导致的,此时为了避免这样的问题,需要再 mutation 定义一个 handler 来处理,如果不想让修改的数据影响到 vuex 的数据,则需要借助各种深拷贝的方式来解决
# 坐标系和 GeoJson
工作中需要做“可视化的需求”,这时需要用到对应的“地图 API”,或者使用图表来绘制地图
# 坐标系
- 关于地图 API,在国内有三大地图可选 -- "高德地图","百度地图',"腾讯地图"
- API 的调用直接看对应平台的文档即可,困扰我的是另外一个问题,在接口联调时,后端问我在地图上标点需要哪种坐标?
- 对于这方面完全不了解的我只能去找找资料了
总结如下
- 地理坐标系又可分为"参心坐标系"和"地心坐标系",而地图使用的是"地心坐标系"
- 常见的地心坐标系又有以下几种
- WGS84(World Geodetic System 1984) GPS 全球定位系统建立的坐标系统,被称为"原始坐标系",一般 GPS 记录的坐标都是基于该坐标系的数据
- GCJ-02(国家测量局 02 号标准) 中国国家测绘局制订的地理信息系统的坐标系统,是在 WGS84 经纬度的基础上执行加密算法而成,被戏称为"火星坐标系"
- BD-09 百度使用的是在 GCJ-02 基础上再一次加密的坐标系。
- 国测局规定:互联网地图在国内必须至少使用 GCJ02 进行首次加密,不允许直接使用 WGS84 坐标下的地理数据,同时任何坐标系均不可转换为 WGS84 坐标。因此不存在将 GCJ-02 坐标转换为 WGS84 坐标的官方转换方法。(但还是有办法进行转换,只不过不官方...)
由于公司的业务目前只服务于国内,所以需要使用 GCJ-02
的坐标才能正常在地图上标点
# GeoJson
- GeoJSON 是一种基于 JSON 的地理空间数据交换格式,基于 JSON 格式来描述地理位置,这里不再赘述,百科已经足够让开发者了解对应的功能。
- 像
高德地图API
和Echarts
就支持用 GeoJson 来更好地绘图
# 线上表现和本地表现不一致?
在带实习生的时候遇到过一个问题,线上项目样式和他本地开发环境中的项目样式是不一致的,而且挂在原型上的方法表现也不一致,仔细排查后有
两个原因
- 开发环境使用的是 cnpm 来 install 包,忽略了
package.lock.json
中的包版本限制,所以样式不一致、 - 本来以为原型方法表现不一致也是一样的问题,但发现又是一个新的问题。因为发布的分支是公共的分支,另外一位团队成员在他的功能分支上安装了一个新的第三方框架包,那个包挂载在 Vue 原型方法和主要框架挂载的原型方法冲突了
解决方案
- 使用 npm,不使用 cnpm,或者使用另外的如
pnpm
,yarn
等可以固定版本的包管理器 - 新的第三方框架包进行按需引入,避免原型方法相互影响
补充
之后自己也遇到过类似问题,而这次是工程上的问题 ———— "引用的第三方包没有单独抽离出 css,vue-cli
在打包时会设置 css.extract
为 ture
去抽离 css,导致开发环境的样式顺序和线上环境的样式顺序加载不一致"
这次采取了最简单的方法,直接引入第三方包的源代码
,交由项目工程进行打包处理,这样就能把第三方包的样式抽离出来,两个环境的样式加载顺序就一致了。
这类问题只能具体情况具体分析,这里只是记录自己遇到的情况,以及怎么去解决而已,仅供参考
# window.close() 注意事项
该方法只能由 window.open()
方法打开的窗口的 window
对象来调用。如果一个窗口不是由脚本打开的,那么,在调用该方法时,JavaScript 控制台会出现类似下面的错误:不能使用脚本关闭一个不是由脚本打开的窗口
或 Scripts may not close windows that were not opened by script.
。
同时也要注意,对于由 HTMLIFrameElement.contentWindow
返回的 window
对象,close()
也没有效果。