浏览器优化相关

161 阅读3分钟

事件机制

事件触发三阶段

  • window 向下往事件目标传播,遇到注册的捕获事件会触发
  • 传播到事件目标触发注册的事件
  • 从事件目标向上往 window 冒泡,遇到注册的冒泡事件会触发

大致就是:事件捕获阶段、处于目标阶段、事件冒泡阶段

事件注册

addEventListener

第三个参数,可以是 Boolean、Object。默认 false,true 是捕获阶段触发,false 是冒泡阶段触发。如果是对象,captrue 和 Boolean 一样,once 是只执行一次,passive 表示不会调用 propagation?

阻止冒泡:event.stopPropagation()

on

重复定义会覆盖...

事件委托

  <ul id="ul">
    <li>1</li>
    <li>2</li>
    <li>3</li>
    <li>4</li>
    <li>5</li>
  </ul>
  <script>
    let ul = document.querySelector('#ul')
    ul.addEventListener('click', (event) => {
      console.log(event.target);
    })
  </script>

网络请求

跨域

协议、域名、端口号,有一个不同,就会跨域。跨域不是浏览器没有发出请求,也不是服务器没返回响应,而是浏览器拦截了响应

跨域主要是防止:CSRF 攻击(Cross-site request forgery):跨站请求伪造。如果没有跨域,假如网站已经是登录态,就可以通过请求获取网站信息。

ajax

    function _ajax() {
      var xhr = new XMLHttpRequest()
      xhr.open('get', _url + '/xhr')
      xhr.send(null)
      xhr.onreadystatechange = function () {
        if (xhr.readyState === 4) {
          console.log('xhr', xhr)
          console.log('xhr', JSON.parse(xhr.response))
        }
      }
    }

jsonp

缺点是只能 get,原理就是利用 script 标签不存在跨域。

// client
son.addEventListener(
  'click',
  function (e) {
    e.stopPropagation()
    var _script = document.createElement('script')
    _script.src = 'http://localhost:3003/jsonp?say=fnSayRes'
    document.body.appendChild(_script)
  })
function fnSayRes(res) {
  console.log('jsonp.res:', res)
}
// server
const Koa = require('koa')
const app = new Koa()

app.use(async (ctx) => {
  const { request } = ctx
  if (request.url.startsWith('/jsonp')) {
    ctx.body = `${request.query.say}('12233')`
  }
})
app.listen(3003, () => {
  console.log('koa.3003 ..........')
})

CORS

设置 Access-Control-Allow-Origin

postMessage

发送方用postMessage给接受方一个消息,然后接收方监听message事件,就是事件驱动

存储

cookie

服务器生成,每次请求都在header中,4kb

localStorage

sessionStorage

indeDB

Service Worker

浏览器渲染

html 加载发生了什么

加载页面时,浏览器把 html 解析成 Dom 树,css 文件生成 CSSOM 以后,再将这两棵树结合成 render 树。 渲染树只会包括:显示的节点、这些节点的样式信息。然后根据渲染树来布局,也叫做回流。

回流

render 树种一些因为元素的尺寸、布局,隐藏等改变需要重建,就是回流。

回流必定引起重绘,布局、几何属性改变就回流

重绘

render 树中元素更新属性,包括外观如颜色背景等,叫做重绘

与 Event Loop 相关

回流、重绘和 Event Loop 有关,下次宏任务执行之前,会判断是否还有微任务,清空微任务后,就会判断浏览器器是否需要更新。

浏览器是 60hz 的,16.6ms 才更新一次。所以 scroll、resize 事件,最少 16.6ms 执行一次。

判断是否触发了媒体查询,更新动画并且发送事件,是否有全屏操作事件?,执行 requestAnimationFrame 回调,IntersectionObserver,更新界面,有空闲的话,执行 requestIdleCallback,都是这一帧做的

减少回流、重绘

  1. 使用 visibility 替换 display,前者占空间,不会回流,只会重绘,后者会引起回流(改变了布局)
  2. 不要使用 table,删掉一个可能会引起整个布局的回流
  3. css 选择符,避免层级过多
  4. 尽量多的使用 requestAnimationFrame 做动画

安全问题

XSS

攻击者将可执行的代码注入到网页中,通过转义字符,将引号、尖括号、斜杠转义和加白名单

CSP

建立白名单,就是告诉浏览器可以加载和执行哪些外部资源。

// 只允许加载本站资源
Content-Security-Policy: default-src ‘self’
// 只允许加载Https协议的图片
Content-Security-Policy: img-src https://*
// 允许加载任何资源
Content-Security-Policy: child-src 'none'

CSRF

中文名:跨站请求伪造。原理就是造出一个后端请求地址,让用户去点击,如果用户是在登录的状态下点击,就会泄露信息,通过一段操作让后端误以为是用户。

如何防御:

  1. get 请求不做数据修改
  2. 不让第三方获取到 cookie
  3. 阻止第三方请求
  4. 请求时附带验证码、token

SameSite

对 cookie 设置 SameSite,该属性表示 cookie 不随跨域请求发送

Referer

验证 Referer,看是否是第三方发起的

Token

服务端随机 Token,每次发送带上,验证有效性

点击劫持

将恶意网站的链接,设置透明,然后诱导客户去点

X-FRAME-OPTIONS,防止 iframe 嵌套点击

通过 js,判断是否有子域,有的话隐藏

中间人攻击

就是攻击者同时和客户端、服务端取得连接,让对方以为是安全的,实际上整个通讯过程都被控制了

解决方法:加一个安全通道,https 就行

性能优化

图片优化

100 _ 100 的图片是 10000 个像素点,假如每个像素点用 rgba 存储,那就是每个像素是 4 个通道。每个通道 1 个字节,图片大小就是:10000 _ 4 / 1024 kb = 39kb

那么优化图片就是:减少像素点,减少每个像素表示的颜色

使用 svg,使用 webp 格式图片,因为webp 使用了更好的图像数据压缩算法

DNS 与解析

预先获取域名的 ip

<link rel="dns-prefetch" href="//yuchengkai.cn">

webpack 性能优化

优化 loader

babel-loader,只检索.js 结尾的,忽略 node_moduels。加上缓存

module.exports = { module: {
  rules: [ {
    // js 文件才使用 babel test: /\.js$/,
    loader: 'babel-loader',
    // 只在 src 文件夹下查找 include: [resolve('src')], // 不会去查找的路径
    exclude: /node_modules/ }
  ] }
}
loader: 'babel-loader?cacheDirectory=true'

HappyPack

受限于node单线程,webpack打包也是单线程。在执行loader时,长时间编译,会导致等待。HappyPack可以将loader同步执行转化成并行,充分利用资源加快打包效率

plugins: [
  new HappyPack({
    id: 'happybabel',
    loaders: ['babel-loader?cacheDirectory'], // 开启 4 个线程
    threads: 4
  }) 
]

DllPlugin

将特定的类库提前打包然后引入,只有当类库版本更新才会重新打包。也实现了将公共代码抽离成单独文件的优化方案

// 单独配置在一个文件中
// webpack.dll.conf.js
const path = require('path')
    const webpack = require('webpack')
    module.exports = {
      entry: {
        // 想统一打包的类库
        vendor: ['react']
      },
      output: {
        path: path.join(__dirname, 'dist'),
        filename: '[name].dll.js',
        library: '[name]-[hash]'
      },
      plugins: [
        new webpack.DllPlugin({
          // name 必须和 output.library 一致
          name: '[name]-[hash]',
          // 该属性需要与 DllReferencePlugin 中一致 
          context: __dirname,
          path: path.join(__dirname, 'dist', '[name]- manifest.json ')
        })
      ]
    }
    // webpack.conf.js
    module.exports = {
      // ...省略其他配置 
      plugins: [
        new webpack.DllReferencePlugin({
          context: __dirname,
          // manifest 就是之前打包出来的 json 文件 
          manifest: require('./dist/vendor-manifest.json'),
        })
      ]
    }

代码压缩

现在只需设置mode:production。就自动压缩了,配置删掉console

resolve.alias:配置别名,会搜索的更快些

减少打包体积

按需加载:为了首屏加载更快,将每个路由页面打包成一个文件,loadsh这种大型类库也可以这样

Scope Hoisting

分析模块之前的依赖关系,尽可能把打包出来的模块合并到一个函数中

module.exports = { 
  optimization: {
    concatenateModules: true 
  }
}
// test.js
export const a = 1
// index.js
import { a } from './test.js'
[
  /* 0 */
  function (module, exports, require) {
    //...
  },
  /* 1 */
  function (module, exports, require) {
    //...
  } 
]

变成

[
  /* 0 */
  function (module, exports, require) {
    //...
  } 
]

Tree Sharking

删除项目中未被引用到代码,Webpack 4 production会自动开启

本文使用 mdnice 排版