本人所有的前端面试大都来自于山月大佬,还有部分来自于简书、思否、知乎
https://q.shanyue.tech/
1 手写promise相关代码
promise是异步编程的一种解决方案,promise有三种状态:
- pending:等待中,表示还没有得到结果
- fulfilled:意味着操作成功完成
- rejected:意味着操作失败
promise函数接收两个参数,resolve和reject
- resolve:将promise对象的状态从pending变为fulfilled,在异步操作成功时调用
- reject:将promise对象的状态从pending变为rejected
promise.all(),如果所有promise都执行成功返回所有成功的promise值,如果有失败则返回第一个失败的值
promise.race(),返回第一个执行完成的promise值,他有可能是fulfilled和rejected状态
应用场景:
1、用户登录后,会同时异步请求所有数据,这时要确保所有数据请求成功,如果有一个不成功就会重定向页面,使用promise.all方法
2、抢票软件,请求了很多卖票渠道,每次只需返回第一个执行完成的promise,使用promise.race方法
Promise.all()手写
function pAll(_promises){
return new Promise((resolve,reject)=>{
//传进来的是一个可迭代对象,需要先转化为数组
const promises=Array.from(_promises)
const len=promises.length
const r=[]
let count=0
for (let i=0;i<len;i++){
//数组中可能包含常量,需要将其转换为promise对象
Promise.resolve(promises[i]).then(0 => {
r[i]=o
if(++count===len){
resolve(r)
}
}).catch(e=>reject(e))
}
})
}
Promise.race()手写
function pRace(_promises){
return new Promise((resolve,reject)=>{
const promises=[..._promises]
promises.forEach(p=>Promise.resolve(p).then(o=>resolve(o)).catch(e=>reject(e)))
})
}
2 history和hash的使用和区别
hash:hash虽然出现在URL中,但不会被包括在http请求中,对后端完全没有影响,因此改变hash不会重新加载页面
history:利用pushState()和replaceState()方法,当他们执行修改时,虽然改变了URL,但是浏览器不会立即向后端发生请求
history相对于直接修改hash具有以下优势:
- pushstate设置的新的URL可以是与当前URL同源的任意URL,而hash只能修改#后面的部分,因此只能设置与当前URL同文档的URL
- pushstate通过stateobject参数可以添加任意类型的数据到记录中,hash只可添加短字符串
history的缺点:
在history模式下,一旦发生刷新,如果服务器没有相应的响应或资源,会报404错误
3 跨域问题
当协议、域名、端口任一不一样就会产生跨域
解决跨域的方案:
(1)Access-Control-Allow-Origin:*
无法携带cookie时,设置withCredentials = false
(2)jsonp:就是script标签的src引入
(3)反向代理
4 手写new操作符
new操作符的流程:
- 生成一个空的obj对象
- 让对象的原型指向构造函数的原型
- 改变构造函数的指向
- 判断类型,如果为值类型,则返回obj,如果为引用类型,则返回引用类型的对象
function _new(constructor,...args){
const obj={}
obj.__proto__=constructor.prototype
let res=constructor.apply(obj,args)
return res instanceof Object?res:obj
}
优化实现:
function _new(constructor,...args){
let obj=Obj.create(constructor.prototype)
let res=constructor.apply(obj,args)
return res instanceof Object?res:obj
}
5 手写解析url参数
以此URL链接为例
'http://www.getui.com?user=superman&id=345&id=678&city=%E6%9D%AD%E5%B7%9E&enabled'
function parseParam(url){
const [href,params]=url.split('?')
//保存参数的对象
const result={}
//判断传入链接是否含有参数
params && params.split('&').map(item=>{
//将参数划分成键和值的形式
let [key,value=true]item.split('=') //当参数没有值时给他一个boolean值
//有的值时编码了的格式需要对其进行转化
value=typeof value==='boolean'?value:decodeURIComponent(value)
//此时需要分键是否重复进行处理
if(!result[key]){ //如果没有重复的键值
result[key]=value
}else{
//有重复,则要将其写成数组的格式
result[key]=result[key] instanceof Array?[...result[key],value]:[result[key],value]
}
})
}
6 http常见状态码
- 200:请求成功
- 201:
- 204:
- 301:永久重定向
- 302:临时重定向
- 304:自上次请求,未修改的文件
- 307:
- 308:
- 400:错误的请求
- 401:未被授权,需要身份验证,如token信息
- 403:请求被拒绝
- 404:资源缺失,接口不存在,或请求的文件不存在
- 405:
- 500:服务器端的未知错误
- 502:网关错误
- 503:服务暂时无法使用
- 504:网关超时,上游应用层迟迟未响应
7 http状态码中301,302和307有什么区别
- 301为永久重定向,该操作比较危险,需要谨慎操作;如果设置了301,但是一段时间后又想取消,但是浏览器中已经有了缓存,还是会重定向。如果设置了301,会将a资源重定向b资源,搜索引擎以前收录了a资源,301后就变成收录b资源
- 302为临时重定向,在重定向时会改变method:把post变为get,于是有了307
- 307为临时重定向,但是不会改变method
8 502和504有什么区别
502:收到了上游响应但无法解析
504:上游响应超时
9 http的proxy的原理
代理服务器会自动提取请求数据包中的http请求数据发送个服务器,并将服务器的http响应数据发送给客户端
http代理的步骤:
- 客户端向代理发起TCP连接
- 代理接收客户端的连接,双方建立连接
- 客户端向代理发送http请求
- 代理解析http请求
- 代理向服务器发起TCP连接
- 服务器接收代理的连接
- 代理向服务器发送http请求
- 服务器发送响应给代理
- 代理发送响应给发送请求的客户端
https代理通常使用Connect方法,通过proxy建立一条隧道,这样proxy无法解密数据
Connect方法代理步骤:
- 客户端向代理发送connect请求
- 代理向服务器发送TCP连接请求
- 当TCP连接建立完成后,代理向客户端返回HTTP/1.0 OK,隧道建立完成
- 代理转发客户端的数据给服务器,转发服务器的数据给客户端,直到任何一方连接结束
10 http2与http1.1相比有什么改进
-
二进制分帧
以二进制代替原本的明文传输,原本的报文消息被划分为更小的数据帧 -
请求多路复用
帧对数据进行顺序标识,这样浏览器收到数据后,就可以按照序列对数据进行合并 -
头部压缩
-
服务端推送
浏览器发送一个请求,服务器主动向浏览器推送与这个请求相关的资源
http/1的几种优化可以弃用:合并文件、内联资源、雪碧图
11 JSONP如何实现跨域
动态创建script标签,通过将返回的json作为参数传入回调函数中
12 什么是CDN
前端性能优化的一种方式,从最近的服务器上请求数据,避免多人访问服务器造成堵塞
13 什么是webpack
webpack是一个模块打包工具,它包含Entry、Output、Loader、Plugins、Module
(1)ES6如何转化成ES5
使用babel工具
(2)webpack的运行时代码
__webpack_modules__:维护所有模块的数组,每个模块都由一个包裹函数(module, module.exports, __webpack_require__) 对模块进行包裹构成。
__webpack_require__:手动实现加载一个模块,对已加载的模块进行缓存,对未加载过的模块,执行id定位到__webpack_modules__中的包裹函数,执行并返回module.exports,并缓存
function f1(){
return webpack({
entry:'/index.js',
mode:'none',
output:{
iife:false,
pathinfo:'verbose'
}
})
}
const __webpack_modules__=[()=>{}]
const __webpack_require__=(id)=>{
const module={exports:{}}
const m=__webpack_modules__[id](module,__webpack_require__)
return module.exports
}
__webpack_require__(0);
(3)如何加载json、image等非js资源
使用相应的loader进行打包
(4)如何加载style样式?
需要使用两个loader:
- css-loader:用于处理css中的url和@import,并将其视为模块引入
- style-loader:将样式注入到DOM中
css-loader的原理就是postcss,借用postcss-value-parser解析css为AST(抽象语法树),并将css中的url和@import解析为模块
使用DOM API加载css资源,由于CSS需要在JS资源加载完后通过DOM API控制加载,容易出现页面抖动,因此需要单独加载CSS资源,可以借助mini-css-extract-plugin将css单独抽离
14 图片懒加载
IntersectionObservation API可以直接监视图片是否在可视范围内
Dataset API 将图片真正的地址保存在dataset.src
当在可是范围内时,img.src=img.dataset.src
15 如何避免xss攻击?
对用户输入的参数进行过滤