前端常见问题分析
http://zoo.zhengcaiyun.cn/blog/article/common-question
在前端开发过程中,常常遇到各种各样的问题和坑点。尤其是随着技术的不断发展和更新,新的问题也不断涌现。对于初学者而言,这些问题往往让人感到十分困惑和无助。因此,本文将旨在探讨一些前端开发过程中常见的问题和坑点以及解决方法,帮助读者更加深入地了解前端开发,并解决实际工作中遇到的问题。
数字
/**
* 为什么 010 会是 8
*/
const num1 = 09 // 9
const num2 = 010 // 8
这边是因为 0 开始的数字js会尝试先把它转成八进制的数字。如果你出现大于 8 的数字,他知道不是八进制还给你转十进制。纯粹的八进制应该用 0o
,类似的还有 0b
二进制和 0x
十六进制,但是他们写的不符合转换条件的话会直接报错。
2. 精度丢失问题:
0.1+0.2 // 0.30000000000000004
2.55.toFixed(1) // '2.5'
2.45.toFixed(1) // '2.5'
2.toFixed(1) // Uncaught SyntaxError: Invalid or unexpected token
2..toFixed(1) // '2.0'
js 计算有精度问题呢?大家一定都是知道的, 今天就是来简单解释一下为什么会出现丢失精度的问题。这边其实分两部分,存储和展示。存储的时候 JavaScript 是以 64 位二进制补码的方式来存储。由于改方式是以 2 为底进行表示的,所以执行某些运算时容易出现误差、溢出、无限循环等问题。
我们可以发现本来应该是 11001100 无限循环被截断了,尾号 11001 的时候1被舍去了,然后进了一位 最后存储成了上图的 1101 的样子。所以 0.1 其实存的比 0.1 要大一点点,0.2 也是一样,而 0.3 比实际小一些。所以计算 0.1+0.2
的时候其实是拿二进制计算的,两个都偏大的数字相加 误差被近一步的放大了。
下面这张图可以看到他们真实存下来的数据转成十进制的样子。实际显示的时候会做近似处理,js 会判断一个数字特别像 0.1 它就显示 0.1 了。
toFixed 问题也是一样。
还有就是有时候我们对一个数字使用 .toFixed .toString 会报错。
0.toString() // Uncaught SyntaxError: Invalid or unexpected token
// 我们期待的是它会隐性转换让我们调用 Number 构造函数上的方法,
// 但是程序会以为你在写一个小数,小数还不合规,所以报错了,
// 解决方法就是拿变量装一下,或者 0..toString()
在 JavaScript 中,采用 64 位二进制补码表示数值类型,即双精度浮点数。符号位(S)、指数位(E)和尾数位(M)的比特数分别为 1 位、11 位和 52 位。在使用 IEEE 754 标准表示双精度浮点数时,使用一些特殊的位表示:其中一个隐含位表示数字 1,在正常项中省略,因此一共有 53 位表示有效数字。
长度问题:
function fn(a,b,c){
return a+b+c
}
fn.length // 3 一般来说fn的长度是形参的个数 但是形参有默认值就不同
function fn1(a = 1,b,c){
return a+b+c
}
fn1.length // 0
function fn2(a,b=1,c){
return a+b+c
}
fn2.length //1
// 它只会统计首个默认之前的参数
对象排序问题:
a.b=1
a.a=1
a.c=1
a[2]=2
a[12]=2
a[1]=2
// 结果 {1: 2, 2: 2, 12: 2, b: 1, a: 1, c: 1}
// 对象的内部key value的存储顺序是这样的
// 如果属性可以转number,提前上来,按升序排列,其他的字符串属性按添加的先后顺序
赋值中断问题:js 里没有事务的机制,不会恢复到操作之前的状态。如果中途失败了,之前赋值和操作过的数据是保留的,失败后的操作不执行。
定时器不准:这里说的不准还不是说一点小误差。定时器由于渲染主进程阻塞也好,延时任务嵌套过深也好,事件循环优先级被排队到后边也好。这些都可以认为是“误差”,但是如果说你 setIntervel 是 10ms,结果它间隔 n 秒调一次函数,那可不是误差了,可能直接会产生 bug。
这个问题的原因是:用户在使用谷歌浏览器的过程中将窗口最小化或切换到其他应用程序中去,浏览器会将当前标签页和其中的 JavaScript 定时器挂起,这将导致定时器延迟调用。通常情况下,浏览器会尽可能保持定时器的准确性,并在恢复标签页后立即执行延迟的定时器。但是,如果计算机负载过重或其他原因导致 JavaScript 的执行速度变慢,定时器可能会更加延迟。经过测试,新版本的浏览器上基本都是至少 1 秒一次。
详细参考 https://developer.mozilla.org/zh-CN/docs/Web/API/setTimeout
竞态问题:异步的竞态问题也是开发中经常遇到的问题。举个例子用户输入搜索就请求相应的商品,用户很快的输入了“手机壳” 3 个字;“手”“手机”“手机壳” 3 个不同参数的请求几乎同时发了出去,异步请求很难保证哪个请求,先回来哪个请求后回来。那加上防抖呢?其实这也不是防抖该应用的场景,弱网环境下请求 5 秒、10 秒返回都说不准,防抖防几秒都不太合适。我根据个人的经验总结了 3 种方式:
// 让要执行的异步函数通过一个链式方式调用
export class SequenceQueue {
promise = Promise.resolve();
excute (promise) {
this.promise = this.promise.then(() => promise);
return this.promise;
}
};
const fetchRef = useRef(0);
const debounceFetcher = useMemo(() => {
const loadOptions = (value: string) => {
fetchRef.current += 1;
const fetchId = fetchRef.current;
setOptions([]);
setFetching(true);
fetchOptions(value).then((newOptions) => {
if (fetchId !== fetchRef.current) {
// for fetch callback order
return;
}
setOptions(newOptions);
setFetching(false);
});
};
return debounce(loadOptions, debounceTimeout);
}, [fetchOptions, debounceTimeout]);
可以进一步封装,将请求封装成 request( url, [option], [queueName] )
, 通过外部传入来指定需要竞态的映射名。也就是将上述的叠加器放在一个 Map 里,使用 queueName 做 Map 的 key。
“如果作为通用的请求中间件封装,处于内存优化考虑,此处可以将 Map 优化成 weakMap。Map 键值对是强引用,如有一个键被引用,那么GC是无法回收对应的值的,weakmap 不存在这样的问题,但要注意 weakMap 只能使用对象做 key。
// 封装
function cancelableRequest(requestPromise) {
const cancelToken = {};
const cancelablePromise = new Promise((resolve, reject) => {
cancelToken.cancel = () => {
reject(new Error('Request was canceled'));
};
Promise.race([requestPromise, cancelToken])
.then(resolve)
.catch(reject);
});
return { promise: cancelablePromise, cancel: cancelToken.cancel };
}
// 使用
const mockApi= () =>
new Promise(resolve => {
setTimeout(() => {
resolve([{ title: 'Post 1' }, { title: 'Post 2' }, { title: 'Post 3' }]);
}, 3000);
});
const { promise, cancel } = cancelableRequest(mockApi());
promise
.then(posts => console.log(posts))
.catch(error => console.error(error.message));
// 取消请求
cancel();
定位:一般来说写 position: fixed 都是想相对窗口定位实现一些弹窗、抽屉或者浮动组件等效果,但是如果父元素中存在 transform 属性的话,固定效果将直接降级变成 position: absolute 的表现。这可能也是大多数UI库选择将 modal、drawer 之类的 fixd 元素都插入在 body 下,和应用本身分离开,可能就是担心有 transform 来影响定位。究其原因是因为包含块的定义:
如果 position 属性为 static、relative 或 sticky,包含块可能由它的最近的祖先块元素(比如说 inline-block, block 或 list-item 元素)的内容区的边缘组成,也可能会建立格式化上下文 (比如说 a table container, flex container, grid container, 或者是 the block container 自身)。
层叠计算:有的时候,如果引入了很多的库,会发现样式会偶发的发生错误。这是因为样式冲突了,那样式的优先级是什么样子的呢?css 全称为 cascader style sheet, 层叠样式表。其层叠的目的就是为了比对样式冲突后的“胜出者”;mdn 里详细的介绍了其比较计算的规则。https://developer.mozilla.org/en-US/docs/Learn/CSS/Building_blocks/Cascade_layers
比对分以下步骤进行
分层就是浏览器画制图画的顺序,浏览器会根据一定的规则划分图层,当然代码也能干预图层的划分比如定位、动画等等,不同图层直接不能相互影响,换句话说一个图层在另一个图层下面的话,尽管 z-index 是 0 也能覆盖 z-index: 100 的元素。
div.navbar ul ul.dropdown-menu li.active > a.btn-primary:hover span.icon {
/* styles */
}
// 虽然其中没有 id 选择器,但它显然比单个 id 选择器的优先级更高。
造成性能问题的原因是多种多样的,大体可以分为 3 种,一是网络,二是渲染,三是计算
一些想当然觉得应该是一致的东西结果不一致,比如前瞻匹配和后瞻匹配的兼容是不一样的。需要兼容IE的话就不能使用后瞻写法
https://developer.mozilla.org/zh-CN/docs/Web/CSS/Containing_block
https://caniuse.com/
https://standards.ieee.org/ieee/754/6210/
如果你觉得这篇内容对你挺有启发,我想邀请你帮我两件小事
1.点个「在看」,让更多人也能看到这篇内容(点了「在看」,bug -1 😊)
政采云技术团队(Zero),Base 杭州,一个富有激情和技术匠心精神的成长型团队。政采云前端团队(ZooTeam),一个年轻富有激情和创造力的前端团队。团队现有 80 余个前端小伙伴,平均年龄 27 岁,近 4 成是全栈工程师,妥妥的青年风暴团。成员构成既有来自于阿里、网易的“老”兵,也有浙大、中科大、杭电等校的应届新人。团队在日常的业务对接之外,还在物料体系、工程平台、搭建平台、智能化平台、性能体验、云端应用、数据分析、错误监控及可视化等方向进行技术探索和实战,推动并落地了一系列的内部技术产品,持续探索前端技术体系的新边界。
如果你想改变一直被事折腾,希望开始能折腾事;如果你想改变一直被告诫需要多些想法,却无从破局;如果你想改变你有能力去做成那个结果,却不需要你;如果你想改变你想做成的事需要一个团队去支撑,但没你带人的位置;如果你想改变本来悟性不错,但总是有那一层窗户纸的模糊… 如果你相信相信的力量,相信平凡人能成就非凡事,相信能遇到更好的自己。如果你希望参与到随着业务腾飞的过程,亲手推动一个有着深入的业务理解、完善的技术体系、技术创造价值、影响力外溢的前端团队的成长历程,我觉得我们该聊聊。任何时间,等着你写点什么,发给 ZooTeam@cai-inc.com