8

本文将从浏览器输入url到页面加载完成中经历的各个阶段来探讨web前端性能可以优化的点

1、浏览器输入URL并按下回车

无优化点

2、浏览器查找当前URL是否存在缓存

彻底弄懂HTTP缓存机制及原理

传送门:https://www.cnblogs.com/chenq...

优化点

服务器对静态资源设置浏览器缓存信息,浏览器在有缓存的情况下直接从本地读取资源。

3、DNS域名解析

地址栏输入的域名并不是最后资源所在的真实位置,域名只是与IP地址的一个映射。网络服务器的IP地址那么多,我们不可能去记一串串的数字,因此域名就产生了,域名解析的过程实际是将域名还原为IP地址的过程。

顺便介绍下,无优化点

4、TCP连接

DNS解析后得到了服务器的ip,接下来就是和服务器建立起连接,这通过TCP的三次握手完成。
具体为:
浏览器:你好~你在吗~我能和你聊会天吗
服务器:嗯!我在~我们聊会吧~
浏览器:好的,那我们开始咯
图片描述

顺便介绍下,无优化点,顺便再说下四次告别

图片描述

TCP连接后数据的传输是双向的,那么当浏览器不想说话了,就会发出中止连接的信号标志,服务器收到后会回一个确认标志给浏览器,这时浏览器就不能再向服务器传输数据了,但是还可以发送信号。为了对服务器的尊重,等服务器把话说完后才会发一个结束标志给浏览器,这时浏览器知道服务器也向自己传输完数据了,回一个确认标志过去,才真正结束这次TCP连接。

5、浏览器向服务器发送HTTP请求

完整的HTTP请求包含请求起始行、请求头部、请求主体三部分。缓存信息是存储在请求头中,在阶段二上的连接有介绍。

优化点

减少请求次数

  • 合并外部请求的js、css文件
  • 对icon文件进行处理。运用CSS精灵合并处理多个icon文件、运用图标字体、把小图标转为base64等

6、浏览器接收响应体

服务器在收到浏览器发送的HTTP请求之后,会将收到的HTTP报文封装成HTTP的Request对象,并通过不同的Web服务器进行处理,处理完的结果以HTTP的Response对象返回,主要包括状态码,响应头,响应报文三个部分。响应体为服务器返回给浏览器的信息,主要由HTML,css,js,图片文件组成。

优化点

压缩响应体

  • 对请求的文件进行打包(webpack),减少文件体积

7、页面渲染

页面是如何渲染的

传送门: https://www.cnblogs.com/tootw...

js的影响

js是会阻塞页面渲染的,那么解决方法有很多,可以把js放在body的底部,或者是异步加载js。
同步异步加载详解传送门:https://blog.csdn.net/qq_3498...
浅谈script标签的defer和async传送门:https://segmentfault.com/a/11...

css的影响

前端页面渲染时会根据DOM结构生成一个DOM树,然后加上CSS样式生成渲染树。如果CSS文件放在<head>标签中,则CSS RuleTree会先于DOM树完成构建,之后浏览器就可以边构建DOM树边完成渲染;反之,CSS文件放在所有页面标签之后,比如<body/>之前,那么当DOM树构建完成了,渲染树才构建,浏览器不得不再重新渲染整个页面,这样造成了资源的浪费。而且页面还可能会出现闪跳的感觉,或者白屏或者布局混乱或者样式很丑,直到CSS加载完成,页面重绘才能恢复正常。因此,一般来讲,css标签应放在标签之间。但如果css文件较大,会让首页白屏时间更长,所以并不是说把css都放顶部是一个完美的方法。权衡利弊,应该把必须的css(js)放顶部,把不那么重要的css(js)放底部。

--------------------- 作者:JiajiaAz 来源:CSDN 原文:https://blog.csdn.net/qq_3265...

优化点

图片懒加载

概念:
访问页面时,先把img元素的背景图片src替换成一张占位图,这样只需请求一次,当图片出现在浏览器的可视区域内时,再设置图片的真实路径,显示图片。

方法:
页面中的img元素,若没有src属性,浏览器就不会发出请求去下载图片,只有通过Javascript设置了图片路径,浏览器才会发送请求。
1)懒加载先在页面中把需要延迟加载的图片统一使用一张占位图进行占位,把真正的路径存在元素“data-url”属性里。
2)页面加载完成后,通过scrollTop判断图片是否在用户的视野,如果在,则将 data-url的值取出来存放到src中。

来自: http://baijiahao.baidu.com/s?...

vue/react项目页面渲染优化

在vue或者react的项目下,怎么解决页面渲染问题呢,毕竟初始的html就是一个空白页面,页面渲染全靠js的render,如果这个入口js过大,必然会导致页面白屏时间过长。
如果可以对入口的js进行代码分割,把后期才会用到的js先独立出来,等用时再引入那么就可以大大减少初始js的体积了,首屏页面渲染起来自然也快。

异步加载js的概念
<button>click to load js</button>
<script>
  document.querySelector('button').onclick = () => {
    let loadJs = document.createElement('script')
    loadJs.src = './load.js'
    document.body.appendChild(loadJs)
    loadJs.onload = () => {
      alert("finish loading!")
    }
  }
</script>

嗯简单来说原理就是这么实现的,设置个触发点,触发后创建个src为需要引进js路径的script Dom,然后设置下加载后的回调函数..

webpack上的实现方法

写在主入口main.js中?

document.querySelector('button').onclick = () => {
  require.ensure([], ()=> {
    // 引入异步加载的js
    let loadJS = require('./asyncJS')
    alert(loadJS.flag)
  }, 'asyncJS')
}

webpack的配置文件?

output: {
  path: path.resolve(__dirname, './dist'),
  filename: '[name].bundle.js',
  publicPath: '../dist/',
  chunkFilename: 'chunks/[name]-[hash].js'
}
webpack 在编译时,会静态地解析代码中的require.ensure(),同时将模块添加到一个分开的 chunk 当中。这个新的 chunk 会被 webpack 通过 jsonp来按需加载。

语法如下:

require.ensure(dependencies: String[], callback: function(require), chunkName: String)

依赖 - dependencies
这是一个字符串数组,通过这个参数,在所有的回调函数的代码被执行前,我们可以将所有需要用到的模块进行声明。

回调 - callback
当所有的依赖都加载完成后,webpack会执行这个回调函数。require对象的一个实现会作为一个参数传递给这个回调函数。因此,我们可以进一步 require() 依赖和其它模块提供下一步的执行。

chunk名称 - chunkName
chunkName 是提供给这个特定的 require.ensure() 的 chunk的名称。通过提供 require.ensure() 不同执行点相同的名称,我们可以保证所有的依赖都会一起放进相同的 文件束(bundle)。

打包出来的目录?

clipboard.png
其中chunks文件夹里的内容就是从main.js中分离出来的,会在被需要时再引入到项目中。

页面加载时是这样?
clipboard.png

clipboard.png

点了按钮就是这样了?

clipboard.png

clipboard.png

顺便介绍下require.ensure() 的坑点

空数组作为参数 require.ensure([], function(require){

require('./a.js'); }); 以上代码保证了拆分点被创建,而且 a.js 被 webpack 分开打包。

依赖作为参数 require.ensure(['./a.js'], function(require) {

require('./b.js'); }); 上面代码, a.js 和 b.js 都被打包到一起,而且从主文件束中拆分出来。但只有 b.js 的内容被执行。a.js 的内容仅仅是可被使用,但并没有被输出。

想去执行 a.js,我们需要异步地引用它,如 require('./a.js'),让它的 JavaScritp 被执行。

vue/react项目中的实现方法

说了这么多,绕回来本题中心,这里以vue为例子来实现按需异步加载功能。按需加载什么呢?当然是组件啦!

异步组件 - 官网解释
在大型应用中,我们可能需要将应用分割成小一些的代码块,并且只在需要的时候才从服务器加载一个模块。为了简化,Vue
允许你以一个工厂函数的方式定义你的组件,这个工厂函数会异步解析你的组件定义。Vue
只有在这个组件需要被渲染的时候才会触发该工厂函数,且会把结果缓存起来供未来重渲染。例如:
Vue.component('async-example', function (resolve, reject) {
  setTimeout(function () {
    // 向 `resolve` 回调传递组件定义
    resolve({
      template: '<div>I am async!</div>'
    })
  }, 1000)
})
如你所见,这个工厂函数会收到一个 resolve 回调,这个回调函数会在你从服务器得到组件定义的时候被调用。你也可以调用 reject(reason) 来表示加载失败。

结合前面讲的直接上例子,基于异步组件我们可以实现vue的异步路由?

export default new Router({
  routes: [
    {
      path: '/',
      name: 'HelloWorld',
      component: resolve => {
        require.ensure([], () => {
          resolve(require('@/components/HelloWorld.vue'))
        })
      }
    },
    {
      path: '/other',
      name: 'other',
      component: resolve => {
        require.ensure([], () => {
          resolve(require('@/components/Other.vue'))
        })
      }
    }
  ]
})

当然require.ensure这种写法比较旧,就是对webpack的兼容性会好点,现在结合es6的写法会更加简洁

你也可以在工厂函数中返回一个 Promise,所以把 webpack 2 和 ES2015 语法加在一起,我们可以写成这样:

Vue.component(
  'async-webpack-example',
  // 这个 `import` 函数会返回一个 `Promise` 对象。
  () => import('./my-async-component')
)
export default new Router({
  routes: [
    {
      path: '/',
      name: 'HelloWorld',
      component: () => import('@/components/HelloWorld.vue')
    },
    {
      path: '/other',
      name: 'Other',
      component: () => import('@/components/Other.vue')
    }
  ]
})

嗯这样就行了

顺便也提下如何处理加载状态

这里的异步组件工厂函数也可以返回一个如下格式的对象:

const AsyncComponent = () => ({
  // 需要加载的组件 (应该是一个 `Promise` 对象)
  component: import('./MyComponent.vue'),
  // 异步组件加载时使用的组件
  loading: LoadingComponent,
  // 加载失败时使用的组件
  error: ErrorComponent,
  // 展示加载时组件的延时时间。默认值是 200 (毫秒)
  delay: 200,
  // 如果提供了超时时间且组件加载也超时了,
  // 则使用加载失败时使用的组件。默认值是:`Infinity`
  timeout: 3000
})

AwesomeHan
125 声望4 粉丝

引用和评论

0 条评论