原文出处

前面一性情能与那些申报

2018/08/22 · 基础技艺 ·
性能

初稿出处: counterxing   

概述

对于后台开采以来,记录日志是一种拾壹分常见的开销习贯,平日大家会动用try...catch代码块来积极抓获错误、对于每一遍接口调用,也会记录下每一回接口调用的光阴开销,以便我们监察和控制服务器接口质量,实行难题排查。

刚进集团时,在张开Node.js的接口开拓时,小编不太习于旧贯每便排查难题都要透过跳板机登上服务器看日志,后来逐渐习贯了这种艺术。

比如:

JavaScript

/** * 获取列表数据 * @parma req, res */ exports.getList = async
function (req, res) { //获取须求参数 const openId =
req.session.userinfo.openId; logger.info(`handler getList, user openId
is ${openId}`); try { // 得到列表数据 const startTime = new
Date().getTime(); let res = await ListService.getListFromDB(openId);
logger.info(`handler getList, ListService.getListFromDB cost time ${new
Date().getTime() – startDate}`); // 对数码管理,再次回到给前端 // … }
catch(error) { logger.error(`handler getList is error,
${JSON.stringify(error)}`); } };

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
* 获取列表数据
* @parma req, res
*/
exports.getList = async function (req, res) {
    //获取请求参数
    const openId = req.session.userinfo.openId;
    logger.info(`handler getList, user openId is ${openId}`);
 
    try {
        // 拿到列表数据
        const startTime = new Date().getTime();
        let res = await ListService.getListFromDB(openId);
        logger.info(`handler getList, ListService.getListFromDB cost time ${new Date().getTime() – startDate}`);
        // 对数据处理,返回给前端
        // …
    } catch(error) {
        logger.error(`handler getList is error, ${JSON.stringify(error)}`);
    }
};

以下代码平常会产出在用Node.js的接口中,在接口中会总计查询DB所耗费时间间、亦只怕总计RPC劳务调用所耗费时间间,以便监测品质瓶颈,对质量做优化;又大概对极度使用try ... catch积极抓获,以便随时对难题开始展览追思、还原难点的场地,进行bug的修复。

而对于前端来讲呢?能够看之下的气象。

不久前在拓展一个急需开拓时,不常开掘webgl渲染印象失利的处境,大概说印象会油然则生分析战败的状态,大家或者根本不掌握哪张印象会深入分析或渲染退步;又或如眼前支出的别的二个急需,大家会做叁个有关webgl渲染时间的优化和影象预加载的须求,假设缺少品质监控,该怎么总计所做的渲染优化和影象预加载优化的优化比例,怎么样验证自个儿所做的作业具备价值吗?大概是经过测验同学的黑盒测量试验,对优化前后的年华实行录屏,分析从踏入页面到映像渲染完毕到底经过了略微帧图像。那样的多寡,大概既不纯粹、又相比片面,设想测量试验同学并不是当真的用户,也不能苏醒真实的用户他们所处的互联网情状。回过头来发掘,我们的项目,尽管在服务端层面做好了日志和品质总结,但在后边三个对特别的督察和总体性的总括。对于前端的性质与这一个申报的势头索求是有必不可缺的。

老大捕获

对于前端来讲,大家需求的不得了捕获无非为以下三种:

  • 接口调用情形;
  • 页面逻辑是否错误,比方,用户踏向页面后页面显示白屏;

对此接口调用处境,在前端平时供给报告客户端相关参数,譬如:用户OS与浏览器版本、央求参数(如页面ID);而对于页面逻辑是还是不是错误难点,常常除了用户OS与浏览器版本外,须要的是报错的仓库音讯及切实报错地方。

那三个捕获方法

大局捕获

可以经过全局监听相当来捕获,通过window.onerror或者addEventListener,看之下例子:

JavaScript

window.onerror = function(errorMessage, scriptUHavalI, lineNo, columnNo,
error) { console.log(‘errorMessage: ‘ + errorMessage); // 万分音信console.log(‘scriptU凯雷德I: ‘ + scriptU汉兰达I); // 分外文件路径console.log(‘lineNo: ‘ + lineNo); // 非凡行号 console.log(‘columnNo: ‘ +
columnNo); // 卓殊列号 console.log(‘error: ‘ + error); // 格外货仓消息// … // 相当上报 }; throw new Error(‘那是一个荒谬’);

1
2
3
4
5
6
7
8
9
10
window.onerror = function(errorMessage, scriptURI, lineNo, columnNo, error) {
  console.log(‘errorMessage: ‘ + errorMessage); // 异常信息
  console.log(‘scriptURI: ‘ + scriptURI); // 异常文件路径
  console.log(‘lineNo: ‘ + lineNo); // 异常行号
  console.log(‘columnNo: ‘ + columnNo); // 异常列号
  console.log(‘error: ‘ + error); // 异常堆栈信息
  // …
  // 异常上报
};
throw new Error(‘这是一个错误’);

图片 1

通过window.onerror事件,可以拿走实际的不行音讯、格外文件的UENVISIONL、卓殊的行号与列号及极度的饭店信息,再捕获格外后,统一举报至我们的日志服务器。

亦或是,通过window.addEventListener主意来进行特别申报,道理同理:

JavaScript

window.addEventListener(‘error’, function() { console.log(error); // …
// 分外上报 }); throw new Error(‘那是三个荒唐’);

1
2
3
4
5
6
window.addEventListener(‘error’, function() {
  console.log(error);
  // …
  // 异常上报
});
throw new Error(‘这是一个错误’);

图片 2

try… catch

使用try... catch虽说能够较好地开始展览丰盛捕获,不至于使得页面由于一处错误挂掉,但try ... catch抓获情势展现过于臃肿,相当多代码应用try ... catch装进,影响代码可读性。

大面积难点

跨域脚本不也许正确捕获格外

常备情况下,大家会把静态财富,如JavaScript剧本放到特地的静态财富服务器,亦大概CDN,看之下例子:

<!DOCTYPE html> <html> <head>
<title></title> </head> <body> <script
type=”text/javascript”> // 在index.html window.onerror =
function(errorMessage, scriptU牧马人I, lineNo, columnNo, error) {
console.log(‘errorMessage: ‘ + errorMessage); // 卓殊新闻console.log(‘scriptUWranglerI: ‘ + scriptUSportageI); // 十分文件路线console.log(‘lineNo: ‘ + lineNo); // 十分行号 console.log(‘columnNo: ‘ +
columnNo); // 非常列号 console.log(‘error: ‘ + error); // 十分仓库信息// … // 极度上报 }; </script> <script
src=”./error.js”></script> </body> </html>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<!DOCTYPE html>
<html>
<head>
  <title></title>
</head>
<body>
  <script type="text/javascript">
    // 在index.html
    window.onerror = function(errorMessage, scriptURI, lineNo, columnNo, error) {
      console.log(‘errorMessage: ‘ + errorMessage); // 异常信息
      console.log(‘scriptURI: ‘ + scriptURI); // 异常文件路径
      console.log(‘lineNo: ‘ + lineNo); // 异常行号
      console.log(‘columnNo: ‘ + columnNo); // 异常列号
      console.log(‘error: ‘ + error); // 异常堆栈信息
      // …
      // 异常上报
    };
 
  </script>
  <script src="./error.js"></script>
</body>
</html>

JavaScript

// error.js throw new Error(‘那是三个不当’);

1
2
// error.js
throw new Error(‘这是一个错误’);

图片 3

结果展现,跨域之后window.onerror常有捕获不到精确的可怜音信,而是统一重临八个Script error

化解方案:对script标签增添贰个crossorigin=”anonymous”,并且服务器加多Access-Control-Allow-Origin

<script src=””
crossorigin=”anonymous”></script>

1
<script src="http://cdn.xxx.com/index.js" crossorigin="anonymous"></script>

sourceMap

万般在生养蒙受下的代码是经过webpack打包后回降混淆的代码,所以我们大概会遇上那样的标题,如图所示:

图片 4

大家开采具备的报错的代码行数都在第一行了,为何吧?那是因为在生育碰到下,我们的代码被压缩成了一条龙:

JavaScript

!function(e){var n={};function r(o){if(n[o])return n[o].exports;var
t=n[o]={i:o,l:!1,exports:{}};return
e[o].call(t.exports,t,t.exports,r),t.l=!0,t.exports}r.m=e,r.c=n,r.d=function(e,n,o){r.o(e,n)||Object.defineProperty(e,n,{enumerable:!0,get:o})},r.r=function(e){“undefined”!=typeof
Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:”Module”}),Object.defineProperty(e,”__esModule”,{value:!0})},r.t=function(e,n){if(1&n&&(e=r(e)),8&n)return
e;if(4&n&&”object”==typeof e&&e&&e.__esModule)return e;var
o=Object.create(null);if(r.r(o),Object.defineProperty(o,”default”,{enumerable:!0,value:e}),2&n&&”string”!=typeof
e)for(var t in e)r.d(o,t,function(n){return e[n]}.bind(null,t));return
o},r.n=function(e){var n=e&&e.__esModule?function(){return
e.default}:function(){return e};return
r.d(n,”a”,n),n},r.o=function(e,n){return
Object.prototype.hasOwnProperty.call(e,n)},r.p=””,r(r.s=0)}([function(e,n){throw
window.onerror=function(e,n,r,o,t){console.log(“errorMessage:
“+e),console.log(“scriptURI: “+n),console.log(“lineNo:
“+r),console.log(“columnNo: “+o),console.log(“error: “+t);var
l={errorMessage:e||null,scriptURI:n||null,lineNo:r||null,columnNo:o||null,stack:t&&t.stack?t.stack:null};if(XMLHttpRequest){var
u=new
XMLHttpRequest;u.open(“post”,”/middleware/errorMsg”,!0),u.setRequestHeader(“Content-Type”,”application/json”),u.send(JSON.stringify(l))}},new
Error(“那是三个错误”)}]);

1
!function(e){var n={};function r(o){if(n[o])return n[o].exports;var t=n[o]={i:o,l:!1,exports:{}};return e[o].call(t.exports,t,t.exports,r),t.l=!0,t.exports}r.m=e,r.c=n,r.d=function(e,n,o){r.o(e,n)||Object.defineProperty(e,n,{enumerable:!0,get:o})},r.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},r.t=function(e,n){if(1&n&&(e=r(e)),8&n)return e;if(4&n&&"object"==typeof e&&e&&e.__esModule)return e;var o=Object.create(null);if(r.r(o),Object.defineProperty(o,"default",{enumerable:!0,value:e}),2&n&&"string"!=typeof e)for(var t in e)r.d(o,t,function(n){return e[n]}.bind(null,t));return o},r.n=function(e){var n=e&&e.__esModule?function(){return e.default}:function(){return e};return r.d(n,"a",n),n},r.o=function(e,n){return Object.prototype.hasOwnProperty.call(e,n)},r.p="",r(r.s=0)}([function(e,n){throw window.onerror=function(e,n,r,o,t){console.log("errorMessage: "+e),console.log("scriptURI: "+n),console.log("lineNo: "+r),console.log("columnNo: "+o),console.log("error: "+t);var l={errorMessage:e||null,scriptURI:n||null,lineNo:r||null,columnNo:o||null,stack:t&&t.stack?t.stack:null};if(XMLHttpRequest){var u=new XMLHttpRequest;u.open("post","/middleware/errorMsg",!0),u.setRequestHeader("Content-Type","application/json"),u.send(JSON.stringify(l))}},new Error("这是一个错误")}]);

在自己的开辟进度中也赶过过这么些标题,小编在付出叁个功力组件库的时候,使用npm link了自家的零部件库,可是由于组件库被npm link后是包裹后的生育条件下的代码,全体的报错都定位到了第一行。

消除办法是翻开webpack原文出处。的source-map,大家选取webpack打包后的改造的一份.map的本子文件就能够让浏览器对错误地方打开追踪了。此处能够参见webpack
document。

事实上正是webpack.config.js中丰盛一行devtool: 'source-map',如下所示,为示范的webpack.config.js

JavaScript

var path = require(‘path’); module.exports = { devtool: ‘source-map’,
mode: ‘development’, entry: ‘./client/index.js’, output: { filename:
‘bundle.js’, path: path.resolve(__dirname, ‘client’) } }

1
2
3
4
5
6
7
8
9
10
var path = require(‘path’);
module.exports = {
    devtool: ‘source-map’,
    mode: ‘development’,
    entry: ‘./client/index.js’,
    output: {
        filename: ‘bundle.js’,
        path: path.resolve(__dirname, ‘client’)
    }
}

webpack卷入后变卦对应的source-map,这样浏览器就可见稳固到现实错误的职位:

图片 5

开启source-map的症结是包容性,如今只有Chrome原文出处。浏览器和Firefox浏览器才对source-map支撑。可是大家对这一类情形也会有化解办法。能够使用引进npm库来帮助source-map,能够参照他事他说加以考察mozilla/source-map。这个npm库不只能够运作在客户端也足以运作在服务端,不过更为推荐的是在服务端使用Node.js对接受到的日志音讯时利用source-map深入分析,防止止源代码的走漏导致危机,如下代码所示:

JavaScript

const express = require(‘express’); const fs = require(‘fs’); const
router = express.Router(); const sourceMap = require(‘source-map’);
const path = require(‘path’); const resolve = file =>
path.resolve(__dirname, file); // 定义post接口 router.get(‘/error/’,
async function(req, res) { // 获取前端传过来的报错对象 let error =
JSON.parse(req.query.error); let url = error.scriptUXC60I; // 压缩文件路径if (url) { let fileUrl = url.slice(url.indexOf(‘client/’)) + ‘.map’; //
map文件路线 // 深入分析sourceMap let consumer = await new
sourceMap.SourceMapConsumer(fs.readFileSync(resolve(‘../’ + fileUrl),
‘utf8’)); // 再次来到二个promise对象 // 剖析原始报错数据 let result =
consumer.originalPositionFor({ line: error.lineNo, // 压缩后的行号
column: error.columnNo // 压缩后的列号 }); console.log(result); } });
module.exports = router;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
const express = require(‘express’);
const fs = require(‘fs’);
const router = express.Router();
const sourceMap = require(‘source-map’);
const path = require(‘path’);
const resolve = file => path.resolve(__dirname, file);
// 定义post接口
router.get(‘/error/’, async function(req, res) {
    // 获取前端传过来的报错对象
    let error = JSON.parse(req.query.error);
    let url = error.scriptURI; // 压缩文件路径
    if (url) {
        let fileUrl = url.slice(url.indexOf(‘client/’)) + ‘.map’; // map文件路径
        // 解析sourceMap
        let consumer = await new sourceMap.SourceMapConsumer(fs.readFileSync(resolve(‘../’ + fileUrl), ‘utf8’)); // 返回一个promise对象
        // 解析原始报错数据
        let result = consumer.originalPositionFor({
            line: error.lineNo, // 压缩后的行号
            column: error.columnNo // 压缩后的列号
        });
        console.log(result);
    }
});
module.exports = router;

正如图所示,大家早就足以看看,在服务端已经打响深入分析出了实际错误的行号、列号,我们能够通过日记的方法展开记录,达到了前面二个分外监察和控制的指标。

图片 6

Vue捕获极度

在本身的门类中就碰见那样的难题,使用了js-tracker这么的插件来归并实行全局的要命捕获和日志上报,结果发掘大家平素捕获不到Vue零件的特别,查阅资料获悉,在Vue中,极度可能被Vue自身给try ... catch了,不会传出window.onerror事件触发,那么大家怎么把Vue零件中的非常香港作家联谊会晤捕获呢?

使用Vue.config.errorHandler这样的Vue大局配置,能够在Vue钦命组件的渲染和观看比赛时期未捕获错误的管理函数。这么些管理函数被调用时,可获得错误消息和Vue
实例。

JavaScript

Vue.config.errorHandler = function (err, vm, info) { // handle error //
`info` 是 Vue 特定的错误新闻,比方错误所在的生命周期钩子 // 只在
2.2.0+ 可用 }

1
2
3
4
5
Vue.config.errorHandler = function (err, vm, info) {
  // handle error
  // `info` 是 Vue 特定的错误信息,比如错误所在的生命周期钩子
  // 只在 2.2.0+ 可用
}

React中,能够选拔ErrorBoundary组件包罗专门的学问组件的秘技进行非常捕获,同盟React 16.0+新出的componentDidCatch API原文出处。,能够兑现统一的不得了捕获和日志上报。

JavaScript

class ErrorBoundary extends React.Component { constructor(props) {
super(props); this.state = { hasError: false }; }
componentDidCatch(error, info) { // Display fallback UI this.setState({
hasError: true }); // You can also log the error to an error reporting
service logErrorToMyService(error, info); } render() { if
(this.state.hasError) { // You can render any custom fallback UI return
<h1>Something went wrong.</h1>; } return
this.props.children; } }

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class ErrorBoundary extends React.Component {
  constructor(props) {
    super(props);
    this.state = { hasError: false };
  }
 
  componentDidCatch(error, info) {
    // Display fallback UI
    this.setState({ hasError: true });
    // You can also log the error to an error reporting service
    logErrorToMyService(error, info);
  }
 
  render() {
    if (this.state.hasError) {
      // You can render any custom fallback UI
      return <h1>Something went wrong.</h1>;
    }
    return this.props.children;
  }
}

选取方法如下:

<ErrorBoundary> <MyWidget /> </ErrorBoundary>

1
2
3
<ErrorBoundary>
  <MyWidget />
</ErrorBoundary>

属性监察和控制

最轻巧易行的属性监察和控制

最广大的习性监察和控制要求则是索要大家总结用户从开首供给页面到全部DOM要素渲染达成的时辰,也便是俗称的首屏加载时间,DOM提供了这一接口,监听documentDOMContentLoaded事件与windowload事件可总计页面首屏加载时间即怀有DOM渲染时间:

<!DOCTYPE html> <html> <head>
<title></title> <script type=”text/javascript”> //
记录页面加载开始时间 var timerStart = Date.now(); </script>
<!– 加载静态财富,如样式能源 –> </head> <body>
<!– 加载静态JS能源 –> <script type=”text/javascript”>
document.add伊夫ntListener(‘DOMContentLoaded’, function() {
console.log(“DOM 挂载时间: “, Date.now() – timerStart); // 质量日志上报
}); window.addEventListener(‘load’, function() {
console.log(“全部财富加载成功时间: “, Date.now()-timerStart); //
质量日志上报 }); </script> </body> </html>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<!DOCTYPE html>
<html>
<head>
  <title></title>
  <script type="text/javascript">
    // 记录页面加载开始时间
    var timerStart = Date.now();
  </script>
  <!– 加载静态资源,如样式资源 –>
</head>
<body>
  <!– 加载静态JS资源 –>
  <script type="text/javascript">
    document.addEventListener(‘DOMContentLoaded’, function() {
      console.log("DOM 挂载时间: ", Date.now() – timerStart);
      // 性能日志上报
    });
    window.addEventListener(‘load’, function() {
      console.log("所有资源加载完成时间: ", Date.now()-timerStart);
      // 性能日志上报
    });
  </script>
</body>
</html>

对此利用框架,如Vue或者说React,组件是异步渲染然后挂载到DOM的,在页面开首化时并从未太多的DOM节点,能够参照下文关于首屏时间收罗自动化的消除方案来对渲染时间进行贿赂。

performance

但是上述时间的监督检查过于简短,比如大家想总括文档的互联网加载耗费时间、分析DOM的耗时与渲染DOM的耗费时间,就不太好办到了,所幸的是浏览器提供了window.performance接口,具体可知MDN文档

图片 7

大约全部浏览器都帮衬window.performance接口,上边来走访在调节台打字与印刷window.performance可以拿走些什么:

图片 8

能够看看,window,performance根本满含有memorynavigationtiming以及timeOriginonresourcetimingbufferfull方法。

  • navigation对象提供了在钦定的时刻段里发生的操作相关音讯,满含页面是加载依旧刷新、产生了有一点次重定向等等。
  • timing目的涵盖延迟相关的品质消息。那是大家页面加载质量优化须求中主要性反映的相关音讯。
  • memoryChrome增加的一个非标准扩大,这些性格提供了一个足以得到到骨干内部存款和储蓄器使用情况的目的。在另外浏览器应该考虑到这一个API的协作管理。
  • timeOrigin则赶回品质衡量起来时的岁月的高精度时间戳。如图所示,精确到了小数点后四个人。
  • onresourcetimingbufferfull艺术,它是三个在resourcetimingbufferfull事件触发时会被调用的event handler。这一个事件当浏览器的能源时间性能缓冲区已满时会触发。能够透过监听这一平地风波触发来预估页面crash,总括页面crash可能率,以便前期的特性优化,如下示例所示:
JavaScript

function buffer\_full(event) { console.log("WARNING: Resource Timing
Buffer is FULL!"); performance.setResourceTimingBufferSize(200); }
function init() { // Set a callback if the resource buffer becomes
filled performance.onresourcetimingbufferfull = buffer\_full; }
&lt;body onload="init()"&gt;

<table>
<colgroup>
<col style="width: 50%" />
<col style="width: 50%" />
</colgroup>
<tbody>
<tr class="odd">
<td><div class="crayon-nums-content" style="font-size: 13px !important; line-height: 15px !important;">
<div class="crayon-num" data-line="crayon-5b8f00bfee161383152889-1">
1
</div>
<div class="crayon-num crayon-striped-num" data-line="crayon-5b8f00bfee161383152889-2">
2
</div>
<div class="crayon-num" data-line="crayon-5b8f00bfee161383152889-3">
3
</div>
<div class="crayon-num crayon-striped-num" data-line="crayon-5b8f00bfee161383152889-4">
4
</div>
<div class="crayon-num" data-line="crayon-5b8f00bfee161383152889-5">
5
</div>
<div class="crayon-num crayon-striped-num" data-line="crayon-5b8f00bfee161383152889-6">
6
</div>
<div class="crayon-num" data-line="crayon-5b8f00bfee161383152889-7">
7
</div>
<div class="crayon-num crayon-striped-num" data-line="crayon-5b8f00bfee161383152889-8">
8
</div>
<div class="crayon-num" data-line="crayon-5b8f00bfee161383152889-9">
9
</div>
</div></td>
<td><div class="crayon-pre" style="font-size: 13px !important; line-height: 15px !important; -moz-tab-size:4; -o-tab-size:4; -webkit-tab-size:4; tab-size:4;">
<div id="crayon-5b8f00bfee161383152889-1" class="crayon-line">
function buffer_full(event) {
</div>
<div id="crayon-5b8f00bfee161383152889-2" class="crayon-line crayon-striped-line">
  console.log(&quot;WARNING: Resource Timing Buffer is FULL!&quot;);
</div>
<div id="crayon-5b8f00bfee161383152889-3" class="crayon-line">
  performance.setResourceTimingBufferSize(200);
</div>
<div id="crayon-5b8f00bfee161383152889-4" class="crayon-line crayon-striped-line">
}
</div>
<div id="crayon-5b8f00bfee161383152889-5" class="crayon-line">
function init() {
</div>
<div id="crayon-5b8f00bfee161383152889-6" class="crayon-line crayon-striped-line">
  // Set a callback if the resource buffer becomes filled
</div>
<div id="crayon-5b8f00bfee161383152889-7" class="crayon-line">
  performance.onresourcetimingbufferfull = buffer_full;
</div>
<div id="crayon-5b8f00bfee161383152889-8" class="crayon-line crayon-striped-line">
}
</div>
<div id="crayon-5b8f00bfee161383152889-9" class="crayon-line">
&lt;body onload=&quot;init()&quot;&gt;
</div>
</div></td>
</tr>
</tbody>
</table>

测算网址品质

使用performancetiming品质,能够获得页面品质相关的数量,这里在许多篇章都有涉嫌关于利用window.performance.timing记录页面品质的篇章,举例alloyteam公司写的初探
performance –
监察和控制网页与程序品质,对于timing的种种品质含义,能够依据摘自此文的下图掌握,以下代码摘自此文作为计量网站品质的工具函数参考:

图片 9

JavaScript

// 获取 performance 数据 var performance = { // memory
是非标准属性,只在 Chrome 有 // 能源难点:我有个别许内部存款和储蓄器 memory: {
usedJSHeapSize: 16一千00, // JS
对象(蕴涵V8引擎内部对象)占用的内部存款和储蓄器,一定小于 totalJSHeapSize
totalJSHeapSize: 35一千00, // 可使用的内部存款和储蓄器 jsHeapSizeLimit: 792000000 //
内部存款和储蓄器大小限制 }, // 经济学难题:作者从何地来? navigation: { redirectCount:
0, // 如若有重定向的话,页面通过一次重定向跳转而来 type: 0 // 0 即
TYPE_NAVIGATENEXT 符合规律进入的页面(非刷新、非重定向等) // 1 即
TYPE_RELOAD 通过 window.location.reload() 刷新的页面 // 2 即
TYPE_BACK_FO宝马X5WALANDD 通过浏览器的升高后退按键步入的页面(历史记录) //
255 即 TYPE_UNDEFINED 非以上措施进入的页面 }, timing: { //
在同三个浏览器上下文中,前三个网页(与当下页面不确定同域)unload
的光阴戳,假诺无前一个网页 unload ,则与 fetchStart 值相等
navigationStart: 144111269一九三五, // 前一个网页(与眼下页面同域)unload
的命宫戳,要是无前一个网页 unload 可能前贰个网页与当下页面差别域,则值为
0 unload伊芙ntStart: 0, // 和 unloadEventStart 相对应,再次回到前四个网页
unload 事件绑定的回调函数实行完结的时辰戳 unload伊夫ntEnd: 0, // 第三个HTTP 重定向发生时的时间。有跳转且是同域名内的重定向才算,不然值为 0
redirectStart: 0, // 最终三个 HTTP
重定向完结时的时日。有跳转且是同域名内部的重定向才算,不然值为 0
redirectEnd: 0, // 浏览器希图好应用 HTTP
恳求抓取文书档案的时光,那爆发在自己商酌本地缓存此前 fetchStart: 1441112692155,
// DNS 域名询问开头的小运,纵然应用了本地缓存(即无 DNS
查询)或长久连接,则与 fetchStart 值相等 domainLookupStart:
1441112692155, // DNS 域名询问完结的岁月,假使运用了地点缓存(即无 DNS
查询)或悠久连接,则与 fetchStart 值相等 domainLookupEnd: 1441112692155,
// HTTP(TCP) 初始树立连接的时刻,假若是长久连接,则与 fetchStart
值相等 //
注意假设在传输层发生了错误且重新创建连接,则这里体现的是新建构的连年起来的时光
connectStart: 1441112692155, // HTTP(TCP)
达成建构连接的年华(达成握手),借使是原原本本连接,则与 fetchStart 值相等
//
注意假使在传输层发生了不当且再度创制连接,则这里显得的是新确立的连天成功的岁月
// 注意这里握手停止,满含平安连接创设达成、SOCKS 授权通过 connectEnd:
1441112692155, // HTTPS 连接起来的时间,假诺不是安枕无忧连接,则值为 0
secureConnectionStart: 0, // HTTP
央求读取真实文书档案早先的时日(实现建设构造连接),包蕴从地点读取缓存 //
连接错误重连时,这里呈现的也是新确立连接的时光 requestStart:
1441112692158, // HTTP
先河收受响应的年华(获取到第三个字节),包含从本地读取缓存
responseStart: 1441112692686, // HTTP
响应全体摄取达成的年月(获取到结尾二个字节),满含从地面读取缓存
responseEnd: 1441112692687, // 开端分析渲染 DOM 树的时辰,此时
Document.readyState 变为 loading,并将抛出 readystatechange 相关事件
domLoading: 1441112692690, // 完结分析 DOM 树的时刻,Document.readyState
变为 interactive,并将抛出 readystatechange 相关事件 // 注意只是 DOM
树剖判达成,那时候并未起初加载网页内的财富 domInteractive:
1441112693093, // DOM 深入分析实现后,网页国内资本源加载开首的时光 // 在
DOMContentLoaded 事件抛出前发生 domContentLoadedEventStart:
1441112693093, // DOM 深入分析实现后,网页国内资本源加载成功的命宫(如 JS
脚本加载推行达成) domContentLoaded伊夫ntEnd: 1441112693101, // DOM
树分析达成,且财富也希图稳当的岁月,Document.readyState 变为
complete,并将抛出 readystatechange 相关事件 domComplete: 1441112693214,
// load 事件发送给文档,也即 load 回调函数开首实施的时间 //
注意若无绑定 load 事件,值为 0 load伊夫ntStart: 1441112693214, //
load 事件的回调函数试行实现的时日 load伊芙ntEnd: 1441112693215 //
字母顺序 // connectEnd: 1441112692155, // connectStart: 1441112692155,
// domComplete: 1441112693214, // domContentLoaded伊芙ntEnd:
1441112693101, // domContentLoadedEventStart: 1441112693093, //
domInteractive: 1441112693093, // domLoading: 1441112692690, //
domainLookupEnd: 1441112692155, // domainLookupStart: 1441112692155, //
fetchStart: 1441112692155, // loadEventEnd: 1441112693215, //
loadEventStart: 1441112693214, // navigationStart: 1441112691933, //
redirectEnd: 0, // redirectStart: 0, // requestStart: 1441112692158, //
responseEnd: 1441112692687, // responseStart: 1441112692686, //
secureConnectionStart: 0, // unloadEventEnd: 0, // unloadEventStart: 0 }
};

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
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
// 获取 performance 数据
var performance = {  
    // memory 是非标准属性,只在 Chrome 有
    // 财富问题:我有多少内存
    memory: {
        usedJSHeapSize:  16100000, // JS 对象(包括V8引擎内部对象)占用的内存,一定小于 totalJSHeapSize
        totalJSHeapSize: 35100000, // 可使用的内存
        jsHeapSizeLimit: 793000000 // 内存大小限制
    },
    //  哲学问题:我从哪里来?
    navigation: {
        redirectCount: 0, // 如果有重定向的话,页面通过几次重定向跳转而来
        type: 0           // 0   即 TYPE_NAVIGATENEXT 正常进入的页面(非刷新、非重定向等)
                          // 1   即 TYPE_RELOAD       通过 window.location.reload() 刷新的页面
                          // 2   即 TYPE_BACK_FORWARD 通过浏览器的前进后退按钮进入的页面(历史记录)
                          // 255 即 TYPE_UNDEFINED    非以上方式进入的页面
    },
    timing: {
        // 在同一个浏览器上下文中,前一个网页(与当前页面不一定同域)unload 的时间戳,如果无前一个网页 unload ,则与 fetchStart 值相等
        navigationStart: 1441112691935,
        // 前一个网页(与当前页面同域)unload 的时间戳,如果无前一个网页 unload 或者前一个网页与当前页面不同域,则值为 0
        unloadEventStart: 0,
        // 和 unloadEventStart 相对应,返回前一个网页 unload 事件绑定的回调函数执行完毕的时间戳
        unloadEventEnd: 0,
        // 第一个 HTTP 重定向发生时的时间。有跳转且是同域名内的重定向才算,否则值为 0
        redirectStart: 0,
        // 最后一个 HTTP 重定向完成时的时间。有跳转且是同域名内部的重定向才算,否则值为 0
        redirectEnd: 0,
        // 浏览器准备好使用 HTTP 请求抓取文档的时间,这发生在检查本地缓存之前
        fetchStart: 1441112692155,
        // DNS 域名查询开始的时间,如果使用了本地缓存(即无 DNS 查询)或持久连接,则与 fetchStart 值相等
        domainLookupStart: 1441112692155,
        // DNS 域名查询完成的时间,如果使用了本地缓存(即无 DNS 查询)或持久连接,则与 fetchStart 值相等
        domainLookupEnd: 1441112692155,
        // HTTP(TCP) 开始建立连接的时间,如果是持久连接,则与 fetchStart 值相等
        // 注意如果在传输层发生了错误且重新建立连接,则这里显示的是新建立的连接开始的时间
        connectStart: 1441112692155,
        // HTTP(TCP) 完成建立连接的时间(完成握手),如果是持久连接,则与 fetchStart 值相等
        // 注意如果在传输层发生了错误且重新建立连接,则这里显示的是新建立的连接完成的时间
        // 注意这里握手结束,包括安全连接建立完成、SOCKS 授权通过
        connectEnd: 1441112692155,
        // HTTPS 连接开始的时间,如果不是安全连接,则值为 0
        secureConnectionStart: 0,
        // HTTP 请求读取真实文档开始的时间(完成建立连接),包括从本地读取缓存
        // 连接错误重连时,这里显示的也是新建立连接的时间
        requestStart: 1441112692158,
        // HTTP 开始接收响应的时间(获取到第一个字节),包括从本地读取缓存
        responseStart: 1441112692686,
        // HTTP 响应全部接收完成的时间(获取到最后一个字节),包括从本地读取缓存
        responseEnd: 1441112692687,
        // 开始解析渲染 DOM 树的时间,此时 Document.readyState 变为 loading,并将抛出 readystatechange 相关事件
        domLoading: 1441112692690,
        // 完成解析 DOM 树的时间,Document.readyState 变为 interactive,并将抛出 readystatechange 相关事件
        // 注意只是 DOM 树解析完成,这时候并没有开始加载网页内的资源
        domInteractive: 1441112693093,
        // DOM 解析完成后,网页内资源加载开始的时间
        // 在 DOMContentLoaded 事件抛出前发生
        domContentLoadedEventStart: 1441112693093,
        // DOM 解析完成后,网页内资源加载完成的时间(如 JS 脚本加载执行完毕)
        domContentLoadedEventEnd: 1441112693101,
        // DOM 树解析完成,且资源也准备就绪的时间,Document.readyState 变为 complete,并将抛出 readystatechange 相关事件
        domComplete: 1441112693214,
        // load 事件发送给文档,也即 load 回调函数开始执行的时间
        // 注意如果没有绑定 load 事件,值为 0
        loadEventStart: 1441112693214,
        // load 事件的回调函数执行完毕的时间
        loadEventEnd: 1441112693215
        // 字母顺序
        // connectEnd: 1441112692155,
        // connectStart: 1441112692155,
        // domComplete: 1441112693214,
        // domContentLoadedEventEnd: 1441112693101,
        // domContentLoadedEventStart: 1441112693093,
        // domInteractive: 1441112693093,
        // domLoading: 1441112692690,
        // domainLookupEnd: 1441112692155,
        // domainLookupStart: 1441112692155,
        // fetchStart: 1441112692155,
        // loadEventEnd: 1441112693215,
        // loadEventStart: 1441112693214,
        // navigationStart: 1441112691935,
        // redirectEnd: 0,
        // redirectStart: 0,
        // requestStart: 1441112692158,
        // responseEnd: 1441112692687,
        // responseStart: 1441112692686,
        // secureConnectionStart: 0,
        // unloadEventEnd: 0,
        // unloadEventStart: 0
    }
};

 

JavaScript

// 总计加载时间 function getPerformanceTiming() { var performance =
window.performance; if (!performance) { // 当前浏览器不支持console.log(‘你的浏览器不扶助 performance 接口’); return; } var t =
performance.timing; var times = {}; //【主要】页面加载成功的年月
//【原因】那大约代表了用户等待页面可用的时辰 times.loadPage =
t.loadEventEnd – t.navigationStart; //【首要】剖判 DOM 树结构的时间
//【原因】反省下你的 DOM 树嵌套是否太多了! times.domReady =
t.domComplete – t.responseEnd; //【主要】重定向的时日
//【原因】拒绝重定向!举例, 就不应当写成
times.redirect = t.redirectEnd – t.redirectStart;
//【首要】DNS 查询时间 //【原因】DNS
预加载做了么?页面内是还是不是使用了太多不一致的域名导致域名查询的日子太长?
// 可应用 HTML5 Prefetch 预查询 DNS ,见:[HTML5
prefetch]()
times.lookupDomain = t.domainLookupEnd – t.domainLookupStart;
//【重要】读取页面第五个字节的小时//【原因】那能够知道为用户得到您的财富占用的时刻,加异地机房了么,加CDN
管理了么?加带宽了么?加 CPU 运算速度了么? // TTFB 即 提姆e To First
Byte 的意思 //
维基百科: times.ttfb
= t.responseStart – t.navigationStart; //【重要】内容加载成功的时刻
//【原因】页面内容通过 gzip 压缩了么,静态能源 css/js 等压缩了么?
times.request = t.responseEnd – t.requestStart; //【重要】推行 onload
回调函数的日子 //【原因】是不是太多不要求的操作都放到 onload
回调函数里进行了,思考过延迟加载、按需加载的战略么? times.loadEvent =
t.load伊夫ntEnd – t.load伊夫ntStart; // DNS 缓存时间 times.appcache =
t.domainLookupStart – t.fetchStart; // 卸载页面包车型客车大运 times.unloadEvent
= t.unload伊芙ntEnd – t.unload伊夫ntStart; // TCP 建设构造连接产生握手的年月
times.connect = t.connectEnd – t.connectStart; return times; }

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
// 计算加载时间
function getPerformanceTiming() {
    var performance = window.performance;
    if (!performance) {
        // 当前浏览器不支持
        console.log(‘你的浏览器不支持 performance 接口’);
        return;
    }
    var t = performance.timing;
    var times = {};
    //【重要】页面加载完成的时间
    //【原因】这几乎代表了用户等待页面可用的时间
    times.loadPage = t.loadEventEnd – t.navigationStart;
    //【重要】解析 DOM 树结构的时间
    //【原因】反省下你的 DOM 树嵌套是不是太多了!
    times.domReady = t.domComplete – t.responseEnd;
    //【重要】重定向的时间
    //【原因】拒绝重定向!比如,http://example.com/ 就不该写成 http://example.com
    times.redirect = t.redirectEnd – t.redirectStart;
    //【重要】DNS 查询时间
    //【原因】DNS 预加载做了么?页面内是不是使用了太多不同的域名导致域名查询的时间太长?
    // 可使用 HTML5 Prefetch 预查询 DNS ,见:[HTML5 prefetch](http://segmentfault.com/a/1190000000633364)            
    times.lookupDomain = t.domainLookupEnd – t.domainLookupStart;
    //【重要】读取页面第一个字节的时间
    //【原因】这可以理解为用户拿到你的资源占用的时间,加异地机房了么,加CDN 处理了么?加带宽了么?加 CPU 运算速度了么?
    // TTFB 即 Time To First Byte 的意思
    // 维基百科:https://en.wikipedia.org/wiki/Time_To_First_Byte
    times.ttfb = t.responseStart – t.navigationStart;
    //【重要】内容加载完成的时间
    //【原因】页面内容经过 gzip 压缩了么,静态资源 css/js 等压缩了么?
    times.request = t.responseEnd – t.requestStart;
    //【重要】执行 onload 回调函数的时间
    //【原因】是否太多不必要的操作都放到 onload 回调函数里执行了,考虑过延迟加载、按需加载的策略么?
    times.loadEvent = t.loadEventEnd – t.loadEventStart;
    // DNS 缓存时间
    times.appcache = t.domainLookupStart – t.fetchStart;
    // 卸载页面的时间
    times.unloadEvent = t.unloadEventEnd – t.unloadEventStart;
    // TCP 建立连接完成握手的时间
    times.connect = t.connectEnd – t.connectStart;
    return times;
}

日志上报

单独的日志域名

对此日记上报利用单独的日志域名的目标是防止对作业形成影响。其一,对于服务器来讲,我们自然不指望占用专门的工作服务器的猜想能源,也不期待过多的日志在专业服务器堆放,变成工作服务器的仓库储存空间相当不够的气象。其二,大家明白在页面起始化的进度中,会对页面加载时间、PV、UV等数据开始展览反馈,那个报告须要会和加载业务数据大概是同一时候刻发生,而浏览器一般会对同三个域名的诉求量有并发数的限制,如Chrome会有对并发数为6个的限量。因而需求对日记系统独立设定域名,最小化对页面加载质量产生的熏陶。

跨域的主题材料

对此单身的日记域名,分明会提到到跨域的标题,接纳的消除方案一般有以下三种:

  • 一种是布局空的Image指标的措施,其原因是呼吁图片并不关乎到跨域的难题;
JavaScript

var url = 'xxx'; new Image().src = url;

<table>
<colgroup>
<col style="width: 50%" />
<col style="width: 50%" />
</colgroup>
<tbody>
<tr class="odd">
<td><div class="crayon-nums-content" style="font-size: 13px !important; line-height: 15px !important;">
<div class="crayon-num" data-line="crayon-5b8f00bfee170123843269-1">
1
</div>
<div class="crayon-num crayon-striped-num" data-line="crayon-5b8f00bfee170123843269-2">
2
</div>
</div></td>
<td><div class="crayon-pre" style="font-size: 13px !important; line-height: 15px !important; -moz-tab-size:4; -o-tab-size:4; -webkit-tab-size:4; tab-size:4;">
<div id="crayon-5b8f00bfee170123843269-1" class="crayon-line">
var url = 'xxx';
</div>
<div id="crayon-5b8f00bfee170123843269-2" class="crayon-line crayon-striped-line">
new Image().src = url;
</div>
</div></td>
</tr>
</tbody>
</table>

  • 利用Ajax反映日志,必须对日记服务器接口开启跨域央求底部Access-Control-Allow-Origin:*,这里Ajax就并不强制行使GET呼吁了,就可以克服URL长度限制的难题。
JavaScript

if (XMLHttpRequest) { var xhr = new XMLHttpRequest();
xhr.open('post', 'https://log.xxx.com', true); //
上报给node中间层处理 xhr.setRequestHeader('Content-Type',
'application/json'); // 设置请求头
xhr.send(JSON.stringify(errorObj)); // 发送参数 }

<table>
<colgroup>
<col style="width: 50%" />
<col style="width: 50%" />
</colgroup>
<tbody>
<tr class="odd">
<td><div class="crayon-nums-content" style="font-size: 13px !important; line-height: 15px !important;">
<div class="crayon-num" data-line="crayon-5b8f00bfee174544186263-1">
1
</div>
<div class="crayon-num crayon-striped-num" data-line="crayon-5b8f00bfee174544186263-2">
2
</div>
<div class="crayon-num" data-line="crayon-5b8f00bfee174544186263-3">
3
</div>
<div class="crayon-num crayon-striped-num" data-line="crayon-5b8f00bfee174544186263-4">
4
</div>
<div class="crayon-num" data-line="crayon-5b8f00bfee174544186263-5">
5
</div>
<div class="crayon-num crayon-striped-num" data-line="crayon-5b8f00bfee174544186263-6">
6
</div>
</div></td>
<td><div class="crayon-pre" style="font-size: 13px !important; line-height: 15px !important; -moz-tab-size:4; -o-tab-size:4; -webkit-tab-size:4; tab-size:4;">
<div id="crayon-5b8f00bfee174544186263-1" class="crayon-line">
if (XMLHttpRequest) {
</div>
<div id="crayon-5b8f00bfee174544186263-2" class="crayon-line crayon-striped-line">
  var xhr = new XMLHttpRequest();
</div>
<div id="crayon-5b8f00bfee174544186263-3" class="crayon-line">
  xhr.open('post', 'https://log.xxx.com', true); // 上报给node中间层处理
</div>
<div id="crayon-5b8f00bfee174544186263-4" class="crayon-line crayon-striped-line">
  xhr.setRequestHeader('Content-Type', 'application/json'); // 设置请求头
</div>
<div id="crayon-5b8f00bfee174544186263-5" class="crayon-line">
  xhr.send(JSON.stringify(errorObj)); // 发送参数
</div>
<div id="crayon-5b8f00bfee174544186263-6" class="crayon-line crayon-striped-line">
}
</div>
</div></td>
</tr>
</tbody>
</table>

在笔者的品类中采纳的是第一种的方式,也正是结构空的Image对象,不过大家清楚对于GET恳请会有长度的界定,需求保障的是供给的长度不会超越阈值。

节省响应大旨

对于大家报告日志,其实对于客户端的话,并无需思量上报的结果,乃至对于报告战败,大家也无需在后面一个做别的交互,所以报告来讲,其实使用HEAD伸手就够了,接口重回空的结果,最大地减小上报日志变成的能源浪费。

会集反映

就像于Sprite图的研究,假设我们的利用须求报告的日志数量过多,那么有至关重要合併日志进行合併的陈说。

解决方案得以是尝尝在用户距离页面可能零部件销毁时发送二个异步的POST呼吁来展开报告,不过尝试在卸载(unload)文书档案在此以前向web服务器发送数据。保险在文书档案卸载期间发送数据平素是一个不方便。因为用户代理平时会忽略在卸载事件管理器中发生的异步XMLHttpRequest,因为那时一度会跳转到下四个页面。所以这里是必须安装为联合的XMLHttpRequest请求吗?

JavaScript

window.add伊芙ntListener(‘unload’, logData, false); function logData() {
var client = new XMLHttpRequest(); client.open(“POST”, “/log”, false);
// 第八个参数注解是一头的 xhr client.setRequestHeader(“Content-Type”,
“text/plain;charset=UTF-8”); client.send(analyticsData); }

1
2
3
4
5
6
7
8
window.addEventListener(‘unload’, logData, false);
 
function logData() {
    var client = new XMLHttpRequest();
    client.open("POST", "/log", false); // 第三个参数表明是同步的 xhr
    client.setRequestHeader("Content-Type", "text/plain;charset=UTF-8");
    client.send(analyticsData);
}

利用同步的办法势必会对用户体验产生影响,以至会让用户感受到浏览器卡死感觉,对于产品来讲,体验拾分不佳,通过查看MDN文档,能够采用sendBeacon()办法,将会使用户代理在有机遇时异步地向服务器发送数据,同期不会延迟页面包车型大巴卸载或影响下一导航的载入品质。那就解决了付出深入分析数据时的兼具的难点:使它可相信,异步况兼不会影响下一页面包车型大巴加载。其余,代码实际上还要比别的本领轻易!

上边包车型客车事例展示了二个争持上的总括代码形式——通过采用sendBeacon()措施向服务器发送数据。

JavaScript

window.addEventListener(‘unload’, logData, false); function logData() {
navigator.sendBeacon(“/log”, analyticsData); }

1
2
3
4
5
window.addEventListener(‘unload’, logData, false);
 
function logData() {
    navigator.sendBeacon("/log", analyticsData);
}

小结

作为前端开垦者来讲,要对成品保持敬畏之心,时刻保持对质量追求极致,对丰硕不可忍受的情态。前端的习性监察和控制与丰盛申报显得特别重大。

代码难免有失水准,对于充足能够运用window.onerror或者addEventListener的章程充裕全局的不胜捕获侦听函数,但恐怕使用这种方法不可能准确捕获到不当:对于跨域的脚本,供给对script标签扩张二个crossorigin=”anonymous”;对于生产条件打包的代码,不大概准确定位到非常爆发的行数,能够应用source-map来化解;而对于使用框架的事态,供给在框架统一的不胜捕获处埋点。

而对此质量的监察,所幸的是浏览器提供了window.performance API,通过这些API,很便利地得到到日前页面质量相关的数据。

而这几个至极和性质数据如何举报呢?一般说来,为了幸免对业务产生的影响,会单独创设日志服务器和日志域名,但对于不一致的域名,又会时有产生跨域的难点。我们得以经过结构空的Image目的来解决,亦可能通过设定跨域乞请底部Access-Control-Allow-Origin:*来消除。另外,如果反映的本性和日志数据高频触发,则可以在页面unload时统一申报,而unload时的异步央求又或许会被浏览器所忽略,且不能够改为共同央求。此时navigator.sendBeacon API可算帮了我们大忙,它可用以通过HTTP将小量数目异步传输到Web服务器。而忽略页面unload时的熏陶。

1 赞 1 收藏
评论

图片 10

You may also like...

发表评论

电子邮件地址不会被公开。 必填项已用*标注

网站地图xml地图