Skip to content

从前端到全栈


一、如何解决移动端 H5 点击有 300ms 延迟

1. 背景

  • double tap to zoom

2. 初期解决方案 FastClick

js
window.addEventListener("load", function() {
  	FastClick.attach(document.body)
}, false)

3. FastClick 原理

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

4. 现代浏览器的改进

image-20220321170114819


  • HTTP 无状态,每次请求都要带 cookie ,以帮助识别身份
  • 服务端也可以向客户端 set-cookie ,cookie 大小限制 4kb
  • 默认有跨域限制:不可跨域共享、传递 cookie
  • HTML5 之前 cookie 常被用于本地存储
  • HTML5 之后推荐使用 localStorage 和 sessionStorage
  • 和跨域限制不同。这里指:禁止网页引入的第三方 JS 设置 cookies

  • 打击第三方广告,保护用户隐私

  • 新增属性 SameSite: Strict / Lax / None; 值可自己选择

    image-20220321174346925

  • cookie 用于登录校验,存储用户标识(如 userId)

  • session 在服务端,存储用户详细信息,和 cookie 信息一一对应

  • cookie + session 是常用的登录验证解决方案

    image-20220321175050647

  • cookie 是 HTTP 规范,而 token 是自定义传递
  • cookie 会默认被浏览器存储,而 token 需自己存储
  • token 默认没有跨域限制

6. JWT ( JSON Web Token )

  • 前端发起登录,后端验证成功之后,返回一个加密的 token

  • 前端自行存储这个 token(其中包括了用户信息,加密了)

  • 以后访问服务端接口,都带着这个 token ,作为用户信息

    image-20220322095235987

7. 答案

  • cookie:HTTP 标准;跨域限制;配合 session 使用
  • token:无标准,无跨域限制;用于 JWT

8. 划重点

  • cookie 的知识点很多,对于 HTTP 也很重要
  • Session 存在的价值
  • token 和 cookie 要对比理解,否则容易混淆

9. ( 连环问 ) Session 和 JWT 哪个更好

  • Session 优点
    1. 原理简单,易于学习
    2. 用户信息存储在服务端,可快速封禁某个用户
  • Session 缺点
    1. 占用服务器内存,硬件成本高
    2. 多进程,多服务器时,不好同步 -- 需使用第三方缓存,如 redis
    3. 默认有跨域限制
  • JWT 优点
    1. 不占用服务端内存
    2. 多进程、多服务器,不受影响
    3. 没有跨域限制
  • JWT 缺点
    1. 用户信息存储在客户端,无法快速封禁某用户
    2. 万一服务端秘钥被泄露,则用户信息全部丢失
    3. token 体积一般大于 cookie ,会增加请求的数据量
  • 答案
    1. 如有严格管理用户信息的需求(保密、快速封禁)推荐 Session
    2. 如没有特殊要求,则使用 JWT (如创业初期的网站)

10. ( 连环问 ) 如何实现 SSO 单点登录

  • 基于 cookie

    1. cookie 默认不可跨域共享,但有些情况下可设置为共享
    2. 主域名相同,如 www.baidu.com image.baidu.com
    3. 设置 cookie domain 为主域名,即可共享 cookie
  • SSO

    1. 主域名完全不同,则 cookie 无法共享

    2. 可使用 SSO 技术方案

      iShot2022-03-22 10.41.01

  • OAuth 2.0

    image-20220322104429670


三、HTTP 协议和 UDP 协议区别

1. 网络协议

  • HTTP 协议在应用层

  • TCP UDP 协议在传输层

  • 严格来说,应该拿 TCP 和 UDP 进行比较

    image-20220322142418277

2. TCP 协议

  • 有连接(三次握手)
  • 有断开(四次挥手)
  • 稳定传输

3. UDP 协议

  • 无连接,无断开
  • 不稳定传输,但效率高
  • 适用视频会议,语音通话

4. ( 连环问 ) HTTP 协议 1.0 1.1 2.0 有什么区别

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

四、WebSocket 和 HTTP 有什么区别

1. WebSocket

  • 支持端对端通讯
  • 可以由 client 发起,也可以由 server 发起
  • 用于:消息通知,直播间讨论区,聊天室,协同编辑

2. 代码

js
// 客户端 html
const ws = new WebSocket('ws://localhost:3001')
ws.onopen = () => {
  console.log('opened')
  ws.send('client opened')
}
ws.onmessage = ev => {
  console.log('客户端收到了信息', ev.data)
}
js
// 服务端 nodejs
const {WebSocketServer} = require('ws')

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

wsServer.on('connection', ws => {
  console.log('connected')

  ws.on('message', msg => {
    console.log('收到了信息', msg.toString())

    // 服务端像客户端发送信息
    setTimeout(() => {
      ws.send('服务端收到了信息' + msg.toString())
    }, 2000)
  })
})

3. WebSocket 连接过程

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

4. WebSocket 和 HTTP 区别

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

5. ws 可升级为 wss (像 https)

js
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})

6. 扩展:实际项目推荐 socket.io ,API 更简洁

js
io.on('connection', socket => {
  // emit an event to the socket
  socket.emit('request', '...')
  // emit an event to all connected sockets
  io.emit('broadcast', '...')
  // listen to the event
  socket.on('reply', () => { /* ... */ })
})

7. 扩展:创建简易聊天室

js
// nodejs
const {WebSocketServer} = require('ws')

const wsServer = new WebSocketServer({port: 3001})
const list = new Set()
wsServer.on('connection', curWs => {
  console.log('connected')

  // 这里不能一直 add , 实际中这里应该有清理缓存的机制
  list.add(curWs)
  curWs.on('message', msg => {
    console.log('收到了信息', msg.toString())
    // 传递给其他客户端
    list.forEach(ws => {
      if (ws === curWs) return
      ws.send(msg.toString())
    })
  })
})

8. ( 连环问 ) WebSocket 和 HTTP 长轮询的区别

  • HTTP 长轮询:客户端发起请求,服务端阻塞,不会立即返回

  • WebSocket:客户端可发起请求,服务端也可发起请求

  • HTTP 长轮询,需处理 timeout ,即 timeout 之后重新请求

    image-20220322165000190


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

1. 步骤

  • 网络请求
  • 解析
  • 渲染

2. 网络请求

  • DNS 查询(得到 IP),建立 TCP 连接(三次握手)
  • 浏览器发起 HTTP 请求
  • 收到请求响应,得到 HTML 源代码
  • 继续请求静态资源
    1. 解析 HTML 过程中,遇到静态资源还会继续发起网络请求
    2. JS CSS 图片 视频等
    3. 注意:静态资源可能有强缓存,此时不必请求

2. 解析:字符串 -> 结构化数据

  • HTML 构建 DOM 树

  • CSS 构建 CSSOM 树 (style tree)

  • 两者结合,形成 render tree

    image-20220323102131807

  • 优化解析

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

3. 渲染:Render Tree 绘制到页面

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

4. 答案

  • 网络请求:DNS 解析,建立 TCP 连接,HTTP 请求
  • 解析:DOM 树,CSSOM 树,Render Tree
  • 渲染:计算、绘制,同时执行 JS

5. ( 连环问 ) 重绘 repaint 重排 reflow 有什么区别

  • 动态网页,随时都会重绘、重排
    1. 网页动画
    2. Modal Dialog 弹窗
    3. 增加 / 删除一个元素,显示 / 隐藏一个元素
  • 重绘 repaint
    1. 元素外观改变,如颜色、背景色
    2. 但元素的尺寸、定位不变,不会影响其他元素的位置
  • 重排 reflow
    1. 重新计算尺寸和布局,可能会影响其他元素的位置
    2. 如元素高度增加,可能会使相邻元素位置下移
  • 区别
    1. 重排比重绘要影响更大,消耗也更大
    2. 尽量避免无意义的重排
      • 集中修改样式,或直接切换 css class
      • 修改前先设置 display: none ,脱离文档流
      • 使用 BFC 特性,不影响其他元素位置
      • 频繁触发(resize scroll)使用节流防抖
      • 使用 createDocumentFragment 批量操作 DOM
      • 优化动画,使用 CSS3 和 requestAnimationFrame
  • 扩展:BFC
    1. Block Format Context 块级格式化上下文
    2. 内部元素无论如何改动,都不会影响其他元素的位置
    3. 触发 BFC 的条件
      • 根节点 <html>
      • float: left / right;
      • overflow: auto / scroll / hidden;
      • display: inline-block / table / table-row / table-cell;
      • display: flex / grid; 的直接子元素
      • position: absolute / fixed;

六、如何实现网页多标签通讯

1. 使用 WebSocket

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

2. 通过 localStorage 通讯

  • 同域 的 A 和 B 两个页面

  • A 页面设置 localStorage

  • B 页面可监听 localStorage 值的修改

    js
    window.addEventListener('storage', e => {
      	console.log('key', e.key)
      	console.log('value', e.newValue)
    })

3. 通过 SharedWorker 通讯

  • SharedWorker 是 WebWorker 的一种

  • WebWorker 可开启 子进程 执行 JS ,但不能操作 DOM

  • SharedWorker 可单独开启一个进程,用于 同域 页面通讯

    js
    // worker.js
    const set = new Set()
    onconnect = event => {
        const port = event.ports[0]
        set.add(port)
    
        // 接收信息
        port.onmessage = e => {
            // 广播消息
            set.forEach(p => {
                if (p === port) return
                p.postMessage(e.data)
            })
        }
        // 发送信息
        port.postMessage('worker.js done')
    }
    js
    // page detail
    const worker = new SharedWorker('./worker.js')
        const btn = document.getElementById('btn')
        btn.addEventListener('click', () => {
            console.log('clicked')
            worker.port.postMessage('detail go...')
     })
    js
    // page list
    const worker = new SharedWorker('./worker.js')
    worker.port.onmessage = e => console.log('list', e.data)

4. 答案

  • WebSocket 需要服务端,成本高,但可以跨域
  • localStorage 简单易用,推荐
  • ShareWorker 调试不方便,不兼容 IE11

5. ( 连环问 ) 网页和 iframe 如何通讯

  • 使用 PostMessage 通讯

  • 注意跨域的限制和判断

    js
    // index.html
    const btn = document.getElementById('btn')
    btn.addEventListener('click', () => {
        console.log('index clicked')
        window.iframe.contentWindow.postMessage('hello', '*')
    })
    window.addEventListener('message', ev => {
        console.log('origin', ev.origin)  // 来源域名
        console.log('index received', ev.data)
    })
    js
    // child.html
    const btn = document.getElementById('btn')
    btn.addEventListener('click', () => {
        console.log('child clicked')
        window.parent.postMessage('world', '*')
    })
    window.addEventListener('message', ev => {
        console.log('origin', ev.origin)  // 来源域名
        console.log('child received', ev.data)
    })

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

1. 区别

image-20220324101914505

2. 答案

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

3. ( 连环问 ) prefetch 和 dns-prefetch 区别

  • preload 和 prefetch

    1. preload 资源在当前页面使用,会 优先 加载

    2. prefetch 资源在未来页面使用,空闲时 加载

      html
      <head>
          <!--    preload-->
          <link rel="preload" href="style.css" as="style">
          <link rel="preload" href="main.js" as="script">
      
          <!--    prefetch-->
          <link rel="prefetch" href="other.js" as="script">
      
          <!--    引用 css-->
          <link rel="stylesheet" href="style.css">
      </head>
      <body>
          <!--    引用 js-->
          <script src="main.js" defer></script>
      </body>
  • dns-prefetch 和 preconnet

    1. dns-prefetch 即 DNS 预查询

    2. preconnect 即 DNS 预连接

      html
      <head>
        	<link rel="dns-prefetch" href="https://xxx.com">
        	<link rel="preconnect" href="https://xxx.com" crossorigin>
      </head>
  • 答案

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

八、什么是 HTTPS 中间人攻击

1. HTTPS 加密传输

  • HTTP 明文传输

  • HTTPS 加密传输 HTTP + TLS / SSL

    iShot2022-03-24 14.21.54

2. 中间人攻击

  • 黑客伪造证书

    image-20220324142646892

九、前端攻击手段有哪些

1. XSS

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

2. CSRF

  • Cross Site Request Forgery 跨站请求伪造
  • CSRF 详细过程
    1. 用户登录了 A 网站,有了 cookie
    2. 黑客诱导用户到 B 网站,并发起 A 网站的请求
    3. A 网站的 API 发现有 cookie ,认为是用户自己操作的
  • CSRF 预防手段
    1. 严格的跨域限制,如判断 referrer (请求来源)
    2. 为 cookie 设置 SameSite ,禁止跨域传递 cookie
    3. 关键接口使用短信验证码

3. 点击劫持

  • Click Jacking

  • 手段:诱导界面上蒙一个透明的 iframe ,诱导用户点击

  • 预防:让 iframe 不能跨域加载

    image-20220324153349191

4. DDos

  • Distribute denial-of-service 分布式拒绝服务
  • 手段:分布式的、大规模的流量访问,使服务器瘫痪
  • 预防:软件层不好做,需硬件预防(如阿里云 WAF)

5. SQL 注入

  • 手段:黑客提交内容时写入 SQL 语句,破坏数据库
  • 预防:处理输入的内容,替换特殊字符

十、描述 koa2 洋葱圈模型

1. Koa2

  • 一个简约、流行的 nodejs 框架

  • 通过 中间件 组织代码

    js
    const Koa = require('koa');
    const app = new Koa();
    
    // logger
    app.use(async (ctx, next) => {
      await next();  // 先执行下一步 x-response-time ,执行完再继续执行
      const rt = ctx.response.get('X-Response-Time');
      console.log(`${ctx.method} ${ctx.url} - ${rt}`);
    });
    
    // x-response-time
    app.use(async (ctx, next) => {
      const start = Date.now();
      await next();  // 先执行下一步 response ,执行完再继续执行
      const ms = Date.now() - start;
      ctx.set('X-Response-Time', `${ms}ms`);
    });
    
    // response
    app.use(async ctx => {
      ctx.body = 'Hello World';
    });
    
    app.listen(3000);
  • 多个中间件以 “ 洋葱圈模型 ” 执行

    iShot2022-03-24 16.16.39