首页
Search
1
Linux 下 Bash 脚本 bad interpreter 报错的解决方法
70 阅读
2
Arch Linux 下解决 KDE Plasma Discover 的 Unable to load applications 错误
52 阅读
3
Arch Linux 下解决 KDE Plasma Discover 的 Unable to load applications 错误
42 阅读
4
如何在 Clash for Windows 上配置服务
40 阅读
5
如何在 IOS Shadowrocket 上配置服务
40 阅读
clash
服务器
javascript
全部
游戏资讯
登录
Search
加速器之家
累计撰写
1,061
篇文章
累计收到
0
条评论
首页
栏目
clash
服务器
javascript
全部
游戏资讯
页面
搜索到
1061
篇与
的结果
2024-10-20
学完这 4 个小技巧,让你的移动端交互体验更加优秀
现在在手机等移动端设备访问的人越来越多,我们前端开发者一直致力于将设计稿还原成页面,供用户访问。但除高度还原设计稿外,交互上的良好体验也是我们应该做到的。 1. 即时反馈 # 我们在玩游戏的过程中,通常都会遇到一个词:“打击感”,通俗的理解就是我们做出的每一个操作,都有很强烈的反馈,比如视觉上的动画变化,听觉上产生的声音,或者移动设备的震动感等。 1.1 按钮的即时反馈 # 在前端页面中,也应当像游戏中的打击感一样,用户任何的操作都应当予以即时的反馈,告诉用户他的操作是有效的,系统已收到他的操作,内部正在处理中。例如用户在点击页面中的按钮时,按钮最好有一种被按下的效果:button:active { transform: translateY(4px); } 若按钮被下压的效果不太适合页面整体的风格,您也可以做一个背景颜色上的变化。 1.2 持续性的反馈 # 每个用户的设备型号、网络状态等情况都不一样,我们不能要求每个用户都在良好的 WiFi 下操作我们的页面。若用户的某个行为产生了网络请求,并要根据请求返回的结果,反馈给用户。这种情况,页面都应当给用户一种持续性的反馈,表示一个动作正在后台执行。如果没有这种效果,即使已经在请求接口了,用户也会认为点击没有反应,会多次的去点击按钮,以期望得到响应。我们可以在这里给自己定下一条规则: 凡是有网络请求的情形,均要有 loading 效果的持续性反馈。 我们通常可以在用户触发的按钮上展示 loading 效果,也可以在全局页面上展示 loading 效果,这个根据每个页面的风格自行选择即可。例如页面上有个红包需要点击按钮开启,当用户点击按钮后,按钮就可以展示出一个旋转的 loading 效果,待接口返回结果再打开红包,展示具体的金额,或者其他的结果。 1.3 页面初始化 # 在现在大部分前后端分离的场景下(同时没有使用同构直出方案),都是先展示出一个没有数据的前端页面,然后请求数据,待数据返回后再渲染页面。这种情形和上面 1.2 中是一样的,不过这个是在刚进入页面就触发的!这里我们也是要展示出 loading 效果的,只不过是 loading 展示的时机的问题。 先一个全局 loading 的开启页,在数据没有返回回来时,看不到任何相关活动元素; 先用初始化的假数据或者兜底数据,渲染一个基本框架,然后在某个位置展示 loading 效果,并请求数据,数据返回后再替换假数据进行渲染。 这两种方式也是各有不同的使用场景,就我个人而言,我更喜欢第 2 种方式,能够第一时间将页面中的元素展示给用户;但如果页面布局因接口的数据改变较大,建议还是采用第 1 种方式,这样 loading 结束时,不会出现页面大幅度闪动的感觉。 1.4 数据的展示 # 我们拿到接口的数据后,通常会有两种展示状态: 无数据,进行“暂无数据”之类的提示; 有数据,正常展示数据; 比如一个展示奖品列表中数据中,这里我们通常会初始化一个 list 变量来接收接口返回的数据:const List = () => { const [list, setList] = useState([]); useEffect(() => { // 设置数据 // setList([]); }, []); return ( {list.length ? ( {list.map((item) => ( {item.title} ))} ) : ( 暂无数据 )} ); }; 在请求接口的过程中,页面会展示什么?“暂无数据”,给用户的第一视觉感受就是:我的奖品丢了。等过一会儿接口返回数据了,然后又重新将数据展示出来。这里,我们就忽略了一个很重要的状态:loading状态。因为“暂无数据”,也是一种结果,不是过程,是要告诉用户,您当前是没有数据的。因此,不能把“暂无数据”作为 loading 状态来展示。const List = () => { const [loading, setLoading] = useState(true); const [list, setList] = useState([]); useEffect(() => { // 设置数据 // setList([]); setLoading(false); // 请求完接口,再把loading状态取消,该展示什么结果就展示什么 }, []); if (loading) { return ( 请求数据中... ); } return ( {list.length ? ( {list.map((item) => ( {item.title} ))} ) : ( 暂无数据 )} ); }; 2. 行为跟随 # 这里我也不太想好用个什么名字,概况来说,告诉用户刚才发生了什么,将用户操作可视化, 来增强用户对操作行为的感知度, 同时也能对元素内容的认知。因用户行为产生的新交互,应当与当前用户的行为相关。 2.1 点击按钮后呼起弹窗 # 用户点击按钮后,会弹出一个弹窗,弹窗可以从按钮所在的方向或者位置,弹出到整个页面的中心。给到用户的感受就是该弹窗与按钮是相关的。 2.2 列表中有对象变动时 # 例如在一个表格或者列表中,有新增、修改或者删除一行(一列)的行为,可以用一个动画和背景色来区分该元素, 过一段时间再恢复正常。 2.3 丝滑的滑动跟随 # 在不添加任何 CSS 属性时,滑动有滚动条区域时,总感觉有一种卡顿感,就是手指滑动时页面就跟着滑动,手指离开则页面停止滑动。这里我们添加上一个属性即可:body { -webkit-overflow-scrolling: touch; } 3. 考虑移动设备的握持姿势 # 在现在手机屏幕越来越大的趋势下,单手握持手机时,大模板只能在以左下角或者右下角为中心的区域活动。因此,在底部区域操作的情况越来越多,例如底部区域的导航,弹窗中点击空白区域即可关闭等等。 3.1 避免滚动穿透 # 在一个可滚动的页面中,呼起一个弹窗,这个弹窗中的内容也比较多,也需要滚动,如果不加处理的话,可能会造成两个区域同时滚动,体验不好。也就是避免滚动穿透。这里我们就要把底层的滚动锁住,只可以滚动处在最上层的区域。这里的原理我就不多讲解,推荐一个我一直在使用的组件tua-body-scroll-lock,该组件导出了 2 个方法: lock: 锁定区域,传入 dom 元素,则表示该 dom 区域内是可以滚动的; unlock: 解除锁定,当弹窗消除时,需要解除被锁定的区域; 在 react 中的使用方式:useEffect(() => { // 锁定body的滚动,只在弹窗内部滚动 // 只有需要设置可以滚动区域时,才使用该方法 if (props.scrollContainer) { lock(props.scrollContainer); } return () => { if (props.scrollContainer) { unlock(props.scrollContainer); } }; }, [props.scrollContainer]); 同时的,我们最好在遮罩区域添加可以关闭弹窗的操作,避免用户伸手够弹窗右上角的关闭按钮。 3.2 原生 select 标签的使用 # 在移动端开发中,下拉框我们使用原生 select 标签时,iOS 和 Android 的表现是不一样的,iOS 会出现在屏幕的底部,滚动选择某个选项;而 Android 中,则是屏幕中间弹出一个弹层,然后可以进行选择。如果图方便的话,其实可以使用原生的 select 标签。但这种方式,总感觉与页面元素之间产生了割裂,因此如果可以的话,尽量模拟出一个 select 标签。 4. 良好的兜底策略 # 每个用户的设备型号、网络状态等情况都不一样。总会因为各种各样的原因,导致页面展示异常。因此,我们应当做好提示和一些兜底策略。 4.1 全屏沉浸式页面应当保持关闭操作 # 通常情况下,在移动端 APP 中打开的页面,顶部都会有一个白色的标题栏。但有些活动页面为了更好地沉浸式体验,会把白色标题栏去掉,同时还去掉了右划退出的操作,只能点击自定义的返回按钮才能退出。例如这个页面,左上角的返回按钮是页面本身自定义的。而这个页面必须是接口正常返回数据后才展示出来,在最开始时,如果有异常时,会展示错误信息,但没有返回按钮。这就导致用户无法退出该活动,只能杀掉 APP 再重新进入。体验非常不好,这里我们就要保证:全屏沉浸式页面不管是哪种状态,应当全程保持关闭操作!当然,现在已经没有这个问题了。 4.2 永远不要相信后台一直很稳定 # 后台经常说的一句话是“不要相信任何从前端传过来的数据”,我们也一样: 永远不要相信后台一直很稳定。 我们要做好接口服务可能会挂掉的预案: 设置请求接口的超时时间,不要让用户无限制等待; 良好的提示; 有条件时,可以自动重试,或者让用户手动尝试重试请求接口; 采用兜底策略遮盖; 前 3 种我们都可以理解,当接口异常并无法继续后续的操作时,应当告知用户有服务有异常了,可以稍后重试。对于第 4 种,通常可能会发生在高并发的抽奖过程中,越是让用户重试,并发量就越高。因此在抽奖异常时,可以直接告诉用户未中奖,而不是“服务异常”之类的话术。要不然,一方面会引起用户的不满,另一方面会造成用户的大量重试。这个百度在春晚发红包中,就有用到过,在服务器短时间内承受到高并发量时,则直接告诉用户未抽中红包;同时,对于一些抽奖会同时发放多个奖品时,也要做好每个奖品服务都可以会挂掉的准备,比如同时会发放 3 个奖品: 服务都正常,正常发放; 2 个正常,就只发放 2 个奖品,左右排列; 只有 1 个服务正常,则只发放 1 个奖品,居中排列; 均异常,则告诉用户未中奖; 千万不要留有空间或者槽位告诉用户“该位置本应该有奖品,但实际上没有”的感觉。 4.3 懒加载 # 懒加载是一个老生常谈的话题,这里我们只针对图片懒加载来进行梳理。在页面中图片比较多时,请尽量使用图片懒加载,并考虑好图片加载失败的情况,可以先创建一个 Image 来先加载图片,加载城后再给到页面中的 dom 元素,否则使用兜底图片:// 判断图片是否可以加载成功 const loadImage = (imgUrl: string): Promise => { return new Promise((resolve, reject) => { const img = new Image(); img.src = imgUrl; if (img.complete) { return resolve(img); } img.onload = () => { resolve(img); }; img.onerror = reject; }); }; // IntersectionObserver的回调,当dom元素进入到可是区域内时 const targetExposeCallback = async (dom: HTMLElement) => { let original = dom.getAttribute('data-original'); if (original) { try { await loadImage(original); } catch (err) { // 1x1的图片 original = 'data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=='; } setLoading(false); if (dom.tagName.toLowerCase() === 'img') { dom.setAttribute('src', original); } else { // eslint-disable-next-line dom.style.backgroundImage = `url("${original}")`; } dom.setAttribute('data-original', ''); } }; 同时,我们在体验的过程中发现,在有些华为手机里,图片还没加载完毕时,会展示一个裂开的图片,如果该图片 alt 注释,也把 alt 注释显示出来,稍过一会儿,等图片加载完毕了,就正常展示图片了。这种情况,我们也可以使用图片懒加载,或者将图片设置为背景图片,避免出现图片裂开的状态。 5. 总结 # 我们在移动端开发的过程中,总会有多种解决方案。如果我们站在用户的角度多想一想,就能让产品的交互体验变的更好。
2024年10月20日
3 阅读
0 评论
0 点赞
2024-10-20
10 分钟内了解判断数组中存在元素的多种方式
在前端开发中,经常会遇到要判断数组中是否存在某个元素。其实判断的方式有很多种,我们一个一个来了解下。我们先来定义一个数组:const arr = [ 13, false, 'abcd', undefined, 13, null, NaN, [1, 2], { a: 123 }, () => Date.now(), new Date('2021/03/04'), new RegExp('abc', 'ig'), Symbol('sym'), ]; 在这个数组中,我们包含了好几种类型:number, boolean, string, undefined, null, array, object, Date, Symbol 等。其中数字 13 出现了 2 次。 1. indexOf # 我们最熟悉的就是indexOf了,毕竟他出现的早,兼容性也好,使用起来也很方便。如果存在该元素,则返回第一次出现的索引值;若整个数组不存在该元素,则返回-1。 1.1 使用方式 # 只要判断返回的数据是不是-1,就能知道数组中是否包含该元素。arr.indexOf(13) >= 0; // true, indexOf返回0 arr.indexOf(2) >= 0; // false, indexOf返回-1 与 indexOf 对应的是 lastIndexOf,从最后往前查找元素,若存在该元素,则返回在数组中的最后一个的索引;若不存在该元素,则返回-1。arr.lastIndexOf(13) >= 0; // true, lastIndexOf返回4, 最后一次出现的索引 两个方法在判断变量是否存在时,调用方式是一样的。 1.2 第 2 个可选参数 # indexOf 和 lastIndexOf 还有第 2 个可选参数 fromIndex,用来表示从哪个索引开始进行搜索。在 indexOf 中,若 fromIndex 超过数组的长度,则直接返回-1,若为负数,则从最后往前数几个索引(arr.length-Math.abs(fromIndex)),然后开始往后搜索。在 lastIndexOf 中,若 fromIndex 达到或超过数组的长度,则搜索整个数组;若为负数,则从最后往前数几个索引(arr.length-Math.abs(fromIndex)),然后开始往前搜索,若负数的绝对值超过了数组的长度,则直接返回-1。arr.indexOf(13, 2); // 4, 从索引值2开始往后查找,首先找到的13的索引值为4 arr.indexOf(13, -10); // 4, 从索引值1(11-10)开始往后检索 arr.lastIndexOf(13, 2); // 0, 从索引值2往前开始搜索 arr.lastIndexOf(13, -2); // 4, 从索引值9(11-2)开始往前搜索 而且 indexOf 和 lastIndexOf 中采用的是严格相等的方式(===)来判断的。arr.indexOf(null); // 5, 在null的前面有几个假值false和undefined,也能准确找到null的索引值 2. includes # indexOf 主要是为了查找元素所在的索引值,只是我们可以用返回的索引值来间接判断数组中是否存在该元素。而在 ES7(ES2016)中添加的includes方法,就是专门用来判断元素是否存在的。返回值为 true 或者 false,true 表示存在,false 表示不存在,简单明了。arr.includes(13); // true arr.includes('abc'); // false arr.includes(false); // true, 存在false元素 同时,includes 方法中也存在第 2 个可选参数 fromIndex,fromIndex 的用法与 indexOf 中的一样。若 fromIndex 超过数组的长度,则直接返回-1,若为负数,则从最后往前数几个索引(arr.length-Math.abs(fromIndex)),然后开始往后搜索。arr.includes(13, 5); // false, 从索引值5开始往后检索,没检索到 到目前为止,后面的几种类型,例如 Array, Object, Date 和 Symbol,我们都没判断呢。我们现在来判断下后面的几个元素:// 使用indexOf判断 arr.indexOf(NaN); // -1 arr.indexOf([1, 2]); // -1 arr.indexOf({ a: 123 }); // -1 arr.indexOf(() => Date.now()); // -1 arr.indexOf(new Date('2021/03/04')); // -1 arr.indexOf(new RegExp('abc', 'ig')); // -1 arr.indexOf(Symbol('sym')); // -1 // 使用includes判断 arr.includes(NaN); // false arr.includes([1, 2]); // false arr.includes({ a: 123 }); // false arr.includes(() => Date.now()); // false arr.includes(new Date('2021/03/04')); // false arr.includes(new RegExp('abc', 'ig')); // false arr.includes(Symbol('sym')); // false 结局很惨,这几种元素在数组中都没有检索到。可是实际上在数组中都是真实存在的。这是因为 indexOf 和 includes 都是采用严格相等的方式(===)来判定的。NaN === NaN; // false, 两个NaN永远也不会相等 [1, 2] === [1, 2]; // false, 每个声明出来的数组都有单独的存储地址 {a: 123} === {a: 123}; // false, 同数组 new Date('2021/03/04')===new Date('2021/03/04'); // false, 看着日期是相同的,但是用new出来的对象进行比较的,肯定是不相等的 Symbol('sym')===Symbol('sym'); // Symbol类型的出现就是为了避免冲突创造出来的类型,括号里的属性仅是为了方便描述而已 针对这些无法被检索的类型,我们就需要自己写函数来判断特殊的类型了。 3. find 和 findIndex # find()和 findIndex()允许我们通过回调函数,来自定义判断的方式。 3.1 find 方法 # find() 方法返回数组中满足提供的测试函数的第一个元素的值。否则返回 undefined。find()方法无法检测数组中的 undefined 元素。因为不存在和存在 undefined 元素,find()方法都会返回 undefined。这里我们就要考虑其他方式了,稍后再讲。arr.find((item) => item === 13); // 13, 找到了元素13 arr.find((item) => item === 3); // undefined, 没找到元素3 arr.find((item) => item === undefined); // undefined, 也不知道是找到了还是没找到 对于上面稍微复杂点的类型,我们就需要特殊的判断了:arr.find((item) => typeof item === 'number' && isNaN(item)); // NaN // array和object类型进行比较时,情况很复杂,因为每个元素的类型都无法确定 // 如果确定都是基本类型,如string, number, boolean, undefined, null等,可以将其转为字符串再比较 // 转字符串的方式也很多,如JSON.stringify(arr), arr.toString(), arr.split('|')等 // 复杂点的,只能一项一项比较,或者使用递归 arr.find((item) => item.toString() === [1, 2].toString()); // [1, 2] arr.find((item) => JSON.stringify(item) === JSON.stringify({ a: 123 })); // {a: 123} arr.find((item) => { if (typeof item === 'function') { return item.toString() === (() => Date.now()).toString(); } return false; }); // () => Date.now() arr.find((item) => { if (item instanceof Date) { return item.toString() === new Date('2021/03/04').toString(); } return false; }); // Thu Mar 04 2021 00:00:00 GMT+0800 arr.find((item) => { if (item instanceof RegExp) { return item.toString() === new RegExp('abc', 'ig').toString(); } return false; }); // /abc/gi // Symbol确实没法比较,只能比较描述是否一样 arr.find((item) => { if (typeof item === 'symbol') { return item.toString() === Symbol('sym').toString(); } return false; }); // Symbol(sym) 上面的判断代码在后面的方法也将会使用到。 3.2 两个元素进行比较 # 我们在上面对比了多种类型元素的比较,稍微来总结下。先来定义一个函数:const compare = (x, y) => {}; 3.2.1 基本类型 # 对于元素是 string, number, boolean, undefined, null 等基本类型的,可以直接进行比较:const compare = (x, y) => { return x === y; }; 3.2.2 NaN 数据 # NaN 用 typeof 来判断是 number 类型,但 NaN 不与任何数字相等,包括它自己。const compare = (x, y) => { if (typeof x === 'number' && isNaN(x) && typeof y === 'number' && isNaN(y)) { return true; } return x === y; }; 3.2.3 Function 与 Date 与 RegExp # 这些类型的,可以将变量转为字符串进行比较:const compare = (x, y) => { if (typeof x === 'number' && isNaN(x) && typeof y === 'number' && isNaN(y)) { return true; } if ( (typeof x === 'function' && typeof y === 'function') || (x instanceof Date && y instanceof Date) || (x instanceof RegExp && y instanceof RegExp) || (x instanceof String && y instanceof String) || (x instanceof Number && y instanceof Number) ) { return x.toString() === y.toString(); } return x === y; }; 对于 object 类型和 array 的,我们可以将每一项拆开,然后利用上面的方式再挨个儿比较。 3.3 findIndex 方法 # 如果还要判断数组中是否存在 undefined,我们可以使用findIndex()方法。 findIndex() 方法返回数组中满足提供的测试函数的第一个元素的索引。若没有找到对应元素则返回-1。 arr.findIndex((item) => item === undefined); // 3 arr.findIndex((item) => item === 3); // -1, 没有找到数字3 其他数据格式的判断,与上面的 find()一样。 4. some # some() 方法测试数组中是不是至少有 1 个元素通过了被提供的函数测试。它返回的是一个 Boolean 类型的值。 注意:如果用一个空数组进行测试,在任何情况下它返回的都是 false。 some()方法与 find()方法的使用方式一样,只不过 some()方法返回的是 boolean 类型的数据。arr.some((item) => item === false); // true arr.some((item) => item === undefined); // true arr.some((item) => typeof item === 'number' && isNaN(item)); // true arr.some((item) => item === 3); // false, 不存在数字3 arr.some((item) => { if (item instanceof Date) { return item.toString() === new Date('2021/03/04').toString(); } return false; }); // true 5. filter # filter() 方法创建一个新数组, 其包含通过所提供函数实现的测试的所有元素。无论找到几个元素或者没有元素,filter()方法都是会返回一个数组,数组中的数据就是我们想要的元素。arr.filter((item) => item === false); // 1 arr.filter((item) => item === undefined); // 1 arr.filter((item) => typeof item === 'number' && isNaN(item)); // 1 arr.filter((item) => item === 13); // 2 arr.filter((item) => item === 3); // 0 arr.filter((item) => { if (item instanceof Date) { return item.toString() === new Date('2021/03/04').toString(); } return false; }); // 1 因此我们可以通过该数组的长度,来判断原数组是否包含我们想要的元素。 6. 总结 # 查找数组中元素的方式有很多,我们可以数组中元素的格式,来选择更合适的方式。如果都是一些基本类型,建议优先选择使用includes()方法;如果格式比较复杂的,建议选择使用some()方法。这两个方法都是直接返回 boolean 类型,无需更多的转换即可直接使用方法的结果。
2024年10月20日
2 阅读
0 评论
0 点赞
2024-10-20
一个操作 cookie 的原生方法 cookieStore
我们平时对 cookie 的增删改查等操作,都是在操作 document.cookie,这里我们介绍一个新方法cookieStore。 1. 平时如何操作 cookie # document.cookie 能获取到当前域所有的 cookie 字符串。每个 cookie 用分号进行隔开:document.cookie; // "a=1; b=2; c=wenzi" 操作 cookie,均是在操作 document.cookie。如下面就是我常用的一段代码:/** * 写cookies * @param {string} name 写cookie的key * @param {string|number} value 写cookie的值 * @param {number} day 存储的时间,默认30天 */ export const setCookie = (name: string, value: string | number, day = 30): void => { const exp = new Date(); exp.setTime(exp.getTime() + day * 24 * 60 * 60 * 1000); document.cookie = `${name}=${escape(value.toString())};path=/;expires=${exp.toUTCString()}`; }; /** * 读取cookies * @param {string} name 要获取的cookie名称 * @param {number|boolean} type 是否直接获取对应的值,若存入真值,则直接返回,否则进行解码 */ export const getCookie = (name: string): string | null => { const reg = new RegExp(`(^| )${name}=([^;]*)(;|$)`); const arr = document.cookie.match(reg); if (arr) { return unescape(arr[2]); } return null; }; /** * 删除cookie * @param name 删除的cookie名称 */ export const delCookie = (name: string) => { if (!name) return; const ex: Date = new Date(); ex.setTime(ex.getTime() - 1); document.cookie = `${name}=; expires=${ex.toUTCString()};path=/`; }; 可以看到设置、获取和删除 cookie,都是在 document.cookie 上进行操作的。 2. 新方式 cookieStore # 现在 Chrome 有了更方便操作 cookie 的方法了cookieStore,这个方法是在 Chrome87 版本加入的,兼容性还不太好。下图是当前日期 2021/03/15 的兼容性概览,可以发现仅仅是 Chrome 体系支持了 cookieStore。不过我们可以先来了解它的用法。 cookieStore 现在只能在https 协议下的域名才能访问的到;其他 http 协议的域名里会提示 cookieStore 为 undefined,或者设置失败。 2.1 基本方法 # cookieStore 是一个类似localStorage的 object 类型变量。可以看到 cookieStore 主要有 5 个方法: set: 设置 cookie,可以是 set(name, value),也可以是 set({name, value}); get: 获取 cookie,可以是 get(name),或者 get({name}); getAll: 获取所有的 cookie; delete: 删除 cookie; onchange: 监听 cookie 的变化; 前 4 个方法天然支持 Promise。接下来我们一个个来了解下。 2.2 设置 cookie # cookieStore.set 方法可以设置 cookie,并返回一个 Promise 状态,表示是否设置成功。cookieStore .set('username', 'wenzi') .then(() => console.log('设置username成功')) .catch(() => console.error('设置username失败')); 如果我们想要设置更多的属性,例如过期时间,可以传入一个 Object 类型:cookieStore .set({ name: 'age', value: 18, expires: new Date().getTime() + 24 * 60 * 60 * 1000, }) .then(() => console.log('设置age成功')) .catch(() => console.error('设置age失败')); value 中所有的数据都会默认先执行toString(),然后再进行存储,因此有些非基本类型的数据,最好先转换好。上面都是我们设置 cookie 成功的情况,那么什么时候会设置失败呢?在本地 localhost 环境会设置失败。本地 localhost,我们是能获取到cookieStore这个全局变量,也能执行相应的方法,但无法设置成功:cookieStore.set('username', 'wenzi'); 浏览器会发出提示,无法在不安全的域名下通过 CookieStore 中的 set 设置 cookie:Uncaught (in promise) TypeError: Failed to execute 'set' on 'CookieStore': Cannot modify a secure cookie on insecure origin 添加 catch 后,就能捕获到这个错误:cookieStore .set('username', 'wenzi') .then(() => console.log('设置username成功')) .catch(() => console.error('设置username失败')); 因此在想使用 cookieStore 时,不能直接通过下面的方式判断,还得新增一个页面 url 的协议来判断:typeof cookieStore === 'object'; // 判断不准确,本地localhost也会存在 应当使用:const isSupportCookieStore = typeof cookieStore === 'object' && location.protocol === 'https:'; // 只有在https协议下才使用cookieStore 2.3 获取 cookie # cookieStore.get(name)方法可以获取 name 对应的 cookie,会以 Promise 格式返回所有的属性:await cookieStore.get('username'); get()方法还可以接收一个 Object 类型,测试后发现,key 的值只能是 name:await cookieStore.get({ name: 'username' }); 当获取的 cookie 不存在时,则返回一个 Promise。 2.4 获取所有的 cookie # cookieStore.getAll()方法可以获取当前所有的 cookie,以 Promise的形式返回的形式返回,数组中的每一项与通过 get()方式获取到的格式一样;若当前域没有 cookie,或者获取不到指定的 cookie,则为空数组;await cookieStore.getAll(); getAll()方法也可以传入一个 name,用来获取对应的 cookie:await cookieStore.getAll('username'); await cookieStore.getAll({ name: 'username' }); 2.5 删除 cookie # cookieStore.delete(name)用来删除指定的 cookie:cookieStore .delete('age') .then(() => console.log('删除age成功')) .catch(() => console.error('删除age失败')); 删除成功后则会提示删除成功。即使删除一个不存在的 cookie,也会提示删除成功。因此,当再次执行上面的代码时,还是会正常提示。同样的,在 localhost 环境下会提示删除失败。 2.6 监听 cookie 的变化 # 我们可以通过添加change事件,来监听 cookie 的变化,无论是通过 cookieStore 操作,还是直接操作 document.cookie,都能监听。添加监听事件:cookieStore.addEventListener('change', (event) => { const type = event.changed.length ? 'change' : 'delete'; const data = (event.changed.length ? event.changed : event.deleted).map((item) => item.name); console.log(`刚才进行了 ${type} 操作,cookie有:${JSON.stringify(data)}`); }); 这里面有 2 个重要的字段changed数组和deleted数组,当设置 cookie 时,则 changed 数组里为刚才设置的 cookie;当删除 cookie 时,则 deleted 数组里为刚才删除的 cookie。 2.6.1 设置操作 # 当调用 set()方法时,会触发 change 事件,同时影响的 cookie 会放在event.changed数组中。通过 document.cookie 设置或者删除的 cookie,均认为是在修改 cookie,而不是删除。 每次设置 cookie 时,即使两次的 name 和 value 完全一样,也会触发change事件。 cookieStore.set('math', 90); 2.6.2 删除操作 # 通过 delete()方法删除一个存在的 cookie 时,会触发 change 事件,被删除的 cookie 会放在event.deleted数组中。如果删除一个不存在的 cookie,则不会触发 change 事件。cookieStore.delete('math'); 可以看到,当第二次删除 name 为 math 的 cookie 时,就没有触发 change 事件。 3. 总结 # 我们整篇了解了下cookieStore的使用和操作,要比我们直接操作 cookie 简便的多,而且还可以通过自身的 change 事件来监听 cookie 的变化。
2024年10月20日
3 阅读
0 评论
0 点赞
2024-10-20
第8页
文字与元素居中的方式我们经常会让元素进行上下左右的居中,这里提供几种方法供大家使用➥Read Moreposted @2019/10/15十大经典排序算法(javascript实现)排序在我们日常开发工作中经常要用到,那么都有哪些排序算法呢,这些排序算法各有什么特点呢➥Read Moreposted @2019/10/13nextjs 如何将静态资源发布到 CDNnextjs 是基于 react 的服务端同构指出框架,在使用的过程中也多多少少遇到过几个问题,其中最大的问题就是静态资源的发布了➥Read Moreposted @2019/09/17JavaScript:如何获取某一天所在的星期如何获取今天或者某一天所在星期的开始和结束日期,或者如何获取整个星期的日期➥Read Moreposted @2019/09/05前端:形成自己的方法论在前端工程化的体系建设中,如何形成自己的一套方法论➥Read Moreposted @2019/09/05如何减少函数参数的输入针对常用的函数,如何能减少必要的参数输入呢?➥Read Moreposted @2019/06/03Can’t perform a React state update on an unmounted componentWarning: 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 t➥Read Moreposted @2019/06/03高考毕业10年后的我活成了...岁月走过不小心踩伤了受了伤的心尖,痛、始终不曾明灭的萦绕着苍白憔悴的思念。是否前世我淡薄寡怜了你一世的情愫?岁月走过光阴的沧海,遗落了一路的伤碎桑田心愿,在年岁风烛蚕食里是否还有痕迹?➥Read Moreposted @2019/06/03记一次项目中分享图片的过程从其他客户端分享到微信朋友圈的步骤是什么呢?又遇到了什么问题呢?➥Read Moreposted @2019/05/21前端中的事件循环eventloop机制js是单线程执行的,那么setTimeout和Promise.resolve等异步机制是如何执行的呢?➥Read Moreposted @2019/05/14能用js重写的都会被typescript重写TypeScript从今天数以百万计的JavaScript开发者所熟悉的语法和语义开始。使用现有的JavaScript代码,包括流行的JavaScript库,并从JavaScript代码中调用TypeScript代码➥Read Moreposted @2019/05/13实现react组件的递归调用项目中有侧边栏的层级菜单或者需要根据接口生成DOM树等操作时,如何实现组件的递归调用呢?➥Read Moreposted @2019/05/12React:实现一个带有loading效果的按钮组件在react中如何实现一个带有loading效果的按钮组件呢?➥Read Moreposted @2019/05/10基于观察者模式实现一个EventEmitter类在实现一些基础类库时,我们是经常需要用到`EventEmitter`模块的,比如在微信中下载某个APP时,前端需要了解当前下载处在哪个状态:刚开始下载、在在下载中、下载的进度、下载已完成、下载失败等等,都是需要我们向发布事件的➥Read Moreposted @2019/05/09页面中长列表滚动的优化当我们有10万条或者更多的数据需要展示在页面中,会出现哪些问题呢,我们应该怎样处理和优化呢?➥Read Moreposted @2019/04/23实现Promise的first等各种变体通过ES6中Promise提供的几个方法,来实现诸如first、last、none、any等各种变体方法➥Read Moreposted @2019/04/09js打乱数组的实战应用在js中,能把数组随机打乱的方法有很多,每个方法都有自己的哪些特点呢,同时若想随机取出数组中的几个元素,怎么办呢?➥Read Moreposted @2019/04/06博客里评论系统的前端总结本篇文章主要是从前端的角度来讲解下本博客评论系统的实现➥Read Moreposted @2019/03/31js中parseInt与Math.floor的区别js中parseInt与Math.floor在使用的过程中,有哪些细致的区别呢?➥Read Moreposted @2019/03/14getScript在前端中请求js文件和jsonpgetScript在前端中使用的非常广泛,那么这里也分享下自己使用的ts版本的getScript,可以用来请求js文件和进行jsonp的请求➥Read Moreposted @2019/02/27
2024年10月20日
3 阅读
0 评论
0 点赞
2024-10-20
js 中你不知道的各种循环测速
在前端 js 中,有着多种数组循环的方式: for 循环; while 和 do-while 循环; forEach、map、reduce、filter 循环; for-of 循环; for-in 循环; 那么哪种循环的执行速度最快呢,我们今天来看一看。在测试循环速度之前,我们先来创建一个有 100 万数据的数组:const len = 100 * 10000; const arr = []; for (let i = 0; i < len; i++) { arr.push(Math.floor(Math.random() * len)); } 测试环境为: 电脑:iMac(10.13.6); 处理器:4.2 GHz Intel Core i7; 浏览器:Chrome(89.0.4389.82) 1. for 循环 # for 循环是我们最常用的一种循环方式了,最大的好处就是结构清晰,能够随时 break 停止。我们先用 10 次的测试来看下:console.log('test for'); for (let k = 0; k < 10; k++) { console.time('for'); let sum = 0; for (let i = 0; i < len; i++) { sum += arr[i] % 100; } console.timeEnd('for'); } 最终得到的结果:在第 1 次和第 2 次时耗时比较多,从第 3 次开始就一直维持在 1.25ms 左右。 2. while 循环和 do-while 循环 # 这两个放在一起,也是他们的结构足够像,而且也能够随时 break 停止。console.log('\ntest while'); for (let k = 0; k < 10; k++) { console.time('while'); let sum = 0; let i = 0; while (i < len) { sum += arr[i] % 100; i++; } console.timeEnd('while'); } console.log('\ntest do-while'); for (let k = 0; k < 10; k++) { console.time('do-while'); let sum = 0; let i = 0; do { sum += arr[i] % 100; i++; } while (i < len); console.timeEnd('do-while'); } while 循环和 do-while 循环的结果几乎一样,我们只看下 while 循环在浏览器上运行的结果:跟 for 循环的速度不相上下。 3. forEach、map 和 reduce 循环 # 接下来来到我们常用的数组三剑客了:forEach, map, reduce 等,这 3 个方法都是在 ES6 标准上新加的语法。 3.1 forEach 的简要介绍 # 这几种方法是无法停止循环的,无论使用break还是return,都无法停止整个循环。我们可以做一个测试,例如我想当遇到 3 的倍数时,即停止循环[1, 2, 3, 4, 5].forEach((item) => { console.log(`before return: ${item}`); if (item % 3 === 0) { return; } console.log(`after return: ${item}`); }); 运行结果如下:从运行的结果可以看到,我们的 return 只是没有执行当时循环时后面的语句,但并没有停止整个循环,后面的 4 和 5 依然正常输出。那循环是否真的像炫迈一样停不下来吗?并不,还有一种方式,可以停止循环。那就是抛出异常:try { [1, 2, 3, 4, 5].forEach((item) => { console.log(`before return: ${item}`); if (item % 3 === 0) { throw new Error('break forEach'); } console.log(`after return: ${item}`); }); } catch (e) {} 在 forEach 中抛出异常后,就可以停止该循环,然后再使用try-catch捕获异常,避免整个服务被挂掉。虽然可以停止 forEach 的循环,但实现起来麻烦了不少。因此若没有停止整个循环的需求,可以使用 forEach, map 等循环方式;否则还是要使用其他的循环方式。 3.3 forEach 等的测速 # 好的,接下来我们就要测试这 3 个循环方式的循环速度了。// forEach 的测试: console.log('\ntest forEach'); for (let k = 0; k < 10; k++) { console.time('forEach'); let sum = 0; arr.forEach((item) => { sum += item % 100; }); console.timeEnd('forEach'); } // map 的测试: console.log('\ntest map'); for (let k = 0; k < 10; k++) { console.time('map'); let sum = 0; arr.map((item) => { sum += item % 100; }); console.timeEnd('map'); } // reduce 的测试: console.log('\ntest reduce'); for (let k = 0; k < 10; k++) { console.time('reduce'); let sum = 0; arr.reduce((_, item) => { sum += item % 100; }, 0); console.timeEnd('reduce'); } 因这 3 个循环的时间差不多,我这里就只截取了 forEach 的测试结果。执行 10 次循环后,forEach 的执行时间差不多在 10.8ms 左右,比上面的 for 循环和 while 循环高了将近 10 倍的运行时间。 4. for-of # ES6 借鉴 C++、Java、C# 和 Python 语言,引入了 for...of 循环,作为遍历所有数据结构的统一的方法。 4.1 for-of 的简要介绍 # 一个数据结构只要部署了 Symbol.iterator 属性,就被视为具有 iterator 接口,就可以用 for...of 循环遍历它的成员。也就是说,for...of 循环内部调用的是数据结构的 Symbol.iterator 方法。for...of 循环可以使用的范围包括数组、Set 和 Map 结构、某些类似数组的对象(比如 arguments 对象、DOM NodeList 对象)、后文的 Generator 对象,以及字符串。for-of 拿到的就是 value 本身,而 for-in 则拿到的是 key,然后通过 key 再获取到当前数据。const fruits = ['apple', 'banana', 'orange', 'lemon']; for (const value of fruits) { console.log(value); // 'apple', 'banana', 'orange', 'lemon' } 4.2 for-of 的循环测速 # 测试 for-of 循环速度的代码:console.log('\ntest for-of'); for (let k = 0; k < 10; k++) { console.time('for-of'); let sum = 0; for (const value of arr) { sum += value % 100; } console.timeEnd('for-of'); } 测试结果:在多次重复同一个循环时,前 2 次的 for-of 循环时间会比较长,得在 15ms 以上。但后续的执行,循环速度就下降到 1.5ms 左右了,与 for 循环的时间差不多。 5. for-in 循环 # for-in 通常用于 object 类型的循环,但也可以用来循环数组,毕竟所有数据类型的祖先都是 object 类型。console.log('\ntest for-in'); for (let k = 0; k < 10; k++) { console.time('for-in'); let sum = 0; for (let key in arr) { sum += arr[key] % 100; } console.timeEnd('for-in'); } 测试结果:for-in 循环的测速数据很惊人,简直是独一档的存在了,最好的时候也至少需要 136ms 的时间。可见 for-in 的循环效率真的很低。数组类型的数据还是不要使用采用 for-in 循环了;Object 类型的可以通过Object.values()先获取到所有的 value 数据,然后再使用 forEach 循环:const obj = {}; for (let i = 0; i < len; i++) { obj[i] = Math.floor(Math.random() * len); } for (let k = 0; k < 10; k++) { console.time('forEach-values'); let sum = 0; Object.values(obj).forEach((item) => { sum += item % 100; }); console.timeEnd('forEach-values'); } 即使多了一步操作,循环时间也大概在 14ms 左右,要比 for-in 快很多。 6. 总结 # 我们把所有的循环数据放到一起对比一下,我们这里将每个循环的测试次数调整为 100 次,横轴是循环的次数,数轴是循环的时间: for 循环、while 循环和 d-while 循环的时间最少; for-of 循环的时间稍长; forEach 循环、map 循环和 reduce 循环 3 者的数据差不多,但比 for-of 循环的时长更长一点; for-in 循环所需要的时间最多; 每种循环的时长不一样,我们在选择循环方式时,除了考虑时间外,也要考虑到语义化和使用的场景。
2024年10月20日
3 阅读
0 评论
0 点赞
1
...
43
44
45
...
213