您的位置:奥门新浦京网址 > Wed前段 > 避免常见的六种HTML5错误用法,前端性能与异常上

避免常见的六种HTML5错误用法,前端性能与异常上

发布时间:2020-01-03 06:40编辑:Wed前段浏览(140)

    一、web缓存的种类

    1.1 数据库缓存

    我们可能听说过memcached,它就是一种数据库层面的缓存方案。数据库缓存是指,当web应用的关系比较复杂,数据库中的表很多的时候,如果频繁进行数据库查询,很容易导致数据库不堪重荷。为了提供查询的性能,将查询后的数据放到内存中进行缓存,下次查询时,直接从内存缓存直接返回,提供响应效率。

    1.2 CDN缓存

    CDN缓存一般是由网站管理员自己部署,为了让他们的网站更容易扩展并获得更好的性能。通常情况下,浏览器先向CDN网关发起Web请求,网关服务器后面对应着一台或多台负载均衡源服务器,会根据它们的负载请求,动态将请求转发到合适的源服务器上。从浏览器角度来看,整个CDN就是一个源服务器,从这个层面来说,浏览器和服务器之间的缓存机制,在这种架构下同样适用。

    1.3 代理服务器缓存

    代理服务器是浏览器和源服务器之间的中间服务器,浏览器先向这个中间服务器发起Web请求,经过处理后(比如权限验证,缓存匹配等),再将请求转发到源服务器。代理服务器缓存的运作原理跟浏览器的运作原理差不多,只是规模更大。

    1.4 浏览器缓存

    每个浏览器都实现了 HTTP 缓存,我们通过浏览器使用HTTP协议与服务器交互的时候,浏览器就会根据一套与服务器约定的规则进行缓存工作。

    1.5 应用层缓存

    应用层缓存是指我们在代码层面上做的缓存。通过代码逻辑,把曾经请求过的数据或资源等,缓存起来,再次需要数据时通过逻辑上的处理选择可用的缓存的数据。

    性能监控

    避免常见的六种HTML5错误用法

    2011/11/02 · HTML5 · 来源: 163 ued     · HTML5

    英文原文:Avoiding common HTML5 mistakes

    一、不要使用section作为div的替代品

    人们在标签使用中最常见到的错误之一就是随意将HTML5的<section>等价于<div>。

    具体地说,就是直接用作替代品(用于样式)。在XHTML或者HTML4中,我们常看到这样的代码:

    XHTML

    <!-- HTML 4-style code --> <div id="wrapper">   <div id="header">     <h1>My super duper page</h1>     <!-- Header content -->   </div>   <div id="main">     <!-- Page content -->   </div>   <div id="secondary">     <!-- Secondary content -->   </div>   <div id="footer">     <!-- Footer content -->   </div> </div>

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    <!-- HTML 4-style code -->
    <div id="wrapper">
      <div id="header">
        <h1>My super duper page</h1>
        <!-- Header content -->
      </div>
      <div id="main">
        <!-- Page content -->
      </div>
      <div id="secondary">
        <!-- Secondary content -->
      </div>
      <div id="footer">
        <!-- Footer content -->
      </div>
    </div>

    而现在在HTML5中,会是这样:

    XHTML

    <!-- 请不要复制这些代码!这是错误的! --> <section id="wrapper">   <header>     <h1>My super duper page</h1>     <!-- Header content -->   </header>   <section id="main">     <!-- Page content -->   </section>   <section id="secondary">     <!-- Secondary content -->   </section>   <footer>     <!-- Footer content -->   </footer> </section>

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    <!-- 请不要复制这些代码!这是错误的! -->
    <section id="wrapper">
      <header>
        <h1>My super duper page</h1>
        <!-- Header content -->
      </header>
      <section id="main">
        <!-- Page content -->
      </section>
      <section id="secondary">
        <!-- Secondary content -->
      </section>
      <footer>
        <!-- Footer content -->
      </footer>
    </section>

    这样使用并不正确:<section>并不是样式容器。section元素表示的是内容中用来帮助构建文档概要的语义部分。它应该包含一个头部。如果你想找一个用作页面容器的元素(就像HTML或者XHTML的风格),那么考虑如Kroc Camen所说,直接把样式写到body元素上吧。如果你仍然需要额外的样式容器,还是继续使用div吧。

    基于上述思想,下面才是正确的使用HTML5和一些ARIA roles特性的例子(注意,根据你自己的设计,你也可能需要加入div)

    XHTML

    <body>   <header>     <h1>My super duper page</h1>     <!-- Header content -->   </header>   <div role="main">     <!-- Page content -->   </div>   <aside role="complementary">     <!-- Secondary content -->   </aside>   <footer>     <!-- Footer content -->   </footer> </body>

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    <body>
      <header>
        <h1>My super duper page</h1>
        <!-- Header content -->
      </header>
      <div role="main">
        <!-- Page content -->
      </div>
      <aside role="complementary">
        <!-- Secondary content -->
      </aside>
      <footer>
        <!-- Footer content -->
      </footer>
    </body>

    如果你还是无法确定使用哪种元素,那么我建议你参考HTML5 sectioning content element flowchart

    二、只在需要的时候使用header和hgroup

    写不需要写的标签当然是毫无意义的。不幸的是,我经常看到header和hgroup被无意义的滥用。你可以阅读一下关于header和hgroup元素的两篇文章做一个详细的了解,其中内容我简单总结如下:

    • header元素表示的是一组介绍性或者导航性质的辅助文字,经常用作section的头部
    • 当头部有多层结构时,比如有子头部,副标题,各种标识文字等,使用hgroup将h1-h6元素组合起来作为section的头部

    header的滥用

    由于header可以在一个文档中使用多次,可能使得这样代码风格受到欢迎:

    XHTML

    <!-- 请不要复制这段代码!此处并不需要header --> <article>   <header>     <h1>My best blog post</h1>   </header>   <!-- Article content --> </article>

    1
    2
    3
    4
    5
    6
    7
    <!-- 请不要复制这段代码!此处并不需要header -->
    <article>
      <header>
        <h1>My best blog post</h1>
      </header>
      <!-- Article content -->
    </article>

    如果你的header元素只包含一个头部元素,那么丢弃header元素吧。既然article元素已经保证了头部会出现在文档概要中,而header又不能包含多个元素(如上文所定义的),那么为什么要写多余的代码。简单点写成这样就行了:

    XHTML

    <article>   <h1>My best blog post</h1>   <!-- Article content --> </article>

    1
    2
    3
    4
    <article>
      <h1>My best blog post</h1>
      <!-- Article content -->
    </article>

    <hgroup>的错误使用

    在headers这个主题上,我也经常看到hgroup的错误使用。有时候不应该同时使用hgroup和header:

    • 如果只有一个子头部
    • 如果hgroup自己就能工作的很好。。。这不废话么

    第一个问题一般是这样的:

    XHTML

    <!-- 请不要复制这段代码!此处不需要hgroup --> <header>   <hgroup>     <h1>My best blog post</h1>   </hgroup>   <p>by Rich Clark</p> </header>

    1
    2
    3
    4
    5
    6
    7
    <!-- 请不要复制这段代码!此处不需要hgroup -->
    <header>
      <hgroup>
        <h1>My best blog post</h1>
      </hgroup>
      <p>by Rich Clark</p>
    </header>

    此例中,直接拿掉hgroup,让heading果奔吧。

    XHTML

    <header>   <h1>My best blog post</h1>   <p>by Rich Clark</p> </header>

    1
    2
    3
    4
    <header>
      <h1>My best blog post</h1>
      <p>by Rich Clark</p>
    </header>

    第二个问题是另一个不必要的例子:

    XHTML

    <!-- 请不要复制这段代码!此处不需要header --> <header>   <hgroup>     <h1>My company</h1>     <h2>Established 1893</h2>   </hgroup> </header>

    1
    2
    3
    4
    5
    6
    7
    <!-- 请不要复制这段代码!此处不需要header -->
    <header>
      <hgroup>
        <h1>My company</h1>
        <h2>Established 1893</h2>
      </hgroup>
    </header>

    如果header唯一的子元素是hgroup,那还要header干神马?如果header中没有其他的元素(比如多个hgroup),还是直接拿掉header吧

    XHTML

    <hgroup>   <h1>My company</h1>   <h2>Established 1893</h2> </hgroup>

    1
    2
    3
    4
    <hgroup>
      <h1>My company</h1>
      <h2>Established 1893</h2>
    </hgroup>

    关于<hgroup>更多的例子和解释,请参阅相关文章

    三、不要把所有列表式的链接放在nav里

    随着HTML5引入了30个新元素(截止到原文发布时),我们在构造语义化和结构化的标签时的选择也变得有些不慎重。也就是说,我们不应该滥用超语义化的元素。不幸的是,nav就是这样一个被滥用的例子。nav元素的规范描述如下:

    nav元素表示页面中链接到其他页面或者本页面其他部分的区块;包含导航连接的区块。

    注意:不是所有页面上的链接都需要放在nav元素中——这个元素本意是用作主要的导航区块。举个具体的例子,在footer中经常会有众多的链接,比如服 务条款,主页,版权声明页等等。footer元素自身已经足以应付这些情况,虽然nav元素也可以用在这里,但通常我们认为是不必要的。

    WHATWG HTML spec

    关键的词语是“主要的”导航。当然我们可以互相喷上一整天什么叫做“主要的”。而我个人是这样定义的:

    • 主要的导航
    • 站内搜索
    • 二级导航(略有争议)
    • 页面内导航(比如很长的文章)

    既然并没有绝对的对错,所以根据一个非正式投票以及我自己的解释,以下的情况,不管你放不放,我反正放在<nav>中:

    • 分页控制
    • 社交链接(虽然有些社交链接也是主要导航,比如“关于”“收藏”)
    • 博客文章的标签
    • 博客文章的分类
    • 三级导航
    • 过长的footer

    如果你不确定是否要将一系列的链接放在nav中,问你自己:“它是主要的导航吗?”为了帮助你回答这个问题,考虑以下首要原则:

    • 如果使用section和hx也同样合适,那么不要用nav — Hixie on IRC
    • 为了方便访问,你会在某个“快捷跳转”中给这个nav标签加一个链接吗?

    如果这些问题的答案是“不”,那就跟<nav>鞠个躬,然后独自离开吧。

    四、figure元素的常见错误

    figure以及figcaption的正确使用,确实是难以驾驭。让我们来看看一些常见的错误,

    不是所有的图片都是figure

    上文中,我曾告诉各位不要写不必要的代码。这个错误也是同样的道理。我看到很多网站把所有的图片都写作figure。看在图片的份上请不要给它加额外的标签了。你只是让你自己蛋疼,而并不能使你的页面内容更清晰。

    规范中将figure描述为“一些流动的内容,有时候会有包含于自身的标题说明。一般在文档流中会作为独立的单元引用。”这正是figure的美妙之处——它可以从主内容页移动到sidebar中,而不影响文档流。

    这些问题也包含在之前提到的HTML5 element flowchart中。

    如果纯粹只是为了呈现的图,也不在文档其他地方引用,那就绝对不是<figure>。其他视情况而定,但一开始可以问自己:“这个图片是否必须和上下文有关?”如果不是,那可能也不是<figure>(也许是个<aside>)。继续:“我可以把它移动到附录中吗?”如果两个问题都符合,则它可能是 <figure>

    Logo并不是figure

    进一步的说,logo也不适用于figure。下面是我常见的一些代码片段:

    XHTML

    <!-- 请不要复制这段代码!这是错的 --> <header>   <h1>     <figure>       <img src="/img/mylogo.png" alt="My company" />     </figure>     My company name   </h1> </header> <!-- 请不要复制这段代码!这也是错的 --> <header>   <figure>     <img src="/img/mylogo.png" alt="My company" />   </figure> </header>

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    <!-- 请不要复制这段代码!这是错的 -->
    <header>
      <h1>
        <figure>
          <img src="/img/mylogo.png" alt="My company" />
        </figure>
        My company name
      </h1>
    </header>
    <!-- 请不要复制这段代码!这也是错的 -->
    <header>
      <figure>
        <img src="/img/mylogo.png" alt="My company" />
      </figure>
    </header>

    没什么好说的了。这就是很普通的错误。我们可以为logo是否应该是H1标签而互相喷到牛都放完回家了,但这里不是我们讨论的焦点。真正的问题在于figure元素的滥用。figure只应该被引用在文档中,或者被section元素围绕。我想你的logo并不太可能以这样的方式引用吧。很简单,请勿使用figure。你只需要这样做:

    XHTML

    <header>   <h1>My company name</h1>   <!-- More stuff in here --> </header>

    1
    2
    3
    4
    <header>
      <h1>My company name</h1>
      <!-- More stuff in here -->
    </header>

    Figure也不仅仅只是图片

    另一个常见的关于figure的误解是它只被图片使用。figure可以是视频,音频,图表,一段引用文字,表格,一段代码,一段散文,以及任何它们或者其他的组合。不要把figure局限于图片。web标准的职责是精确的用标签描述内容。

    五、不要使用不必要的type属性

    这是个常见的问题,但并不是一个错误,我认为我们应该通过最佳实践来避免这种风格。

    在HTML5中,script和style元素不再需要type属性。然而这些很可能会被你的CMS自动加上,所以要移除也不是那么的轻松。但如果你是手工编码或者你完全可以控制你的模板的话,那真的没有什么理由再去包含type属性。所有的浏览器都认为脚本是javascript而样式是css样式,你没必要再多此一举了。

    XHTML

    <!-- 请不要复制这段代码!它太冗余了! --> <link type="text/css" rel="stylesheet" href="css/styles.css" /> <script type="text/javascript" src="js/scripts" /></script>

    1
    2
    3
    <!-- 请不要复制这段代码!它太冗余了! -->
    <link type="text/css" rel="stylesheet" href="css/styles.css" />
    <script type="text/javascript" src="js/scripts" /></script>

    其实只需要这样写:

    XHTML

    <link rel="stylesheet" href="css/styles.css" /> <script src="js/scripts" /></script>

    1
    2
    <link rel="stylesheet" href="css/styles.css" />
    <script src="js/scripts" /></script>

    甚至指定字符集的代码都可以省略掉。Mark Pilgrim在Dive into HTML5的语义化一章中作出了解释。

    六、form属性的错误使用

    HTML5引入了一些form的新属性,以下是一些使用上的注意事项:

    布尔属性

    一些多媒体元素和其他元素也具有布尔属性。这里所说的规则也同样适用。

    有一些新的form属性是布尔型的,意味着它们只要出现在标签中,就保证了相应的行为已经设置。这些属性包括:

    • autofocus
    • autocomplete
    • required

    坦白的说,我很少看到这样的。以required为例,常见的是下面这种:

    XHTML

    <!-- 请不要复制这段代码! 这是错的! --> <input type="email" name="email" required="true" /> <!-- 另一个错误的例子 --> <input type="email" name="email" required="1" />

    1
    2
    3
    4
    <!-- 请不要复制这段代码! 这是错的! -->
    <input type="email" name="email" required="true" />
    <!-- 另一个错误的例子 -->
    <input type="email" name="email" required="1" />

    严格来说,这并没有大碍。浏览器的HTML解析器只要看到required属性出现在标签中,那么它的功能就会被应用。但是如果你反过来写equired=”false”呢?

    XHTML

    <!-- 请不要复制这段代码! 这是错的! --> <input type="email" name="email" required="false" />

    1
    2
    <!-- 请不要复制这段代码! 这是错的! -->
    <input type="email" name="email" required="false" />

    解析器仍然会将required属性视为有效并执行相应的行为,尽管你试着告诉它不要去执行了。这显然不是你想要的。

    有三种有效的方式去使用布尔属性。(后两种只在xthml中有效)

    • required
    • required=""
    • required="required"

    上述例子的正确写法应该是:

    XHTML

    <input type="email" name="email" required />

    1
    <input type="email" name="email" required />

    赞 收藏 评论

    图片 1

    六、对于缓存机制,现在可以做的有哪些?

    我在浏览资料的时候发现了一个caching checklist,比较具有参考价值,我们可以遵循建议合理的利用缓存机制:

    1 使用一致的网址:如果在不同的网址上提供相同的内容,那么将会多次获取和存储相同的内容。提示:网址是区分大小写的!2 确保服务器提供验证码 (ETag):通过验证码,如果服务器上的资源未被更改,就不必传输相同的字节。3 确定代理缓存可以缓存哪些资源:对所有用户的响应完全相同的资源很适合由 CDN 或其他代理缓存进行缓存。4 确定每个资源的最优缓存周期:不同的资源可能有不同的更新要求。审查并确定每个资源适合的 max-age。5 确定网站的最佳缓存层级:对 HTML 文档组合使用包含内容指纹码的资源网址以及短时间或 no-cache 的生命周期,可以控制客户端获取更新的速度。6 变动最小化:有些资源的更新比其他资源频繁。如果资源的特定部分(例如 JavaScript 函数或一组 CSS 样式)会经常更新,应考虑将其代码作为单独的文件提供。这样,每次获取更新时,剩余内容(例如不会频繁更新的库代码)可以从缓存中获取,确保下载的内容量最少。

    1
    1 使用一致的网址:如果在不同的网址上提供相同的内容,那么将会多次获取和存储相同的内容。提示:网址是区分大小写的!2 确保服务器提供验证码 (ETag):通过验证码,如果服务器上的资源未被更改,就不必传输相同的字节。3 确定代理缓存可以缓存哪些资源:对所有用户的响应完全相同的资源很适合由 CDN 或其他代理缓存进行缓存。4 确定每个资源的最优缓存周期:不同的资源可能有不同的更新要求。审查并确定每个资源适合的 max-age。5 确定网站的最佳缓存层级:对 HTML 文档组合使用包含内容指纹码的资源网址以及短时间或 no-cache 的生命周期,可以控制客户端获取更新的速度。6 变动最小化:有些资源的更新比其他资源频繁。如果资源的特定部分(例如 JavaScript 函数或一组 CSS 样式)会经常更新,应考虑将其代码作为单独的文件提供。这样,每次获取更新时,剩余内容(例如不会频繁更新的库代码)可以从缓存中获取,确保下载的内容量最少。

     

    sourceMap

    通常在生产环境下的代码是经过webpack打包后压缩混淆的代码,所以我们可能会遇到这样的问题,如图所示:

    图片 2

    我们发现所有的报错的代码行数都在第一行了,为什么呢?这是因为在生产环境下,我们的代码被压缩成了一行:

    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后是打包后的生产环境下的代码,所有的报错都定位到了第一行。

    解决办法是开启webpacksource-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,这样浏览器就能够定位到具体错误的位置:

    图片 3

    开启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.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;

    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;

    如下图所示,我们已经可以看到,在服务端已经成功解析出了具体错误的行号、列号,我们可以通过日志的方式进行记录,达到了前端异常监控的目的。

    图片 4

    五、已经缓存的响应,如何更新或废弃?

    一般情况下,浏览器发出的所有 HTTP 请求会首先被路由到浏览器的缓存,以查看是否缓存了可以用于实现请求的有效响应。如果有匹配的响应,会直接从缓存中读取响应,这样就避免了网络延迟以及传输产生的数据成本。然而,如果我们希望更新或废弃已缓存的响应,该怎么办?

    假设我们已经告诉访问者某个 CSS 样式表缓存长达 24 小时 (max-age=86400),但是设计人员刚刚提交了一个更新,我们希望所有用户都能使用。我们该如何通知所有访问者缓存的 CSS 副本已过时,需要更新缓存?

    实际上以前没有请求过该资源的新的用户会得到更新的资源,但是请求过资源的用户将在过期时间达到之前一直得到旧的被缓存的资源,直到他手动的去清理了浏览器的缓存。手动清理浏览器缓存这种事可能只有程序员才会做,那么我们要怎么做才能让用户得到更新后的资源呢?

    其实很简单,我们可以在资源的内容更改后,更改资源的网址,强制用户下载新响应。比如在资源链接后添加参数:

    图片 5

    全局捕获

    可以通过全局监听异常来捕获,通过window.onerror或者addEventListener,看以下例子:

    JavaScript

    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
    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('这是一个错误');

    图片 6

    通过window.onerror事件,可以得到具体的异常信息、异常文件的URL、异常的行号与列号及异常的堆栈信息,再捕获异常后,统一上报至我们的日志服务器。

    亦或是,通过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('这是一个错误');

    图片 7

    三、使用Etag验证缓存的HTTP响应

    通常情况下,请求一个资源的过程大概是这样的:

    图片 8

    我在 再看Ajax  中整理了HTTP请求的请求头和响应头的一些参数,这里就看下Etag的作用。

    3.1 Etag的主要作用

    服务器通过 ETag HTTP 头传递验证码,大概是像‘‘x123cef’’这样的字符串。当浏览器在资源过期后再次请求时,浏览器默认会通过If-None-Match传递Etag的验证码,通过验证码可以进行高效的资源更新检查:如果资源未更改,则不会传输任何数据。

    Etag就主要用来在响应过期之后,验证资源是否被修改。

    3.2 Etag的工作原理

    如上图,服务器在第一次返回响应的时候设置了缓存的时间120s,假设浏览器在这120s经过之后再次请求服务器相同的资源,首先,浏览器会检查本地缓存并找到之前的响应,不幸的是,这个响应现在已经’过期’,无法在使用。此时,浏览器也可以直接发出新请求,获取新的完整响应,但是这样做效率较低,因为如果资源未被更改过,我们就没有理由再去下载与缓存中已有的完全相同的字节。

    于是就到了Etag发挥作用的时候了,通常服务器生成并返回在Etag中的验证码,常常是文件内容的哈希值或者某个其他指纹码。客户端不必了解指纹码是如何生成的,只需要在下一个请求中将其发送给服务器(浏览器默认会添加):如果指纹码仍然一致,说明资源未被修改,服务器会反悔304 Not Modified,这样我们就可以跳过下载,利用已经缓存了的资源,并且该资源会继续缓存120s。就像这样:

    图片 9

    try… catch

    使用try... catch虽然能够较好地进行异常捕获,不至于使得页面由于一处错误挂掉,但try ... catch捕获方式显得过于臃肿,大多代码使用try ... catch包裹,影响代码可读性。

    透过浏览器看HTTP缓存

    2016/01/17 · HTML5 · HTTP, 缓存

    原文出处: 大额_skylar(@大额大额哼歌等日落)   

    作为前端开发人员,对于我们的站点或应用的缓存机制我们能做的似乎不多,但这些却是与我们关注的性能息息相关的部分,站点没有做任何缓存机制,我们的页面可能会因为资源的下载和渲染变得很慢,但大家都知道去找前端去解决页面慢的问题而不会去找服务端的开发人员。因此,了解相关的缓存机制和充分的利用它似乎就变得必不可少。

    web端的缓存机制其实有多种,我在这里只是学习和整理了以浏览器为载体的HTTP缓存机制,看看它是如何工作的。

    文章目录:

    •   一、web缓存的种类
    •   二、为什么需要浏览器缓存?我们需要做些什么?
    •   三、使用Etag验证缓存的HTTP响应
    •   四、什么是Cache-Control?如何定义Cache-Control策略?
    •   五、已经缓存的响应,如何更新或废弃?
    •   六、对于缓存机制,现在可以做的有哪些?
    •         七、扩展阅读

     

    最简单的性能监控

    最常见的性能监控需求则是需要我们统计用户从开始请求页面到所有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.addEventListener('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节点,可以参考下文关于首屏时间采集自动化的解决方案来对渲染时间进行打点。

    七、扩展阅读

    [web缓存机制系列]

    [Google Developer Browser Caching]

    [HTTP Caching]

    [Caching Tutorial]

    [HTTP Caching FAQ MDN]

    [浏览器缓存机制]

    1 赞 11 收藏 评论

    图片 10

    概述

    对于后台开发来说,记录日志是一种非常常见的开发习惯,通常我们会使用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渲染时间的优化和影像预加载的需求,如果缺乏性能监控,该如何统计所做的渲染优化和影像预加载优化的优化比例,如何证明自己所做的事情具有价值呢?可能是通过测试同学的黑盒测试,对优化前后的时间进行录屏,分析从进入页面到影像渲染完成到底经过了多少帧图像。这样的数据,可能既不准确、又较为片面,设想测试同学并不是真正的用户,也无法还原真实的用户他们所处的网络环境。回过头来发现,我们的项目,虽然在服务端层面做好了日志和性能统计,但在前端对异常的监控和性能的统计。对于前端的性能与异常上报的可行性探索是有必要的。

    二、为什么需要浏览器缓存?我们需要做些什么?

    我们知道通过HTTP协议,在客户端和浏览器建立连接时需要消耗时间,而大的响应需要在客户端和服务器之间进行多次往返通信才能获得完整的响应,这拖延了浏览器可以使用和处理内容的时间。这就增加了访问服务器的数据和资源的成本,因此利用浏览器的缓存机制重用以前获取的数据就变成了性能优化时需要考虑的事情。

    那么有什么建议吗?当然。

    为每个资源指定一个明确的缓存策略,用以定义资源是否可以缓存,由谁来缓存,可以缓存多久,并且在缓存时间到期时如何有效地重新验证。当服务器返回一个响应时,它需要在响应头中提供Cache-Control和ETag。

      说到浏览器中的缓存机制,其实就相当于HTTP协议定义的缓存机制,因为浏览器为我们实现了它。一般情况下我们会想到到HTTP响应头中的Expires,Cache-Control,Last-Modified.If-Modified-Since,Etag这样的与缓存相关的响应头信息。

      但是这里我们说服务器返回一个响应时提供必要的Cache-Control和Etag即可。这是为什么呢?

      因为Cache-Control与Expires的作用一致,Last-Modified与ETag的作用也相近。但它们有以下区别:

               图片 11

      现在默认浏览器均默认使用HTTP 1.1,所以Expires和Last-Modified的作用基本可以忽略,具备Cache-Control和Etag即可。

      当然用户的行为也会影响浏览器的缓存,像这样:

      图片 12

     

    但我们先不考虑用户的操作的影响,来看看服务器提供Cache-Control和ETag响应头来进行的缓存是如何工作的。

    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>

    本文由奥门新浦京网址发布于Wed前段,转载请注明出处:避免常见的六种HTML5错误用法,前端性能与异常上

    关键词:

上一篇:H5直播起航,的黑魔法

下一篇:没有了