0%

前言

博客中的大部分文章 大部分都有一个引子,要说明的通常是一些零零碎碎或者大多耳熟能详的 “知识点”

我认为遇到了问题 解决了问题 然后才会形成一个知识点而不是为了应用某个知识点去搭建场景,否则 真的是过目即忘
解决最近一个问题之前 并没有计划写关于防抖和节流的问题,问题引导你说 需要重视这一块儿了

此文系 不要再问我 XX 系列之 不要再问我函数节流和防抖了

阅读全文 »

开篇

要说 React 设计体现了响应式编程思想

UI=f(state)

程墨 Morgan 的总结真是恰到好处

stateReact 很重要,setState 作为管理 state 的重要方法自然也是头等公民

当然了,如果只是简单用法,API 足够了,你知道如何设置,如何更新,或许能解决眼前的需求,但是需求稍微复杂一点,可能会被动陷入 setState 怪圈

在哪里跌倒就把哪里买下来,被坑了之后,痛定思痛决定研究下 setState 怪象,最后发现 react 真是博大精深

文章太太太长了,不太感兴趣的话,可以拉到最后总结,也能避免入坑

阅读全文 »

背景:
前端 er 需要关注的点,缓存
它在移动端上尤其严重,因为手机随时随地会缓存你的资源,要想清缓存,不像 PC 使用强制刷新,还要手动找到浏览器的缓存,有时候还要重启等
所以 用实践理解缓存机制 写下此文记录

为了对比理解本文会涉及到

  • DNS 缓存
  • CDN 缓存
  • 浏览器缓存 (HTTP 缓存)

阅读全文 »

2018 年终总结

背景

跨域这两个字就像狗皮膏药一样儿粘在每一个前端 er 身上 我遇见了很多开发者一般都是为了应付面试 随便背几个方案 知道概念 但是不知道为什么要这么干
到了真正的工作 开发环境有 webpack-dev-server 搞定 线上有运维大哥会配好,配什么我不管 反正不会跨域就是了
但是.. 这样儿混日子 你的良心不会痛吗?

痛定思痛 决心不定时更新 不要再问我 XX 的问题系列 之 不要再问我跨域的问题了

其实团队的小伙伴分享过类似的 但是不动手试一下 跟你面试前的死记硬背本质上没有任何区别

阅读全文 »

前段时间写了前端防刷逻辑 作此记录
当时的需求


防刷逻辑 1 24 小时内 一个手机号只能提交三次 第四次提交的时候 提示 已提交成功 请耐心等待 400
防刷逻辑 2 24 小时内 提交三个手机号 提交第四个时 提示 401

阅读全文 »

引出问题

一个很简单的需求 页面有一张小图 点击是个 swiper 实现的图集 同时有一个灰色的蒙层 蒙层底部页面不可滑动 关闭蒙层 页面可恢复正常

实现方式

1
2
3
4
5
6
7
8
9
10
11
12
13
function bodyScroll(event){
event.preventDefault();
}
function _switchTag(type) {
if (type === 'on') {
window.addEventListener('touchmove', bodyScroll);
} else {
window.removeEventListener('touchmove', bodyScroll);
}
}

_switchTag(on) 页面不可滑动
_switchTag(off) 页面恢复滑动

移动端的效果如下
android
jsworke

ios
jsworke

我不知道为什么无效 直到我在模拟器上看到了

jsworke

啊哦 报错了🦢

Unable to preventDefault inside passive event listener due to target being treated as passive
来自 google 的解释 https://developers.google.com/web/updates/2017/01/scrolling-intervention

大概的意思是说

1
2
3
4
5
6
7
由于浏览器必须要在执行事件处理函数之后,才能知道有没有调用过 preventDefault() ,这就导致了浏览器不能及时响应滚动,略有延迟。

所以为了让页面滚动的效果如丝般顺滑,从 chrome56 开始,在 window、document 和 body 上注册的 touchstart 和 touchmove 事件处理函数,会默认为是 passive: true。浏览器忽略 preventDefault() 就可以第一时间滚动了。

举例:
wnidow.addEventListener('touchmove', func) 效果和下面一句一样
wnidow.addEventListener('touchmove', func, { passive: true })

这就导致了这个问题

1
如果在以上这 3 个元素的 touchstart 和 touchmove 事件处理函数中调用 e.preventDefault() ,会被浏览器忽略掉,并不会阻止默认行为

所以出现了以上视频中问题

解决方案

那么我们如何来解决这个问题 即不要让浏览器忽略掉 e.preventDefault ()?

  1. window.addEventListener(‘touchmove’, func, { passive: false })

设置 passive: false 之后的结果
android
jsworke
ios
jsworke
浏览器
jsworke
问题完美解决

1
2
3
4
5
6
7
8
9
10
11
12
13
你看到这里可以结束了 如果你还想再了解一点点
👇👇👇👇

#### 你可能不知道的addEventListener

很久之前addEventListener的参数是这样儿的
`addEventListener(type, listener, useCapture)`

后来也就是控制监听器是在捕获阶段执行还是在冒泡阶段执行的 useCapture 参数,变成了可选参数
`addEventListener(type, listener [,useCapture])`

再后来 DOM 规范做了修订addEventListener() 的第三个参数可以是个对象值了,也就是说第三个参数现在可以是两种类型的值了 变成这样儿式儿的

addEventListener(type, listener[, useCapture ])
addEventListener(type, listener[, options ])

1
2
3
扩展新的选项,从而自定义更多的行为,目前规范中 options 对象可用的属性有三个:


addEventListener(type, listener, {
capture: false, 等价于 useCapture 默认值 false
passive: false, 是否让阻止默认事件失效 true: 失效 false:不失效
once: false // 表明该监听器是一次性的,执行一次后就被自动 removeEventListener 掉,还没有浏览器实现它 默认值 false
})

1
2
3

还想再说一点 那我设置了 passive的事件 这么移除呢 这里给出了方法

你可以直接省略第三个参数
window.removeEventListener(‘touchmove’, func)

如果添加了 第一个参数 capture 可以这样移除

window.removeEventListener(‘touchmove’, func, true)
window.removeEventListener(‘touchmove’, func, {capture :true})

为什么会有 passive 这个概念

像这样儿的代码

1
2
3
4
document.addEventListener("touchstart", function(e){
... // 浏览器不知道这里会不会有 e.preventDefault()
})

由于 touchstart 事件对象的 cancelable 属性为 true,也就是说它的默认行为可以被监听器通过 preventDefault () 方法阻止,那它的默认行为是什么呢,通常来说就是滚动当前页面(还可能是缩放页面),如果它的默认行为被阻止了,页面就必须静止不动。但浏览器无法预先知道一个监听器会不会调用 preventDefault (),它能做的只有等监听器执行完后再去执行默认行为,而监听器执行是要耗时的,有些甚至耗时很明显,这样就会导致页面卡顿。视频里也说了,即便监听器是个空函数,也会产生一定的卡顿,毕竟空函数的执行也会耗时。

有 80% 的滚动事件监听器是不会阻止默认行为的,也就是说大部分情况下,浏览器是白等了。所以,passive 监听器诞生了,passive 的意思是 “顺从的”,表示它不会对事件的默认行为说 no,浏览器知道了一个监听器是 passive 的,它就可以在两个线程里同时执行监听器中的 JavaScript 代码和浏览器的默认行为了

引出问题

为什么有这篇文章.
最近的开发中遇到这么一个问题
如下图
gifqian
扫码进入网页 点击弹出覆盖整个手机屏幕的层 此时点击浏览器的返回 会直接回退到之前扫码页面
其实 这个逻辑很合理 因为它没有历史记录 没有所谓的上一个页面 程序上是合理的
但用户体验无疑是差到极致
对于用户来讲 可能我只是想把当前的弹层关掉而不是退出网页

那么如何解决呢?

解决方案

没有历史记录 那我们就手动造出来一条 “历史记录”,让程序的返回时 能够有迹可循
最终效果
gifqian

相关代码

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
 componentDidMount(){
//监听popstate事件
window.addEventListener('popstate',() => {
this.navLeftClick();
})
}
//弹层的返回按钮
navLeftClick = () => {
this.setState({
showBrandContainer: false
})
}

componentWillUnmount() {
// 离开页面的时候取消监听popstate
window.removeEventListener('popstate',(state) => {
this.back();
})
}

selectBrand = () => {
window.history.pushState({page: 1}, "title 1", "?page=1"); //向history对象push一条state

<!-- 实现参数透传----
let search = window.location.search;
window.history.pushState({ page: 1 }, "", search);
实现参数透传---- -->

this.setState({
showBrandContainer: true //开启弹层
})
}

History

DOM 中的 window 对象通过 window.history 方法提供了对浏览器历史记录的读取,让你可以在用户的访问记录中前进和后退
从 HTML5 开始,我们可以开始操作这个历史记录堆栈
前进 window.history.forward();
后退 window.history.back();
向前移动 N 页 window.history.go(-N);
向后移动 N 页 window.history.go(N);
你甚至可以通过检查浏览器历史记录的 length 属性来找到历史记录堆栈中的页面总数
window.history.length

HTML5 history 新特性 pushState、replaceState

HTML5 引入了 histtory.pushState () 和 history.replaceState () 这两个方法,他们允许添加和修改 history 实体。同时,这些方法会和 window.onpostate 事件一起工作,关于 window.popstate 可参考 window.popstate
pushState:向 history 添加当前页面的记录 使用 history.pushState () 方法来修改 referrer
replaceState:和 pushState 的用法完全一样,区别就是它用于修改当前页面在 history 中的记录

一个🌰

1
2
3
4
5
6
假设http://10.70.134.53:3000/opt/financial 控制台执行了JS
var stateObj = { foo: "test" }; history.pushState(stateObj, "page 2","test.html");
url地址栏变为 http://10.70.134.53:3000/opt/test.html,但浏览器不会加载bar.html页面,即使这个页面存在也不会加载。
此时 如果你点击浏览器的返回 浏览器就貌似有了前一页
如下图:

gifqian

总结:

关于 popstate 事件 需要注意的几点

  • 调用 history.pushState () 或者 history.replaceState () 不会触发 popstate 事件.
  • popstate 事件只会在浏览器某些行为下触发,比如点击后退、前进按钮 (或者在 JavaScript 中调用 history.back ()、history.forward ()、history.go () 方法).

也就是说 要触发该事件 你需要两步

  1. 添加并激活一个历史记录条目 (history.pushState)
  2. . 改变历史记录条目 (用户行为,比如后退,前进)

前言:state 是 react 中重要的概念, react 是通过管理状态来实现对组件的管理,那么 react 是如何控制组件的状态 又是如何利用状态来管理组件的呢?

我们所知道的版本 大概是 通过 this.state 来访问 state,通过 setState() 方法来更新 state,当 this.state() 被调用的时候 React 会重新调用 render 方法来重新渲染 UI

那好的 先来看一道题吧

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
export default class SetState extends React.Component {
constructor(){
super();
this.state = {
val:0
}
}

componentWillMount(){
this.setState({val:this.state.val+1});
console.log('componentWillMount第一次输出',this.state.val)
this.setState({val:this.state.val+1});
console.log('componentWillMount第二次输出',this.state.val)
}
componentDidMount(){
// debugger;
this.setState({val:this.state.val+1});
console.log('componentDidMount第一次输出',this.state.val)
this.setState({val:this.state.val+1});
console.log('componentDidMount第二次输出',this.state.val)
setTimeout(()=>{
// debugger;
console.log('开始setTimeout',this.state.val)
this.setState({val:this.state.val+1});
console.log('第三次输出',this.state.val)

this.setState({val:this.state.val+1});
console.log('第四次输出',this.state.val)
},0)
}

render(){
return null;
}

}

这道题的答案是 0 0 1 1 2 3 4

假如结果与你心中的答案并不完全相同,那么你应该感兴趣这背后究竟发生了什么.

阅读全文 »

曾经 你一定遇到过类似这样儿的题目

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
console.log('script start')

setTimeout(function() {
console.log('timer over')
}, 0)

Promise.resolve().then(function() {
console.log('promise1')
}).then(function() {
console.log('promise2')
})
console.log('script end')

输出结果:
script start
script end
promise1
promise2

如果你很轻松的答对并且能说出原理 那么恭喜你,倘若有些疑问,那么读完这篇文章,你一定会彻底搞懂它的运行原理。

阅读全文 »