@色少1年前
https://juejin.cn/post/7348414849397588006
https://juejin.cn/post/7348414849397588006
当在 el-input 输入框内按回车时,如果该输入框位于 el-form 表单的第一个,就会触发表单的提交事件。而当 el-form 表单内有多个 el-input 输入框时,其他输入框的回车操作不会执行任何操作。
可以通过在 el-form 标签上使用 @submit.native.prevent 注解来解决这个问题。该注解表示对一个组件绑定系统原生事件,并阻止默认事件(如 form 的 submit 事件默认提交会刷新页面,.prevent 修饰符可以阻止该默认事件)。
上码:
1 2 3 4 5 | <el-form :model="query" ref="Form" :inline="true" @submit.native.prevent> <el-form-item label="名称" prop="title"> <el-input v-model="queryParams.title" clearable placeholder="请输入"/> </el-form-item> </el-form> |
通过添加 @submit.native.prevent 注解,可以阻止表单提交时刷新页面的默认行为,从而避免在第一个 el-input 输入框内按回车时触发表单提交事件的问题。
.native 表示对一个组件绑定系统原生事件
.prevent 表示阻止默认事件(如form的submit事件默认提交会刷新页面,.prevent修饰符可以阻止该默认事件)
最近有些奇奇怪怪的需求。比如后端没读法取服务器权限因此没法做到文件导出或者利用oss导出(公司没钱)。导致导出的时传文件流给前端让前端获取。最奇怪是居然不用get用post说是文件过大怕前端接收不全。。这什么扯淡玩意。。get只是浏览器对url长度做了限制罢了。
算了遇到这么弱的后端你只能配合没法子。唯一用blob的好处就是防止盗链和可以加密。自我安慰吧。
因此产生了如何在封装了http请求里面利用axios获取文件流导出excel?
下面上代码
重点:responseType: ‘blob’
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 | const axiosReq = (url, params) => { axios .post(url, params, { responseType: "blob", headers: { "Content-Type": "application/json" } }) .then(res => { console.log(res, "axios"); const blob = new Blob([res.data], { type: "application/zip;" }); const downloadElement = document.createElement("a"); const href = window.URL.createObjectURL(blob); const filename = res.headers["content-disposition"] .split(";")[1] .split("=")[1]; //filename; downloadElement.href = href; downloadElement.download = filename; //命名下载名称 downloadElement.click(); window.URL.revokeObjectURL(href); //下载完成进行释放 }) .catch(e => { ElMessage.error(e.message); }); }; |
promise.then?Promise 的成功和失败情况的回调函数
1 2 3 | var p1 = new Promise((resolve, reject) => { resolve('成功!'); }); |
P1.then().then().then().then() 假设中间一个then 出错了后面的then会执行吗?
写了多年代码的你肯定说被catch掉后面肯定不执行啊!OTL~没catch呢?就是只是问执行不执行?
1 2 3 4 5 6 | var p1 = new Promise((resolve, reject) =>{ resolve("成功!"); });p1 .then(()=>{console.info(1)}) .then(()=>{throw new Error("出错了";)}) .then(()=>{console.info(3)},(e)=>{console.info(e)}) |
神奇的一幕出现了居然是执行的并且作为下一个then的参数传入。其实then的方法早在mdn上有解析。
问题又来了?神奇的菜鸟问那第二个错了,第三个传入了第二参数,第四个还会执行吗?

真是神奇。并且后面的then居然是返回resolve 真是神奇的一幕。翻查mdn
既然then 是回调函数总会返回和执行这就产生新的问题了?那怎样才能中断呢?
1 2 3 4 5 6 7 | var p1 = new Promise((resolve, reject) => { resolve('成功!'); });p1 .then(()=>{console.info(1)}) .then(()=>{return new Promise((resolve,reject)=>{})}) .then(()=>{console.info(3)},(e)=>{console.info('33')}) .then(()=>{console.info(4)},(e)=>{;console.info(e)}) |
for…in 可以遍历对象的所有可枚举属性,包括对象本身的和对象继承来的属性
Object.keys()方法可以遍历到所有对象本身的可枚举属性,但是其返回值为数组
Object.values()与Object.keys()遍历对象的特性是相同的,但是其返回的结构是以遍历的属性值构成的数组
Object.entries()的返回值为Object.values()与Object.keys()的结合,也就是说它会返回一个嵌套数组,数组内包括了属性名与属性值
其返回结果与Object.keys()对应,但是他得特性与其相反,会返回对象得所有属性,包括了不可枚举属性
Object.getOwnPropertySymbols()会返回对象内的所有Symbol属性,返回形式依旧是数组,值得注意的是,在对象初始化的时候,内部是不会包含任何Symbol属性
Reflect.ownKeys()返回的是一个大杂烩数组,即包含了对象的所有属性,无论是否可枚举还是属性是symbol,还是继承,将所有的属性返回
遍历目标 | 方法 |
---|---|
遍历对象本身的可枚举属性不包含继承来的属性(不包括Symbol()) |
Object.keys() Object.values() Object.entries() |
遍历对象本身的可枚举属性包括继承来的属性(不包括Symbol()) | for…in |
遍历对象本身的所有属性(不包括Symbol()) | Object.getOwnPropertyNames() |
遍历对象的Symbol()属性 | Object.getOwnPropertySymbols() |
遍历对象的所有属性 | Reflect.ownKeys() |
返回值 | 方法 |
---|---|
返回数组 |
Object.keys() Object.values() Object.entries() Object.getOwnPropertyNames() Object.getOwnPropertySymbols() Reflect.ownKeys() |
不返回值 | for…in |
遍历值 | 方法 |
---|---|
遍历属性 | Reflect.ownKeys() Object.getOwnPropertyNames() Object.keys() |
遍历属性值 | Object.getOwnPropertySymbols() Object.values() |
遍历全部 | for…in Object.entries() |
new 运算符创建一个用户定义的对象类型的实例或具有构造函数的内置对象类型之一
实现new,首先就要知道 new 操作,里面到底做了些啥?
实现方法
function myNew(Parent, ...args) { // 创建一个空对象,基础父级的原型 let obj = Object.create(Parent.prototype) // 把this对象和剩余参数给构造函数 let result = Parent.apply(obj, args) // 最后返回对象 return typeof result === 'object' ? result : obj } // new构造函数的模拟实现 const _new = function(){ let obj = new Object(); let _constructor = [].shift.call(arguments); // 使用中间函数来维护原型关系 const F = function(){}; F.prototype = _constructor.prototype; obj = new F(); let res = _constructor.apply(obj,arguments); return typeof res === 'object' ? res || obj : obj; }
状态码【200】表示【请求成功】,一般在GET和POST请求中可以见到;
状态码【302】表示【资源临时移动】;
状态码【304】表示【所请求的资源并未修改】;
状态码【403】表示【服务器拒绝执行客户端的请求】通常表示用户通过了身份验证,但缺少权限对给定的资源进行访问或者操作;场景:用户登录成功,但是无权进行读写操作。
状态码【404】表示【服务器找不到客户端所请求的资源(网页)】;
状态码【500】表示【服务器内部错误】。
状态码【401】表示【请求没有被认证或者认证失败】
通常由web服务器返回,而不是web应用。
场景:token失效、token缺失、token伪造,导致服务端无法识别身份。
总结
401和403的主要区别在于
重点不同:401着重于认证,403着重于授权
返回对象不同:401通常由web服务器返回,403由web应用返回
场景不同:401表示用户未通过身份授权、验证,403表示用户可能通过了身份验证,但缺少指定权限
坑之背景:
ERP项⽬中不同⻚⾯请求同⼀个接⼝/trace/item/staff/list(只有线上有此接⼝,其他灰度没发,直接请求到这
⾥, ⼀个字,⽆语)参数⼀致, 服务端配置⼀致
1、商品操作⽇志
使⽤jQuery 的$.ajax的⽅式请求, 响应正常
2、交易 –> 发货⽐例 –> 变更记录
使⽤axios.get⽅式,就⼀直报错,⼀直报错,⼀直报错,Network
Erorr
坑之分析:
为什么相同的相同的请求,不同的⽅式,结果会不⼀样?这个时候去怀疑后端配置不对,显然有点耍⽆赖。没有办法,冷静下来想想, axios
请求⽅式有问题, 那⼀定是它做了什么不为⼈知的事情,那我们从这个往下查。
#1. 由于ERP特定要求, 前端请求会去改写请求头,在header⾥⾯添
就是这个操作会导致触发Request Method为OPTIONS的预检请求
(preflight)。⽽$.ajax是不会触发这个动作。后端该请求的Allow-
Method⾥⾯不⽀持OPTIONS⽅式,就出现了上图中的预检请求不通过
详⻅:https://www.jb51.net/article/193303.htm
#2. 让ERP后端这个接⼝⽀持OPTIONS⽅式,然后再测再报错
#3. ngnix跨域设置没有加域名,添加跨域域名,再测再报错
原来preflight请求还要校验请求头, 那就让后端加上呗。nginx⾥⾯加或后端代码⾥⾯加,之⼀即可
Access-Control-Allow-Headers 添加CompanyId,TrackId
总结: 要解决此类问题拢共分三步(某个特定请求级别)
1. Allow-Method 允许 OPTIONS⽅式
2. Access-Control-Allow-Origin 对其他各灰度环境开放
3. Access-Control-Allow-Headers 添加CompanyId,TrackId等需要
允许通过的字段
基本流程:
1、用户在浏览器中输入url地址
面试延伸:浏览器进程与线程的认知程度。
浏览器是多进程的 浏览器之所以能够运⾏,是因为系统给它的进程分配了资源(cpu、内存)
简单点理解,每打开⼀个 Tab ⻚,就相当于创建了⼀个独⽴的浏览器进程。
2、浏览器解析域名得到服务器ip地址
浏览器会首先从缓存中找是否存在域名,如果存在就直接取出对应的ip地址,如果没有就开启一个DNS域名解析器。DNS域名解析器会首先访问顶级域名服务器,将对应的ip发给客户端;然后访问根域名解析器,将对应的ip发给客户端;最后访问本地域名服务器,得到最终的ip地址。
面试延伸:
DNS 缓存:DNS 存在着多级缓存,从离浏览器的距离排序的话,有以下⼏种: 浏览 器缓存,系统缓存,路由器缓存,IPS 服务器缓存,根域名服务器缓存,顶级域名服 务器缓存,主域名服务器缓存
DNS 负载均衡:DNS 可以返回⼀个合适的机器的 IP 给⽤户,例如可以根据每台机 器的负载量,该机器离⽤户地理位置的距离等等,这种过程就是 DNS 负载均衡,⼜ 叫做 DNS 重定向。⼤家⽿熟能详的 CDN(Content Delivery Network) 就是利⽤ DNS
的重定向技术,DNS 服务器会返回⼀个跟⽤户最接近的点的 IP 地址给⽤户,CDN
节点的服务器负责响应⽤户的请求,提供所需的内容
3、TCP三次握手建立客户端和服务器的连接
因为HTTP是基于TCP的可靠传输,所以在发送http数据报之前,需要先进行TCP的三次握手建立连接。三次握手过程如下:
第一次握手:客户端—>服务端 ack=1,seq=x(x随机生成)
第二次握手:服务端—>客户端 ACK=1,ack=x+1,seq=y(y随机生成)
第三次握手:客户端—>服务端 ACK=1,ack=y+1,seq=x+1
完成第三次握手时,实际上客户端已经与服务器建立了连接,所以第三次握手的报文已经可以携带数据了。
面试延伸:HTTP 协议 80/8080, HTTPS 协议 443。http 请求格式。
常⽤⽅法有: GET, POST, PUT, DELETE, OPTIONS, HEAD。
请求报头:请求报头允许客户端向服务器传递请求的附加信息和客户端⾃⾝的信息。
常⻅ 的请求报头有: Accept , Accept-Charset , Accept-Encoding , Accept-Language , Content-Type , Authorization , Cookie , User-Agent 等。 Accept ⽤于指定客户端⽤于接受哪些类型的信息, Accept-Encoding 与 Accept 类似,它⽤于 指定接受的编码⽅式。 Connection 设置为 Keep-alive ⽤于告诉客户端本次 HTTP 请求结束 之后并不需要关闭 TCP 连接,这样可以使下次 HTTP 请求使⽤相同的 TCP 通道,节省
TCP 连接建⽴的时间。
请求正⽂: 当使⽤ POST, PUT 等⽅法时,通常需要客户端向服务器传递数据。这些数据 就储存在请求正⽂中。在请求包头中有⼀些与请求正⽂相关的信息,例如: 现在的 Web
应⽤通常采⽤ Rest 架构,请求的数据格式⼀般为 json。这时就需要设置 Content-Type: application/json 。
4、客户端发送HTTP请求获取服务器端的静态资源
面试延伸:HTTP 响应报⽂也是由三部分组成: 状态码, 响应报头和响应报⽂。
状态码是由 3 位数组成,第⼀个数字定义了响应的类别,且有五种可能取值:
1xx:指⽰信息–表⽰请求已接收,继续处理。
2xx:成功–表⽰请求已被成功接收、理解、接受。
3xx:重定向–要完成请求必须进⾏更进⼀步的操作。
4xx:客户端错误–请求有语法错误或请求⽆法实现。
5xx:服务器端错误–服务器未能实现合法的请求。
平时遇到⽐较常⻅的状态码有: 200, 204, 301, 302, 304, 400, 401, 403, 404, 422, 500
响应报头: 常⻅的响应报头字段有: Server, Connection…
响应报⽂: 服务器返回给浏览器的⽂本信息,通常 HTML, CSS, JS, 图⽚等⽂件就放在这⼀部分
5、服务器发送HTTP响应报文给客户端,客户端获取到页面静态资源
6、TCP四次挥手关闭客户端和服务器的连接
数据传输完毕后,TCP会进行四次挥手断开连接,释放资源。四次挥手过程如下:
第一次挥手:客户端—>服务器 FIN=1,ack=1,seq=u 客户端状态变为FIN_WAIT_1
第二次挥手:服务器—>客户端 ACK=1,ack=u+1,seq=v 服务器状态变为CLOSE_WAIT,TCP进入半关闭状态
第三次挥手:服务器—>客户端 FIN=1,ACK=1,ack=u+1,seq=w 服务器状态变为LAST_ACK
第四次挥手:客户端—>服务器 ACK=1,ack=w+1,seq=u+1 客户端状态变为TIME_WAIT,此时TCP未释放,需要等待计时器计时完成后,客户端状态变为CLOSED
面试延伸:
TCP、UDP 和 HTTP 的区别
TCP、UDP:传输层协议
HTTP: 应⽤层协议
TCP(传输控制协议,Transmission Control Protocol):
(类似打电话) ⾯向连接、传输可靠(保证数据正确性)、有序(保证数据顺序)、传输⼤量数据(流模式)、速度慢、对系统资源的要求多,程序结构较复杂,每⼀条 TCP 连接只能是点到点的,TCP ⾸部开销 20 字节。
UDP(⽤户数据报协议,User Data Protocol):(类似发短信) ⾯向⾮连接 、传输不可靠(可能丢包)、⽆序、传输少量数据(数据报模式)、速度 快,对系统资源的要求少,程序结构较简单 , UDP ⽀持⼀对⼀,⼀对多,多对⼀和多对多的交互通信,UDP 的⾸部开销⼩,只有 8 个字节。
7、浏览器解析文档资源并渲染页面
浏览器解析文档资源并渲染页面流程:
(1)解析html资源,构建DOM Tree
(2)解析css资源,构建CSS Rule Tree
(3)JS通过DOM API和CSS OM API来操作DOM Tree和CSS Tree
(4)解析完成后综合DOM Tree和CSS Tree会生成Render Tree,计算每个元素的位置,这个过程就是回流(layout or reflow)
(5)调用操作系统Native GUI的绘制
(6)页面绘制完成
涉及到的其他知识点:
1、Render Tree的生成
DOM Tree和CSS Tree结合会生成Render Tree,是由可视化元素按照其顺序生成的树形结构,非可视化元素是不会出现到渲染树中的。
非可视化元素:head、display:none;(注意:visibility:hidden的元素会出现在渲染树中)
2、回流和重绘
回流(reflow,也叫重排、布局):某部分的变化影响了布局,浏览器需要重新渲染。(如元素大小、位置的改变)
重绘(repaint):元素的某一部分发生改变,尺寸、位置没有改变。(字体颜色、背景颜色的改变)
引起回流的几个主要原因:
(1)网页初始化
(2)JS操作DOM树(增加、删除元素等)
(3)某些元素的尺寸改变
(4)CSS属性的改变
浏览器的“dirty”系统:
为了避免页面细小的改变就引起回流和重绘,“dirty”系统会将这些改变操作积攒一批再进行操作,这又叫异步reflow或者增量异步reflow。有些特殊情况不会这么做:resize窗口、改变了页面默认的字体等,这些操作会直接触发回流。
编写代码时如何减少回流和重绘:
(1)修改样式不要逐条修改,定义CSS样式的class,直接修改元素的className
(2)不要将DOM节点的属性值放在循环中当成循环的变量
(3)为动画的HTML元素使用fixed或absolute的position属性,修改它们的CSS就不会触发reflow
(4)把DOM离线后修改,设置display:none或者clone元素到内存中,修改完成再显示回页面
(5)不要使用table布局,一个微小的改变就可能引起整个table的重新布局
3、性能优化
(1)提升HTML的加载速度
– 页面精简,删除不必要的内容,将内嵌的JS和CSS移至外部文件,使用压缩工具等
– 减少文件数量,合并文件,减少请求次数
– 减少域名查询,外部引入的资源尽量少使用不同的域名
– 使用缓存,重用数据
– 优化页面元素的加载顺序
– 使用合法的标签
– 根据浏览器类型选择合适的策略
(2)编写合理的CSS
– DOM的深度尽量浅
– 使用合法的CSS属性
– 不要为ID选择器指定类名或标签名
– 避免使用后代选择器,尽量使用子选择器
– 避免使用通配符
(3)关于JS标签
js标签的加载和执行特点:载入后立即执行,执行时会阻塞页面后续内容
– 将所有的js标签放在页面底部,保证脚本执行前已完成DOM渲染
– 尽可能合并脚本
– 减少内联js的使用
– 注意多个js标签的引入顺序
– 使用defer属性,该属性可以使脚本在文档完全呈现以后再执行
– 使用async属性,可以使当前脚本不必等待其他脚本的执行,也不必阻塞文档的呈现
toLocaleString介绍
toLocaleString用于将对象根据语言的不同转换成某种语言环境下的字符串,同时也可以根据传入的参数来判断具体的表现形式。本文主要介绍Number和Date类型的toLocaleString方法.
参考:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Number/toLocaleString
谈一个面试中经常会被问道的问题:如何对数字进行千位符格式化?一般人可能想到的做法是换成字符数组循环手动插入,或者使用正则的方法。其实知道了toLocaleString完全可以不需要这么做,一行代码就能搞定。
1 2 | const num = 1122333444455551 num.toLocaleString() //1,122,333,444,455,551 |
Number.prototype.toLocaleString
支持两个参数。一个是语言代码,表示将数字格式化成哪国语言;另一个是格式化时的可选的一些属性,包括localeMatcher、style、currency、currencyDisplay、useGrouping、minimumIntegerDigits、minimumFractionDigits、maximumFractionDigits、minimumSignificantDigits、maximumSignificantDigits等。例如:
1 2 3 4 5 6 | const num = 1276482; num.toLocaleString('zh', { style: 'decimal' }) // 1,276,482,纯数字格式 num.toLocaleString('zh', { style: 'percent' }) // 127,648,200%,百分数格式 num.toLocaleString('zh', { style: 'currency', currency: 'CNY' }); // ¥1,276,482.00,人民币形式 num.toLocaleString('zh', { style: 'currency', currency: 'cny', currencyDisplay: 'code' }); // CNY1,276,482.00,currency不区分大小写 num.toLocaleString('zh', { style: 'currency', currency: 'cny', currencyDisplay: 'name' }); // 1,276,482.00人民币 |
上面提到的比较长的属性用来控制整数、小数位数和有效数字位数,可以分为两组。第一组是minimumIntegerDigits、minimumFractionDigits、maximumFractionDigits,应用场景是自动补0。
1 2 3 4 5 6 | const num = 1413.56; num.toLocaleString('zh', { minimumIntegerDigits: 7 }); //0,001,413.56 // 如果不想要分隔符可以指定useGrouping为false num.toLocaleString('zh', { minimumIntegerDigits: 7, useGrouping: false }); // 0001413.56 num.toLocaleString('zh', { minimumFractionDigits: 3, useGrouping: false }); // 1413.560 num.toLocaleString('zh', { maximumFractionDigits: 1, useGrouping: false }); // 1413.6 |
另一组是minimumSignificantDigits和maximumSignificantDigits,用来控制有效数字位数,只要设置了这一组属性,第一组的属性就会被忽略。
1 2 3 | const num = 141.56; num.toLocaleString('zh', { minimumSignificantDigits: 8 useGrouping: false }); // 141.56000 num.toLocaleString('zh', { maximumSignificantDigits: 3, useGrouping: false }); // 142 |
Date.prototype.toLocaleString
Date类型的语言代码一般用的不多,保持默认或者使用zh即可,格式化时的常用的属性主要有weekday、year、month、day、hour、minute。
weekday表示星期几(中文形式没有),可选属性有narrow、short、long,用来控制缩写的形式
1 2 3 4 | const date = new Date(); date.toLocaleString('en', { weekday: 'narrow' }); // M date.toLocaleString('en', { weekday: 'short' }); // Mon date.toLocaleString('en', { weekday: 'long' }); // Monday |
timeZoneName表示时区的表现形式,有short和long两个值
1 2 3 | const date = new Date(); date.toLocaleString('zh', { timeZoneName: 'short' }); // 2018/4/23 GMT+8 下午2:17:37 date.toLocaleString('zh', { timeZoneName: 'long' }); // "2018/4/23 中国标准时间 下午2:17:37" |
剩下的属性可以取值为numeric和2-digit来控制位数展示。不过好像对于小时、分钟、秒不起作用
1 2 3 | const date = new Date(Date.UTC(2012, 1, 2, 3, 1, 1)); // 2012/2/2 11:01:01 date.toLocaleString('zh', { hour12: false, year: 'numeric', month: 'numeric', day: 'numeric', hour: 'numeric', minute: 'numeric', second: 'numeric' }); // 2012/2/2 11:01:01 date.toLocaleString('zh', { hour12: false, year: '2-digit', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', second: '2-digit' }); // 12/02/02 11:01:01 |
兼容性
MDN地址:toLocaleString