我的博客来源:https://1024bibi.com/2018/01/01/前端面试知识体系(一)/
防抖和节流有什么区别,分别用于什么场景
- 节流:限制执行频率,有节奏的执行;
- 防抖:限制执行次数,多次密集的触发只执行一次;
防抖 debounce
代码语言:javascript复制function debounce(fn, delay = 200) {
let timer = 0
return function () {
if (timer) clearTimeout(timer);
timer = setTimeout(() => {
fn.apply(this, arguments); // 透传 this 和参数
timer = 0
}, delay)
}
}
代码语言:javascript复制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>
代码语言:javascript复制<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
代码语言:javascript复制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
代码语言:javascript复制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节点,后者不会
类数组 变成 数组
代码语言:javascript复制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组件通讯有几种方式
代码语言:javascript复制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严格模式有什么特点
代码语言:javascript复制'use strict' // 全局开启
function fn() {
'use strict' // 某个函数开启
}
- 全局变量必须先声明
- 禁止使用 with
- 创建eval作用域
- 禁止this指向window
- 函数参数不能重名
JS内存垃圾回收用什么算法
垃圾回收 GC
代码语言:javascript复制什么是垃圾回收?
function fn1() {
const a = 'aa'
console.log(a)
const obj = { x: 100 }
console.log(obj)
}
fn1()
代码语言:javascript复制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')
代码语言:javascript复制引用计数(之前)
// 对象被 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内存泄漏
检测内存变化
代码语言:javascript复制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内存泄漏的场景有哪些
- 被全局变量,函数引用,组件销毁时未清除
- 被全局事件,定时器引用,组件销毁时未清除
- 被自定义事件引用,组件销毁时未清除
JS内存泄漏的场景有哪些-扩展-WeakMap和Weak
代码语言:javascript复制// 标记清除算法
const data = {}
function fn1() {
const obj = { x: 100 }
data.obj = obj
}
fn1()
代码语言:javascript复制const map = new Map()
function fn1() {
const obj = { x: 100 }
map.set('a', obj)
}
fn1()
代码语言:javascript复制// 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三者有什么区别
- Ajax(Asynchronous Javascript and XML),一种技术统称
- Fetch,一个具体的原生API 浏览器原生API,用于网络请求 和XMLHttpRequest一个级别 Fetch语法更加简洁,易用,支持Promise
- Axios,是一个第三方库 最常用的网络请求lib 内部可用XMLHttpRequest和Fetch来实现
- lib(库)和API(原生的函数)的区别
- fetch 和 XMLHttpRequest 全局的API
用XMLHttpRequest实现Ajax
代码语言:javascript复制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连接
- 先建立连接(确保双方都有收发消息的能力)
- 再传输内容(如发送给一个get请求)
- 网络连接是TCP协议,传输内容是HTTP协议
- SYN SYN ACK ACK
四次挥手-关闭连接
代码语言:javascript复制1. FIN ->
2. ACK <-
3. FIN <-
4. ACK ->
HTTP跨域时为何要发送options请求
跨域请求
- 浏览器同源策略
- 同源策略一般限制Ajax网络请求,不能跨域请求server
- 不会限制
<link> <img> <script> <iframe>
加载第三方资源
JSONP
代码语言:javascript复制// 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: {} })'
代码语言:javascript复制// 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)有什么
单线程和异步
- JS是单线程的(无论在浏览器还是nodejs)
- 浏览器中JS执行和DOM渲染共用一个线程
- 异步 宏任务 和 微任务 宏任务,如 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')
代码语言:javascript复制// 同步任务 -> 异步任务 -> 宏任务
// 微任务要比宏任务要快
// 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异步
- Nodejs同样使用ES语法,也是单线程,也需要异步
- 异步任务也分:宏任务 微任务
- 但是,它的宏任务和微任务,分不同类型,有不同优先级
虚拟DOM(vdom)真的很快吗
- vdom: Virtual DOM,虚拟DOM 用JS对象模拟DOM节点数据
- 组件化 数据视图分离,数据驱动视图 只关注业务数据,而不用再关心DOM变化
- vdom并不快,js直接操作dom才是最快的 但”数据驱动视图“要有合适的技术方案,不能全部dom重建 vdom就是目前最合适的技术方案(并不是因为它快,而是合适)
遍历一个数组用for和forEach哪个更快
- for更快
- forEach每次都要创建一个函数来调用,而for不会创建函数
- 函数需要独立的作用域,会有额外的开销
nodejs如何开启多进程,进程如何通讯-进程和线程的
进程 process vs 线程 thread 进程,OS 进行资源分配和调度的最小单位,有独立内存空间 线程,OS 进行运算调度的最小单位,共享进程内存空间 JS是单线程的,但可以开启多进程执行,如WebWorker js 不可以开启一个线程
为何需要多进程?
- 多核CPU,更适合处理多进程
- 内存较大,多个进程才能更好的利用(单进程有内存上限)
- 总之,“压榨”机器资源,更快,更节省 单个进程内存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 进程
代码语言:javascript复制// 子进程,计算
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)
})
代码语言:javascript复制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')
})
代码语言:javascript复制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的实现原理
- JS无法直接调用 native API
- 需要通过一些特定的“格式”来调用
- JS Bridge的常见实现方式
- 注册全局API
- 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引起的关注
- 组建树转换为链表,可分段渲染
- 渲染时可以暂停,去执行其他高优任务,空闲时再继续渲染
- 如何判断空闲? - requestIdleCallback
区别
- requestAnimationFrame 每次渲染完都会执行,高优
- 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>
代码语言:javascript复制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() {}
// 创建一次被缓存
代码语言:javascript复制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之后触发)禁止掉
现代浏览器的改进
代码语言:javascript复制<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
- cookie HTTP无状态,每次请求都要带cookie,以帮助识别身份 服务端也可以向客户端set-cookie,cookie大小限制4kb 默认有跨域限制:不可跨域共享、传递cookie
- withCredentials 前端设置跨域cookie共享 cookie本地存储 HTML5之前cookie常被用于本地存储 HTML5之后推荐使用localStorage和sessionStorage
- 现在浏览器开始禁止第三方cookie 和跨域限制不同。这里是:禁止网页引入的第三方JS设置cookie 打击第三方广告,保护用户隐私 新增属性 SameSite: Strict/Lax/None;值可自己选择
- 浏览器的Cookie新增加了一个SameSite属性,用来防止CSRF攻击和用户追踪
- cookie和session cookie用于登录验证,存储用户标识 session在服务端,存储用户详细信息,和cookie信息一一对应 cookie和session是常见登录验证解决方案
HTTP请求中token和cookie有什么区别-token
- token vs cookie cookie是HTTP规范,而token是自定义传递 cookie会默认被浏览器存储,而token需自己存储 token默认没有跨域限制
- jwt (json web token) 可以取代 cookie和session 前端发起登录,后端验证成功之后,返回一个加密的token 前端自行存储这个token(其中包含了用户信息,加密了) 以后访问服务端接口,都带着这个token,作为用户信息
- cookie:HTTP标准;跨域限制;配合session使用
- token:无标准;无跨域限制;用于JWT
session和JWT哪个更好
- session缺点 占用服务端内存,硬件成本高 多进程,多服务器时,不好同步,需使用第三方缓存,如redis 默认有跨域限制
- session优点 原理简单,易于学习 用户信息存储在服务端,可快速禁某个用户
- jwt 优点 不占用服务端内存 多进程,多服务器 不受影响 没有跨域限制
- jwt 缺点 用户信息存储在客户端,无法快速封禁某用户 万一服务端秘钥被泄漏,则用户信息全部丢失 token体积一般大于cookie,会增加请求的数据量
如有严格管理用户信息的需求(保密,快速封禁)推荐session 如没有特殊要求,则使用jwt
如何实现SSO单点登录
- 基于cookie cookie默认不可跨域共享,但有些情况下可设置为共享 主域名相同,如www.baidu.com image.baidu.com 设置cookie domain为主域名,即可共享cookie
- sso 主域名完全不同,则cookie无法共享 可使用sso技术方案
HTTP协议和UDP协议有什么区别
网络协议 HTT P协议在应用层 TCP UDP 协议再传输层 严格来说,应该拿TCP和UDP进行比较
OSI的体系结构
代码语言:javascript复制7. 应用层
6. 表示层
5. 会话层
4. 运输层
3. 网络层
2. 数据链路层
1. 物理层
TCP/IP的体系结构
代码语言:javascript复制1. 应用层(各种应用层协议,如DNS,HTTP,SMTP等)
2. 运输层(TCP或UDP)
3. 网际层(IP)
4. 网络接口层
TCP协议
- 有连接(三次握手)
- 有断开(四次挥手)
- 稳定传输
UDP协议
- 无连接,无断开
- 不稳定传输,但效率高
- 如视频会议,语音通话
HTTP是应用层,TCP UDP是传输层 TCP有连接,有断开,稳定传输 UDP无连接,无断开,不稳定传输,但效率高
HTTP协议1.0和1.1和2.0有什么区别
- HTTP1.0 弃用 最基础的HTTP协议 支持基本的GET,POST的方法
- HTTP1.1 缓存策略 cache-control E-tag等 支持长连接Connection: keep-alive,一次TCP连接多次请求 断点续传,状态码 206 支持新的方法PUT DELETE等,可用于Restful API
- 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资源在未来页面使用,空闲时加载
代码语言:javascript复制<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预连接
多个域名时,当前已经解析完,预查询,预连接
代码语言:javascript复制<link rel="dns-prefetch" href="域名">
<link rel="dns-preconnect" href="" crossorigin></link>
prefetch 是资源预获取(和preload相关) dns-prefetch 是DNS预查询(和preconnect相关)
前端攻击手段有哪些,该如何预防
- xss Cross Site Script 跨站脚本攻击 手段:黑客将JS代码插入到网页内容中,渲染时执行JS代码 预防:特殊字符替换(前端或者后端)
const newStr = str.replaceAll('<', '<').replaceAll('>', '>')
- Vue React 默认屏蔽xss攻击 除了用 vue v-html react dangerouslySetInnerHTML
- CSRF Cross Site Request Forgery 跨站请求伪造 手段:黑客诱导用户去访问另一个网站的接口,伪造请求 预防:严格的跨站限制 验证码机制
- CSRF详细过程 用户登录了A网站,有了cookie 黑客诱导用户到B网站,并发起A网站的请求 A网站的API发现有cookie,认为是用户自己操作的
- CSRF预防手段 严格的跨域请求限制,如判断referrer(请求来源) 为cookie设置SameSite,禁止跨域传递cookie 关键接口使用短信验证码
- 点击劫持 Click Jacking 手段:诱导界面上蒙一个通明的iframe,诱导用户点击 预防:让iframe不能跨域加载
- 点击劫持 预防
if (top.location.hostname !== self.location.hostname) {
alert("您正在访问不安全的页面,即将跳转到安全页面!“)
top.location.href = self.location.href
}
hearders
X-Frame-Options: sameorigin
- DDoS DIstribute denial-of-service分布式拒绝服务 手段:分布式的,大规模的流量访问,使服务器瘫痪 预防:软件层不好做,需硬件预防(如阿里云WAF)
- SQL注入 手段:黑客提交内容时写入SQL语句,破坏数据库 预防:处理输入的内容,替换特殊字符
- xss, ddos, csrf, sql注入, 点击劫持
WebSocket和HTTP协议有什么区别
- WebSocket 支持端对端通讯 可以由client发起,也可以由server发起 用于:消息通知,直播间讨论区,聊天室,协同编辑
npm init -y
npm install ws --save
npm install nodemon --save-dev
代码语言:javascript复制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)
代码语言:javascript复制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 })
代码语言:javascript复制扩展:实际项目推荐socket.io, api更简洁
io.on('connection', socket => {
socket.emit('request', /*...*/)
io.emit('broadcast', ...)
socket.on('reply', () => {})
})
代码语言:javascript复制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长轮询的区别
- 区别 HTTP长轮询:客户端发起请求,服务端等待,不会立即返回 WebSocket:客户端可发起请求,服务端也可发起请求
- 注意: 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有什么
- 网页动画
- Modal Dialog 弹窗
- 增加/删除一个元素,显示/隐藏一个元素
重绘 repaint
- 元素外观改变,如颜色,背景色
- 但元素的尺寸,定位不变,不会影响其他元素的位置
重排 reflow
- 重新计算尺寸和布局,可能会影响其他元素的位置
- 如元素高度增加,可能会使相邻元素位置下移
区别
- 重排比重绘要影响更大,消耗也更大
- 所以,要尽量避免无意义的重排
减少重排的方法 1/2
- 集中修改样式,或直接切换 css class
- 修改之前先设置 display: none, 脱离文档流
- 使用bfc特性,不影响其他元素位置
减少重排的方法 2/2
- 频繁触发(resize scroll)使用节流和防抖
- 使用 createDocumentFragment 批量操作 DOM
- 优化动画,使用 CSS3 和 requestAnimationFrame
扩展:BFC
- Block Format Context 块级格式化上下文
- 内部的元素无论如何改动,都不会影响其他元素的位置
触发 BFC 的条件 1/2
- 根节点
<html>
- float: left/right
- overflow: auto/scroll/hidden;
触发 BFC 的条件 2/2
- display: inline-block / table / table-row / table-cell;
- display: flex/grid; 的直接子元素
- position: absolute / fixed;
如何实现网页多标签tab通讯
使用 WebSocket
- 无跨域限制
- 需要服务端支持,成本高
通过 localStorage 通讯
- 同域的 A 和 B 两个页面
- A页面设置 localStorage
- B页面可监听到 localStorage 值的修改
通过 SharedWorker 通讯
- SharedWorker 是 WebWorker 的一种
- WebWorker 可开启子进程执行 JS,但不能操作 DOM
- SharedWorker 可单独开启一个进程,用于同域页面通讯