面试题
js面试题
__proto__和prototype之间的关系是什么?
所以的对象都有__proto__属性
每一个构造函数都有自己的prototype,而且对应的prototype都有自己的constructor和__proto__
每一个实例对象都有自己的__proto__来对应构造函数的prototype
.call(), .apply(), .bind()的区别和作用以及如何实现
.call(), .apply(), .bind()作用是改变函数执行上下文,简而言之是改变函数执行时this的指向,区别在于调用方法和参数传递上
call
⾸先 context 为可选参数,如果不传的话默认上下⽂为 window
接下来给 context 创建⼀个 fn 属性,并将值设置为需要调⽤的函数
因为 call 可以传⼊多个参数作为调⽤函数的参数,所以需要将参数剥离出来
然后调⽤函数并将对象上的函数删除
Function.prototype.myCall=function(context) {
if(typeofthis!="function") {
thrownewTypeError("Error");
}
context=context??window;
context.fn=this;
constargs=[...arguments].slice(1);
constresult=context.fn(...args);
deletecontext.fn;
returnresult;
}
Function.prototype.myApply=function(context) {
if(typeofthis!=='function') {
thrownewTypeError('Error') }
context=context??window
context.fn=this
letresult
// 处理参数和 call 有区别
if(arguments[1]) {
result=context.fn(...arguments[1])
}else{
result=context.fn()
}
deletecontext.fn
returnresult
}
constobj={
name:'txc'
}
functionfn(...args) {
console.log(this,args);
}
Function.prototype.myBind=function(ctx) {
console.log(this);
if(typeofthis!=='function') {
thrownewTypeError('error')
}
letargs=[...arguments].slice(1)
letfn=this
returnfunction() {
returnfn.apply(ctx,args.concat(...arguments))
}
}
fn.myBind(obj,1)(2,3,4)
js的基础数据类型有几种,了解包装对象吗?
boolean
null
undefined
string
number
symbol
包装对象:因为基础数据类型是没有方法的,比如undefined,null,但是有一种包装对象可以将基础类型包装为对象然后调用对象的方法
functionmySplit(str,method,arg) {
letobj=newString(str)
returnobj[method](arg)
}
letarr1="a b c"
letresult=mySplit(arr1,'split',' ')
console.log(result);
什么是宏任务什么是微任务(都是异步任务)
微任务是需要异步执行的函数,是在主程序执行之后,在宏任务结束之前
宏任务时间颗粒度比较大,执行的时间间隔不能精确的控制
常见的微任务:
- Promise.then
- Promise.catch
- Process.nextTick(Node.js)
- MutaionObserver
- script(理解为外层的同步代码)
- setTimeout/setInterval
- setImmediate, I/O (Node.js)
console.log(111);
letp=newPromise((resolve,reject)=>{
console.log(222);
resolve(11)
})
p.then(()=>{
console.log(333);
})
setTimeout(()=>{
console.log(444);
})
console.log(555);
//顺序:111 -> 222 -> 555 -> 333 -> 444
// 先执行宏任务script,然后执行同步代码,同步代码执行完毕后,再执行所有的微任务,微任务执行完毕后再去执行下一个宏任务setTimeout
客户端缓存有几种方式?浏览器出现from disk,from memory的策略是什么(强缓存策略)?
强缓存(cache-control)
// nodejs端
ctx.set(cache-control,'max-age=3600')
协商缓存
让客户端和服务端协商来进行缓存
使用no-cache来设置
使用Last-Modified/if-Modify-Since
使用Etag/if-None-Match优先级高于Last-Modified/if-Modify-Since
缓存关系
强缓存高于协商缓存,协商缓存中Etag/if-None-Match优先级高于Last-Modified/if-Modify-Since
说一下CORS的简单请求和复杂请求的区别?
CORS(Cross-origin resource sharing) 跨域资源共享,用于避开浏览器的同源策略。
相关头部的设置
简单请求:
复杂请求:
节流和防抖是什么?在什么场景下使用?实现一个节流和防抖函数?
防抖:interval时间内,如果有fn再次触发,则会清除上次的函数执行,重新设置新的延迟函数,当停止触发并且超过interval事件间隔,则会执行传入的fn函数。
// 防抖
constdebounce=function(fn,delay=500) {
lettimeout
returnfunction() {
clearTimeout(timeout)
timeout=setTimeout(()=>{
fn.apply(this,arguments)
},delay)
}
}
constfn=function(){
console.log('fn');
}
constmyDebounce=debounce(fn)
letbtn=document.querySelector('#btn')
btn.onclick=myDebounce
// 节流
constthrottle=function(fn,delay=3000) {
lettimeout;
returnfunction() {
if(!timeout) {
timeout=setTimeout(()=>{
timeout=null
fn.apply(this,arguments)
},delay)
}
}
};
constfn=function() {
console.log('fn');
};
constmyThrottle=throttle(fn);
letbtn=document.querySelector('#btn');
btn.onclick=myThrottle;
实现一个myMap函数,实现map的功能
letarr=['张三','李四','王武']
// const arr1 = arr.map((item, index, arr) => {
// // console.log(item, index, arr);
// return item
// })
// console.log(arr1);
Array.prototype.myMap=function(fn) {
letrestArr=[]
for(leti=0;i restArr.push(fn(this[i],i,this)) } returnrestArr } arr.myMap((item,index,arr)=>{ console.log(item,index,arr); }) 节流:连续触发,但是一段时间内只会触发一次 from memory cache代表内存中的缓存 from disk cache代表硬盘中的缓存,浏览器读取缓存的顺序是memory -> disk max-age=xxx 缓存内容将在多少秒后失效 no-store 所有内容都不会缓存,不使用强制缓存也不使用协商缓存 no-cache 客户端缓存内容,但是是否需要使用缓存需要协商缓存来验证决定 private 所有内容只有客户端可以缓存 public 所有内容都被缓存(客户端和服务器都可缓存) websocket如何实现点对点和广播通信的? websocket是一种全双工通信协议,websocket让服务端和客户端通信变的简单,最大的特点是可以通过服务端主动推送消息到客户端。前端基于nodejs和websocket实现点对点和广播通信。 和http一样,WebSocket也是应用层协议。浏览器和服务器只要完成一次握手的动作,然后浏览器和服务器之间就会脱离http协议,而是用WebSocket自己的ws协议。客户端和服务器端就可以通过tcp连接直接交换数据。 说了这么多好处,接下来讲讲到到底怎么用。 首先要创建一个WebSocket对象: varSocket=newWebSocket(url,[protocol]) WebSocket 协议本质上是一个基于 TCP 的协议。 为了建立一个 WebSocket 连接,客户端浏览器首先要向服务器发起一个 HTTP 请求,这个请求和通常的 HTTP 请求不同,包含了一些附加头信息,其中附加头信息"Upgrade: WebSocket"表明这是一个申请协议升级的 HTTP 请求,服务器端解析这些附加的头信息然后产生应答信息返回给客户端,客户端和服务器端的 WebSocket。连接就建立起来了,双方就可以通过这个连接通道自由的传递信息,并且这个连接会持续存在直到客户端或者服务器端的某一方主动的关闭连接。 WebSocket实例 Socket.onopen:连接建立时触发 Socke.onmessage:客户端接受服务端数据时触发 Socket.onerror:通信错误时触发 Socket.onclose:连接关闭时触发 WebSocket事件 Socket.send():向服务器发送数据 Socket.close():关闭连接 WebSocket方法 Socket.readyState:这是个只读属性,y用来表示连接状态 0:未连接 1:连接已建立 2.连接z正在关闭 3.连接已关闭或打不开连接 Socket.bufferedAmount:z也是只读属性。主要是计算还没有被send()发出的UTF-8文本字节数。 WebSocket都有哪些属性 对象创建好了,接下来让我们了解下 以上代码中的第一个参数 url, 指定连接的 URL。第二个参数 protocol 是可选的,指定了可接受的子协议. Token一般存放到哪里,Token存放到cookie和放到localStorage,sessionStorage中的区别? 浏览器为什么要阻止跨域请求?如何解决跨域跨域每次都会发送到服务端吗? 浏览器阻止跨域请求的原因是同源策略,就是协议,域名,端口号只要有一个不一样就会造成跨域 http://192.168.0.26:9090 // 域名不同 端口号不用造成跨域 http://192.168.0.48:9097 解决跨域: jsonp跨域(创建一个script) document.domain + iframe跨域 postMessage解决跨域 postMessage是HTML5 XMLHttpRequest Level 2中的API,且是为数不多可以跨域操作的window属性之一,它可用于解决以下方面的问题:a.) 页面和其打开的新窗口的数据传递b.) 多窗口之间消息传递c.) 页面与嵌套的iframe消息传递d.) 上面三个场景的跨域数据传递 用法:postMessage(data,origin)方法接受两个参数data: html5规范支持任意基本类型或可复制的对象,但部分浏览器只支持字符串,所以传参时最好用JSON.stringify()序列化。origin: 协议+主机+端口号,也可以设置为"*",表示可以传递给任意窗口,如果要指定和当前窗口同源的话设置为"/"。 //a.html:(http://www.domain1.com/a.html) variframe=document.getElementById('iframe'); iframe.onload=function() { vardata={ name:'aym' }; // 向domain2传送跨域数据 iframe.contentWindow.postMessage(JSON.stringify(data),'http://www.domain2.com'); }; // 接受domain2返回数据 window.addEventListener('message',function(e) { alert('data from domain2 ---> '+e.data); },false); // b.html:(http://www.domain2.com/b.html) // 接收domain1的数据 window.addEventListener('message',function(e) { alert('data from domain1 ---> '+e.data); vardata=JSON.parse(e.data); if(data) { data.number=16; // 处理后再发回domain1 window.parent.postMessage(JSON.stringify(data),'http://www.domain1.com'); } },false); nginx反向代理(add_header Access-Control-Allow-Origin *;) cors(服务端解决的跨域问题) //普通跨域请求:只服务端设置Access-Control-Allow-Origin即可,前端无须设置,若要带cookie请求:前后端都需要设置。 // 前端设置是否带cookie xhr.withCredentials=true; websocket协议跨域 WebSocket protocol是HTML5一种新的协议。它实现了浏览器与服务器全双工通信,同时允许跨域通讯,是server push技术的一种很好的实现。原生WebSocket API使用起来不太方便,我们使用Socket.io,它很好地封装了webSocket接口,提供了更简单、灵活的接口,也对不支持webSocket的浏览器提供了向下兼容。
/*let socket = new WebSocket("ws://localhost:8080");
socket.onopen = function() {
socket.send("秋风的笔记");
};
socket.onmessage = function(e) {
console.log(e.data);
};*/
varsocket=io('http://www.domain2.com:8080');
// 连接成功处理
socket.on('connect',function() {
// 监听服务端消息
socket.on('message',function(msg) {
console.log('data from server: ---> '+msg);
});
// 监听服务端关闭
socket.on('disconnect',function() {
console.log('Server socket has closed.');
});
});
document.getElementsByTagName('input')[0].onblur=function() {
socket.send(this.value);
};
// nodejs后台
varhttp=require('http');
varsocket=require('socket.io');
// 启http服务
varserver=http.createServer(function(req,res) {
res.writeHead(200, {
'Content-type':'text/html'
});
res.end();
});
server.listen('8080');
console.log('Server is running at port 8080...');
// 监听socket连接
socket.listen(server).on('connection',function(client) {
// 接收信息
client.on('message',function(msg) {
client.send('hello:'+msg);
console.log('data from client: ---> '+msg);
});
// 断开处理
client.on('disconnect',function() {
console.log('Client socket has closed.');
});
});
nodejs中间件正向代理(KoaServerHttpProxy)
什么是xss攻击?如何防范xss攻击?
xss可以分为存储型,反射型和dom型,dom型是属于JavaScript的安全漏洞,其他两种是属于服务端的安全漏洞
Cross-Site Scripting(跨站脚本攻击)简称 XSS,是一种代码注入攻击。攻击者通过在目标网站上注入恶意脚本,使之在用户的浏览器上运行。利用这些恶意脚本,攻击者可获取用户的敏感信息如 Cookie、SessionID 等,进而危害数据安全。XSS 的本质是:恶意代码未经过滤,与网站正常的代码混在一起;浏览器无法分辨哪些脚本是可信的,导致恶意脚本被执行。
预防存储型和反射型 XSS 攻击
存储型和反射型 XSS 都是在服务端取出恶意代码后,插入到响应 HTML 里的,攻击者刻意编写的“数据”被内嵌到“代码”中,被浏览器所执行。
预防这两种漏洞,有两种常见做法:
改成纯前端渲染,把代码和数据分隔开。
对 HTML 做充分转义。
纯前端渲染
纯前端渲染的过程:
浏览器先加载一个静态 HTML,此 HTML 中不包含任何跟业务相关的数据。
然后浏览器执行 HTML 中的 JavaScript。
JavaScript 通过 Ajax 加载业务数据,调用 DOM API 更新到页面上。
在纯前端渲染中,我们会明确的告诉浏览器:下面要设置的内容是文本(.innerText),还是属性(.setAttribute),还是样式(.style)等等。浏览器不会被轻易的被欺骗,执行预期外的代码了。
但纯前端渲染还需注意避免 DOM 型 XSS 漏洞(例如 onload 事件和 href 中的 javascript:xxx 等,请参考下文”预防 DOM 型 XSS 攻击“部分)。
在很多内部、管理系统中,采用纯前端渲染是非常合适的。但对于性能要求高,或有 SEO 需求的页面,我们仍然要面对拼接 HTML 的问题。
转义 HTML
如果拼接 HTML 是必要的,就需要采用合适的转义库,对 HTML 模板各处插入点进行充分的转义。
常用的模板引擎,如 doT.js、ejs、FreeMarker 等,对于 HTML 转义通常只有一个规则,就是把 & < > " ' / 这几个字符转义掉,确实能起到一定的 XSS 防护作用,但并不完善:
预防 DOM 型 XSS 攻击
DOM 型 XSS 攻击,实际上就是网站前端 JavaScript 代码本身不够严谨,把不可信的数据当作代码执行了。
在使用 .innerHTML、.outerHTML、document.write() 时要特别小心,不要把不可信的数据作为 HTML 插到页面上,而应尽量使用 .textContent、.setAttribute() 等。
如果用 Vue/React 技术栈,并且不使用 v-html/dangerouslySetInnerHTML 功能,就在前端 render 阶段避免 innerHTML、outerHTML 的 XSS 隐患。
DOM 中的内联事件监听器,如 location、onclick、onerror、onload、onmouseover 等, 标签的 href 属性,JavaScript 的 eval()、setTimeout()、setInterval() 等,都能把字符串作为代码运行。如果不可信的数据拼接到字符串中传递给这些 API,很容易产生安全隐患,请务必避免。
简述http状态码
2XX成功
3XX重定向
4XX客户端错误
404 not found
403 forbidden 表示对请求资源的访问服务器被拒绝
401 unauthorized 认证错误
400 报文存在语法错误
5XX服务器错误
Promise.allSettled()了解吗
Promise.allSettled()方法接受一组 Promise 实例作为参数,包装成一个新的 Promise 实例。只有等到所有这些参数实例都返回结果,不管是fulfilled还是rejected,包装实例才会结束。
回调函数是什么,解决什么问题,会产生什么问题
回调函数主要解决的是异步问题,主要缺点是会产生回调地狱问题
解决办法是通过Promsie和async/await来解决
先执行宏任务,执行结束后看是否有可执行的微任务,如果没有可执行的微任务就继续执行下一个宏任务,如果有可执行的微任务就先执行微任务,待所有的微任务结束后再去执行下一个宏任务。
宏任务微任务执行顺序:
宏任务执行结束有可执行的微任务吗?执行所有微任务yesno
a=1ab条件a测试
常见的宏任务:
同步和异步
先判断是否是同步还是异步,同步的话放到同步队列中去,异步的话放入异步队列中去,通过时间循环机制先执行同步再执行异步,放到执行栈中去
如果中断ajax请求
原生可以通过XMLHttpRequest中的abort来中断ajax请求,但是ajax不能阻止向服务器请求,只能停止当前请求
this的指向问题,箭头函数this的指向
如果是函数直接调用的话不是严格模式下的this指向window,如果是严格模式下的this指向的是undefined,如果在script中type='module'的情况下指向的是undefined
如果是对象的话,调用对象内部的方法this指向的是对象
如果是new 一个实例对象的话this指向的是实例对象本身
如果是apply,call,bind的话就是函数调用的那个对象
箭头函数的this:箭头函数没有本身的this,会找到外层不是箭头函数的this指向就是箭头函数this的指向
bind
apply