# 面试综合汇总

# 如何解决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]]
            }
        }
    } 

# 数组去重

  1. 双重for循环
  2. Set数据结构
  3. indexOf
  4. sort排序后再用for循环判断当前值是否等于上一个值和下一个值 更多的方法就在这里:JavaScript数组去重(12种方法) (opens new window) (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

  1. 面向字节流、面向链接的可靠的传输层协议
  2. 传输数据前会进行三次握手,建立可靠的连接
  3. 校验数据的完整性
  4. 一对一

# UDP

  1. 面向报文的不可靠的传输层协议
  2. 传输数据前不会进行握手,会直接发送数据
  3. 不会校验数据的完整性
  4. 一对多

# 场景

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到页面的呈现


  1. 在浏览器地址栏内按下第一个键后浏览器会调用自己的算法,去书签栏或者历史记录中将我们可能访问的URL显示出来
  2. 点击要访问的URL后,浏览器会先检测URL是否合法,如果没问题会调用网络线程来准备发送网络请求
  3. 先在HTTP应用层内构建请求行,但不会发送网络请求,会先在强缓存中查找强缓存是否有效
  4. 强缓存无效的话,就会调用DNS域名解析将URL解析成IP地址
  5. 此时进入TCP传输层,进行TCP三次握手,握手完毕后将请求报文分割并打上标记生成数据包,将处理后的数据包转发给网络层
  6. 网络层拿到数据包后,调用ARP协议,通过IP地址反查出MAC地址
  7. 拿到IP地址、MAC地址、数据包后,在数据链路层内发起请求
  8. 服务端收到请求后,一层层的将报文剥开,其中就会把在传输层分割的报文组装起来,接着对请求会进行校验,比如是否有缓存字段、请求是否有权限。如果缓存有效,那么就会返回304状态码提醒浏览器使用缓存,这里其实就是协商缓存的步骤
  9. 如果缓存过期或者没设置,那么服务端就会返回请求的文件,如HTML、CSS和JS文件,浏览器接收到文件后,服务器会检测报文中的Connection的值是否等于keep-alive,如果不是keep-alive就会断开链接,但在HTTP 1.1协议后,Connection默认为keep-alive
  10. 浏览器接收到HTML文件后就会进行处理,这个过程是交给渲染引擎的GUI线程来做,根据HTML文件中定义的charset和doctype来解析文档,GUI线程调用标记化算法和建树算法,实际上就是词法分析和语法分析,生成以document为根节点的DOM树
  11. CSS的解析也是同理,只不过会先将CSS文件格式化成styleSheet对象,然后标准化这个对象,比如color: red这个属性会被格式化成16进制的数,最后将计算的结果挂载到window.getStyleComputed上,我们可以通过JS代码访问,但会引起回流
  12. CSS解析和HTML解析互不干扰,但JS文件就会造成阻塞,因为渲染引擎中的JS线程和GUI线程是互斥的,且JS引擎的优先级比GUI线程高,会将GUI线程挂起,所以script会阻塞页面解析,要放在底部,而link CSS在头部
  13. 在拿到CSSOM树和DOM树后,会将二者合成为布局树,精确的计算出每一个节点所处的位置以及样式
  14. 浏览器在渲染前会进行图层处理,图层分为普通图层和复杂图层,而普通文档流内所有的元素所处的就是一个复杂图层,每个复杂图层都会被GPU单独绘制,所以它们之间的重绘不会影响其他图层,提成为复杂图层的方式有:
  • 拥有层叠上下文的特点,如scroll
  • 设置z-index

但要注意设置z-index的元素如果本身层叠上下文的等级就比较低,会引起层爆炸,在它上面的图层都会被提升成复杂图层,页面可能会崩溃

  1. 将绘制指令传入渲染队列中,通过合成线程生成图块和位图,开始渲染页面,所以常说要尽量使用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模块的区别


  1. CommonJS是运行时加载,因为它导出的是一个对象,对象只有在运行的时候才会创建,ES6在编写的时候就已经确定了模块间的依赖关系
  2. CommonJS导出的是值拷贝,导出后CommonJS内部的值改变不会影响外部,除非重新加载,而ES6模块导出的是值引用,内部的值改变会影响
  3. CommonJS使用require导入,ES6通过[import from]的形式
  4. CommonJS的this指向当前的模块,ES6的this指向undefined,因为它使用的是严格模式

# 模块的异步加载


可以使用AMD和CMD,但我没有了解过,可以放入到Promise中,也可以使用script的defer/async属性实现异步加载

# 实现一个一组异步请求按顺序执行你有哪些方法?


  1. Generator函数
  2. reduce不断的叠加then

# ##是并发的还是串行的?

并发的,但必须等数组中所有的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地址

# 说一下回流和重绘


# 回流

元素的几何属性发生改变

  1. 修改DOM结构,节点的增删改查
  2. 调用[scroll, client, getStyleComputed]等方法

# 重绘

重绘是指元素几何属性没有改变,而外观发生变化

# 它们之间的关系

回流必定引起重绘,但重绘不一定引起回流

# 如何避免回流和重绘

  1. 创建文档碎片:document.createFragment()
  2. 对于复杂的样式,直接以class来修改
  3. 尽量别用上面数组中说到的方法
  4. 动画写在position值为absolute/fixed的元素上,也就是脱离了普通文档留的元素
  5. 尽量使用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 />

如果以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)

# ##功能符知道吗?

让一段动画不连续,具体自行百度

# 用过哪些移动端的调试工具?


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代表服务端):

  1. 首先C向S发送了一套自己支持的加密套件、client_random以及协议版本号给S
  2. S拿到这些数据后,会验证协议版本号,然后返回server_random、具体的加密套件、server_params以及证书
  3. C拿到这些数据后,会对证书进行验证,验证通过会发送client_params给S
  4. 接着C调用[ECDHE算法],传入client_params和server_params,计算出一个pre_random值,接着调用[伪随机数算法]传入pre_random、client_random、server_random计算出一个secret值
  5. 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中使用的是双端对比法,从节点的两端开始对比,当指针相遇表示对比完成,根据不同的情况进行处理:

  1. 新节点有,旧节点没有 => 添加
  2. 和上面相反 => 删除
  3. 都没有 => 跳过
  4. 都有 => 检测是否还有子节点,然后继续进行对比

处理完毕后,调用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插件吗


  1. 新建一个构造函数,然后在其原型上重写apply方法
  2. 给apply方法传入一个compiler对象(webpack实例)
  3. 给compiler对象注册对应的hooks事件
  4. 如果想访问每次编译后的文件,可使用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构建流程


  1. 通过读取并合并传入的options和shell语句的配置,得到一个最终的参数
  2. 通过这最终的参数实例化一个compiler(webpack实例),为webpack事件流挂载自定义hooks
  3. 如果当前是watch模式,则调用compiler.watch方法来执行构建,否则调用compiler.run
  4. 通过入口文件,实例化Compilation对象回调其compilation钩子,递归调用loader从右向左翻译文件(webpack是函数式编程,所以是从右向左,并不是因为技术难点)
  5. 通过Compilation对象我们可以访问到当前模块的资源、生成的文件资源以及修改的文件。将编译好的文件生成AST,然后递归这个过程,直到所有的模块都得到处理,最后调用compilation.seal对chunk进行整理、优化和封装,得到最后的内容
  6. 最后的内容我们可以通过Compilation.assets来访问,至于plugin的处理会根据自身对应的钩子出现在webpack的各个生命周期内

# webpack插件原理


根据Tapable的钩子事件,贯穿整个webpack的生命周期,在对应的生命周期中执行

# webpack在配置插件的时候是一个数组那它有顺序吗


没有,是根据plugin定义的触发时的生命周期来决定的

# 让你从零开始构建一个webpack项目你可以吗


完全可以。先loader后plugin

# 为什么TCP要三次握手而不是两次


  1. 第一次握手客户端发送SYN(同步序列编码)
  2. 服务端收到SYN后对其进行处理,然后将处理后的结果放入ACK中,最后将ACK和新的SYN发送给客户端
  3. 客户端收到后发送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

# ##可以吗

不行,和用index为key结果一样,无法复用节点

# 如果让你设计一个双向绑定你会如何设计


主要实现Observer类和Compiler类

#


要么就劫持它,每次访问时都加1

# token放在Cookie和放在localStorage、sessionStorage中有什么不同吗


token放在web存储中更容易遭到攻击,因为web存储可以被JS代码访问,如果碰到XSS攻击那就不好了,而cookie可以设置httpOnly

# Cookie存在哪些安全问题?如何预防?


  1. 可以被JS代码访问,所以得设置httpOnly
  2. 可能会被中间人攻击劫持,那就得配置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算法的缺点


只会同级比较

# ##有什么缺点?Vue3为什么用Proxy?

  1. 检测数组时会产生性能问题,所以Vue不得不重新数组方法
  2. 只会在初始化时添加响应式,无法监听后面添加的数据
  3. 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-Controllast-modified/if-modified-sinceETag/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相对安全一些


  1. 参数放在请求体中
  2. 除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个概念,幂等和副作用,每次操作的结果一样就是幂等,不会对服务器资源进行更改就是没有副作用,就讲一下使用上的区别吧:

区别

  1. GET参数放在URL中且有长度限制,POST放在请求体中,没有限制
  2. GET用来向服务器请求资源,POST主要用来向服务器发送资源
  3. GET是幂等且没有副作用,POST相反
  4. GET只能URL编码,只能接受ASCII字符,POST没有限制
  5. GET以及参数会留下历史记录,而POST和参数不会

# 说一下你所知道的缓存方案


配合webpack,HTML文件采用协商缓存,JS CSS和图片采用强缓存且文件名都加上hash值,当css文件改变时,JS文件的hash值也会随之改变,所以我们可以使用contenthash

# 项目中的环境变量是如何控制的?


最方便的事情就是在webpack中设置mode