# 如何解决a标点击后hover事件失效的问题?
严格按照[L V H A]的顺序:Link => Visited => Hover => active
# 点击一个input依次触发的事件
onmouseenter => onmousedown => onfocus => onclick
# 响应式的好处
从用户的角度上出发,可以让用户获得更好的浏览体验,从开发者的角度上出发,降低了代码的重复性,开发人员可以将更多的精力放到其他部分
# null和undefined的区别
# 语义上
null
:代表空对象undefined
:代表未定义的值
# 检测上
typeof null === "object"
typeof undefined === "undefined"
# 隐式类型转换上
Number(null) => 0
Number(undefined) => NaN
# 其他角度
- 函数的默认返回值是undefined
- 原型链的终点是null
- JS底层中的对象机器码是以"000"开头,而null的机器码全都是0
# 冒泡排序算法和数组去重
# 冒泡排序
let arr = []
for (let i = 0; i < arr.length; i++) {
for (let j = 0; j < arr.length - i; j++) {
if (arr[j] > arr[j + 1]) {
[arr[j], arr[j + 1]] = [arr[j + 1], arr[j]]
}
}
}
# 数组去重
- 双重for循环
- Set数据结构
- indexOf
- sort排序后再用for循环判断当前值是否等于上一个值和下一个值 更多的方法就在这里:JavaScript数组去重(12种方法) (opens new window)
# 描述一下Promise
Promise是JS异步编程解决方案之一,它的链式调用出现提高了代码的可阅读性和可维护性。在它之前我们只能通过[回调函数]和[事件]解决异步回调的问题,并且容易出现臭名昭著的[回调地狱]
# Promise.all中如果有一个抛出异常了会如何处理
会直接抛出错误。
# Promise.all的实现代码:
static all(promiseArr) {
let index = 0,
res = []
return new Promise((resolve, reject) => {
for (let i = 0; i < promiseArr.length; i++) {
Promise.resolve(promiseArr[i]).then(
value => {
res[i] = value
index++
if (index === promiseArr.length) {
resolve(res)
}
},
reason => reject(reason)
)
}
})
}
# Promise为什么能链式调用
因为Promise.then会返回一个新的Promise实例
# 描述一下EventLoop的执行过程
- EventLoop这个是运行在浏览器渲染引擎中的事件处理线程中
- JS脚本以宏任务的形式来执行
- 在执行栈中先执行同步代码,碰到微任务将微任务压入微任务队列,碰到宏任务压入宏任务队列
- 当同步代码执行完毕后,先清空微任务队列,再清空宏任务队列,在清空的过程中如果依然碰到异步代码,那么就放入到下一次EventLoop中执行
- 执行完毕后,会进入渲染阶段,渲染阶段会受以下因素的影响:
- 屏幕帧率改变,如果页面性能太差,为了不丢帧,浏览器选择降低帧率
- 浏览器判断本次渲染是否会造成视觉上的改变,比如背景色改变
map of animation frame callbacks
为空
- 确定要渲染后,会根据不同的事件进行渲染:
- 对需要渲染的文档,如果窗口发生了变化,就会调用resize事件
- 对需要渲染的文档,如果页面发生了滚动,就会调用scroll事件
- 对需要渲染的文档,执行requestAnimationFrame的回调
- 调用IntersectionObserver的回调,重新渲染页面
- 最后会检查task队列和microTask是否为空,如果为空会调用idle空闲周期算法,检测
requestIdleCallback
是否为空,如果不为空就会执行里面的回调
- 最后说下,requestAnimationFrame和requestIdleCallback,前者是在渲染前执行的,因为动画会更改DOM结构。后者是用来处理计算量大但不紧急的事件,当队列内部没有任务执行时,会清空它内部的回调,你也可以传入timeout参数,强制[timeout]秒后执行,但它会阻塞其他代码的执行。最后提一嘴,React的时间切片渲染就用到了这个技术,不过因为兼容问题,他们在postMessage中自己实现了一套
- scroll和resize自带节流
- 这里举几个微任务和宏任务的例子:
- 微任务
Promise.then async/await MutationObserver
- 宏任务
setTimeout setInterval setImmediate
- 微任务
# document window html body的层级关系
window => document => html => body
# addEventListener函数的第三个参数
默认为false,也就是允许冒泡,如果改为true,那么就只会在捕获阶段执行
# 有写过原生的自定义事件吗
通过两个构造函数可实现原生的自定义事件:new CustomEvent和new Event
# new Event
用法:第一个参数是事件名称,第二个参数是修饰符,通过dispatchEvent派发,addEventListener调用
let event = new Event('XX', {cancelable:false; bubbles: true})
document.dispatchEvent(event)
# new CustomEvent
用法:和new Event一样,但接受三个参数,第二个参数是detail,是一个对象,内部是参数键值对,通过e.detail拿到传递的参数
let event = new CustomEvent('XX', detail: {要传的参数}, {cancelable:false; bubbles: true})
document.dispatchEvent(event)
# 使用场景
个人觉得这个东西可理解为观察者模式,主页面派发这个事件,其他页面监听这个事件,当这个事件被派发了之后,就会监听,然后做出相应的回调
# 冒泡和捕获的具体过程
- 冒泡:
target => body => html => document
- 捕获:
document => html => body => target
事件委托就是利用了冒泡,页面中的事件流也分为三个阶段:事件捕获 => target => 事件冒泡
# 描述下原型链
JS没有类的概念,class也只是ES5中寄生组合继承的语法糖而已,所以需要依靠原型链实现继承。在JS中每一个对象都会有原型链,每个函数都会有原型,且原型链指向的是原型,原型又指向的是构造函数的原型,所以:
Object.__proto__ === Function.prototype
Function.prototype.__proto__ === Object.prototype
Object.prototype.__proto__ === null
不考虑
Object.prototype.__proto__
的情况下,时刻记住__proto__永远指向prototype,prototype.__proto__也永远指向prototype
# 手写new
function New(Func, ...args) {
let obj = Object.create(Func.prototype),
res = Func(...args)
return res instanceof Object ? res : obj
}
# typeof和instanceof的区别
typeof主要用来检测原始值,instanceof主要用来检测对象类型
# typeof为什么对null错误的显示
这是一个历史悠久的BUG,也可以理解为当初想用它来表示空对象、空容器的意思,随着JS的发展变得毫无意义,但年代久远,已经无法修正
# 详细说下instanceof
它是用来检测[被检测的对象]的原型链上是否存在[检测它的对象],如果存在,就表示由它构建而来,且null也会被正确判断
# 一句话描述一下this,另外函数内的this是在什么时候确定的?
this保存的是当前执行上下文的环境。在执行的时候确定的,指向最后调用它的对象,我们可以通过call apply bind来改变它的指向
# apply/call/bind和不同
- call和apply都是立即改变,bind则是返回一个函数等待下一次调用
- call和bind的参数形式相同,apply的形式是数组
- call的性能比apply高
# webpack中的Loader和Plugin有什么区别
# Loader
本质上是转换器,因为webpack本身只能识别JS代码,所以需要loader去处理不同类型的文件,将其转化为JS代码。执行时期是在webpack进行初始化的时候就会执行,在module.rules数组中配置
# Plugin
本质上是插件,用于扩展webpack现有的功能。执行时期贯穿在webpack整个生命周期内,不同的插件执行时期不同,在Plugins数组中配置
# HTTP和TCP的不同
HTTP是应用层协议,是对两台计算机之间传输图片、文字、媒体信息等超文本数据定义了规范和约束,也就是怎么传输数据,而TCP是传输层协议,规定了怎么才能把数据完整无误的传输到另外一台计算机
# TCP和UDP的区别
# TCP
- 面向字节流、面向链接的可靠的传输层协议
- 传输数据前会进行三次握手,建立可靠的连接
- 校验数据的完整性
- 一对一
# UDP
- 面向报文的不可靠的传输层协议
- 传输数据前不会进行握手,会直接发送数据
- 不会校验数据的完整性
- 一对多
# 场景
UDP多用于直播、游戏等领域,传输效率上比TCP高出不少,但精确度上远不如,所以TCP常用于传输文件等场景
# 介绍一下虚拟DOM
通过创建JS对象来模拟页面上的真实DOM,几乎所有前端框架都会用到这种技术。首先将我们传入的模板字符串进行分割成字节流,然后传入字节流构建一颗类似于真实DOM的DOM树,虚拟DOM好处是配合diff算法,能够提高页面元素的复用性,只重新渲染更改的部分
# 盒模型
- 标准盒模型:content + padding + margin + border
- IE怪异盒模型:content(content + padding) + margin + border
IE的怪异盒模型中内部的content是指真实的内容大小,而外面的content是内容区域 + padding填充部分。可通过box-sizing来规定盒模型,在实际开发中,可以在入口文件规定好盒模型
# 输入URL到页面的呈现
- 在浏览器地址栏内按下第一个键后浏览器会调用自己的算法,去书签栏或者历史记录中将我们可能访问的URL显示出来
- 点击要访问的URL后,浏览器会先检测URL是否合法,如果没问题会调用网络线程来准备发送网络请求
- 先在HTTP应用层内构建请求行,但不会发送网络请求,会先在强缓存中查找强缓存是否有效
- 强缓存无效的话,就会调用DNS域名解析将URL解析成IP地址
- 此时进入TCP传输层,进行TCP三次握手,握手完毕后将请求报文分割并打上标记生成数据包,将处理后的数据包转发给网络层
- 网络层拿到数据包后,调用ARP协议,通过IP地址反查出MAC地址
- 拿到IP地址、MAC地址、数据包后,在数据链路层内发起请求
- 服务端收到请求后,一层层的将报文剥开,其中就会把在传输层分割的报文组装起来,接着对请求会进行校验,比如是否有缓存字段、请求是否有权限。如果缓存有效,那么就会返回304状态码提醒浏览器使用缓存,这里其实就是协商缓存的步骤
- 如果缓存过期或者没设置,那么服务端就会返回请求的文件,如HTML、CSS和JS文件,浏览器接收到文件后,服务器会检测报文中的Connection的值是否等于keep-alive,如果不是keep-alive就会断开链接,但在HTTP 1.1协议后,Connection默认为keep-alive
- 浏览器接收到HTML文件后就会进行处理,这个过程是交给渲染引擎的GUI线程来做,根据HTML文件中定义的charset和doctype来解析文档,GUI线程调用标记化算法和建树算法,实际上就是词法分析和语法分析,生成以document为根节点的DOM树
- CSS的解析也是同理,只不过会先将CSS文件格式化成styleSheet对象,然后标准化这个对象,比如color: red这个属性会被格式化成16进制的数,最后将计算的结果挂载到window.getStyleComputed上,我们可以通过JS代码访问,但会引起回流
- CSS解析和HTML解析互不干扰,但JS文件就会造成阻塞,因为渲染引擎中的JS线程和GUI线程是互斥的,且JS引擎的优先级比GUI线程高,会将GUI线程挂起,所以script会阻塞页面解析,要放在底部,而link CSS在头部
- 在拿到CSSOM树和DOM树后,会将二者合成为布局树,精确的计算出每一个节点所处的位置以及样式
- 浏览器在渲染前会进行图层处理,图层分为普通图层和复杂图层,而普通文档流内所有的元素所处的就是一个复杂图层,每个复杂图层都会被GPU单独绘制,所以它们之间的重绘不会影响其他图层,提成为复杂图层的方式有:
- 拥有层叠上下文的特点,如scroll
- 设置z-index
但要注意设置z-index的元素如果本身层叠上下文的等级就比较低,会引起层爆炸,在它上面的图层都会被提升成复杂图层,页面可能会崩溃
- 将绘制指令传入渲染队列中,通过合成线程生成图块和位图,开始渲染页面,所以常说要尽量使用opacity transform等属性,因为它们会调用GPU单独绘制,也就是所谓的硬件加速
# JSON的原理以及手写一个实现
# 原理
原理是img script audio等标签中的src属性不会产生跨域问题。将回调函数名称当做参数发送给服务器,服务器传入此函数需要的数据当做形参,然后返回并执行。而我们早就准备好了这个回调函数,所以就直接执行了
# 实现
需要和后端搭配,代码如下,思路就是先拼接URL参数,然后发请求,再写一个回调挂到window上:
function jsonp({ url, params, cb }) {
let createUrl = () => {
let dataStr = ''
for (let k in params) {
dataStr += `${k}=${params[k]}&`
}
dataStr += `callback=${cb}`
return `${url}?${dataStr}`
}
return new Promise((resolve, reject) => {
let script = document.createElement('script')
script.src = createUrl()
document.body.appendChild(script)
// 添加回调
window[cb] = data => {
resolve(data)
document.body.removeChild(script)
}
})
}
# 浏览器为什么要跨域?如果是因为安全的话那小程序或者其他的为什么没有跨域?
因为Web环境较为开放,浏览器跨域也是为了抵御XSS攻击,而小程序对于敏感的接口都是由后端去请求微信官方的接口,由此可见小程序的安全是由微信官方来做的
# CORS跨域的原理
浏览器在每次发起请求的时候都会带上Origin字段,让浏览器与Access-Control-Allow-Origin进行对比,如果能够匹配上,那就可以正常请求数据
CORS又分为简单请求和非简单请求,非简单请求每次都会发送一个预检请求(预检请求的方式是OPTIONS方法,它会有一个Max-Age的字段,在有效时间内不会再次发送预检请求)判断访问权限是否还在有效期内
# CORS预请求OPTIONS就一定是安全的吗?
同源策略只会防止不同来源的读取,依然要注意CSRF攻击
# 在深圳的网页上输入百度,是怎么把这个请求发到北京的
通过CDN层层分发下去
# Vue的响应式原理
Vue 2.X中使用Object.defineProperty进行劫持,当数据被访问触发get时,响应式数据就会将当前的添加到对应的Dep中,当响应式数据发生改变的时候,就会触发Dep中的notify方法,从而实现响应式更新
Vue 3.0中使用的是Proxy劫持了整个Data对象,与Vue 2.X不同的是,它是当读到响应式数据的时候才会对其进行初始化,这样做的好处是提高了不少的性能
# 那在这个响应式中一个数据改变它是怎么通知要更新的,也就是如何把数据和页面关联起来?
在new Vue的时候,会调用initState方法初始化data,从而对data进行响应式处理,触发响应式数据get时,会将自己添加到对应的Dep中收集依赖,当响应式数据发生改变时会触发set中的notify方法,调用对应Watcher中的updateComponent方法,然后调用v_update(v_node)更新页面
# CommonJS和ES6模块的区别
- CommonJS是运行时加载,因为它导出的是一个对象,对象只有在运行的时候才会创建,ES6在编写的时候就已经确定了模块间的依赖关系
- CommonJS导出的是值拷贝,导出后CommonJS内部的值改变不会影响外部,除非重新加载,而ES6模块导出的是值引用,内部的值改变会影响
- CommonJS使用require导入,ES6通过[import from]的形式
- CommonJS的this指向当前的模块,ES6的this指向undefined,因为它使用的是严格模式
# 模块的异步加载
可以使用AMD和CMD,但我没有了解过,可以放入到Promise中,也可以使用script的defer/async属性实现异步加载
# 实现一个一组异步请求按顺序执行你有哪些方法?
- Generator函数
- reduce不断的叠加then
# Promise.all()是并发的还是串行的?
并发的,但必须等数组中所有的Promise改变状态后才会返回最终的结果
# webpack几种hash的实现原理
# hash和chunkhash
获取Compilation下所有的modules,在这些modules建立阶段时生成的hash作为参数生成一个新的hash,因为一个chunk下可能会有多个module,所以chunkhash也借鉴每个module的hash,最后生成自己独有的hash
# contenthash
通过mini-css-extract-plugin和JavascriptModulesPlugin,收集chunk的hash,进行一定处理后生成自己的hash
# webpack中如何处理图片的?
我们可以通过添加url-loader来处理图片。在添加好这个loader后,设置它的options.limit限制大小,如果图片大小超过这个数那么就会返回其配置的publicPath,如果没有超过,那么就会返回base64URL地址
# 说一下回流和重绘
# 回流
元素的几何属性发生改变
- 修改DOM结构,节点的增删改查
- 调用[scroll, client, getStyleComputed]等方法
# 重绘
重绘是指元素几何属性没有改变,而外观发生变化
# 它们之间的关系
回流必定引起重绘,但重绘不一定引起回流
# 如何避免回流和重绘
- 创建文档碎片:document.createFragment()
- 对于复杂的样式,直接以class来修改
- 尽量别用上面数组中说到的方法
- 动画写在position值为absolute/fixed的元素上,也就是脱离了普通文档留的元素
- 尽量使用transform opacity filter等属性,因为复杂图层中的绘制都是由GPU单独绘制
# 实现水平垂直居中的几种方式
flex布局,margin:auto,transform,position
# flex的兼容性怎样
其它的主流浏览器包括安卓和IOS基本上都支持了,可以去can i see网站上去看兼容性
# 移动端中css你是使用什么单位
整体布局flex + vw + rem灵活搭配,具体的元素使用px
# rem和em的区别
rem根据html的font-size来决定自身大小,em根据父级的font-size
# 在移动端中怎样初始化根元素的字体大小
先加个头:<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no" />
如果以iPhone 6为例,750的设备像素,那么我们就直接设置一个变量:@design_fontSize: 75
根字体的font-size:@design_fontSize / 750 / 2 * 100vw
再加一个[min-width]
和[max-width]
# 移动端中不同手机html默认的字体大小都是一样的吗
默认字体大小是16px,最小可识别的字体大小是12px,之前以为移动端最小字体是8px,后来去查了下,确实是12px
# 如果让你实现一个一直旋转的动画你会如何做
animation:@keyframe的名字 + 持续的时间 + 动画效果 + 延迟多少秒执行 + 执行多少次(这里可以用infinity)
# animation有一个steps()功能符知道吗?
让一段动画不连续,具体自行百度
# 用过哪些移动端的调试工具?
chrome和wenire(使用起来有些许麻烦,但挺香),详情:https://juejin.im/post/5c947f5251882568396a6773
# V8的垃圾回收是发生在什么时候?
在浏览器空闲时间进行垃圾回收
# 具体说一下垃圾回收机制
为了提高内存的利用率JS引擎会自动进行垃圾回收,主要讲堆内存,栈内存是上下文改变就会全部回收。垃圾回收两个概念,新生代空间和老生代空间
# 新生代空间(占比小)
指的是存活时间较短的对象,采用Scavenge算法将新生代空间[平分]为From空间和To空间,当From空间占满后,会调用Scavenge算法对From空间进行整理,然后把存活对象复制到To空间,最后两者的角色再互换,如此循环
# 老生代空间(占比高)
指的是存活时间较长的对象,新生代空间晋升到老生代空间需要满足两个条件:已经被Scavenge处理过和To空间被占满超过25%,而清理的过程是先进行标记化清除,将内存中的对象都打上标记,然后将强引用和使用中的变量取消标记,最后把标记了的对象都进行清除并整理内存空间,但这一步是最消耗资源的,所以又采用了增量标记的方案,在JS代码执行过程中时不时的进行GC
# 在项目中如何把http的请求换成https
一般都会用全局变量来保存域名字段,但看还有个方案:<meta http-equiv ="Content-Security-Policy" content="upgrade-insecure-requests">
(有点不好记,equiv的意思是平等、等同的意思,Content-Security-Policy是内容安全策略,upgrade-insecure-requests是升级不安全请求)
# 知道meta标签有把http换成https的功能吗?
<meta http-equiv ="Content-Security-Policy" content="upgrade-insecure-requests">
# http请求可以怎么拦截
CDN引入:
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
# 为什么要有拦截器?
请求拦截是为了防止在弱网的情况下一个请求被重复发送,响应拦截是为了更好的处理服务端的错误处理
# 请求拦截
在发送请求前,先设置一个拦截器:axios.interceptors.request.use(func1, func2),它和Promise一样接收2个函数作为成功和失败的回调,在func1里面我们可以自由添加对象和方法,然后在axios.get(以get方法为例)成功的回调中使用这些方法,你也可以进行一系列的拦截操作,比如我不想你用Get方法请求:
axios.interceptors.request.use(
config => {
if (config.method === 'get') {
console.log('狗贼,休想发请求');
return false
}
// 一定要记得返回config
return config
},
error => {
console.log(error);
}
)
axios.get(baseUrl).then(
v => {
console.log(v)
},
r => console.log(r)
)
# 响应拦截
同理,可以看看下面的代码,然后自己感悟一下,真的非常easy,毕竟咱们都是手写过Promise的人,
axios.interceptors.response.use(
response => {
console.log('恭喜你,请求成功')
return response
},
error => console.log(error)
)
axios.get(baseUrl).then(
v => {
console.log(v)
},
r => console.log(r)
)
# https的加密方式
通过对称性加密和非对称性加密进行混合加密,最开始通过非对称性加密传递密钥,后续的通信就使用对称性加密进行通信
# 混合加密的好处
非对称性通信的安全性更高,但速度比较慢,而对称性加密的安全性比较低,但速度快,将两者的优势结合在一起既可以提高通信效率,也可以提高安全性。不过也存在消息被完全替换的风险,所以需要使用数字签名来校验数据完整性
# 浏览器如何验证服务器的身份
通过数字签名和数字证书,下面简单的说下:
# 数字签名
数字签名首先会对整个内容用Hash函数生成一个消息摘要的东西(可以理解为hash值),然后用发送方的私钥进行加密,最后将摘要和内容一起发送给客户端
# 数字证书
数字证书由第三方安全机构(也就是CA)进行颁发,http想升级成https也是需要这个证书才可以进行升级,运营人员进行按要求提交申请后,过程和数字签名一样,只不过是用机构自己的私钥进行加密,最后将[明文信息和加密后的签名组成的证书]发送给服务器。通信时,浏览器会调用Hash函数生成一个信息摘要,然后浏览器使用内置的CA公钥进行解密,如果一致那就证明来源可靠
# ETag首部字段说一下
if-match if-none-match,也就是协商缓存
# 你们的token一般是存放在哪里的
localStorage或者Cookie
# Token会不会被伪造?
可以。黑客可以通过各种技术手段攻击JWT(JSON Web Token),使其失效,有以下几种方式:
- 敏感信息泄露
- 将算法修改为None
- 密钥混淆攻击
- 无效签名
- 暴力破解密钥
- 密钥泄露
- 操纵KID
- 操纵头部参数
最后提一嘴,XSS攻击一旦成功,不管是cookie还是Token都能干翻,甚至不需要拿,XSS可以直接发起ajax请求,另外Token只是防御CSRF攻击的。具体的可以看看这里:https://cloud.tencent.com/developer/article/1552824
# https工作流程
https是在http的基础上加了一层SSL/TSL安全协议(TSL是升级版的SSL),下面是加密方式(C代表客户端,S代表服务端):
- 首先C向S发送了一套自己支持的加密套件、client_random以及协议版本号给S
- S拿到这些数据后,会验证协议版本号,然后返回server_random、具体的加密套件、server_params以及证书
- C拿到这些数据后,会对证书进行验证,验证通过会发送client_params给S
- 接着C调用[ECDHE算法],传入client_params和server_params,计算出一个pre_random值,接着调用[伪随机数算法]传入pre_random、client_random、server_random计算出一个secret值
- S也会按照这样的步骤计算出一个secret值,双方进行对比,如果一致,就会使用最开始选择的加密方式和secret进行通信
# 前后端如何验证一个用户是否下线了
前端请求带上Token,发送给后端进行验证,同时设置响应拦截器:axios.interceptors.response.use
# CSP白名单知道吗?
明确告诉客户端哪些资源可以直接加载。另外,CSP的实现全部由浏览器来做,最大作用是防御XSS攻击,即使XSS发现了漏洞也无法注入脚本,在页面中加入:
<meta http-equiv="Content-Security-Policy" content="script-src 'self'">
和http升级成https是一样的,仅content内容不同,开发者只需进行配置,具体链接:https://juejin.im/post/5cd44b65f265da038b203420#heading-8
# Vue的diff算法
diff算法遵循同级比较,即只要同一级的节点不同,哪怕子节点完全相同也会被替换,这样做降低了时间复杂度。首先跳过在模板编译时所标记的静态节点,在Vue 2.X中使用的是双端对比法,从节点的两端开始对比,当指针相遇表示对比完成,根据不同的情况进行处理:
- 新节点有,旧节点没有 => 添加
- 和上面相反 => 删除
- 都没有 => 跳过
- 都有 => 检测是否还有子节点,然后继续进行对比
处理完毕后,调用patch方法生成真实DOM
# 浏览器的兼容?
从HTML CSS JS三个方面来答,个人理解CSS使用normalize.css抹平浏览器之间的差异,JS影响较大的应该是attachEvent和addEventListener
# 如何实现一个findIndex
findIndex接收一个函数,返回满足条件的数组下标:
Array.prototype.findIndex = function (cb) {
for (let i = 0; i < this.length; i++) {
if (cb(this[i])) {
return i
}
}
}
# 移动端布局有哪些方案?
我个人的回答是flex + rem/vw + px,至于其他的方案还真不知道
# 如果一个移动端的项目要显示在PC端上保证结构稳定你会如何做?
首先保证在移动端上显示正常,然后设置一个最大的max-width来限定死
# 具体说一下splitChunksPlugin
在webpack 4.X中的production模式中是自动启用的,具体链接:https://juejin.im/post/5c05309cf265da612d190705
# 有自己写过webpack插件吗
- 新建一个构造函数,然后在其原型上重写apply方法
- 给apply方法传入一个compiler对象(webpack实例)
- 给compiler对象注册对应的hooks事件
- 如果想访问每次编译后的文件,可使用compilation.assets方法
# 说一下Vue-Router的实现原理
# Hash模式
window.hash获取hash值,监听hashchange事件
# History模式
利用H5的History的API,pushState replaceState,通过popstate事件手动触发,除此之外还有history.go/back/forwards
# abstract模式
如果没有检测到有浏览器的api,比如在node环境,那么就会自动进入这个模式
# Vue-Router初始化是发生在什么时候
beforeCreate的时候,调用Vue.use来注册插件
# webpack构建流程
- 通过读取并合并传入的options和shell语句的配置,得到一个最终的参数
- 通过这最终的参数实例化一个compiler(webpack实例),为webpack事件流挂载自定义hooks
- 如果当前是watch模式,则调用compiler.watch方法来执行构建,否则调用compiler.run
- 通过入口文件,实例化Compilation对象回调其compilation钩子,递归调用loader从右向左翻译文件(webpack是函数式编程,所以是从右向左,并不是因为技术难点)
- 通过Compilation对象我们可以访问到当前模块的资源、生成的文件资源以及修改的文件。将编译好的文件生成AST,然后递归这个过程,直到所有的模块都得到处理,最后调用compilation.seal对chunk进行整理、优化和封装,得到最后的内容
- 最后的内容我们可以通过Compilation.assets来访问,至于plugin的处理会根据自身对应的钩子出现在webpack的各个生命周期内
# webpack插件原理
根据Tapable的钩子事件,贯穿整个webpack的生命周期,在对应的生命周期中执行
# webpack在配置插件的时候是一个数组那它有顺序吗
没有,是根据plugin定义的触发时的生命周期来决定的
# 让你从零开始构建一个webpack项目你可以吗
完全可以。先loader后plugin
# 为什么TCP要三次握手而不是两次
- 第一次握手客户端发送SYN(同步序列编码)
- 服务端收到SYN后对其进行处理,然后将处理后的结果放入ACK中,最后将ACK和新的SYN发送给客户端
- 客户端收到后发送ACK,表示握手完毕
# HTTP和TCP的区别
HTTP是应用层协议,规定了计算机之间传输文字、图片和媒体文件等数据的规范和定义,而TCP是传输层协议,规定了数据如果完整的、可靠的传输
# 什么情况会阻塞页面的加载
script标签添加了async,css文件里面有@import
# script放在body头部就一定会阻塞吗
添加了defer就不会
# 添加删除了DOM节点会发生什么?
回流,又要重新构建DOM树和CSSOM树
# js中改变transform的left和right对比于css修改transform
前者引起回流,后者引起重绘
# 什么是GPU加速
图层的渲染会交给GPU来做,而CSS中的transform filter opacity等属性会交给合成线程来做,不会经历重绘和回流
# 进程和线程的区别
一个程序只有一个进程,一个进程有多个线程,浏览器的每一个Tab页就是一个线程
# HTTP/2对比HTTP/1.1
# 头部压缩
通过HPACK算法,在服务端通过静态字典表、动态字典表和Huffman算法,将常用的头部字段保存起来,传输的时候只传索引
# 多路复用
通过二进制分帧,将数据转化成二进制的帧和流,帧是指数据的最小单位,流是多个帧组成的,然后给每个帧被打上标记,记录自己属于哪个流,然后在客户端乱序发送,最后在服务端根据标识重新组成流,这个方式也解决了HTTP的队头阻塞问题,但不是TCP的阻塞问题。TCP的阻塞是因为丢包重传,而HTTP是按顺序处理请求
# 二进制传输
采用二进制传输,比HTTP/1.1的文本格式传输具有更好的扩展性
# 服务器推送
在HTTP/1.1的时候服务器只能被动的响应,在HTTP/2中服务器也能主动给客户端推送消息
# 为什么说HTTPS比HTTP安全呢
使用了SSL/TSL协议,在传输中更有安全性,通过服务器证书去验证服务器身份,通过数字签名验证数据是否被篡改过
# 说一下对称加密和非对称加密
对称加密就是加密和解密都是一把密钥,传输速度上更快,但安全性较差。而非对称性加密私钥被存放在服务器,公钥加密只能私钥来解,私钥加密只能公钥来解,传输效率低,但更加安全。另外这两种加密方式的公钥中都没有数字证书这类东西,所以无法验证服务器身份
# HTTP请求的什么时候用的对称加密什么时候非对称加密
建立通信阶段使用非对称性加密,建立完毕后使用对称性加密进行传输
# 对称加密的原理
加密和解密都使用同一个密钥进行
# 如果让你去实现一个CSRF攻击你会怎做?
用户访问B站点,生成B的Cookie,用户没有退出,然后访问了C,C响应后拿B的Cookie对B发起请求,所以难点是如何跨域拿到Cookie,拿不到Cookie是因为跨域问题,那么可以尝试使用抓包工具找到后台接口,然后使用nginx或者JSONP进行跨域请求
# Vue中key的作用
提高节点的复用。在Vue 2.X中diff算法先使用双端对比法进行对比,当双端对比法结束后如果没有节点被复用,就会来对比key,所以它是提高了节点的复用以及diff的效率
# 还知道其他攻击方式吗
XSS攻击和伪造Token
# 如果我将key设置为了一个Math.random()可以吗
不行,和用index为key结果一样,无法复用节点
# 如果让你设计一个双向绑定你会如何设计
主要实现Observer类和Compiler类
# 如何实现if (a===1 && a===2 && a===3)
要么就劫持它,每次访问时都加1
# token放在Cookie和放在localStorage、sessionStorage中有什么不同吗
token放在web存储中更容易遭到攻击,因为web存储可以被JS代码访问,如果碰到XSS攻击那就不好了,而cookie可以设置httpOnly
# Cookie存在哪些安全问题?如何预防?
- 可以被JS代码访问,所以得设置httpOnly
- 可能会被中间人攻击劫持,那就得配置secure和用HTTPS,所以会涉及到购买CA等一系列问题
# SameSite设置为了lax之后是怎样来控制Cookie的发送的
大多数情况下不发送第三方的Cookie,但导航到目标网址的GET请求除外
# 如果顶级域名不同会发送吗
可以通过设置请求头进行发送,withCredentials: true
# 如果使用jsonp的话会有什么安全问题吗?
将Content-Type设置为text/html,应该是application/json,否则会导致XSS攻击,还有一个就是CSRF的劫持攻击,所以得对Referer进行判断
# SSR的使用场景
很多网站是出于效益(seo)的考虑才启用服务端渲染,性能倒是在其次
# requestAnimationFrame属于宏任务还是微任务
它不属于宏任务也不属于微任务,因为它是独立于主线程之外的任务,不归主线程管
# script与css还有页面的渲染顺序
按照这些标签的排列顺序来的,CSS文件和HTML文件的解析两者互不干扰,但JS引擎会影响GUI渲染线程
# script标签的async是什么时候加载的
async模式下,JS异步加载完毕立即执行
# 说一下==数据类型转换吧
- 原始值,等号两边不是相同的数据类型,都会转成Number对比,String => Number Boolean => Number; 对象, 则先valueOf再toString;
- NaN != NaN {} != {} [] != [] Null == Undefined
# diff算法的缺点
只会同级比较
# Object.defineProperty()有什么缺点?Vue3为什么用Proxy?
- 检测数组时会产生性能问题,所以Vue不得不重新数组方法
- 只会在初始化时添加响应式,无法监听后面添加的数据
- Proxy更为方便的监听数据,也不会因为监听数组而造成性能上的影响,相当于在原数据上隔了一层Proxy
# nextTick实现原理
首先进行嗅探环境,判断当前的环境中是否存在这几个API:Promise.then => MutationObserver => setImmediate => setTimeout,按优先级压入渲染watcher
# nextTick中的waiting是什么时候变为true的呢
在下一次DOM更新循环结束之后
# Vue3有哪些新的API或者有做哪些优化?
# 性能上
Diff算法更高效,在模板编译时就确定每个节点的类型,对创建的事件进行缓存,如果没有改变,那么就直接读取缓存
# Tree-shaking
这个词之前只在webpack中听过,意思是将无用的代码删除,Vue3也新增了这个功能
# 有关HTTP缓存的首部字段说一下
- HTTP/1.0
Expires
- HTTP/1.1
Cache-Control
,last-modified/if-modified-since
,ETag/if-match
# HTTP中的keep-alive有了解吗?
HTTP/1.1中默认开启长连接,在首部的Connection字段中设置,防止传输完之后就断开TCP,让TCP可以传递多条数据
# 在一次传输中它是如何保证每个数据包之间的顺序的?
TCP会将每个数据包标记一个编码
# 为什么说GET会留下历史记录?
因为参数都是在URL上显示的,所以会留下历史记录
# GET可以上传图片吗?
GET也行,但是一般不用GET,GET会把诸如input的信息都打印在url上,加上URL的长度受浏览器限制
# GET就一定是幂等的吗?
不一定,如果用GET来做了POST的事情,那么它就不是幂等的
# 为什么说POST相对安全一些
- 参数放在请求体中
- 除FF浏览器外,它会先发送两次请求,只有在第一次请求响应之后才会发送带有请求体的第二次请求
# 如果一个按钮点击进行GET请求会留下历史记录吗?
Form提交才有,但在开发者工具的network中都有记录的
# position属性有哪些值分别介绍一下
# relative
相对于自身在普通文档流中的位置定位
# absolute
绝对定位,相对于第一个父级position值为absolute relative fixed的元素定位
# fixed
固定定位,相对于浏览器窗口,但是父级设置了transform的话,就会失效
# sticky:
粘性定位。任何父级容器设置了overflow:hidden的值就会失效,而且父级高度要大于sticky元素的高度
更详细的可以看这里:https://www.zhangxinxu.com/wordpress/2018/12/css-position-sticky/
# static:默认定位
# 普通文档流是个怎样的层级关系
将窗体自上而下分成一行一行,并在每行中按从左至右的挨次排放元素
# inline-block的使用场景
个人的理解是可以代替float属性,将元素排列在一行,不会对后面的元素造成影响
# GET和POST的区别
引入2个概念,幂等和副作用,每次操作的结果一样就是幂等,不会对服务器资源进行更改就是没有副作用,就讲一下使用上的区别吧:
区别
- GET参数放在URL中且有长度限制,POST放在请求体中,没有限制
- GET用来向服务器请求资源,POST主要用来向服务器发送资源
- GET是幂等且没有副作用,POST相反
- GET只能URL编码,只能接受ASCII字符,POST没有限制
- GET以及参数会留下历史记录,而POST和参数不会
# 说一下你所知道的缓存方案
配合webpack,HTML文件采用协商缓存,JS CSS和图片采用强缓存且文件名都加上hash值,当css文件改变时,JS文件的hash值也会随之改变,所以我们可以使用contenthash
# 项目中的环境变量是如何控制的?
最方便的事情就是在webpack中设置mode
← 1 自检100题 3 Promise面试题 →