前端面试知识体系(一)

5,050 阅读14分钟

我的博客来源:1024bibi.com/2018/01/01/…

防抖和节流有什么区别,分别用于什么场景

  • 节流:限制执行频率,有节奏的执行;
  • 防抖:限制执行次数,多次密集的触发只执行一次;

防抖 debounce

function debounce(fn, delay = 200) {
	let timer = 0
	
	return function () {
		if (timer) clearTimeout(timer);
		
		timer = setTimeout(() => {
			fn.apply(this, arguments); // 透传 this 和参数
			timer = 0
		}, delay)
	}
}
const input = document.getElementById('input')
input.addEventListener('keyup', debounce(() => {
	console.log('发起搜索', input.value)
}), 300)

节流 throttle

例如:drag或scroll期间触发某个回调,要设置一个时间间隔

px % em rem vw/vh 有什么区别

px 和 %

  • px基本单位,绝对单位(固定的),其他的都是相对单位
  • % 相对于父元素的宽度比例
  • 元素内的垂直居中
<style>
 #container {
  width: 200px;
  height: 200px;
  position: relative;
  background-color: #ccc;
 }
 #box {
  width: 100px;
  height: 100px;
  position: absolute;
  left: 50%;
  top: 50%;
  margin-top: -50px;
  margin-left: -50px;
  background-color: blue;
 }
</style>
<div id="container">
 <div id="box"></div>
</div>

em 和 rem

  • em 相对于当前元素的 font-size
  • rem 相对于根节点的 font-size
<div style="font-size: 20px;">
 <p style="text-indent: 2em; font-size: 40px;">首行缩进</p> // font-size: 40px; text-indent: 80px;
 <p style="text-indent: 2em;">哪吒, 算法猫叔</p> // font-size: 20px; text-indent: 40px;
</div>
<style>
 @media only screen and (max-width: 374px) {
  // iphone5 或者更小的尺寸,以 iphone5 的宽度(320px)比例设置 font-size
  html {
   font-size: 86px;
  }
 }
 @media only screen and (min-width: 375px) and (max-width: 413px) {
  // iphone6/7/8 和 iphone x
  html {
   font-size: 100px;
  }
 }
 @media only screen and (min-width: 414px) {
  // iphone6p 或者更大的尺寸,以 iphone6p 的宽度(414px)比例设置 font-size
  html {
   font-size: 110px;
  }
 }
 p {
  font-size: .16rem;
 }
</style>

vw / vh

  • vw 屏幕宽度的 1%
  • vh 屏幕高度的 1%
  • vmin 两者的最小值,vmax 两者的最大值
<div id="div1"> div1 </div>
<div id="div2"> div2 </div>
<div id="div3"> div3 </div>
<style>
 div {
  border: 1px solid #ccc;
  margin-top: 20px;
 }
 #div1 {
  width: 10vw;
  height: 10vh;
 }
 #div2 {
  width: 10vmax;
  height: 10vmax;
 }
 #div3 {
  width: 10vmin;
  height: 10vmin;
 }
</style>

什么时候不能使用箭头函数

箭头函数有什么缺点?

  • 没有 arguments
  • 无法通过 apply call bind 改变 this
  • 某些箭头函数代码难以阅读

什么时候不能使用箭头函数?

  • 不适用-对象方法
const obj = {
 name: '哪吒,B站,算法猫叔',
 getName: () => {
  return this.name
 }
}
console.log(obj.getName())
  • 不适用-原型方法
const obj = {
 name: '哪吒,B站,算法猫叔'
}
obj.__proto__.getName = () => {
 return this.name
}
console.log( obj.getName() )
  • 不适用-构造函数
const Foo = (name, age) => {
 this.name = name
 this.age = age
}
const f = new Foo('张三', 20)
// 报错 Foo is not a constructor
  • 不适用-动态上下文中的回调函数
const btn1 = document.getElementById('btn1')
btn1.addEventListener('click', () => {
 // console.log(this === window)
 this.innerHTMl = 'clicked'
})
  • 不适用-Vue生命周期和method : vue组件本质上一个 JS 对象
  • React 组件(非Hooks)本质上是一个 es6 class,class里面适用箭头函数没问题
{
 data() { return { name:  '哪吒,B站:算法猫叔' } },
 methods: {
  getName: () => {
   // 报错 Cannot read properties of undefined (reading 'name')
  return this.name
  },
  // getName() {
  //  return this.name // 正常
  // }
 },
 mounted: () => {
  // 报错
 },
 // mounted() {
 //  正常
 // }
}

JS中for-in和for-of有什么区别

for of 去遍历可以generator

const arr = [10, 20, 30]
for (let val of arr) {
 console.log(val); // 值
}

const str = 'abc'
for (let c of str) {
 console.log(c);
}

function fn() {
 for (let arg of arguments) {
  console.log(arg)
 }
}
fn(100, 200, 'aaa')

const pList  = document.getElementsByTagName('p')
// querySelectorAll('p')
for (let p of pList) {
 console.log(p)
}
  • 遍历对象: for ... in 可以,for ... of 不可以
  • 遍历Map Set:for...of 可以,for...in 不可以
  • 遍历generator:for...of 可以,for ... in 不可以
对象,数组,字符串可枚举的,就可以使用for ... in 循环
const obj1 = { x: 100 }
Object.getOwnPropertyDescriptors(obj1)
x:
configurable: true
enumerable: true
value: 100
writeable: true
  • 可枚举 vs 可迭代

for ... in 用于可枚举数据,如对象,数组,字符串,得到key

for ... of 用于可迭代数据,如数组,字符串,Map,Set,得到value

for-await-of有什么作用

for await...of 用于遍历多个Promise

function createPromise(val) {
 return new Promise((resolve) => {
  setTimeout(() => {
   resolve(val)
  }, 1000)
 })
}

(async function () {
 const p1 = createPromise(100)
 const p2 = createPromise(200)
 const p3 = createPromise(300)
 // const res1 = await p1
 // console.log(res1)
 // const res2 = await p2
 // console.log(res2)
 // const res3 = await p3
 // console.log(res3)
 const list = [p1, p2, p3]
 // Promise.all(list).then(res => console.log(res))

 for await (let res of list) {
  console.log(res)
 }
})()

offsetHeight - scrollHeight - clientHeight区别

盒子模型: width, height, padding, border, margin, box-sizing

offsetHeight offsetWidth : border + padding + content

clientHeight clientWidth: padding + content

scrollHeight scrollWidth: padding + 实际内容尺寸

HTMLCollection和NodeList有什么区

  • DOM是一颗树,所有节点都是Node
  • Node是Element的基类
  • Element是其他HTML元素的基类,如HTMLDivElement
const p1 = document.getElementById('p1')
class Node {}

// document
class Document extends Node {}
class DocumentFragment extends Node {}

// 文本和注释
class CharacterData extends Node {}
class Comment extends CharacterData {}
class Text extends CharacterData {}

// elem
class Element extends Node {}
class HTMLElement extends Element {}
class HTMLDivElement extends HTMLElement {}
class HTMLInputElement extends HTMLElement {}
  • HTMLCollection 是 Element 的集合
  • NodeList 是 Node 集合
<p id="p1">
 <b>node</b> vs <em>element</em>
</p>

<script>
 const p1 = document.getElementById('p1')
 console.log(p1.children)
 
 console.log(p1.childNodes)
 // [b,text,em,comment]
</script>

划重点:

  • 获取 Node 和 Element 的返回结果可能不一样
  • 如 elem.childNodes 和 elem.children 不一样
  • 前者包含Text和Comment节点,后者不会

类数组 变成 数组

const arr1 = Array.from(list)
const arr2 = Array.prototype.slice.call(list)
const arr3 = [...list]

Vue中computed和watch有什么区别

  • computed 用于计算产生新的数据
  • watch 用于监听现有数据
watch: {
 name(newValue, oldValue) {
  console.log('watch name', newValue, oldValue)
 }
},
computed: {
 userInfo() {
  return this.name + this.city
 }
}

computed有缓存 watch没有缓存

Vue组件通讯有几种方式

props和$emit

$parent

自定义事件

$refs

$attr

provide/inject

vuex

---

$attrs $listeners

vue3 移除 $listeners

上一级没有接收到的

props: ['x'], // $attrs

emits: ['getX'], // $listeners

Object.keys(this.$attrs)

<l3 v-bind="$attrs"></l3>

dom结点: inheritAttrs: false

---

this.$parent
this.$refs

provide: {
 info: 'aaa'
}

provide() {
 return {
  info: computed(() => this.name)
 }
}

---

父子组件

上下级组件(跨多级)通讯

全局组件

Vuex中action和mutation有什么区别

mutation: 原子操作,必须同步代码

action: 可包含多个mutation;可包含异步代码

JS严格模式有什么特点

'use strict' // 全局开启

function fn() {
 'use strict' // 某个函数开启
}
  • 全局变量必须先声明
  • 禁止使用 with
  • 创建eval作用域
  • 禁止this指向window
  • 函数参数不能重名

JS内存垃圾回收用什么算法

垃圾回收 GC

什么是垃圾回收?

function fn1() {
 const a = 'aa'
 console.log(a)
 
 const obj = { x: 100 }
 console.log(obj)
}
fn1()
function fn2() {
 const obj = { x: 100 }
 window.obj = obj
}
fn2()


function getDataFns() {
 const data = {} // 闭包
 return {
  get(key) {
   return data[key]
  },
  set(key, value) {
   data[key] = value
  }
 }
}
const { get, set } = getDataFns()
set('x', 100)
get('x')
引用计数(之前)

// 对象被 a 引用
let a = { x: 100 }
let a1 = a
a = 10
a1 = null

// 循环引用
function fn3() {
 const obj1 = {}
 const obj2 = {}
 obj1.a = obj2
 obj2.a = obj1
}
fn3()

// ie6-7 内存泄漏的 bug
var div1 = document.getElementById('div1')
div1.a = div1
div1.someBigData = {}

标记清除(现代)
// JS 根 window

JS闭包是内存泄漏吗

闭包的数据是不可以被垃圾回收的

如何检测JS内存泄漏

检测内存变化

const arr = []
for (let i = 0; i < 10 * 10000; i++) {
 arr.push(i)
}

function bind() {
 // 模拟一个比较大的数据
 const obj = {
  str: JSON.stringify(arr) // 简单的拷贝
 }
 window.addEventListener('resize', () => {
  console.log(obj)
 })
}

let n = 0
function start() {
 setTimeout(() => {
  bind()
  n++
  
  // 执行 50 次
  if (n < 50) {
   start()
  } else {
   alert('done')
  }
 }, 200)
}

document.getElementById('btn1').addEventListener('click', () => {
 start()
})

JS内存泄漏的场景有哪些

  1. 被全局变量,函数引用,组件销毁时未清除
  2. 被全局事件,定时器引用,组件销毁时未清除
  3. 被自定义事件引用,组件销毁时未清除

JS内存泄漏的场景有哪些-扩展-WeakMap和Weak

// 标记清除算法
const data = {}
function fn1() {
 const obj = { x: 100 }
 data.obj = obj
}
fn1()
const map = new Map()
function fn1() {
 const obj = { x: 100 }
 map.set('a', obj)
}
fn1()
// WeakMap WeakSet 弱引用
<script>
 const wMap = new WeakMap(); // 弱引用
 function fn1() {
  const obj = { x: 100 }
  wMap.set(obj, 100) // WeakMap的key,只能是引用类型
 }
 fn1()
 // WeakSet
</script>

Ajax-Fetch-Axios三者有什么区别

  1. Ajax(Asynchronous Javascript and XML),一种技术统称
  2. Fetch,一个具体的原生API 浏览器原生API,用于网络请求 和XMLHttpRequest一个级别 Fetch语法更加简洁,易用,支持Promise
  3. Axios,是一个第三方库 最常用的网络请求lib 内部可用XMLHttpRequest和Fetch来实现
  4. lib(库)和API(原生的函数)的区别
  5. fetch 和 XMLHttpRequest 全局的API

用XMLHttpRequest实现Ajax

function ajax1(url, sucessFn) {
 const xhr = new XMLHttpRequest();
 xhr.open("GET", url, false);
 xhr.onreadystatechange = function () {
  // 这里的函数异步执行
  if (xhr.readyState == 4) {
   if (xhr.status == 200) {
    successFn(xhr.responseText);
   }
  }
 }
 xhr.send(null);
}

function ajax2(url) {
 return fetch(url).then(res => res.json());
}

请描述TPC三次握手和四次挥手

建立TCP连接

  1. 先建立连接(确保双方都有收发消息的能力)
  2. 再传输内容(如发送给一个get请求)
  3. 网络连接是TCP协议,传输内容是HTTP协议
  4. SYN SYN+ACK ACK

四次挥手-关闭连接

1. FIN ->
2. ACK <-
3. FIN <-
4. ACK ->

HTTP跨域时为何要发送options请求

跨域请求

  1. 浏览器同源策略
  2. 同源策略一般限制Ajax网络请求,不能跨域请求server
  3. 不会限制<link> <img> <script> <iframe>加载第三方资源

JSONP

// www.aaa.com网页
<script>
 window.onSuccess = function(data) {
  console.log(data)
 }
</script>
<script src="https://www.bbb.com/api/getData"></script>

// https://www.bbb.com... 返回了一段字符串
'onSuccess({ errno: 0, data: {} })'
// CORS 配置允许跨域(服务端)
response.setHeader("Access-Control-Allow-Origin", "http://localhost:8011") // 或者"*"
response.setHeader("Access-Control-Allow-Headers", "X-Requested-With")
response.setHeader("Access-Control-Allow-Methods", "PUT,POST,GET,DELETE,OPTIONS")
response.setHeader("Access-Control-Allow-Credentials", "true") // 允许跨域接收 cookie

options请求,是跨域请求之前的预检查;浏览器自行发起的,无需我们干预,不会影响实际的功能

浏览器和nodejs事件循环(EventLoop)有什么

单线程和异步

  1. JS是单线程的(无论在浏览器还是nodejs)
  2. 浏览器中JS执行和DOM渲染共用一个线程
  3. 异步 宏任务 和 微任务 宏任务,如 setTimeout setInterval 网络请求 微任务,如 promise async / await 微任务在下一轮DOM渲染之前执行,宏任务在之后执行 微任务: MutationObserver 监听DOM树的变化,Mutation observer 是用于代替 Mutation events 作为观察DOM树结构发生变化时,做出相应处理的API MutationObserver接口提供了监视对DOM树所做更改的能力。它被设计为旧的Mutation Events功能的替代品,该功能是DOM3 Events规范的一部分。
const p = document.createElement('p')
p.innerHTML = 'new paragraph'
document.body.appendChild(p)

const list = document.getElementsByTagName('p')
console.log('length---', listh.length)

console.log('start')
// 渲染之后
setTimeout(() => {
 const list = document.getElementsByTagName('p')
 console.log('length on timeout---', list.length) // 2
 alert('阻塞 timeout')
})
// 渲染之前
Promise.resolve().then(() => {
 const list = document.getElementsByTagName('p')
 console.log('length on promise.then---', list.length) // 2
 alert('阻塞 promise')
})
console.log('end')
// 同步任务 -> 异步任务 -> 宏任务
// 微任务要比宏任务要快
// Event Loop

<script>
 console.log('start')
 setTimeout(() => {
  console.log('timeout')
 })
 Promise.resolve().then(() => {
  console.log('promise then')
 ))
 console.log('end')

 // ajax(url, fn) // 300ms
 
 // Event Loop 继续监听
 // 宏任务 MarcoTask Queue
 // () => {
 //   console.log('timeout')
 // }
 // fn
 
 // DOM 渲染
 // 微任务 MicroTask Queue
 // () => {
 //   console.log('promise then')
 // }
</script>

Nodejs异步

  1. Nodejs同样使用ES语法,也是单线程,也需要异步
  2. 异步任务也分:宏任务+微任务
  3. 但是,它的宏任务和微任务,分不同类型,有不同优先级

虚拟DOM(vdom)真的很快吗

  1. vdom: Virtual DOM,虚拟DOM 用JS对象模拟DOM节点数据
  2. 组件化 数据视图分离,数据驱动视图 只关注业务数据,而不用再关心DOM变化
  3. vdom并不快,js直接操作dom才是最快的 但”数据驱动视图“要有合适的技术方案,不能全部dom重建 vdom就是目前最合适的技术方案(并不是因为它快,而是合适)

遍历一个数组用for和forEach哪个更快

  • for更快
  • forEach每次都要创建一个函数来调用,而for不会创建函数
  • 函数需要独立的作用域,会有额外的开销

nodejs如何开启多进程,进程如何通讯-进程和线程的

进程 process vs 线程 thread 进程,OS 进行资源分配和调度的最小单位,有独立内存空间 线程,OS 进行运算调度的最小单位,共享进程内存空间 JS是单线程的,但可以开启多进程执行,如WebWorker js 不可以开启一个线程

为何需要多进程?

  1. 多核CPU,更适合处理多进程
  2. 内存较大,多个进程才能更好的利用(单进程有内存上限)
  3. 总之,“压榨”机器资源,更快,更节省 单个进程内存2G左右
nodejs如何开启多进程

// console.info(process.pid)
const http = require('http')

const server = http.createServer()
server.listen(3000, () => {
 console.log('localhost: 3000')
})

console.info(process.pid)

// WebWorker 进程
// fork
const http = require('http')
const server = http.createServer((req, res) => {
 if (req.url === '/get-sum') {
   console.info('主进程 id', process.id)
   
   res.end('hello')
 }
})
server.listen(3000, () => {
 console.info('localhost: 3000')
})

// cluster 进程
// 子进程,计算
function getSum() {
 let sum = 0
 for (let i = 0; i < 10000; i++) {
  sum += i
 }
 return sum
}

process.on('message', data => {
 console.log('子进程 id', process.pid)
 console.log(‘子进程接受到的信息:', data)

 const sum = getSum()
 // 发送消息给主进程
 process.send(sum)
})
const http = require('http')
const fork = require('child_process').fork

const server = http.createServer((req, res) => {
 if (req.url === '/get-sum') {
  console.info('主进程 id', process.pid)

   // 开启子进程
  const  computeProcess = fork('./compute.js')
  computeProcess.send('开始计算')

  computeProcess.on('message', data => {
    console.info('主进程接受到的信息:', data)
    res.end('sum is' + data)
  })

  computeProcess.on('close', () => {
    console.info('子进程因报错而退出')
    computeProcess.kill()
    res.end('error')
  })
 }
})

server.listen(3000, () => {
 console.info('localhost: 3000')
})
const http = require('http')
const cpuCoreLength = require('os').cpus().length
const cluster = require('cluster')

if (cluster.isMaster) {
 for (let i = 0; i < cpuCoreLength; i++) {
  cluster.fork() // 开启子进程
 }
 cluster.on('exit', worker => {
  console.log('子进程退出')
  cluster.fork() // 进程守护
 })
} else {
 // 多个子进程会共享一个 TCP 连接,提供一份网络服务
  const server = http.createServer((req, res) => {
  res.writeHead(200)
  res.end('done')
 })
 server.listen(3000)
}

开启子进程 child_process.fork 和 cluster.fork 使用 send 和 on 传递消息

请描述js-bridge的实现原理

  1. JS无法直接调用 native API
  2. 需要通过一些特定的“格式”来调用
  3. JS Bridge的常见实现方式
    1. 注册全局API
    2. URL Scheme
// 封装 JS-bridge
const sdk = {
 invoke(url, data = {}, onSuccess, onError) {
   const iframe = document.createElement('iframe')
   iframe.style.visibility = 'hidden'
   document.body.appendChild(iframe)
   
   iframe.onload = () => {
    const content = iframe1.contentWindow.document.body.innerHTML
   }
 }
}

requestIdleCallback和request

由React fiber引起的关注

  1. 组建树转换为链表,可分段渲染
  2. 渲染时可以暂停,去执行其他高优任务,空闲时再继续渲染
  3. 如何判断空闲? - requestIdleCallback

区别

  1. requestAnimationFrame 每次渲染完都会执行,高优
  2. requestIdleCallback 空闲时才执行,低优
<p>requestAnimationFrame</p>
<button id="btn1">change</button>
<div id="box"></div>

<script>
const box = document.getElementById('box')
document.getElementById('btn1').addEventListener('click', () => {
 let curWidth = 100
 const maxWidth = 400
 function addWidth() {
  curWidth = curWidth + 3
  box.style.width = `${curWidth}px`
  if (curWidth < maxWidth) {
   window.requestIdleCallback(addWidth) // 时间不用自己控制
  }
 }
})
</script>
start
end
timeout
requestAnimationFrame
requestIdleCallback

window.onload = () => {

 console.info('start')
 setTimeout(() => {
  console.info('timeout')
 })
 // 宏任务,顺序交换也一样
 // 高优
 window.requestAnimationFrame(() => {
  console.info('requestAnimationFrame')
 })
 // 低优
 window.requestIdleCallback(() => {
  console.info('requestIdleCallback')
 })

 console.info('end')
}

Vue每个生命周期都做了什么

  • beforeCreate 创建一个空白的Vue实例 data method 尚未被初始化,不可使用

  • created vue实例初始化完成,完成响应式绑定 data method都已经初始化完成,可调用 尚未开始渲染模板

  • beforeMount 编译模版,调用render生成vdom 还没有开始渲染DOM

    $el null element没有

  • mounted 完成DOM渲染 组件创建完成 开始由“创建阶段”进入“运行阶段”

  • beforeUpdate data发生变化之后 准备更新DOM(尚未更新DOM)

  • updated data发生变化,且DOM更新完成 (不要在updated中修改data,可能会导致死循环)

  • beforeUnmount 组件进入销毁阶段(尚未销毁,可正常使用) 可移除,解绑一些全局事件,自定义事件

  • unmounted 组件被销毁了 所有子组件也都被销毁了

  • keep-alive组件 onActivated 缓存组件被激活 onDeactivated 缓存组件被隐藏

<keep-alive>
 <Child1 v-if="num === 1"></Child1>
 <Child2 v-else></Child2>
</keep-alive>

// Child1 2
created() {
 console.log() // keep-alive 中只创建
}
activated() {}
deactivated() {}

// 创建一次被缓存
child1 created
child1 activated
child2 created
child1 deactivated
child2 activated
child2 deactivated
child1 activated
  • vue什么时候操作DOM比较合适 mounted和updated都不能保证子组件全部挂载完成 使用$nextTick渲染DOM

    只有nextTick操作DOM才是最安全的

$nextTick
mounted() {
 this.$nextTick(function () {
  // 仅在整个视图都被渲染之后才会运行的代码
 })
}
  • ajax 应该在那个生命周期? 有两个选择:created 和 mounted 推荐:mounted
  • vue3 Composition API 生命周期有何区别? 用setup代替了beforeCreate和created 使用Hooks函数的形式,如mounted改为onMounted()
import { onUpdated, onMounted } from 'vue'
export default {
 setup() {
  onMounted(() => {
 
  })
  onUpdated(() => {
  
  })
 }
}

Vue2和Vue3和React三者的diff算法有什么

介绍diff算法 diff算法很早就有

tree diff优化 只比较同一层级,不跨级比较 tag 不同则删掉重建(不再去比较内部的细节) 子节点通过key区分(key的重要性)

vue3最长递增子序列 vue2 双端比较 React 仅右移

Vue-router的MemoryHistory是什么

Hash, WebHistory, MemoryHistory( v4 之前叫做 abstract history)

移动端H5点击有300ms延迟,该如何解决

FastClick原理 监听touchend事件(touchstart touchend会先于click触发) 使用自定义DOM事件模拟一个click事件 把默认的click事件(300ms之后触发)禁止掉

现代浏览器的改进

<head>
 <meta charset="UTF-8">
 <meta name="viewport" content="width=device-width, initial-scale=1.0">
 <meta http-equiv="X-UA-Compatible" content="ie=edge">
</head>

HTTP请求中token和cookie有什么区别-cookie

  1. cookie HTTP无状态,每次请求都要带cookie,以帮助识别身份 服务端也可以向客户端set-cookie,cookie大小限制4kb 默认有跨域限制:不可跨域共享、传递cookie
  2. withCredentials 前端设置跨域cookie共享 cookie本地存储 HTML5之前cookie常被用于本地存储 HTML5之后推荐使用localStorage和sessionStorage
  3. 现在浏览器开始禁止第三方cookie 和跨域限制不同。这里是:禁止网页引入的第三方JS设置cookie 打击第三方广告,保护用户隐私 新增属性 SameSite: Strict/Lax/None;值可自己选择
  4. 浏览器的Cookie新增加了一个SameSite属性,用来防止CSRF攻击和用户追踪
  5. cookie和session cookie用于登录验证,存储用户标识 session在服务端,存储用户详细信息,和cookie信息一一对应 cookie和session是常见登录验证解决方案

HTTP请求中token和cookie有什么区别-token

  1. token vs cookie cookie是HTTP规范,而token是自定义传递 cookie会默认被浏览器存储,而token需自己存储 token默认没有跨域限制
  2. jwt (json web token) 可以取代 cookie和session 前端发起登录,后端验证成功之后,返回一个加密的token 前端自行存储这个token(其中包含了用户信息,加密了) 以后访问服务端接口,都带着这个token,作为用户信息
  3. cookie:HTTP标准;跨域限制;配合session使用
  4. token:无标准;无跨域限制;用于JWT

session和JWT哪个更好

  1. session缺点 占用服务端内存,硬件成本高 多进程,多服务器时,不好同步,需使用第三方缓存,如redis 默认有跨域限制
  2. session优点 原理简单,易于学习 用户信息存储在服务端,可快速禁某个用户
  3. jwt 优点 不占用服务端内存 多进程,多服务器 不受影响 没有跨域限制
  4. jwt 缺点 用户信息存储在客户端,无法快速封禁某用户 万一服务端秘钥被泄漏,则用户信息全部丢失 token体积一般大于cookie,会增加请求的数据量

如有严格管理用户信息的需求(保密,快速封禁)推荐session 如没有特殊要求,则使用jwt

如何实现SSO单点登录

  1. 基于cookie cookie默认不可跨域共享,但有些情况下可设置为共享 主域名相同,如www.baidu.com image.baidu.com 设置cookie domain为主域名,即可共享cookie
  2. sso 主域名完全不同,则cookie无法共享 可使用sso技术方案

HTTP协议和UDP协议有什么区别

网络协议 HTT P协议在应用层 TCP UDP 协议再传输层 严格来说,应该拿TCP和UDP进行比较

OSI的体系结构

7. 应用层
6. 表示层
5. 会话层
4. 运输层
3. 网络层
2. 数据链路层
1. 物理层

TCP/IP的体系结构

1. 应用层(各种应用层协议,如DNSHTTPSMTP等)
2. 运输层(TCPUDP3. 网际层(IP4. 网络接口层

TCP协议

  1. 有连接(三次握手)
  2. 有断开(四次挥手)
  3. 稳定传输

UDP协议

  1. 无连接,无断开
  2. 不稳定传输,但效率高
  3. 如视频会议,语音通话

HTTP是应用层,TCP UDP是传输层 TCP有连接,有断开,稳定传输 UDP无连接,无断开,不稳定传输,但效率高

HTTP协议1.0和1.1和2.0有什么区别

  1. HTTP1.0 弃用 最基础的HTTP协议 支持基本的GET,POST的方法
  2. HTTP1.1 缓存策略 cache-control E-tag等 支持长连接Connection: keep-alive,一次TCP连接多次请求 断点续传,状态码 206 支持新的方法PUT DELETE等,可用于Restful API
  3. HTTP2.0 可压缩header,减少体积 多路复用,一次TCP连接中可以多个HTTP并行请求 服务端推送

什么是HTTPS中间人攻击,如何预防

HTTP S 加密传输 HTTP明文传输 HTTP S 加密传输 HTTP+TLS/SSL

script标签的defer和async有什么区别

<script src="xxx.js" async 或 defer></script>

无:HTML暂停解析,下载JS,执行JS,再继续解析HTMl defer:HTML继续解析,并行下载JS,HTML解析完再执行JS async: HTML继续解析,并行下载JS,执行JS,再解析HTM L

prefetch和dns-prefetch分别

preload和prefetch preload资源在当前页面使用,会优先加载 prefetch资源在未来页面使用,空闲时加载

<head>

<link rel="preload" href="style.css" as="style">
<link rel="preload" href="main.js" as="script">

<link rel="prefetch" href="other.js" as="script">

<link rel="stylesheet" href="style.css">

</head>
<body>
<script src="main.js" defer></script>
</body>

dns-prefetch 和 preconnect dns-prefetch即DNS预查询 preconnect即DNS预连接

多个域名时,当前已经解析完,预查询,预连接

<link rel="dns-prefetch" href="域名">
<link rel="dns-preconnect" href="" crossorigin></link>

prefetch 是资源预获取(和preload相关) dns-prefetch 是DNS预查询(和preconnect相关)

前端攻击手段有哪些,该如何预防

  1. xss Cross Site Script 跨站脚本攻击 手段:黑客将JS代码插入到网页内容中,渲染时执行JS代码 预防:特殊字符替换(前端或者后端)

const newStr = str.replaceAll('<', '&lt;').replaceAll('>', '&gt;')

  1. Vue React 默认屏蔽xss攻击 除了用 vue v-html react dangerouslySetInnerHTML
  2. CSRF Cross Site Request Forgery 跨站请求伪造 手段:黑客诱导用户去访问另一个网站的接口,伪造请求 预防:严格的跨站限制 + 验证码机制
  3. CSRF详细过程 用户登录了A网站,有了cookie 黑客诱导用户到B网站,并发起A网站的请求 A网站的API发现有cookie,认为是用户自己操作的
  4. CSRF预防手段 严格的跨域请求限制,如判断referrer(请求来源) 为cookie设置SameSite,禁止跨域传递cookie 关键接口使用短信验证码
  5. 点击劫持 Click Jacking 手段:诱导界面上蒙一个通明的iframe,诱导用户点击 预防:让iframe不能跨域加载
  6. 点击劫持 预防
if (top.location.hostname !== self.location.hostname) {
 alert("您正在访问不安全的页面,即将跳转到安全页面!“)
 top.location.href = self.location.href
}

hearders

X-Frame-Options: sameorigin
  1. DDoS DIstribute denial-of-service分布式拒绝服务 手段:分布式的,大规模的流量访问,使服务器瘫痪 预防:软件层不好做,需硬件预防(如阿里云WAF)
  2. SQL注入 手段:黑客提交内容时写入SQL语句,破坏数据库 预防:处理输入的内容,替换特殊字符
  3. xss, ddos, csrf, sql注入, 点击劫持

WebSocket和HTTP协议有什么区别

  1. WebSocket 支持端对端通讯 可以由client发起,也可以由server发起 用于:消息通知,直播间讨论区,聊天室,协同编辑
npm init -y
npm install ws --save
npm install nodemon --save-dev
const { WebSocketServer } = require('ws')

const wsServer = new WebSocketServer({ port: 3000 })

wsServer.on('connection', ws => {
 console.info('connected')
 
 ws.on('message', msg => {
  console.info('收到了信息', msg.toString)
  
  // 服务端向客户端发送信息
  setTimeout(() => {
   ws.send('服务端已经收到了信息:' + msg.toString())
  }, 2000)
 })
})



<button id="btn-send">发送消息</button>

const ws = new WebSocket('ws://127..0.0.1:3000')
ws.onopen = () => {
 console.info('opened')
 ws.send('client opened')
}
ws.onmessage = event = {
 console.info('收到了信息', event.data)
}

const btnSend = document.getElementById('btn-send')
btnSend.addEventListener('click', () => {
 console.info('clicked')
 ws.send('当前时间' + Date.now())
})

WebSocket 连接过程 先发起一个 HTTP 请求 成功之后再升级到 WebSocket 协议,再通讯

WebSocket 和 HTTP 区别 WebSocket 协议名是 ws:// , 可双端发起请求 WebSocket 没有跨域限制 通过send和onmessage通讯(HTTP通过req和res)

ws可升级为 wss (像https)
import { createServer } from 'https'
import { readFileSync } from 'fs'
import { WebSocketServer } from 'ws'

const server = createServer ({
 cert: readFileSync('/path/to/cert.pem'),
 key: readFileSync('/path/to/key.pem')
})

const wss = new WebSocketServer({ server })
扩展:实际项目推荐socket.io, api更简洁
io.on('connection', socket => {
 socket.emit('request', /*...*/)
 io.emit('broadcast', ...)
 socket.on('reply', () => {})
})
const { WebSocketServer } = require('ws')

const wsServer = new WebSocketServer({ port: 3000 })

const list = new Set()

wsServer.on('connection', curWs => {
 console.info('connected')

 list.add(curWs)

 curWs.on('message', msg => {
  console.info('received message', msg.toString())
  
  // 传递给其他客户端
  list.forEach(ws => {
   if (ws === curWs) return
   ws.send(msg.toString())
  })
 })
})

WebSocket和HTTP长轮询的区别

  1. 区别 HTTP长轮询:客户端发起请求,服务端等待,不会立即返回 WebSocket:客户端可发起请求,服务端也可发起请求
  2. 注意: HTTP长轮询,需处理timeout,即 timeout 之后重新发请求

从输入URL到网页显示的完整过程

步骤: 网络请求: DNS查询(得到IP),建立TCP连接(三次握手) 浏览器发起HTTP请求 收到请求响应,得到HTML源代码

继续请求静态资源 解析HTML过程中,遇到静态资源还会继续发起网络请求 JS CSS 图片 视频等 注意:静态资源可能有强缓存,此时不必请求

解析:字符串 -> 结构化数据 HTML构建DOM树 CSS构建CSSOM树(style tree) 两者结合,形成 render tree

渲染 解析过程很复杂 CSS 可能来自 <style> <link> JS 可能内嵌,或外链,还有 defer async 逻辑 img 可能内嵌(base64),可能外链

优化解析 CSS放在<head>中,不要异步加载CSS JS放在<body>最下面(或合理使用 defer async) <img> 提前定义 width height

渲染:Render Tree 绘制到页面 计算各个DOM的尺寸,定位,最后绘制到页面 遇到JS可能会执行(参考 defer async) 异步CSS,图片加载,可能会触发重新渲染

网络请求:DNS解析,HTTP请求 解析:DOM树,CSSOM树,Render Tree 渲染:计算,绘制,同时执行JS

网页重绘repaint和重排reflow有什么

  1. 网页动画
  2. Modal Dialog 弹窗
  3. 增加/删除一个元素,显示/隐藏一个元素

重绘 repaint

  1. 元素外观改变,如颜色,背景色
  2. 但元素的尺寸,定位不变,不会影响其他元素的位置

重排 reflow

  1. 重新计算尺寸和布局,可能会影响其他元素的位置
  2. 如元素高度增加,可能会使相邻元素位置下移

区别

  1. 重排比重绘要影响更大,消耗也更大
  2. 所以,要尽量避免无意义的重排

减少重排的方法 1/2

  1. 集中修改样式,或直接切换 css class
  2. 修改之前先设置 display: none, 脱离文档流
  3. 使用bfc特性,不影响其他元素位置

减少重排的方法 2/2

  1. 频繁触发(resize scroll)使用节流和防抖
  2. 使用 createDocumentFragment 批量操作 DOM
  3. 优化动画,使用 CSS3 和 requestAnimationFrame

扩展:BFC

  1. Block Format Context 块级格式化上下文
  2. 内部的元素无论如何改动,都不会影响其他元素的位置

触发 BFC 的条件 1/2

  1. 根节点 <html>
  2. float: left/right
  3. overflow: auto/scroll/hidden;

触发 BFC 的条件 2/2

  1. display: inline-block / table / table-row / table-cell;
  2. display: flex/grid; 的直接子元素
  3. position: absolute / fixed;

如何实现网页多标签tab通讯

使用 WebSocket

  1. 无跨域限制
  2. 需要服务端支持,成本高

通过 localStorage 通讯

  1. 同域的 A 和 B 两个页面
  2. A页面设置 localStorage
  3. B页面可监听到 localStorage 值的修改

通过 SharedWorker 通讯

  1. SharedWorker 是 WebWorker 的一种
  2. WebWorker 可开启子进程执行 JS,但不能操作 DOM
  3. SharedWorker 可单独开启一个进程,用于同域页面通讯