Web 前端机能剖析(二)

扼要申明

在上一篇文章《Web 前端机能剖析(一)》中,我们对前端机能相干的学问进行了进修和讨论,而且做了一个实验性子的项目用来实践和考证,本文附上主要功能模块 – web-performance.js 的源码,作为对web前端机能剖析的进修纪录。

Performance API

能够完成对网页机能的监控,重假如依托 Performance API。

  1. 《JavaScript 规范参考教程(alpha)》
  2. MDN文档

模块源码

web-performance.js

/**
 * ------------------------------------------------------------------
 * 网页机能监控
 * ------------------------------------------------------------------
 */
(function (win) {

  // 兼容的数组推断要领
  if (!Array.isArray) {
    Array.isArray = function (arg) {
      return Object.prototype.toString.call(arg) === '[object Array]';
    };
  }

  // 模块定义
  function factory() {
    var performance = win.performance;

    if (!performance) {
      // 当前浏览器不支持
      console.log("Browser does not support Web Performance");
      return;
    }

    var wp = {};
    wp.pagePerformanceInfo = null; // 纪录页面初始化机能信息
    wp.xhrInfoArr = []; // 纪录页面初始化完成前的 ajax 信息


    /**
     * performance 基础要领 & 定义主要信息字段
     * ------------------------------------------------------------------
     */

    // 盘算首页加载相干时候
    wp.getPerformanceTiming = function () {
      var t = performance.timing;
      var times = {};

      //【主要】页面加载完成的时候, 这险些代表了用户守候页面可用的时候
      times.pageLoad = t.loadEventEnd - t.navigationStart;
      //【主要】DNS 查询时候
      // times.dns = t.domainLookupEnd - t.domainLookupStart;
      //【主要】读取页面第一个字节的时候(白屏时候), 这能够理解为用户拿到你的资本占用的时候
      // TTFB 即 Time To First Byte 的意义
      times.ttfb = t.responseStart - t.navigationStart;
      //【主要】request要求耗时, 即内容加载完成的时候
      // times.request = t.responseEnd - t.requestStart;
      //【主要】剖析 DOM 树结构的时候
      // times.domParse = t.domComplete - t.responseEnd;
      //【主要】用户可操纵时候
      times.domReady = t.domContentLoadedEventEnd - t.navigationStart;
      //【主要】实行 onload 回调函数的时候
      times.onload = t.loadEventEnd - t.loadEventStart;
      // 卸载页面的时候
      // times.unloadEvent = t.unloadEventEnd - t.unloadEventStart;
      // TCP 竖立衔接完成握手的时候
      times.tcpConnect = t.connectEnd - t.connectStart;

      // 最先时候
      times.startTime = t.navigationStart;

      return times;
    };

    // 盘算单个资本加载时候
    wp.getEntryTiming = function (entry) {

      // entry 的时候点都是相对于 navigationStart 的相对时候

      var t = entry;
      var times = {};

      // 重定向的时候
      // times.redirect = t.redirectEnd - t.redirectStart;
      // DNS 查询时候
      // times.lookupDomain = t.domainLookupEnd - t.domainLookupStart;
      // TCP 竖立衔接完成握手的时候
      // times.connect = t.connectEnd - t.connectStart;

      // 用户下载时候
      times.contentDownload = t.responseEnd - t.responseStart;
      // ttfb 读取首字节的时候 守候效劳器处置惩罚
      times.ttfb = t.responseStart - t.requestStart;

      // 挂载 entry 返回
      times.resourceName = entry.name; // 资本称号, 也是资本的绝对路径
      times.entryType = entry.entryType; // 资本类型
      times.initiatorType = entry.initiatorType; // link <link> | script <script> | redirect 重定向
      times.duration = entry.duration; // 加载时候

      // 纪录最先时候
      times.connectStart = entry.connectStart;

      return times;
    }

    // 依据 type 猎取响应 entries 的 performanceTiming
    wp.getEntriesByType = function (type) {
      if (type === undefined) {
        return;
      }
      var entries = performance.getEntriesByType(type);
      return entries;
    };


    /**
     * 页面初始化机能
     * ------------------------------------------------------------------
     */
    // 猎取文件资本加载信息 js/css/img
    wp.getFileResourceTimingInfo = function () {
      var entries = performance.getEntriesByType('resource');
      var fileResourceInfo = {
        number: entries.length, // 加载文件数目
        size: 0, // 加载文件大小
      };
      return fileResourceInfo;
    };

    // 猎取页面初始化完成的耗时信息
    wp.getPageInitCompletedInfo = function () {

      // performance.now() 是相对于 navigationStart 的时候
      var endTime = performance.now();
      var pageInfo = this.getPerformanceTiming();
      pageInfo.pageInitCompleted = endTime;
      pageInfo.pageUrl = win.location.pathname;
      pageInfo.pageId = this.currentPageId;

      return pageInfo;
    };


    /**
     * xhr 相干
     * ------------------------------------------------------------------
     */
    // 处置惩罚 xhr headers 信息, 猎取传输大小
    wp.handleXHRHeaders = function (headers) {
      // Convert the header string into an array of individual headers
      var arr = headers.trim().split(/[\r\n]+/);

      // Create a map of header names to values
      var headerMap = {};
      arr.forEach(function (line) {
        var parts = line.split(': ');
        var header = parts.shift();
        var value = parts.join(': ');
        headerMap[header] = value;
      });

      return headerMap;
    };

    // 猎取 xhr 资本加载信息, 即一切的 ajax 要求的信息
    wp.getXHRResourceTimingInfo = function () {
      var entries = performance.getEntriesByType('resource');
      if (entries.length === 0) {
        return;
      }
      var xhrs = [];
      for (var i = entries.length - 1; i >= 0; i--) {
        var item = entries[i];
        if (item.initiatorType && (item.initiatorType === 'xmlhttprequest')) {
          var requestId;
          if (item.name.lastIndexOf('?r=') > -1) {
            requestId = item.name.substring(item.name.lastIndexOf('?r=') + 3);
          }
          var xhr = this.getEntryTiming(item);
          if (requestId) {
            xhr.requestId = requestId;
          }
          xhrs.push(xhr);
        }
      }
      return xhrs;
    };

    // 经由过程 requestId 猎取特定 xhr 信息
    wp.getDesignatedXHRByRequestId = function (requestId, serviceName, headers) {
      var entries = performance.getEntriesByType('resource');
      if (entries.length === 0) {
        return;
      }
      var xhr;
      for (var i = entries.length - 1; i >= 0; i--) {
        var item = entries[i];
        if (item.initiatorType && (item.initiatorType === 'xmlhttprequest')) {
          if (item.name.indexOf(requestId) > -1) {
            xhr = this.getEntryTiming(item);
            break;
          }
        }
      }

      var headerMap = this.handleXHRHeaders(headers);
      xhr.requestId = requestId;
      xhr.serviceName = serviceName;
      xhr.pageId = this.currentPageId;
      xhr.pageUrl = win.location.pathname;
      xhr.transferSize = headerMap['content-length'];
      xhr.startTime = performance.timing.navigationStart + parseInt(xhr.connectStart);
      xhr.downloadSpeed = (xhr.transferSize / 1024) / (xhr.contentDownload / 1000);

      return xhr;
    };


    /**
     * 客户端存取 xhr 数据
     * ------------------------------------------------------------------
     */
    // 存储 xhr 信息到客户端 localStorage 中
    wp.setItemToLocalStorage = function (xhr) {
      var arrayObjectLocal = this.getItemFromLocalStorage();
      if (arrayObjectLocal && Array.isArray(arrayObjectLocal)) {
        arrayObjectLocal.push(xhr);
        try {
          localStorage.setItem('webperformance', JSON.stringify(arrayObjectLocal));
        } catch (e) {
          if (e.name == 'QuotaExceededError') {
            // 假如 localStorage 超限, 移除我们设置的数据, 不再存储
            localStorage.removeItem('webperformance');
          }
        }
      }
    };

    // 猎取客户端存储的 xhr 信息, 返回数组情势
    wp.getItemFromLocalStorage = function () {
      if (!win.localStorage) {
        // 当前浏览器不支持
        console.log('Browser does not support localStorage');
        return;
      }
      var localStorage = win.localStorage;
      var arrayObjectLocal = JSON.parse(localStorage.getItem('webperformance')) || [];
      return arrayObjectLocal;
    };

    // 移除客户端存储的 xhr 信息
    wp.removeItemFromLocalStorage = function () {
      if (!win.localStorage) {
        // 当前浏览器不支持
        console.log('Browser does not support localStorage');
        return;
      }
      localStorage.removeItem('webperformance');
    };


    /**
     * 东西要领
     * ------------------------------------------------------------------
     */
    // 天生唯一标识
    wp.generateGUID = function () {
      var d = new Date().getTime();
      if (typeof performance !== 'undefined' && typeof performance.now === 'function') {
        d += performance.now();
      }
      return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
        var r = (d + Math.random() * 16) % 16 | 0;
        d = Math.floor(d / 16);
        return (c === 'x' ? r : (r & 0x3 | 0x8)).toString(16);
      });
    };


    /**
     * 封装 xhr 要求
     * ------------------------------------------------------------------
     */
    wp.ajax = (function () {
      var URL = '../UpdataProfilerHandler.aspx';
      var ajax = function (type, input, success, error) {
        var data = 'name=' + type + '&data=' + escape(JSON.stringify(input));
        var xhr = new XMLHttpRequest();
        xhr.open('POST', URL, true);
        xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
        xhr.onreadystatechange = function () {
          if ((xhr.readyState == 4) && (xhr.status == 200)) {
            var result = JSON.parse(xhr.responseText);
            success && success(result);
          }
        };
        xhr.send(data);
      };

      return ajax;
    })();


    /**
     * 上报效劳器
     * ------------------------------------------------------------------
     */

    // 上报效劳器页面初始化机能信息
    wp.sendPagePerformanceInfoToServer = function () {
      var pageInfo = this.getPageInitCompletedInfo();

      this.showInfoOnPage(pageInfo, 'page'); // 要在纪录 this.pagePerformanceInfo 之前挪用
      this.showInfoOnPage(this.xhrInfoArr, 'ajax');

      this.pagePerformanceInfo = JSON.parse(JSON.stringify(pageInfo));

      try {
        this.ajax('Page', pageInfo, function () {
          console.log('send page performance info success')
        });
      } catch (e) {
        throw e;
      }
    };

    // 上报效劳器 xhr 信息
    wp.sendXHRPerformanceInfoToServer = function () {
      var xhrInfo = this.getItemFromLocalStorage();
      if (!xhrInfo || xhrInfo.length === 0) {
        return;
      }
      try {
        this.ajax('Ajax', xhrInfo, function () {
          console.log('send ajax performance info success')
          wp.removeItemFromLocalStorage();
        });
      } catch (e) {
        throw e;
      }
    };

    // 上报效劳器
    wp.sendPerformanceInfoToServer = function () {
      if (this.pagePerformanceInfo) {
        return;
      }
      this.sendPagePerformanceInfoToServer();
      this.sendXHRPerformanceInfoToServer();
    };


    /**
     * 当前页面数据展现(开辟调试用)
     * ------------------------------------------------------------------
     */
    // 页面信息形貌
    // var pageInfoDescribe = {
    //   pageLoad: '加载用时(ms)',
    //   pageInitCompleted: '初始化完成(ms)'
    // };

    // 要求信息形貌
    // var xhrInfoDescribe = {
    //   serviceName: '效劳称号',
    //   ttfb: '效劳器处置惩罚(ms)',
    //   contentDownload: '数据下载(ms)',
    //   transferSize: '数据大小(byte)',
    //   downloadSpeed: '下载速率(kb/s)'
    // };

    // 纪录页面初始化完成前的 ajax 信息, 或许打印初始化完成后的 ajax 信息到页面
    wp.recordAjaxInfo = function (xhr) {
      if (!this.pagePerformanceInfo) {
        this.xhrInfoArr.push(xhr);
      } else {
        this.showInfoOnPage(xhr, 'action');
      }
    };

    // 在当前页面显现相干信息
    wp.showInfoOnPage = function (info, type) {
      // 假如传入参数为空或调试开关未翻开 return
      if (!win.localStorage.getItem('windProfiler') || !info) {
        return;
      }
      info = JSON.parse(JSON.stringify(info));
      var debugInfo = document.getElementById(this.currentPageId);
      if (debugInfo === null) {
        debugInfo = document.createElement('div');
        debugInfo.id = this.currentPageId;
        debugInfo.className = 'debuginfo';
        document.body.appendChild(debugInfo);

        var style = document.createElement('style');
        style.type = "text/css";
        style.innerHTML = 'div.debuginfo{' +
          'background-color: #000;' +
          'color: #fff;' +
          'border: 1px solid sliver;' +
          'padding: 5px;' +
          'width: 500px;' +
          'height: 300px;' +
          'position: absolute;' +
          'right: 10px;' +
          'bottom: 10px;' +
          'overflow: auto;' +
          'z-index: 9999;' +
          '}' +
          'div.debuginfo table th, td{' +
          'padding: 5px;' +
          '}';
        document.getElementsByTagName('head').item(0).appendChild(style);
      }

      var title, message, table = '',
        th = '',
        td = '',
        tableHead = '<table style="border-collapse: separate;" border="1">',
        tableEnd = '</table>';
      if (type === 'page') {
        title = '页面信息';
        th += '<tr><th>加载用时(ms)</th><th>初始化完成(ms)</th></tr>';
        td += '<tr><td>' + info.pageLoad.toFixed(2) + '</td><td>' + info.pageInitCompleted.toFixed(2) + '</td></tr>';
      } else if (type === 'ajax') {
        title = '要求信息(初始化)';
        th += '<tr><th>效劳称号</th><th>效劳器耗时</th><th>下载耗时</th><th>数据大小</th><th>下载速率(kb/s)</th></tr>';
        for (var i = 0; i < info.length; i++) {
          td += '<tr><td>' + info[i].serviceName + '</td><td>' + info[i].ttfb.toFixed(2) + '</td><td>' + info[i].contentDownload.toFixed(2) +
            '</td><td>' + info[i].transferSize + '</td><td>' + info[i].downloadSpeed.toFixed(2) + '</td></tr>';
        }
      } else if (type === 'action') {
        title = '要求信息(用户操纵)';
        td += '<td>' + info.serviceName + '</td><td>' + info.ttfb.toFixed(2) + '</td><td>' + info.contentDownload.toFixed(2) +
          '</td><td>' + info.transferSize + '</td><td>' + info.downloadSpeed.toFixed(2) + '</td>';
        var actionTable = debugInfo.querySelector('.action');
        if (actionTable === null) {
          var html = '<table class="action" style="border-collapse: separate;" border="1">';
          html += '<tr><th>效劳称号</th><th>效劳器耗时</th><th>下载耗时</th><th>数据大小</th><th>下载速率(kb/s)</th></tr>';
          html += '<tr>' + td + '</tr>';
          html += '</table>';
          debugInfo.innerHTML += '<p>' + title + '</p>';
          debugInfo.innerHTML += html;
        } else {
          var tr = actionTable.insertRow(-1);
          tr.innerHTML = td;
        }
        return;
      }

      table += tableHead + th + td + tableEnd;
      debugInfo.innerHTML += '<p>' + title + '</p>';
      debugInfo.innerHTML += table + '<br>';
    };

    /**
     * 对外接口, 掌握调试页面的开关
     * ------------------------------------------------------------------
     */
    performance.windProfiler = (function (win) {
      var profiler = {
        openClientDebug: function () {
          try {
            win.localStorage.setItem('windProfiler', 'debug');
            console.log('调试已翻开,请革新页面');
          } catch (e) {
            throw e;
          }
        },
        closeClientDebug: function () {
          try {
            win.localStorage.removeItem('windProfiler');
            console.log('调试已封闭');
          } catch (e) {
            throw e;
          }
        }
      };
      return profiler;
    })(win);


    /**
     * 事宜绑定
     * ------------------------------------------------------------------
     */
    // 监听 DOMContentLoaded 事宜, 猎取文件资本加载信息
    win.document.addEventListener('DOMContentLoaded', function (event) {
      // var resourceTimingInfo = wp.getFileResourceTimingInfo();
    });

    // 监听 load 事宜, 猎取 PerformanceTiming 信息
    win.addEventListener('load', function (event) {
      // setTimeout(function () {
      //   wp.sendPagePerformanceInfoToServer();
      // }, 0);
    });

    // 天生当前页面唯一 id
    wp.currentPageId = wp.generateGUID();

    return wp;
  }

  /**
   * 模块导出, 兼容 CommonJS AMD 及 原生JS
   * ------------------------------------------------------------------
   */
  if (typeof module === "object" && typeof module.exports === "object") {
    module.exports = factory();
  } else if (typeof define === "function" && define.amd) {
    define(factory);
  } else {
    win.WebPerformance = factory();
  }

})(typeof window !== 'undefined' ? window : global);

ajax-request.js

/**
 * 封装 jquery ajax
 * 比方:
 * ajaxRequest.ajax.triggerService(
 *   'apiCommand', [敕令数据] )
 *   .then(successCallback, failureCallback);
 * );
 */
var WebPerformance = require('./web-performance'); // 网页机能监控模块
var JSON2 = require('LibsDir/json2');
var URL = '../AjaxSecureHandler.aspx?r=';
var requestIdentifier = {};
var ajaxRequest = ajaxRequest || {};
(function ($) {
  if (!$) {
    throw 'jquery猎取失利!';
  }

  ajaxRequest.json = JSON2;
  ajaxRequest.ajax = function (userOptions, serviceName, requestId) {
    userOptions = userOptions || {};

    var options = $.extend({}, ajaxRequest.ajax.defaultOpts, userOptions);
    options.success = undefined;
    options.error = undefined;

    return $.Deferred(function ($dfd) {
      $.ajax(options)
        .done(function (result, textStatus, jqXHR) {
          if (requestId === requestIdentifier[serviceName]) {
            ajaxRequest.ajax.handleResponse(result, $dfd, jqXHR, userOptions, serviceName, requestId);
          }
        })
        .fail(function (jqXHR, textStatus, errorThrown) {
          if (requestId === requestIdentifier[serviceName]) {
            // jqXHR.status
            $dfd.reject.apply(this, arguments);
            userOptions.error.apply(this, arguments);
          }
        });
    });
  };

  $.extend(ajaxRequest.ajax, {
    defaultOpts: {
      // url: '../AjaxSecureHandler.aspx',
      dataType: 'json',
      type: 'POST',
      contentType: 'application/x-www-form-urlencoded; charset=UTF-8'
    },

    handleResponse: function (result, $dfd, jqXHR, userOptions, serviceName, requestId) {
      if (!result) {
        $dfd && $dfd.reject(jqXHR, 'error response format!');
        userOptions.error(jqXHR, 'error response format!');
        return;
      }

      if (result.ErrorCode != '200') {
        // 效劳器已毛病
        $dfd && $dfd.reject(jqXHR, result.ErrorMessage);
        userOptions.error(jqXHR, result);
        return;
      }

      try {
        // 将此次要求的信息存储到客户端的 localStorage
        var headers = jqXHR.getAllResponseHeaders();
        var xhr = WebPerformance.getDesignatedXHRByRequestId(requestId, serviceName, headers);
        WebPerformance.setItemToLocalStorage(xhr);
        WebPerformance.recordAjaxInfo(xhr); // 要在胜利的回调之前挪用
      } catch (e) {throw e}

      if (result.Data) {
        // 将大于2^53的数字(16位以上)包裹双引号,防止溢出
        var jsonStr = result.Data.replace(/(:\s*)(\d{16,})(\s*,|\s*})/g, '$1"$2"$3');
        var resultData = ajaxRequest.json.parse(jsonStr);
        $dfd.resolve(resultData);
        userOptions.success && userOptions.success(resultData);

      } else {
        $dfd.resolve();
        userOptions.success && userOptions.success();
      }
    },

    buildServiceRequest: function (serviceName, input, userSuccess, userError, ajaxParams) {
      var requestData = {
        MethodAlias: serviceName,
        Parameter: input
      };

      var request = $.extend({}, ajaxParams, {
        data: 'data=' + escape(ajaxRequest.json.stringify(requestData)),
        success: userSuccess,
        error: function (jqXHR, textStatus, errorThrown) {
          console.log(serviceName, jqXHR);
          if (userError && (typeof userError === 'function')) {
            userError(jqXHR, textStatus, errorThrown);
          }
        }
      });

      return request;
    },

    triggerService: function (serviceName, input, success, error, ajaxParams) {
      var request = ajaxRequest.ajax.buildServiceRequest(serviceName, input, success, error, ajaxParams);

      // 天生此次 ajax 要求唯一标识
      var requestId = requestIdentifier[serviceName] = WebPerformance.generateGUID();
      request.url = URL + requestId;
      return ajaxRequest.ajax(request, serviceName, requestId);
    }
  });

})(jQuery);

module.exports = ajaxRequest;
    原文作者:澄海风
    原文地址: https://segmentfault.com/a/1190000018665325
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞