FE 每周问题总结
这篇文章,旨在不在分散的记录每周遇到的问题
2020/04/13 add typeof判断类型
2020/04/13 add js内存溢出bug
2020/04/21 add 区分 prettier eslint
2020/05/15 add webhookh
2020/06/01 add yarn vs npm
2020/06/21 add 单元测试
2020/06/28 add 时间标准化
2020/07/02 add 环境变量
2020/07/04 add 前端构建
2020/07/07 add 浏览器缓存策略
2020/09/01 add 文件上传后乱码问题
2020/09/04 add axios baseURL
2020/09/08 add npx
2020/09/24 add 前端优化
2020/10/13 add axios res node 使用 import
2020/10/28 add commonjs amd/cmd
2020/11/02 add 浏览器请求 favicon
2020/11/03 add koa 与 express
2020/11/13 add 前端缓存
2020/12/15 add a 标签安全问题
2020/12/18 add callback
2020/12/18 add promise & bluebird
2020/12/21 add cors
2020/12/21 add request.context
2020/12/24 add 前端位置
2020/12/24 add difference between ajax fetch axios
2021/01/08 add javascript 转义符
2021/01/31 add 错误栈
2021/02/02 add 七层网络、四层网络
2021/02/19 add JavaScript Number
2021/02/19 add chrome 第三方 cookie
2021/02/23 add 由接口 pending 引发的思考
2021/03/28 add 浏览器 options 请求, node buffer.alloc
2021/04/12 add 排错指南
2021/04/17 add lock file lockfileVersion
2021/04/24 add URL 携带参数
2021/05/23 add node 并发请求问题
2021/05/31 add 前端环境变量配置
2021/06/25 add http cookie
2021/08/13 add loading 加载时机、vuex
2021/08/22 add content-type vs responseType
2021/08/23 add code review
2021/11/01 add 补充浏览器缓存策略
2021/11/07 add content-disposition and download
2021/11/10 add commonjs 规范与浏览器
2021/11/14 add 前端多区域时区问题、转正则表达式问题
2021/12/05 add 修正 loading 加载时机
2021/12/05 add 前端性能优化之 webp
2022/01/03 add router.push 与 location.href 的区别
2022/01/07 add 前端性能优化之懒加载
2022/01/15 add 使用 router.replace 和 router.push 的时机
2023/05/21 add bff
文章目录
- FE 每周问题总结
- express 与 koa
- 排错指南
- 前端 code review 注意点
- node 并发请求问题
- http
- loading 加载时机与 vuex dispatch action
- URL 携带业务参数
- lock file
- node buffer
- 由接口 pending 引发的思考
- Chrome 第三方 cookie 的问题
- JavaScript Number
- 七层网络,四层网络
- 错误栈
- JavaScript escape character
- Difference between ajax fetch axios
- 前端位置
- request.context
- cors
- bluebird
- callback
- a 标签安全问题
- 浏览器请求 favicon
- commonjs amd/cmd
- node.js 使用 import
- 前端优化
- npx
- axios
- HTML content-type
- 浏览器缓存
- 时间标准化
- 单元测试
- yarn vs npm
- webhooks
- 区分 prettier eslint
- typeof 判断类型
- js内存溢出bug
- commonjs 规范与浏览器
- 前端性能优化
- router & location.href & router-link
- bff
express 与 koa
- 洋葱模型复习
排错指南
- postman 能够接收到,但前端接收不到,很大的原因是跨域,此时应该联系服务端修正相关内容
前端 code review 注意点
我们在开发过程中也许最初会按自己的代码风格来编写,但随着开发周期的拉长可能因为需求的更改而出现冗余代码或者说不符合「最简」「可复用」原则的代码,虽然 code review 可以发现一些问题,但不建议将其作为一道可依赖的关卡。基于此,我们应该怎么做?
references: Code Review 总结
node 并发请求问题
Handling multiple parallel HTTP requests in Node.js
正如上面 Stack Overflow 中提出的问题一样,很多人对于如果我同时发五个请求,一个请求需要 20s 返回,那是不是最后一个请求要 100s 才能返回呢?,答案当然不是的
A simple guide to JavaScript concurrency in Node.js and a few traps that come with it
- Node took a slightly different approach to handling multiple concurrent requests at the same time if you compare it to some other popular servers like Apache. Spawning a new thread for each request is expensive. Also, threads are doing nothing when awaiting other operations’ result (i.e.: database read). That’s why Node is using one thread instead.Such an approach has numerous advantages. No overhead comes with creating new threads. Also, your code is much easier to reason about, as you don’t have to worry about what will happen if two threads access the same variable. It’s because that simply cannot happen. There are some drawbacks as well. Node isn’t the best choice for applications that mostly deal with CPU intensive computing. On the other hand, it excels at handling multiple I/O requests. So, let’s focus on this part for a bit.
CPU intensive computing: Processes or tasks which run on a computer require various resources like CPU cycles, memory, disk or network which are managed by the operating system, so that each task executes efficiently (without waiting for a resource if possible).Sorting, search, graph traversal, matrix multiply are all CPU operations.
- node 的异步 I/O 机制对处理高并发问题很友好,每接收一个请求,使用异步调用处理请求,不用等待结果,可以继续运行其他操作,也就是说可以继续接受请求。
what is I/O operations?
These are operations that communicate with stuff from the outside of your application. It means HTTP requests, disk reads and writes or database operations, just to name a few. I/O in Node comes in two “flavors”: blocking and non-blocking. It’s quite important to distinguish these two. “Blocking” in “blocking I/O operations” is quite self-descriptive. It means that next operations are blocked and that they have to wait as long as the currently running operation is taking.
// blocking-io
fs.readFileSync(filePath1)
fs.readFileSync(filePath2)
console.log(‘Logging after both reads are finished’)
// non-blocking-io
fs.readFile(filePath1, (err, content) => {
if (err) // handle error
// otherwise do something with the content
})
fs.readFile(filePath2, (err, content) => { /* same story */ })
console.log(‘Will happen before any of the reads is finished’)
- When such I/O operations are completed, how does Node know that it’s time to handle them?因此需要一个“管理器”来管理所有的异步操作,那就是事件循环。
asyncOperation(param1, param2, function(error, result) { /* … */ })
The interesting part here is a function that you pass as the last argument. It’s a callback function that will be called once your operation is finished, but not immediately. We agreed that such things shouldn’t happen anytime and Event Loop will make sure of that. Once your operation is finished, instead of calling the callback right away, it will place it into a special queue. Once it’s possible to handle it safely, your callbacks will be pushed to the stack and then executed one by one. You should be careful here, even if your I/O operation was asynchronous, the callback will be handled on the main thread, assuming it’s a JS operation. So, if you’re doing something time consuming, you’ll be blocking the Event Loop.
- 然而当 node 在处理 CPU intensive operations 时处理 I/O 操作会导致后者直接 no response,这也是单线程的缺点。但也有应对方法那就是「Cluster Mode」 What Cluster Mode gives you is a very basic load balancer. It will distribute the load in the round-robin approach between the nodes.(具体内容不再赘述)
当然以上内容可以结合更深层次的关于宏任务(http requests 就是宏任务)和微任务的解读,可以看 https://www.bilibili.com/video/BV1K4411D7Jb
http
http content-type vs responseType
HTTP消息头(HTTP headers)-常用的HTTP请求头与响应头
前段时间遇到一个问题 restAPI 中提示返回的是二进制格式,但由于我请求时未设置 responseType 导致返回格式非预期的二进制。
http request response 报文中均含有 content-type header 标识
request: content-type: 请求体的MIME类型 (用于POST和PUT请求中)
response: content-type:当前内容的MIME类型
而 responseType 则是请求方设定的希望返回的数据类型,不写默认就是 text,这样就会导致(服务端返回了正确的类型,我却没收到正确的类型)这种事。
content-type changed automated??
最近在处理一些问题的时候,发现 http 请求中当添加一些特殊的 header 时或某些插件就会导致 content-type 变成
application/octet-stream
axios 会根据 data 的格式自动设置 content-type 为 application/json
or application/x-www-form-urlencoded
, 而对于 form-data 类型的数据则会删掉用户设置的 content-type 交由浏览器进行设置 axios
姑且不去追究是添加特殊 header 导致的 content-type 改变还是直接修改了 content-type,而application/octet-stream
则意味着当前传输的内容的格式是「任意二进制数据」(in RFC 2046),而这个配置往往和content-disposition
联用于 response 中表示要求保存这个返回结果。我是否需要从Content-Type:application / octet-stream处下载文件?
Do I need Content-Type: application/octet-stream for file download?
content-disposition attachment and dowanload
如content-disposition中所述,content-disposition 是用来设置浏览器返回内容的展现形式,而通常我们将其设置为 attachment 来实现让浏览器下载某个文件。
前段时间遇到一个用户浏览器弹不出下载框,这里总结一下并备日后出现具体 case 具体分析。
- 起初我考虑用户浏览器加了特殊插件阻止了这类弹出下载框的行为(当然不排除这种现象)
- 虽然我们进行了设置但是每个人的浏览器设置不同,有的则不会弹出下载框直接下载到默认位置,比如在
chrome://settings/downloads
中设置「是否下载前询问每个文件的保存位置」 - 其他行为:axios 发 get 请求会导致这种现象,You Can’t Prompt a File Download With the Content Disposition Header Using Axios (XHR). Sorry.,因此通常,我们都是打开一个新页面(在前端 window.open)直接在浏览器发起请求此时就会得到下载文件的提示。(但我遇到的场景显然不适用于此,这里记录一下)
loading 加载时机与 vuex dispatch action
为什么将 loading 加载时机这种与业务强相关的内容和 vuex 联系起来呢,且看下面的分解
使用 loading 我们可以解决在 vuex 中设置了初始值 state,但通过 dispatch action 更新 state 这期间造成的“页面闪烁”问题。那么我们到底具体什么位置写 loading 呢?
- 最好是 store 中,将 loading 作为 state 的一个变量,在 action 中通过 mutation 修改
通常的一种做法是我们在 get 请求中加入 loading,每次修改即进行 put 请求后再 get 所有数据。由此带来一个问题:每次 put 请求后页面都会闪烁一下。为了解决这个问题:
- 仅在 post 请求后进行重新 get 所有数据,同时结合(vue transition-group)[https://cn.vuejs.org/v2/api/#transition-group]来让体检更好
- 仅在首次渲染页面即使用 get 请求时进行 loading 加载(即使用方式 2),put 请求后,直接拿 put 请求的结果 mutate 数据,直接渲染而不是再去 get 数据。(参考 https://www.apple.com.cn/ 的体验)
- 其次是在组件内部(适用于除了处理 loading 还要处理组件内部一些其他非公用 store 逻辑的场景),使用 mapActions 本质还是使用 store.dispatch,而 dispatch 本质就是 promise,我们可以在 promise.finally() 中处理 loading 逻辑
URL 携带业务参数
业务上经常遇到这种场景:我们希望在 URL 中携带一些参数以便于我们刷新页面或将此 URL 分享给其他人的时候能显示搜索后的结果。这里汇集了几个使用 Vue 的过程中一些解决方案以及自己最终选择的认为最优的方案:方案 3 或 方案 4
- 场景1:
特点:
- 信息放在 query
- 涉及「初次进入页面」和「用户交互」改动
- localStorage + handleChange 放在 actions 里面
技术: - 其中利用 vuex-router-sync 将 route 同步到了 store rootState 中
- 其中利用 localStorage 持久化参数
缺点: - 多了一步 getters,没有复用到「用户交互」流程
- 场景2:
特点:
- 信息放在 param
- 涉及「初次进入页面」,由于可以直接根据 params 匹配路由所以较简单,
- localStorage + handleChange 放在 actions 里面
技术: - 其中利用 vuex-router-sync 将 route 同步到了 store rootState 中
- 其中利用 localStorage 持久化参数
- 场景 3
特点:
- 信息放在 query 中
- 涉及「初次进入页面」和「用户交互」,主要通过组件中监听 query 变化实现视图更新,
技术: - 其中利用 vuex-router-sync 将 route 同步到了 store rootState 中
看法: - 这种方式以 watch Query 为关键点,将「初次进入页面」和「用户交互」流程整体复用,是一种不错的使用方式。但需要注意的是,这种方式需要在切换 params 的时候注意清空 query
- 场景 4
特点:
- 信息放在 query 中
- 涉及「初次进入页面」和「用户交互」,主要通过组件中监听 query 变化实现视图更新,
看法: - 无需将 query 注入到 vuex 中。算是场景 3 的另一种方式。
tips:
- 当我们将信息放在 query 中向服务端进行传递时,默认此时全部都是字符串
- 如果想保留数据类型则可以通过向服务端 post json 结构数据的方式,这样就可以保留基本类型。(我们可以用 axios 等第三方 http 请求库)
lock file
最近做业务的时候由于本地 npm 版本较低导致 install 之后出现 package-lock.json 和线上版本出现了大量的 diff,这里记录一下相关概念和理解
什么时候不能在 Node.js 中使用 Lock Files
是什么
lock file 描述了整个依赖关系树,它在创建时被解析,包括具有特定版本的嵌套依赖关系。在 npm 名为 package-lock.json ,在 yarn 中名为 yarn.lock。在这两个npm和yarn它们被放置你的package.json 的旁边
为什么要有 lock file
package.json 中 dependencies 字段显示你的项目应该安装的依赖项,但不显示这些依赖项的依赖项。依赖项可以指定精确版本或 semver 范围。对于 semver 范围,npm 或 yarn 将会选择最适合的版本。
什么时候需要 lock file 什么时候不需要 lock file
需要 lock file
当在构建 Web 程序或服务器之类的应用时,这非常有用,因为我们希望在 CI 环境中模拟用户的行为。因此,如果在源代码控制(如 git)中跟踪我们的 lock file,就可以确保每个开发人员以及服务器或构建系统还有 CI 系统都能够使用相同版本的依赖项。
tips:about npm install <7 for lockFileVersion=1 and >7 for lockFileVersion=2.
不需要 lock file
npm 发包的时候实际并不会将 lock file 进行捆绑,因此在写 npm 包的时候应该做的是把 lock file 放在 .gitignore 里面
其他
官方文档中指出 npm-shrinkwrap.json 是和 package-lock.json 一样的文件。
摆脱了 package-lock.json 并不意味着无法固定我们所拥有的依赖关系和子依赖关系。我们可以用另一个名为 npm-shrinkwrap.json
的文件。
它与 package-lock.json 基本相同,并由 npm shrinkwrap 生成并实际的打包并发布到 npm 注册表中。
因此,通过将 npm shrinkwrap 添加到 npm 脚本作为 prepack 脚本甚至是 git commit hook,可以确保在你的开发环境中,与你的用户和 CI 中使用相同版本的依赖项。通过使用 shrinkwrap 文件,你可以确定精确的版本,但它也会阻止人们获得自动安装的关键补丁程序。
node buffer
JavaScript 语言自身只有字符串数据类型,没有二进制数据类型。
但在处理像TCP流或文件流时,必须使用到二进制数据。因此在 Node.js中,定义了一个 Buffer 类,该类用来创建一个专门存放二进制数据的缓存区。
事情的起因是我试图在 node 中构造一个 200k 的数据,比较不方便的使用了 for 循环,实际使用 buffer.alloc 非常方便
由接口 pending 引发的思考
TTFB: Time To First Byte
https://www.cnblogs.com/both-eyes/p/10573713.html
网站加载 Waiting (TTFB) 时间过长的原因和解决办法
浏览器 options 请求
场景: 由于同源策略的影响,在其应对方案 CORS 中对于非简单请求必须使用 options 请求。
OPTIONS请求方法的主要用途有两个:
1、获取服务器支持的HTTP请求方法;
2、用来检查服务器的性能。例如:AJAX进行跨域请求时的预检,需要向另外一个域名的资源发送一个HTTP OPTIONS请求头,用以判断实际发送的请求是否安全。
通常实际业务中发出的请求都是非简单请求,因为 Content-Type 字段的类型是 application/json 时就被认定是非简单请求,不过为了避免调试受影响,可以打开浏览器 devtool 的时候选择 network 的 XHR 请求即可过滤掉 options 请求。
当我们在写服务端内容时,很多场景会遇到前端或其他域名来请求本接口从而导致出现跨域问题,而每次复杂请求都发 options 请求,不过可以通过设置 Access-Control-Max-Age 来降低频率
浏览器 stalled
stalled 是什么
stalled阶段是浏览器得到要发出这个请求的指令,到请求可以发出的等待时间,一般是代理协商、以及等待可复用的TCP连接释放的时间,不包括DNS查询、建立TCP连接等时间等。
导致 stalled 的原因
- 单一服务发送时候stalled过长,往往是丢包所致,这也意味着网络或服务端有问题。
- 多个服务并发导致stalled过长,是浏览器对同一个主机域名的并发连接数有限制,过长的请求是被阻塞了,处在队列中等待tcp连接
综上一般出现 stalled 的情况是由于前端问题而非服务端问题。
改进措施
- 将资源合理分布到多台主机上,可以提高并发数,但是增加并行下载数量也会增大 开销,这取决于带宽和CPU速度,过多的并行下载会降低性能;
- 脚本置于页面底部;
TTFB
网站加载 Waiting (TTFB) 时间过长的原因和解决办法
TTFB 是什么?
既然上文提到了前端导致的 stalled 情况,那么一般接口 pending 什么情况是服务端导致的呢,一般就去参考 TTFB。
TTFB(Time to First Byte)是指从客户端开始和服务端交互到服务端开始向客户端浏览器传输数据的时间(包括DNS、socket连接和请求响应时间),是能够反映服务端响应速度的重要指标。大多数服务器的 TTFB 时间都在 50 ms 以下。
什么导致的 TTFB 过长
- DNS查询
- 服务器响应,一般动态网页会造成这种现象
- 网络原因
- 重定向等
改进措施
- 减少 DNS 查找
- 减少DNS查找次数,最理想的方法就是将所有的内容资源都放在同一个域(Domain)下面,这样访问整个网站就只需要进行一次DNS查找,这样可以提高性能。
但理想总归是理想,上面的理想做法会带来另外一个问题,就是由于这些资源都在同一个域,而HTTP /1.1 中推荐客户端针对每个域只有一定数量的并行度,那么就会出现下载资源时的排队现象,这样就会降低性能。折衷的做法是:建议在一个网站里面使用至少2个域,但不多于4个域来提供资源。- 注意 http2 多路复用已经解决了 http1 对每个域并行数量的限制
- 使用静态网页
- 使用 CDN
Chrome 第三方 cookie 的问题
chrome有一个版本是可以在无痕模式下阻止第三方cookie的吗?
最近在用户反馈群中了解有些用户跳出登录态或无线在登录页跳转,最后查出来是 Chrome 无痕模式下出现了默认设置为阻止第三方 cookie
HTTP Cookie/Web Cookie 或 浏览器 Cookie
HTTP Cookie 主要用于以下三个方面:
- 会话状态管理(如用户登录状态、购物车、游戏分数或其它需要记录的信息)
- 个性化设置(如用户自定义设置、主题等)
- 浏览器行为跟踪(如跟踪分析用户行为等)
那么 set-cookie 和 cookie 的区别有哪些呢?
set-cookie 是响应头部向用户代理即浏览器发送 cookie 信息
Set-Cookie: <cookie名>=<cookie值>
可以在服务端响应头中设置多个 set-cookie,但其实是不能拼接的,最终表现是多个 set-cookie 响应头
而从浏览器端向服务器发请求的时候添加的则是 Cookie 请求头:
GET /sample_page.html HTTP/1.1
Host: www.example.org
Cookie: yummy_cookie=choco; tasty_cookie=strawberry
此时多个 cookie 是写在一个 cookie 请求头里面的,可进行拼接
Cookie 的缺点
如果被问到话,可以从大小、安全、增加请求大小等方面回答。
- 不同浏览器有不同的限制,但建议 cookie 一般要小于 20 个,总大小小于 4k
- 由于 cookie 被读取导致的 csrf(sameSite 防范),xss(httpOnly 防范)
- 由于每次请求都携带 cookie 会增加请求的大小
(其他可参考 references)
JavaScript Number
references:
在 JavaScript 中整数和浮点数都属于 Number 数据类型,所有数字都是以 64 位浮点数形式储存,即便整数也是如此,其为采用 IEEE 754 标准的 64 位双精度浮点数。
关于原理不再赘述,有需要看 reference 即可,主要记录一下解决方案。
- 最大整数为
2^53-1
,我们可以通过用String
类型的表示来取值或传值,否则会丧失精度,当然也可以使用 BigInt但注意做运算时不能和Number
类型混淆。 0.1+0.2!==0.3
类的浮点数问题一定程度的可以通过toFixed
来解决。其他方式此处不赘述。
七层网络,四层网络
七层网络协议模型指的是:开放式系统互联通信参考模型(英语:Open System Interconnection Reference Model,缩写为 OSI),简称为OSI模型(OSI model),一种概念模型,由国际标准化组织提出,一个试图使各种计算机在世界范围内互连为网络的标准框架。
四层是指TCP/IP四层模型,主要包括:应用层、运输层、网际层和网络接口层
错误栈
深入理解 JavaScript Errors 和 Stack Traces
在用 node 写服务端的时候避免不了处理错误信息,我们常常需要去对其进行追踪
- 每当有一个函数调用,就会将其压入栈顶。在调用结束的时候再将其从栈顶移出。
- 当 Error 发生的时候,通常会抛出一个 Error 对象。Error 对象也可以被看做一个 Error 原型,用户可以扩展其含义,以创建自己的 Error 对象。
Error.prototype 对象通常包含下面属性:
constructor - 一个错误实例原型的构造函数
message - 错误信息
name - 错误名称
这几个都是标准属性,有时不同编译的环境会有其独特的属性。
在一些环境中,例如 Node 和 Firefox,甚至还有 stack 属性,这里面包含了错误的 Stack trace。一个 Error 的堆栈追踪包含了从其构造函数开始的所有堆栈帧。
- 使用堆栈追踪即 stack trace
Error.captureStackTrace 函数的第一个参数是一个 object 对象,第二个参数是一个可选的 function。捕获堆栈跟踪所做的是要捕获当前堆栈的路径(这是显而易见的),并且在 object 对象上创建一个 stack 属性来存储它。
JavaScript escape character
最近有这样一个需求:想在前端显示字符串时不将转义字符进行转义显示,也就是
\n
而不是一个换行。尝试了很多种方式,最后定下来JSON.stringify
将字符串转化下。
reference:
Escape characters in JavaScript
http://www.ecma-international.org/ecma-262/5.1/#sec-7.8.4
-
使用正则匹配
\
替换为\\
显然无法进行匹配,但若使用下面一种匹配方式又存在遗漏某些转义字符的可能
-
es6 新增了 String 的一个原型方法
String.raw
,然后明显我们将它结合与模板语法用时遇到了瓶颈,显然并不符合预期因此摒弃
const a = 'test\nok';
const b = String.raw`${a}`
- 最终方案: 使用 JSON.stringify 将字符串转化,关于其 polyfill 可以看 https://github.com/douglascrockford/JSON-js/blob/master/json2.js
此时转化后它的行为和 mongodb 存库时行为一致,如存test\vtest
转化为test\u000test
等。(mongodb 文档数据库,存储的是文档(Bson->json的二进制化).因此并不意外)
转正则表达式
references:
How to escape regular expression special characters using javascript?
Escape string for use in Javascript regex
通常我们会使用 new RegExp
去将一个字符串转为正则表达式,而此时这些转义字符则会添乱导致报错,通常的处理方法如下:
const escapeRegExp = (text) => {
return text.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&');
}
Difference between ajax fetch axios
详细介绍了三者,下面简单总结下。
ajax: Asynchronous JavaScript And XML, Making background HTTP requests using JavaScript
fetch: 浏览器提供的原生 AJAX 接口
axios: Promise based HTTP client for the browser and node.js
个人总结:fetch axios 本质实质都是 ajax 实现,关于两者区别如浏览器兼容性可以参考 What is difference between Axios and Fetch?,目前 node 开发中更常用 axios, 作为 http client 可以支持相关的 http 协议请求方法,如新引入的 patch 等。
前端位置
起因是前段时间做了个鼠标移动到相关链接上出现图片预览的功能,限于所用组件需要写一些原生的内容(表格用的 ag-grid)
-
获取相对于视口的元素位置(仍然是由于组件问题需要获取这个位置):Element.getBoundingClientRect()
-
注: getBoundingClientRect 和 clientWidth offsetWidth (它们的介绍参见我的另一篇博客offsetWidth&offsetLeft详解)一样具有性能问题,会引起 reflow,成为常见的性能瓶颈。具体参考What forces layout / reflow
tips:
- 处理鼠标移动或者获取位置相关的问题经常会遇到抖动问题,当然有时真的是抖动问题,此时则需要 debounce;有时注意是元素叠加,此时则需要调整元素位置
- 做产品要多找有价值的竞品进行参考,让产品更精致符合主流,而不是 personalize
request.context
该字段标识请求的上下文,每个请求都有独立的 context,尤其因为 http 协议是无状态的而不是维护全局 context。
也正是这种原因会导致使用中用 A 做入口函数同时引入 B 且 B 中有使用 context 时 context 为 undefined 的情况,此时解决方式是将 context 一起传递给 B
cors
今天有个用户询问跨域失败的问题,当时我没仔细看错误直接转接给了同事,实际上后面仔细看错误是个我很清楚且能够提供建议的错误,有时候工作中不妨多思考一下,但当然不会的地方一定要及时询问别人。
首先从报错上可以很清楚的知道如果 request 中设置了携带 cookie 如
fetch("http://localhost:3000/login", {
method: "post",
mode: "cors",
credentials: "include",
headers: {'Content-Type': "application/json"}
});
同理使用jq的ajax也一样
$.ajax({
url: 'http://localhost:3000/login',
type: 'post',
xhrFields: {
withCredentials: true
},
headers:{'Content-Type': "application/json"}
});
此时必须 response 头中 Access-control-allow-credentials
为 true
,若不是浏览器就报异常,当然也有一种我没遇到的现象就是照样返回 200 但是把服务器返回的内容屏蔽掉了。
同时在排查这类问题时可以使用本地写个 html 文件引入
axios
发个请求。(一种很好的排查问题的思路)
bluebird
使用 bluebird 实现更强大的 Promise
bluebird.js - 让所有浏览器都支持 ES6 Promise 对象
Bluebird 的一个实用功能是把不使用 Promise 的已有API包装成返回 Promise 的新 API。大部分 NodeJS 的标准库 API 和不少第三方库的 API 都使用了回调方法的模式,也就是在执行异步操作时,需要传入一个回调方法来接受操作的执行结果和可能出现的错误。对于这样的方法,Bluebird 可以很容易的将它们转换成使用 Promise 的形式。因此在有需要的时候可以使用
bluebird
来优化代码,特此记录
callback
reference:
callback 本身是 node 一大重要特点:异步 IO 的重要应用。在 Node 中,绝大多数的操作都是以异步的方式进行调用,而回调函数也是最好的接收异步调用返回数据的方式,但实际上随着如 promise async 等避免回调地狱的出现,我们可以更像写同步函数那样写异步函数。
a 标签安全问题
reference:
使用标签时,你可能会忽略的一个安全问题
- 需要明确从一个网页通过
a
标签跳往新页面时是会携带原网页的referer
的,有时我们需要这个referer
,有时不需要则根据 reference 中的方法移除即可。 - 如果原页面和跳转页面同域,则将可以在新页面直接操作原页面,因此这是一个潜在的安全隐患
而预防这种安全隐患的方式是:在带有target="_blank"的 <a>
标签中,加上rel="noopener"
属性。如果使用window.open的方式打开页面,将opener对象置为空。这样的副作用是:在某些低版本浏览器中,新页面中拿不到referer信息。
浏览器请求 favicon
最近使用 faas 时候,用浏览器发起请求 url 会发起两次请求,使用 curl 则能解决这种问题
favicon.ico 图标用于收藏夹图标和浏览器标签上的显示,如果不设置,浏览器会请求网站根目录的这个图标,如果网站根目录也没有这图标会产生 404【当您浏览不同的域时,浏览器会在后台向 http://<your.domain.com>/favicon.ico 发送请求】。因此通常情况会直接将这个图标放在 public 目录下,要不然直接禁止产生这个请求。
commonjs amd/cmd
JavaScript笔记(CommonJS规范,以及exports、module.exports和export、export default区别)
- 关于
exports
与module.exports
的区别
module.exports与exports,export与export default之间的关系和区别
- “exports is assigned the value of module.exports before the module is evaluated.”
- 上面这句话的意思可以理解为每个 node 模块前面都有一个
var exports = module.exports;
- 基于以上,不要轻易使用
exports
,推荐使用module.exports
node.js 使用 import
可以参考 StackOverflow 中的发展历程:
- node 13 之前都是采用
改文件后缀为 mjs
以及运行时添加命令行指令 --experimental-modules
的方式 - node 13 后采用
package.json 修改 "type": "module"
即可。
前端优化
- 要善于去使用 localStorage 去处理那些容易引起 UI 变化的变量,且这些变量并不容易发生改变。
- 注意点:命名要规整
npx
需要明确以下几点即可:
- Node 自带 npm 模块,所以可以直接使用 npx 命令。
- npx 想要解决的主要问题,就是调用项目内部安装的模块
- 理由是:运行的时候,会到node_modules/.bin路径和环境变量$PATH里面,检查命令是否存在。
- 避免全局安装模块 eg:
$ npx create-react-app my-react-app
上面代码运行时,npx
将create-react-app
下载到一个临时目录,使用以后再删除。所以,以后再次执行上面的命令,会重新下载create-react-app
。
axios
axios 相对路径,绝对路径
业务场景:我们想在 va 区域请求不同的域名对应与相应域名对应的服务端 api
- 此时可以将 baseURL 设置为
/api
的形式,此为相对路径 - 若为
https://aaa/api
就是绝对路径 - 同时参考当我们实例化 axios 对象时的 baseUrl 作用:
baseURL
will be prepended tourl
unlessurl
is absolute. It can be convenient to setbaseURL
for an instance of axios to pass relative URLs to methods of that instance.
axios response 循环引用
res 是 axios 封装的返回,里面的字段有循环引用(主要是 res.config),因此不能直接序列化 res,一般取出 res.data 就行
reference: axios在 node 端使用,响应response 用JSON.stringify 序列化 会 报循环引用的错误
HTML content-type
Content-Type(内容类型),一般是指网页中存在的 Content-Type,用于定义网络文件的类型和网页的编码,决定浏览器将以什么形式、什么编码读取这个文件,这就是经常看到一些 PHP 网页点击的结果却是下载一个文件或一张图片的原因。
Content-Type 标头告诉客户端实际返回的内容的内容类型。
Content-Type: text/html; charset=utf-8
Content-Type: multipart/form-data; boundary=something
浏览器缓存
有关于浏览器缓存策略上分类其实无非协商缓存和强缓存,expire 是 http1.0 的产物,而 cache-control 是 http1.1 的产物,同时存在时后者优先级高。
比如通常对于经常不需要改动的静态资源如 js css 文件往往设置较大的 cache-control,而此时通过一些比较通用的如 webpack 打包时可配置 js css 名字含有动态 hash
下文将围绕几个我所提出的问题进行解疑。
references:
一文读懂前端缓存🌟🌟🌟🌟🌟
from memory cache / from disk cache 有什么区别?
这两个其实是从缓存位置上区分的不同缓存,一般我们在使用 Chrome 的 devtool 的时候会从访问资源的 size 栏中看到这两种不同来源标识。而缓存从位置上区分有如下几种:
- Service Worker
- Memory Cache
- 存在内存中,浏览器 tab 关闭即失效。
- 几乎所有的网络请求资源都会被浏览器自动加入到 memory cache 中。但是也正因为数量很大但是浏览器占用的内存不能无限扩大这样两个因素,memory cache 注定只能是个“短期存储”。常规情况下,浏览器的 TAB 关闭后该次浏览的 memory cache 便告失效 (为了给其他 TAB 腾出位置)。而如果极端情况下 (例如一个页面的缓存就占用了超级多的内存),那可能在 TAB 没关闭之前,排在前面的缓存就已经失效了。
- memory cache 是浏览器为了加快读取缓存速度而进行的自身的优化行为,不受开发者控制,也不受 HTTP 协议头的约束,算是一个黑盒。但若真的不想走缓存,此时可以将 cache-control 设置为 no-store 则绝对不使用缓存,每次都重发请求。
- Disk Cache
- 存在硬盘中,持久化。
disk cache 会严格根据 HTTP 头信息中的各类字段来判定哪些资源可以缓存,哪些资源不可以缓存;哪些资源是仍然可用的,哪些资源是过时需要重新请求的。当命中缓存之后,浏览器会从硬盘中读取资源,虽然比起从内存中读取慢了一些,但比起网络请求还是快了不少的。绝大部分的缓存都来自 disk cache。
- 网络请求
补充:
当浏览器要请求资源时
-
调用 Service Worker 的 fetch 事件响应
-
查看 memory cache
-
查看 disk cache。这里又细分:
- 如果有强制缓存且未失效,则使用强制缓存,不请求服务器。这时的状态码全部是 200
- 如果有强制缓存但已失效,使用对比缓存,比较后确定 304 还是 200
-
发送网络请求,等待网络响应
-
把响应内容存入 disk cache (如果 HTTP 头信息配置可以存的话)
-
把响应内容 的引用 存入 memory cache (无视 HTTP 头信息的配置)
-
把响应内容存入 Service Worker 的 Cache Storage (如果 Service Worker 的脚本调用了 cache.put())
cache-control 只能用在响应头中吗?
不是,cache-control 可以用在请求头和响应头中,但一般用在响应头里面即由服务端来设置缓存与否。
时间标准化
有很多处理时间的库,比如 dayjs moment.js,但其实在 format 时间的时候会踩坑
- HH:MM:SS 和 HH:mm:ss 是不同的 reference:Time and Date Formats
不同区域时区处理问题
一般我们会在中国站点使用东八区,而国际站点往往使用 UTC 即 GMT+0 的时间,以下是开发中的一些小 tip
dayjs 是前端甚至 node 服务端常用来规范时间的包,有以下两个 tip:
- 将时间戳规范化为 UTC 时间,可以使用
dayjs(param).utcOffset().format()
- 将一个日期(日期带有区域的不唯一性,可能是一个东八区时间,也可能是个统一世界标准时)转化为标准的时间戳,可以使用
dayjs(param).utc(true)
将一个日期认为是 UTC 时间转出时间戳
// 若我们认为 2021/11/14 18:26 为 UTC 时间,转为时间戳后即为 1636914360000,东八区时间即为 2021-11-15 02:26:00
const utc1 = dayjs('2021/11/14 18:26').utc(true).valueOf()
// 若我们认为 2021/11/14 18:26 为 东八区 时间,转为时间戳后即为 1636885560000
const utc2 = dayjs('2021/11/14 18:26').utc().valueOf()
注: 这些当然可以通过文档确认,此处只做简单记录。
单元测试
在我的服务端学习笔记(1) 中有介绍过集成测试和单元测试的区别,最近写业务代码需要写单元测试,因此对于 jest的使用也有了一些新的认识,下面列举几个常用配置项:
更多信息可以参考开发文档:JEST
很全面的 VUE 单元测试文档:VUE Test Utils
- moduleNameMapper
- in jest 就是根目录的意思
- collectCoverage
- collectCoverageFrom
前端单元测试覆盖面:
- 涉及逻辑更改后视图的变化
- 涉及进入组件后 dispatch action 的触发
- 工具函数是否正确返回
注: 前端单元测试将更注重于显示层面的测试,不是像集成测试那样去和服务端进行数据交互,因此耗时也将大幅度减少,因此可以利用 husky (git hooks make easy)将其集成到 git 工作流中
yarn vs npm
Difference between npm and yarn
两者都是「包管理器」,yarn 是为了弥补 npm 安装速度慢,日志多以及旧版本的 npm 不 lock 依赖导致出现的一系列问题(但是后来 npm 更新加入了 package-lock.json)
yarn: Yet Another Resource Negotiator
dependencies vs devdependencies
dependencies和devDependencies的区别?
How can I install all deps in NODE_ENV production?
-
dependencies
- 生产环境需要的依赖,打包时会包含
- 如果想将包安装在 dependencies 中执行 npm install --save 即可
-
devdependencies
- 开发环境需要的依赖,打包时不包含,NODE_ENV=production 时默认不安装 devdependencies
- 如果想将包安装在 devdependencies 中执行 npm install --save-dev 即可
webhooks
概念
webhook是一种web回调或者http的push API,是向APP或者其他应用提供实时信息的一种方式。Webhook在数据产生时立即发送数据,也就是你能实时收到数据。这一种不同于典型的API,需要用了实时性需要足够快的轮询。这无论是对生产还是对消费者都是高效的,唯一的缺点是初始建立困难。
Webhook有时也被称为反向API,因为他提供了API规则,你需要设计要使用的API。Webhook将向你的应用发起http请求,典型的是post请求,应用程序由请求驱动。
区分 prettier eslint
ESLint 是什么呢?
是一个开源的 JavaScript 的 linting 工具,使用 espree 将 JavaScript 代码解析成抽象语法树 (AST),然后通过AST 来分析我们代码,从而给予我们两种提示:
- 代码质量问题:使用方式有可能有问题(problematic patterns)
- 代码风格问题:风格不符合一定规则 (doesn’t adhere to certain style guidelines)
prettier
ESLint 主要解决了两类问题,但其实 ESLint 主要解决的是代码质量问题。另外一类代码风格问题其实 Airbnb JavaScript Style Guide 并没有完完全全做完,因为这些问题"没那么重要",代码质量出问题意味着程序有潜在 Bug,而风格问题充其量也只是看着不爽。
Prettier 声称自己是一个有主见 (偏见) 的代码格式化工具 (opinionated code formatter),Prettier 来不需要我们再思考究竟是用 single quote,还是 double quote 这些乱起八糟的格式问题,Prettier 帮你处理。最后的结果,可能不是你完全满意,但是,绝对不会丑,况且,Prettier 还给予了一部分配置项,可以通过 .prettierrc 文件修改。
所以相当于 Prettier 接管了两个问题其中的代码格式的问题,而使用 Prettier + ESLint 就完完全全解决了两个问题。但实际上使用起来配置有些小麻烦,但也不是什么大问题。因为 Prettier 和 ESLint 一起使用的时候会有冲突
相应的插件:
eslint-plugin-prettier
typeof 判断类型
最近在项目中使用
lodash
时提示isFunction
函数已经deprecated了,无奈想自己验证是否为函数,只好====>
js内存溢出bug
我对于这件事情的处理还是过分的依赖国内网站,应该拓宽一下戏路
=======>最终问题通过更新node版本进行解决
commonjs 规范与浏览器
references:
浏览器默认不支持 commonjs 规范,因为它是同步的,浏览器端每加载一个文件(require 进来的文件),要发网络请求去取,如果网速慢,就非常耗时,浏览器就要一直等 require 返回,就会一直卡在那里,阻塞后面代码的执行,从而阻塞页面渲染,使得页面出现假死状态,更直白的讲是缺少 module、
exports、require、global 这几个环境变量,
- 同时基于此也有了 webpack 等打包工具把 commonjs 规范的文件构建成浏览器能运行的文件。
前端性能优化
前端性能优化之 webp
为什么要使用 webp 图片?
- webp 是谷歌提出的一种新型图片格式,它的 key points 在于图片清晰度不变(肉眼效果)但文件体积明显缩小。
- 文件减小后,渲染速度会提升。
如何将 jpg png 文件转换成 webp 图片文件?
imagemin
const imagemin = require('imagemin');
const PNGImages = 'assets/images/*.png';
const JPEGImages = 'assets/images/*.jpg';
const output = 'build/images';
const imageminWebp = require('imagemin-webp');
const convertPNGToWebp = () =>
imagemin([PNGImages], output, {
use: [
imageminWebp({
quality: 85,
}),
]
});
const convertJPGToWebp = () =>
imagemin([JPGImages], output, {
use: [
imageminWebp({
quality: 75,
}),
]
});
Promise.all([convertPNGToWebp, convertJPGToWebp])
.catch(error => console.log(error));
如何使用 webp 文件,同时兼容那些不支持 webp 的浏览器呢
<picture>
<source srcset="sample_image.webp" type="image/webp">
<source srcset="sample_image.jpg" type="image/jpg">
<img src="sample_image.jpg" alt="">
</picture>
使用此标记,理解 image/webp 媒体类型的浏览器将下载 Webp 图片并显示它,而其他浏览器将下载 JPEG 图片。
任何不支持 的浏览器都将跳过所有 source 标签,并加载底部 img 标签。因此,我们通过提供对所有浏览器类的支持,逐步增强了我们的页面。
如何在 css 中使用 webp 文件呢
可以参考 my vue project config来配置通过 modernizrr 来自动检测为能展示 webp 图像的浏览器根元素(html)上添加特殊标签
前端性能优化之懒加载
路由懒加载:为了尽量避免首页白屏,提高首屏组件加载速度,一般在路由中通过 import 引入一个组件即可。
{
path: '/',
name: 'HelloWorld',
component: ()=>import("@/components/HelloWorld")
}
组件懒加载:为了需要某个组件的时候才执行它,在自适应布局中则需要这种组件加载方式,一般使用 webpack dynamic import 结合
{
path: '/',
name: 'HelloWorld',
component: ()=>import(/* webpackChunkName: "Hello" */ /* webpackMode: "eager" */"@/components/HelloWorld")
}
其中: webpackChunkName: "Hello"
应用于调试时找到具体的 chunk,webpackMode: "eager"
则用于将本 chunk 打包在父 chunk 内,减少一个查找 chunk 的 http request
router & location.href & router-link
需求背景:在一个 Vue 项目中实现点击跳转到一个新 tab or 点击跳转但不切 tab
- 针对:只跳转,但不切 tab
这是明显的 spa,实际上只需要使用 router.push 即可
- 针对:跳转到新 tab
Vue.js, How to open a link in a new tab
可以使用 a 标签,设置 href 和 target="_blank" rel="noopener noreferrer"
(rel 设置为安全防范)后即可跳转到新 tab
那么既然上述方式能够完美解决问题了,我们为什么还需要使用 router-link 呢,使用 router-link 同时搭配 target=“_blank” 即可实现跳转新 tab.并且它的优势如下:
- 无论是 HTML5 history 模式还是 hash 模式,它的表现行为一致,所以,当你要切换路由模式,或者在 IE9 降级使用 hash 模式,无须作任何变动。
- 在 HTML5 history 模式下,router-link 会守卫点击事件,让浏览器不再重新加载页面。
- 当你在 HTML5 history 模式下使用 base 选项之后,所有的 to 属性都不需要写 (基路径) 了。
router.replace vs router.push
router.push 会向路由栈中添加一条记录,router.replace 则会替换路由栈中的当前一条记录。关于他们的区别,想必很多人都很熟悉。那么在实际的开发中,它们到底怎么使用呢?
时长使用 ios 设备的人会注意,ios 很注意 native 应用的右滑回退功能,即便是 Safari 中的网页。那么理所当然我们应该在开发 web 页面时在需要右滑回退的位置使用 router.push
,而那些无效的路由(或者说不希望会被回退追溯到的路由)则使用 router.replace
bff
BFF(Backends for frontends)避坑指南
概念
BFF
(Backends For Frontends) 服务于前端的后端。后端各种微服务、API之间的一层胶水代码。主要的业务场景请求转发、数据组织、接口适配、权鉴和SSR。
作用
- 数据处理:字段名处理、数据结构处理、冗余数据处理以适配前端不同的展示逻辑
- 数据缓存:对一些特殊场景的数据进行缓存,提高性能,进而提升用户体验
- 接口聚合:让后端更注重服务基础能力的开发,更聚焦业务模型本身,界面的交互与逻辑由
BFF
层来完成 - 服务于多个终端,
Web
端、H5 活动页、小程序、IOS、Android… 底层基础服务不需要考虑不同设备的兼容逻辑,这个需求前端同学更清楚