防抖和节流有什么区别,分别用于什么场景
- 节流:限制执行频率,有节奏的执行;
- 防抖:限制执行次数,多次密集的触发只执行一次;
防抖 debounce
1 2 3 4 5 6 7 8 9 10 11 12
| function debounce(fn, delay = 200) { let timer = 0 return function () { if (timer) clearTimeout(timer); timer = setTimeout(() => { fn.apply(this, arguments); timer = 0 }, delay) } }
|
1 2 3 4
| const input = document.getElementById('input') input.addEventListener('keyup', debounce(() => { console.log('发起搜索', input.value) }), 300)
|
节流 throttle
例如:drag或scroll期间触发某个回调,要设置一个时间间隔
px % em rem vw/vh 有什么区别
px 和 %
- px基本单位,绝对单位(固定的),其他的都是相对单位
- % 相对于父元素的宽度比例
- 元素内的垂直居中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| <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
1 2 3 4
| <div style="font-size: 20px;"> <p style="text-indent: 2em; font-size: 40px;">首行缩进</p> <p style="text-indent: 2em;">哪吒, 算法猫叔</p> </div>
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| <style> @media only screen and (max-width: 374px) { html { font-size: 86px; } } @media only screen and (min-width: 375px) and (max-width: 413px) { html { font-size: 100px; } } @media only screen and (min-width: 414px) { html { font-size: 110px; } } p { font-size: .16rem; } </style>
|
vw / vh
- vw 屏幕宽度的 1%
- vh 屏幕高度的 1%
- vmin 两者的最小值,vmax 两者的最大值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| <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
- 某些箭头函数代码难以阅读
什么时候不能使用箭头函数?
1 2 3 4 5 6 7
| const obj = { name: '哪吒,B站,算法猫叔', getName: () => { return this.name } } console.log(obj.getName())
|
1 2 3 4 5 6 7
| const obj = { name: '哪吒,B站,算法猫叔' } obj.__proto__.getName = () => { return this.name } console.log( obj.getName() )
|
1 2 3 4 5 6
| const Foo = (name, age) => { this.name = name this.age = age } const f = new Foo('张三', 20)
|
1 2 3 4 5
| const btn1 = document.getElementById('btn1') btn1.addEventListener('click', () => { this.innerHTMl = 'clicked' })
|
- 不适用-Vue生命周期和method : vue组件本质上一个 JS 对象
- React 组件(非Hooks)本质上是一个 es6 class,class里面适用箭头函数没问题
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| { data() { return { name: '哪吒,B站:算法猫叔' } }, methods: { getName: () => { return this.name }, }, mounted: () => { }, }
|
JS中for-in和for-of有什么区别
for of 去遍历可以generator
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| 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')
for (let p of pList) { console.log(p) }
|
- 遍历对象: for … in 可以,for … of 不可以
- 遍历Map Set:for…of 可以,for…in 不可以
- 遍历generator:for…of 可以,for … in 不可以
1 2 3 4 5 6 7 8
| 对象,数组,字符串可枚举的,就可以使用for ... in 循环 const obj1 = { x: 100 } Object.getOwnPropertyDescriptors(obj1) x: configurable: true enumerable: true value: 100 writeable: true
|
for … in 用于可枚举数据,如对象,数组,字符串,得到key
for … of 用于可迭代数据,如数组,字符串,Map,Set,得到value
for-await-of有什么作用
for await…of 用于遍历多个Promise
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| 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 list = [p1, p2, p3]
for await (let res of list) { console.log(res) } })()
|
盒子模型: 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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| const p1 = document.getElementById('p1') class Node {}
class Document extends Node {} class DocumentFragment extends Node {}
class CharacterData extends Node {} class Comment extends CharacterData {} class Text extends CharacterData {}
class Element extends Node {} class HTMLElement extends Element {} class HTMLDivElement extends HTMLElement {} class HTMLInputElement extends HTMLElement {}
|
- HTMLCollection 是 Element 的集合
- NodeList 是 Node 集合
1 2 3 4 5 6 7 8 9 10 11
| <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) </script>
|
划重点:
- 获取 Node 和 Element 的返回结果可能不一样
- 如 elem.childNodes 和 elem.children 不一样
- 前者包含Text和Comment节点,后者不会
类数组 变成 数组
1 2 3
| const arr1 = Array.from(list) const arr2 = Array.prototype.slice.call(list) const arr3 = [...list]
|
Vue中computed和watch有什么区别
- computed 用于计算产生新的数据
- watch 用于监听现有数据
1 2 3 4 5 6 7 8 9 10
| watch: { name(newValue, oldValue) { console.log('watch name', newValue, oldValue) } }, computed: { userInfo() { return this.name + this.city } }
|
computed有缓存 watch没有缓存
Vue组件通讯有几种方式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54
| props和$emit
$parent
自定义事件
$refs
$attr
provide/inject
vuex
---
$attrs $listeners
vue3 移除 $listeners
上一级没有接收到的
props: ['x'],
emits: ['getX'],
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严格模式有什么特点
1 2 3 4 5
| 'use strict'
function fn() { 'use strict' }
|
- 全局变量必须先声明
- 禁止使用 with
- 创建eval作用域
- 禁止this指向window
- 函数参数不能重名
JS内存垃圾回收用什么算法
垃圾回收 GC
1 2 3 4 5 6 7 8 9 10
| 什么是垃圾回收?
function fn1() { const a = 'aa' console.log(a) const obj = { x: 100 } console.log(obj) } fn1()
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| 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')
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| 引用计数(之前)
let a = { x: 100 } let a1 = a a = 10 a1 = null
function fn3() { const obj1 = {} const obj2 = {} obj1.a = obj2 obj2.a = obj1 } fn3()
var div1 = document.getElementById('div1') div1.a = div1 div1.someBigData = {}
标记清除(现代)
|
JS闭包是内存泄漏吗
闭包的数据是不可以被垃圾回收的
如何检测JS内存泄漏
检测内存变化
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| 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++ if (n < 50) { start() } else { alert('done') } }, 200) }
document.getElementById('btn1').addEventListener('click', () => { start() })
|
JS内存泄漏的场景有哪些
- 被全局变量,函数引用,组件销毁时未清除
- 被全局事件,定时器引用,组件销毁时未清除
- 被自定义事件引用,组件销毁时未清除
JS内存泄漏的场景有哪些-扩展-WeakMap和Weak
1 2 3 4 5 6 7
| const data = {} function fn1() { const obj = { x: 100 } data.obj = obj } fn1()
|
1 2 3 4 5 6
| const map = new Map() function fn1() { const obj = { x: 100 } map.set('a', obj) } fn1()
|
1 2 3 4 5 6 7 8 9 10
| <script> const wMap = new WeakMap(); function fn1() { const obj = { x: 100 } wMap.set(obj, 100) } fn1() </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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| 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
四次挥手-关闭连接
1 2 3 4
| 1. FIN -> 2. ACK <- 3. FIN <- 4. ACK ->
|
HTTP跨域时为何要发送options请求
跨域请求
- 浏览器同源策略
- 同源策略一般限制Ajax网络请求,不能跨域请求server
- 不会限制
<link> <img> <script> <iframe>
加载第三方资源
JSONP
1 2 3 4 5 6 7 8 9 10
| <script> window.onSuccess = function(data) { console.log(data) } </script> <script src="https://www.bbb.com/api/getData"></script>
'onSuccess({ errno: 0, data: {} })'
|
1 2 3 4 5
| 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")
|
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规范的一部分。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| 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) alert('阻塞 timeout') })
Promise.resolve().then(() => { const list = document.getElementsByTagName('p') console.log('length on promise.then---', list.length) alert('阻塞 promise') }) console.log('end')
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
|
<script> console.log('start') setTimeout(() => { console.log('timeout') }) Promise.resolve().then(() => { console.log('promise then') )) console.log('end')
</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左右
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| nodejs如何开启多进程
const http = require('http')
const server = http.createServer() server.listen(3000, () => { console.log('localhost: 3000') })
console.info(process.pid)
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') })
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| 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) })
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| 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') })
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| 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 { 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
1 2 3 4 5 6 7 8 9 10 11 12
| 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 空闲时才执行,低优
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| <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>
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| 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 缓存组件被隐藏
1 2 3 4 5 6 7 8 9 10 11 12 13
| <keep-alive> <Child1 v-if="num === 1"></Child1> <Child2 v-else></Child2> </keep-alive>
created() { console.log() } activated() {} deactivated() {}
|
1 2 3 4 5 6 7
| child1 created child1 activated child2 created child1 deactivated child2 activated child2 deactivated child1 activated
|
1 2 3 4 5 6
| $nextTick mounted() { this.$nextTick(function () { }) }
|
- ajax 应该在那个生命周期?
有两个选择:created 和 mounted
推荐:mounted
- vue3 Composition API 生命周期有何区别?
用setup代替了beforeCreate和created
使用Hooks函数的形式,如mounted改为onMounted()
1 2 3 4 5 6 7 8 9 10 11
| 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之后触发)禁止掉
现代浏览器的改进
1 2 3 4 5
| <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的体系结构
1 2 3 4 5 6 7
| 7. 应用层 6. 表示层 5. 会话层 4. 运输层 3. 网络层 2. 数据链路层 1. 物理层
|
TCP/IP的体系结构
1 2 3 4
| 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资源在未来页面使用,空闲时加载
1 2 3 4 5 6 7 8 9 10 11 12 13
| <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预连接
多个域名时,当前已经解析完,预查询,预连接
1 2
| <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不能跨域加载
- 点击劫持 预防
1 2 3 4 5 6 7 8
| 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发起
用于:消息通知,直播间讨论区,聊天室,协同编辑
1 2 3
| npm init -y npm install ws --save npm install nodemon --save-dev
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| 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)
1 2 3 4 5 6 7 8 9 10 11
| 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 })
|
1 2 3 4 5 6
| 扩展:实际项目推荐socket.io, api更简洁 io.on('connection', socket => { socket.emit('request', ) io.emit('broadcast', ...) socket.on('reply', () => {}) })
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| 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 可单独开启一个进程,用于同域页面通讯