如何解决 a 标点击后 hover 事件失效的问题?

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

# 功能符知道吗?

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

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

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

# 如果我将 key 设置为了一个 Math.random()可以吗

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

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

主要实现 Observer 类和 Compiler 类

# 如何实现 if (a===1 && a===2 && a===3)

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

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

  1. 可以被 JS 代码访问,所以得设置 httpOnly
  2. 可能会被中间人攻击劫持,那就得配置 secure 和用 HTTPS,所以会涉及到购买 CA 等一系列问题

大多数情况下不发送第三方的 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?

  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