HTTP协议

HTTP协议

1.简介

HTTP协议(Hyper Text Transfer Protocol,超文本传输协议),是用于从万维网(WWW:World Wide Web )服务器传输超文本到本地浏览器的传送协议。

HTTP基于TCP/IP通信协议来传递数据。

HTTP基于客户端/服务端(C/S)架构模型,通过一个可靠的链接来交换信息,是一个无状态的请求/响应协议。

2.特点

(1)HTTP是无连接:无连接的含义是限制每次连接只处理一个请求。服务器处理完客户的请求,并收到客户的应答后,即断开连接。采用这种方式可以节省传输时间。

(2)HTTP是媒体独立的:只要客户端和服务器知道如何处理的数据内容,任何类型的数据都可以通过HTTP发送。客户端以及服务器指定使用适合的MIME-type内容类型。

(3)HTTP是无状态:无状态是指协议对于事务处理没有记忆能力。缺少状态意味着如果后续处理需要前面的信息,则它必须重传,这样可能导致每次连接传送的数据量增大。另一方面,在服务器不需要先前信息时它的应答就较快。

2.通信流程

4.消息结构

HTTP使用统一资源标识符(Uniform Resource Identifiers, URI)来传输数据和建立连接。一旦建立连接后,数据消息就通过类似Internet邮件所使用的格式[RFC5322]和多用途Internet邮件扩展(MIME)[RFC2045]来传送。

客户端请求消息:请求行、请求头部、空行和请求数据。

GET /hello.txt HTTP/1.1          

User-Agent: curl/7.16.3 libcurl/7.16.3          

OpenSSL/0.9.7l zlib/1.2.3          

Host: www.example.com 

Accept-Language: en  

服务端响应消息:状态行、消息报头、空行和响应正文。

HTTP/1.1 200 OK         

Date: Mon, 27 Jul 2009 12:28:53 GMT         

Server: Apache         

Last-Modified: Wed, 22 Jul 2009 19:15:56 GMT         

ETag: "34aa387-d-1568eb00"         

Accept-Ranges: bytes         

Content-Length: 51         

Vary: Accept-Encoding         

Content-Type: text/plain         

5.请求方法

GET 请求指定的页面信息,并返回实体主体。

HEAD 类似于get请求,只不过返回的响应中没有具体的内容,用于获取报头

POST 向指定资源提交数据进行处理请求(例如提交表单或者上传文件)。数据被包含在请求体中。POST请求可能会导致新的资源的建立和/或已有资源的修改。

PUT 从客户端向服务器传送的数据取代指定的文档的内容。

DELETE 请求服务器删除指定的页面。

CONNECT HTTP/1.1协议中预留给能够将连接改为管道方式的代理服务器。

OPTIONS 允许客户端查看服务器的性能。

TRACE 回显服务器收到的请求,主要用于测试或诊断。

6.状态码

HTTP状态码分类

1** 信息,服务器收到请求,需要请求者继续执行操作

2** 成功,操作被成功接收并处理

3** 重定向,需要进一步的操作以完成请求

4** 客户端错误,请求包含语法错误或无法完成请求

5** 服务器错误,服务器在处理请求的过程中发生了错

HTTP状态码列表

100 Continue 继续。客户端应继续其请求

101 Switching Protocols 切换协议。服务器根据客户端的请求切换协议。只能切换到更高级的协议,例如,切换到HTTP的新版本协议

200 OK 请求成功。一般用于GET与POST请求

201 Created 已创建。成功请求并创建了新的资源

202 Accepted 已接受。已经接受请求,但未处理完成

203 Non-Authoritative Information 非授权信息。请求成功。但返回的meta信息不在原始的服务器,而是一个副本

204 No Content 无内容。服务器成功处理,但未返回内容。在未更新网页的情况下,可确保浏览器继续显示当前文档

205 Reset Content 重置内容。服务器处理成功,用户终端(例如:浏览器)应重置文档视图。可通过此返回码清除浏览器的表单域

206 Partial Content 部分内容。服务器成功处理了部分GET请求

300 Multiple Choices 多种选择。请求的资源可包括多个位置,相应可返回一个资源特征与地址的列表用于用户终端(例如:浏览器)选择

301 Moved Permanently 永久移动。请求的资源已被永久的移动到新URI,返回信息会包括新的URI,浏览器会自动定向到新URI。今后任何新的请求都应使用新的URI代替

302 Found 临时移动。与301类似。但资源只是临时被移动。客户端应继续使用原有URI

303 See Other 查看其它地址。与301类似。使用GET和POST请求查看

304 Not Modified 未修改。所请求的资源未修改,服务器返回此状态码时,不会返回任何资源。客户端通常会缓存访问过的资源,通过提供一个头信息指出客户端希望只返回在指定日期之后修改的资源

305 Use Proxy 使用代理。所请求的资源必须通过代理访问

306 Unused 已经被废弃的HTTP状态码

307 Temporary Redirect 临时重定向。与302类似。使用GET请求重定向

400 Bad Request 客户端请求的语法错误,服务器无法理解

401 Unauthorized 请求要求用户的身份认证

402 Payment Required 保留,将来使用

403 Forbidden 服务器理解请求客户端的请求,但是拒绝执行此请求

404 Not Found 服务器无法根据客户端的请求找到资源(网页)。通过此代码,网站设计人员可设置”您所请求的资源无法找到”的个性页面

405 Method Not Allowed 客户端请求中的方法被禁止

406 Not Acceptable 服务器无法根据客户端请求的内容特性完成请求

407 Proxy Authentication Required 请求要求代理的身份认证,与401类似,但请求者应当使用代理进行授权

408 Request Time-out 服务器等待客户端发送的请求时间过长,超时

409 Conflict 服务器完成客户端的PUT请求是可能返回此代码,服务器处理请求时发生了冲突

410 Gone 客户端请求的资源已经不存在。410不同于404,如果资源以前有现在被永久删除了可使用410代码,网站设计人员可通过301代码指定资源的新位置

411 Length Required 服务器无法处理客户端发送的不带Content-Length的请求信息

412 Precondition Failed 客户端请求信息的先决条件错误

413 Request Entity Too Large 由于请求的实体过大,服务器无法处理,因此拒绝请求。为防止客户端的连续请求,服务器可能会关闭连接。如果只是服务器暂时无法处理,则会包含一个Retry-After的响应信息

414 Request-URI Too Large 请求的URI过长(URI通常为网址),服务器无法处理

415 Unsupported Media Type 服务器无法处理请求附带的媒体格式

416 Requested range not satisfiable 客户端请求的范围无效

417 Expectation Failed 服务器无法满足Expect的请求头信息

500 Internal Server Error 服务器内部错误,无法完成请求

501 Not Implemented 服务器不支持请求的功能,无法完成请求

502 Bad Gateway 充当网关或代理的服务器,从远端服务器接收到了一个无效的请求

503 Service Unavailable 由于超载或系统维护,服务器暂时的无法处理客户端的请求。延时的长度可包含在服务器的Retry-After头信息中

504 Gateway Time-out 充当网关或代理的服务器,未及时从远端服务器获取请求

505 HTTP Version not supported 服务器不支持请求的HTTP协议的版本,无法完成处理

webpack正确使用wasm

webpack版本

"webpack": "4.20.2",
"webpack-cli": "3.1.0",
"webpack-dev-server": "3.1.5"

如果直接打包,会提示WebAssembly module is included in initial chunk.

This is not allowed, because WebAssembly download and compilation must happen asynchronous.wasm模块是要下载,然后编译才能使用。必须使用import()在使用分割wasm的模块中。

最简单办法。重新创建一个入口bootstrap.js,这入口用 import() 加载以前的入口文件。这样可以共享wasm模块实例。完全忽略wasm模块下载编译过程,和使用js模块一模一样。(内部由webpack实现了)

bootstrap.js

import("./index")
  .catch(e => console.error("Error importing `index.js`:", e));

这样不需要使用fetch(‘simple.wasm’),下载一个wasm文件,然后编译,这样的用法很难使用。假如这wasm模块,在多处文件中使用。

fetch('simple.wasm').then(response =>
  response.arrayBuffer()
).then(bytes =>
  WebAssembly.instantiate(bytes, importObject)
).then(results => {
  results.instance.exports.exported_func();
});

https://github.com/laopo001/ts-template/tree/wasm

从零写一个webgl游戏引擎(二)

为什么需要测试,更具体的说,为什么合入代码前,一定要跑通测试用例?原因主要有三点:

  • 确保本次 commit 不会影响其它部分的逻辑。
  • 确保本次 commit 的功能代码无 bug。
  • 还有以后重构时候不会出现bug。

使用持续集成自动测试代码,github有很多免费的持续集成工具。如travis,appveyor,

jest

Jest 是 Facebook 出品的一个测试框架,相对其他测试框架,其一大特点就是就是内置了常用的测试工具,比如自带断言、测试覆盖率工具,实现了开箱即用,而且可以和typescirpt和好的一起使用。如果测试不需要浏览器的代码配合travis是很好用的。

karma

使用karma是因为jest不能运行在浏览器,也就不能测试webgl相关的东西。因此这里使用了appveyor这个持续集成工具,它可以使用windows系统环境,可以在IE浏览器,,firefox,chrome上测试代码。

总结
  • 使用git commit钩子,每次commit必须测试通过才能提交。
  • jest测试库的核心代码,配合travis。
  • karma测试依赖浏览器的代码,使用appveyor工具。

Box<T>,Rc<T>,RefCell<T>,Weak<T>

Box

在堆上存储数据,并且可确定大小,不能共享所有权

Rc

引用计数智能指针,可以共享所有权,允许相同数据有多个所有者

RefCell

对于引用和 Box,借用规则的不可变性作用于编译时。对于 RefCell,这些不可变性作用于 运行时。对于引用,如果违反这些规则,会得到一个编译错误。而对于RefCell,违反这些规则会 panic!。。RefCell 正是用于当你确信代码遵守借用规则,而编译器不能理解和确定的时候。

类似于 Rc,RefCell 只能用于单线程场景。如果尝试在多线程上下文中使用RefCell,会得到一个编译错误。

如下为选择 Box,Rc 或 RefCell 的理由:
  • Rc 允许相同数据有多个所有者;Box 和 RefCell 有单一所有者。
  • Box 允许在编译时执行不可变(或可变)借用检查;Rc仅允许在编译时执行不可变借用检查;RefCell 允许在运行时执行不可变(或可变)借用检查。
  • 因为 RefCell 允许在运行时执行可变借用检查,所以我们可以在即便 RefCell 自身是不可变的情况下修改其内部的值。

从零写一个webgl游戏引擎(一)

webgl3d的引擎(类库),常见的有threejs,playcanvas,babylonjs。以及unity3d可以用llvm转asmjs 导出webgl。它们都很好用的,自己写一个是为了学习一下webgl原理。github地址 hypergl,主要从上面的3个js webgl框架吸收灵感,还有从零开始手敲次世代游戏引擎的文章。

功能

  • 使用语言typescript,ts是javascirpt的超集,它的类型系统,可以让我在编译就可以排查出大部分bug。
  • 使用handlebars和handlebars-loader,生成webgl中shader代码,还可以预编译。
  • 使用webworker多线程,主线程主要是用来gpu渲染,后台线程用来 加载模型,格式化,物理碰撞。(一帧的时间是16ms。在16ms中既要完成js运算,又要完成gpu渲染,数据大了是很难完成的)。
  • 使用flatbufferjs。在webworker线程给主线程中传递渲染数据。直接移动Arraybuffer,实现高性能传递数据。

如何写出高性能的js代码一

如何写出高性能的js代码

下面是v8引擎优化js性能的

快速访问属性

js是一门动态语言,属性是可以动态添加,动态删除的。大部分js引擎使用字典式数据结构作为对象属性的存储。每个属性访问都需要动态查找来解决该属性在内存中的位置,这样是很慢的。在其他静态语言中,在编译时,已经确定了偏移,属性在内存中按照偏移直接获取。只需一个指令。

v8为了减少对象访问属性的时间。不使用动态查找访问属性,而是,动态地在幕后创建隐藏类。在V8中,一个对象当一个新的属性添加时会改变它的隐藏类。

如下一个函数:

function Point(x, y) {
  this.x = x;
  this.y = y;
}

当执行 new Point(1,2) 时,会创建一个对应Point的隐藏类C0。这时还是一个空对象。

map_trans_a

当执行 this.x = x; 时,这时对象上面添加了x属性,v8会基于隐藏类C0创建隐藏类C1。这时Point对应的隐藏类是C1。

当执行 this.y = y; 时,这时对象上面又添加了y属性,v8会基于隐藏类C1创建隐藏类C2。并对应隐藏类C2。

v8这样做有两个好处。1.属性访问不需要查字典,V8使用经典的基于类的优化,2.内联缓存。有关内联缓存的更多信息,请参见Efficient Implementation of the Smalltalk-80 System

根据如上分析,我们创建对象的时候,不要随意添加删除属性。

// 慢
var a={};
a.name=123;
a.age=11;
// 快
var a={name:123,age,123}

在某些情况下,如大量的添加属性,删除属性。 Fast mode(快速模式)会转成 Dictionary mode(字典模式)。这种情况是可以通过黑魔法转换的,如下

  1. Dictionary mode(字典模式):字典模式也成为哈希表模式,V8 引擎使用哈希表来存储对象。
  2. Fast mode(快速模式):快速模式使用类似 C 语言的 struct 来表示对象。

如下代码

function toFastProperties(obj) {
    /*jshint -W027*/
    function f() {}
    f.prototype = obj;
    ASSERT("%HasFastProperties", true, obj);
    return f;
    eval(obj);
}

Bluebird 代码中 f.prototype = obj 是使属性访问变快的关键。当把一个对象设置为另一个对象的 prototype 时,V8 引擎对对象的结构重新进行了优化。

var a={name:123};
toFastProperties(a);

Design-Elements

开启 V8 对象属性的“fast”模式

JavaScript 引擎基础:Shapes 和 Inline Caches

JS 数组排序

var data = [
  {value: 4}, 
  {value: 2}, 
  {value: undefined}, 
  {value: undefined}, 
  {value: 1}, 
  {value: undefined}, 
  {value: undefined}, 
  {value: 7}, 
  {value: undefined}, 
  {value: 4}
];

data 是个数组,数组的每一项都是一个拥有 value 作为 key 的对象,值为数字或者 undefined。

data
  .sort((x, y) => x.value - y.value)
  .map(x => x.value);

data
  .map(x => x.value)
  .sort((x, y) => x - y)

在 ES6 规范 22.1.3.24 节写道:

Calling comparefn(a,b) always returns the same value v when given a specific pair of values a and b as its two arguments. Furthermore, Type(v) is Number, and v is not NaN. Note that this implies that exactly one of a < b, a = b, and a > b will be true for a given pair of a and b.

简单翻译一下就是:第二个参数 comparefn 返回一个数字,并且不是 NaN。一个注意事项是,对于参与比较的两个数 a 小于 b、a 等于 b、a 大于 b 这三种情况必须有一个为 true。

所以严格意义上来说,这段代码是有 bug 的,因为比较的结果出现了 NaN。

在 MDN 文档上还有一个细节:

如果 comparefn(a, b) 等于 0, a 和 b 的相对位置不变。备注:ECMAScript 标准并不保证这一行为,而且也不是所有浏览器都会遵守。
翻译成编程术语就是:sort 排序算法是不稳定排序。


// %RemoveArrayHoles returns -1 if fast removal is not supported.
var num_non_undefined = %RemoveArrayHoles(array, length);

if (num_non_undefined == -1) {
  // There were indexed accessors in the array.
  // Move array holes and undefineds to the end using a Javascript function
  // that is safe in the presence of accessors.
  num_non_undefined = SafeRemoveArrayHoles(array);
}

中间的注释:Move array holes and undefineds to the end using a Javascript function。排序之前会把数组里面的 undefined 移动到最后。因此第二个排序算法会把 undefined 移动到最后,然后对剩余的数据 [4,2,1,7,4] 进行排序。

而在第一种写法时,数组的每一项都是一个 Object,然后最 Object 调用 x.value - y.value 进行计算,当 undefined 参与运算时比较的结果是 NaN。当返回 NaN 时 V8 怎么处理的呢?我前面标注过,再贴一次:

var order = comparefn(tmp, element);
if (order > 0) {  // <---- 这里
  a[j + 1] = tmp;
} else {
  break; // 
}


[1, 23, 2, 3].sort() 
// [1,2,23,3] js默认排序是字典序。

从 V8 源码看 JS 数组排序的诡异问题

codePointAt和fromCodePoint

字符的 Unicode 表示法

JavaScript 允许采用\uxxxx形式表示一个字符,其中xxxx表示字符的 Unicode 码点。

"\u0061" // "a"

但是只限于\u0000~\uFFFF的。如下,要使用两个才能代表大于\uFFFF的字符。

"\ud83c\udf0f"  //  "🌏"
"\u20BB7"  //  "₻7"

charCodeAt <=> fromCharCode

都是ES5中提出的方法。charCodeAt 是UTF-16 字符转换成编号。String.fromCharCode 是通过编号转字符串。但是这两个不能识别 32 位的 UTF-16 字符(Unicode 编号大于0xFFFF,如'🌏'(127759)),如图是不能识别的。js会识别’🌏’的长度为2.分成两部分。如下。

'🌏'.length===2 //true
'🌏'.charCodeAt(0)  // 55356
'🌏'.charCodeAt(1)  // 57103
'\ud83c\udf0f' // "🌏"

codePointAt<=> fromCodePoint

这是es6的方法,能够正确处理 4 个字节储存的字符。

而且es6还改进了,将码点放入大括号,就能正确解读该字符。

"\u{20BB7}"
// "𠮷"

"\u{41}\u{42}\u{43}"
// "ABC"

let hello = 123;
hell\u{6F} // 123

'\u{1F680}' === '\uD83D\uDE80'
// true

字符串遍历,因此在遍历大于\uffff的字符时,用for是bug的。 要用es6 Iterator(遍历器)。

let text = "🌏";

for (let i = 0; i < text.length; i++) {
  console.log(text[i]);
}
// �
// �

for (let i of text) {
  console.log(i);
}

JavaScript 共有 6 种方法可以表示一个字符。

'\z' === 'z'  // true
'\172' === 'z' // true
'\x7A' === 'z' // true
'\u007A' === 'z' // true
'\u{7A}' === 'z' // true

字符串的扩展

断点续传 之 HTTP状态码206和416

HTTP/1.1 206状态码表示 服务器已经成功处理了部分GET请求。类似于FlashGet或者迅雷这类的HTTP 下载工具都是使用此类响应实现断点续传或者将一个大文档分解为多个下载段同时下载。可以解决大文件下载问题,更快的下载速度。

416状态码表示是由于读取文件时设置的Range有误造成的,RANGE start不能超出文件的size.

判断服务器是否支持range。如果http头有Accept-Ranges: bytes,状态码206

Origin: http://localhost:5000
Range: bytes=0-0
Referer: http://localhost:5000/
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.84 Safari/537.36

Accept-Ranges: bytes
Access-Control-Allow-Methods: GET
Access-Control-Allow-Origin: *
Access-Control-Expose-Headers: Content-Range, Content-Type
Access-Control-Max-Age: 0
Connection: keep-alive
Content-Length: 1
Content-Range: bytes 0-0/69101328
Content-Type: application/x-msdownload

服务器返回了Content-Range: bytes 0-0/69101328,这个69101328就是全部的大小。这样可以使用Range:头分段下载。

如图分段下载可断点续传,可以暂停,然后继续下载。

最后用Blob合并所有的ArrayBuffer,用Url api下载下来。

ArrayBuffer => Blob 下载

var a = document.createElement('a');
  var url = window.URL.createObjectURL(blob);
  var filename = 'dd_3.4.8.exe';
  a.href = url;
  a.download = filename;
  a.click();

注意

我们在分段下载的时候,可能在下载的过程中,文件修改了。这是合成的文件就出了问题。下载完后可以使用If-Match(If-Range)通过判断Etag是否相等,来判断文件版本。如果Etag变化了,返回416的状态码。需要重新下载。

Safari一些坑

1.在异步操作中不能打开新标签页,解决如下。

const newTab = window.open('')
    setTimeout(() => {
      newTab.location.href = 'http://www.baidu.com'
    }, 3000)

2.不支持Access-Control-Allow-Headers 为 * 匹配

在跨越请求的时候,自定义http头,不能*匹配。

Math.min(),Math.max()

Math.min()和Math.max()都可以不传参数运行的。

Math.min() === Infinity
Math.max() === -Infinity

Math.min 的参数是 0 个或者多个。如果是多个参数很容易理解,返回参数中最小的。内部实现的时候是用js最大的数Infinity对比的,因此没有参数,直接返回Infinity。同理Math.max() === -Infinity。

for in ,Object.keys,Object.getOwnPropertyNames,Object.getOwnPropertySymbols区别

class A {
  name = 'A';
  [Symbol.toStringTag] = "A";
  get getter() {
    return 'aaaa';
  }
  constructor() {
    Object.defineProperty(this, 'enumerable', {
      enumerable: false
    })
  }
  prototype() { }
}

let a = new A();
a.q = q => 132;
let arr = [];
for (let x in a) {
  arr.push(x);
}
console.log('for in', arr);
console.log('Object.keys', Object.keys(a))
console.log('Object.getOwnPropertyNames', Object.getOwnPropertyNames(a))
console.log('Object.getOwnPropertySymbols', Object.getOwnPropertySymbols(a))

结论

for in:所有可枚举的,还有getter,包括原型链上面的

Object.keys:所有可枚举的,不包括原型链

Object.getOwnPropertyNames:所有的(包括不能枚举的),不包括原型链

Object.getOwnPropertySymbols:所有的Symbol属性,不包括原型链(以上都不能列出Symbol属性)

标签模板

alert`123`
// 等同于
alert(123)

ES6 还为原生的 String 对象,提供了一个raw方法。

String.raw方法,往往用来充当模板字符串的处理函数,返回一个斜杠都被转义(即斜杠前面再加一个斜杠)的字符串,对应于替换变量后的模板字符串。

String.rawHi\u000A!;

// 返回 “Hi\u000A!”

let a = 5;
let b = 10;

function tag(s, v1, v2) {
  console.log(s[0]);
  console.log(s[1]);
  console.log(s[2]);
  console.log(v1);
  console.log(v2);

  return "OK";
}

tag`Hello ${ a + b } world ${ a * b}`;
// "Hello "
// " world "
// ""
// 15
// 50
// "OK"

模板编译

let template = `
<ul>
  <% for(let i=0; i < data.length; i++) { %>
    <li><%= data[i] %></li>
  <% } %>
</ul>
`;

上面代码在模板字符串之中,放置了一个常规模板。该模板使用<%…%>放置 JavaScript 代码,使用<%= … %>输出 JavaScript 表达式。

function compile(template){
  const evalExpr = /<%=(.+?)%>/g;
  const expr = /<%([\s\S]+?)%>/g;

  template = template
    .replace(evalExpr, '`); \n  echo( $1 ); \n  echo(`')
    .replace(expr, '`); \n $1 \n  echo(`');

  template = 'echo(`' + template + '`);';

  let script =
  `(function parse(data){
    let output = "";

    function echo(html){
      output += html;
    }

    ${ template }

    return output;
  })`;

  return script;
}

let parse = eval(compile(template));
div.innerHTML = parse([ "broom", "mop", "cleaner" ]);

//   <ul>
//     <li>broom</li>
//     <li>mop</li>
//     <li>cleaner</li>
//   </ul>

event loop,macrotask机制,microtask机制 解释

macrotask:宏任务,setTimeout。

microtask:微任务,promise。

当js栈上为空,这是读取微任务队列,当microtask(微任务队)列call完清空(js栈上为空),再去读macrotask(宏任务)

如下面的例子

<div class="outer-test">
  <div class="inner-test">test</div>
</div>
<div class="test">test2</div>

var outer = document.querySelector('.outer-test');
var inner = document.querySelector('.inner-test');
var test = document.querySelector('.test');

let log2 = console.log

function onClick() {
  log2('click');

  setTimeout(function () {
    log2('timeout');
  }, 0);

  Promise.resolve().then(function () {
    log2('promise');
  });
}

inner.addEventListener('click', onClick);
outer.addEventListener('click', onClick);

test.addEventListener('click', () => {
  inner.click()
})

点test 和 test2 的区别。点击test2,在第一次输出click后,js栈上不为空,因为它是 () => {

inner.click()

})调用的,因此不会立即调用microtask(微任务)。等到冒泡完成,然后去执行。

test2 输出如下

click
click
promise
promise
setTimeout
setTimeout

https://jakearchibald.com/2015/tasks-microtasks-queues-and-schedules/

performance

这个一个性能分析的对象。

performance.timing

/ 计算加载时间
function getPerformanceTiming () {  
    var performance = window.performance;

    if (!performance) {
        // 当前浏览器不支持
        console.log('你的浏览器不支持 performance 接口');
        return;
    }

    var t = performance.timing;
    var times = {};

    //【重要】页面加载完成的时间
    //【原因】这几乎代表了用户等待页面可用的时间
    times.loadPage = t.loadEventEnd - t.navigationStart;

    //【重要】解析 DOM 树结构的时间
    //【原因】反省下你的 DOM 树嵌套是不是太多了!
    times.domReady = t.domComplete - t.responseEnd;

    //【重要】重定向的时间
    //【原因】拒绝重定向!比如,http://example.com/ 就不该写成 http://example.com
    times.redirect = t.redirectEnd - t.redirectStart;

    //【重要】DNS 查询时间
    //【原因】DNS 预加载做了么?页面内是不是使用了太多不同的域名导致域名查询的时间太长?
    // 可使用 HTML5 Prefetch 预查询 DNS ,见:[HTML5 prefetch](http://segmentfault.com/a/1190000000633364)            
    times.lookupDomain = t.domainLookupEnd - t.domainLookupStart;

    //【重要】读取页面第一个字节的时间
    //【原因】这可以理解为用户拿到你的资源占用的时间,加异地机房了么,加CDN 处理了么?加带宽了么?加 CPU 运算速度了么?
    // TTFB 即 Time To First Byte 的意思
    // 维基百科:https://en.wikipedia.org/wiki/Time_To_First_Byte
    times.ttfb = t.responseStart - t.navigationStart;

    //【重要】内容加载完成的时间
    //【原因】页面内容经过 gzip 压缩了么,静态资源 css/js 等压缩了么?
    times.request = t.responseEnd - t.requestStart;

    //【重要】执行 onload 回调函数的时间
    //【原因】是否太多不必要的操作都放到 onload 回调函数里执行了,考虑过延迟加载、按需加载的策略么?
    times.loadEvent = t.loadEventEnd - t.loadEventStart;

    // DNS 缓存时间
    times.appcache = t.domainLookupStart - t.fetchStart;

    // 卸载页面的时间
    times.unloadEvent = t.unloadEventEnd - t.unloadEventStart;

    // TCP 建立连接完成握手的时间
    times.connect = t.connectEnd - t.connectStart;

    return times;
}

performance.now()

performance.now() 与 Date.now() 不同的是,返回了以微秒(百万分之一秒)为单位的时间,更加精准。注意 Date.now() 输出的是 UNIX 时间,即距离 1970 的时间,而 performance.now() 输出的是相对于 performance.timing.navigationStart(页面初始化) 的时间。

performance.mark(name) performance.measure(name, startMark, endMark);

使用 performance.mark() 标记各种时间戳(就像在地图上打点),保存为各种测量值(测量地图上的点之间的距离),便可以批量地分析这些数据了。performance.measure()是测量mark点的间隔。使用完成后可以清楚。

window.performance.clearMarks();window.performance.clearMeasures();

performance.getEntries()

getEntries() 对于给定的filter,此方法返回 PerformanceEntry 对象数组. 数组成员(入口)可以在显式的时间点用 performance marks或measures 来创建(例如调用mark() 方法)。

重要

performance.getEntries({ entryType: “resource”});这个是获取页面静态资源加载的情况,可以用来做前端监控。如果某个js或图标加载不出来,则立马告警。

转载自AlloyTeam

js获取安全的随机数

常见的获取随机数是Math.random(),生成[0,1)中间浮点数,包括0,但是不能提供像密码一样安全的随机数字,不能使用它们来处理有关安全加密的事情。

如果需要处理加密的问题(如RSA算法),使用Web Crypto API 来代替, 和更精确的window.crypto.getRandomValues() 方法。这个api可以在IE11,firefox,chrome等浏览器上。如果需要兼容低版本IE,使用Math.random()模拟,只是不太安全。

window.crypto.getRandomValues

var array = new Uint32Array(10);
window.crypto.getRandomValues(array);

console.log("Your lucky numbers:");
for (var i = 0; i < array.length; i++) {
    console.log(array[i]);
}

前端加载优化2

DNS 预读取

DNS 作为互联网的基础协议,其解析的速度似乎很容易被网站优化人员忽视。现在大多数新浏览器已经针对DNS解析进行了优化,典型的一次DNS解析需要耗费 20-120 毫秒,减少DNS解析时间和次数是个很好的优化方式。DNS Prefetching 是让具有此属性的域名不需要用户点击链接就在后台解析,而域名解析和内容载入是串行的网络操作,所以这个方式能 减少用户的等待时间,提升用户体验 。

  • 打开和关闭 DNS 预读取

  • 通过response header

X-DNS-Prefetch-Control: on X-DNS-Prefetch-Control: off

  • 通过 标签
    <meta http-equiv="x-dns-prefetch-control" content="off">

  • 强制查询特定主机名(<link> 元素也可以使用不完整的 URL 的主机名来标记预解析,但这些主机名前必需要有双斜线:)

<link rel="dns-prefetch" href="http://www.spreadfirefox.com/">

<link rel="dns-prefetch" href="//www.spreadfirefox.com">

WebP图片格式

WebP是Google新推出的影像技术,它可让网页图档有效进行压缩,同时又不影响图片格式兼容与实际清晰度,进而让整体网页下载速度加快。
gulp-webp这个转WebP的gulp插件还是蛮好用的。

不过WebP的图像格式,在浏览器上面兼容还不够好。

前端加密

有时,在数据传输的过程要加密,一般在前端生成16位的随机字符串加上iv(初始偏移向量),使用aes对数据加密,然后把key用后端提供的公钥进行rsa加密。

aes加密

rsa加密

Node-RSA一个rsa加密的库,也可以浏览器中使用。

RSA是一种块加密的算法,所以对于明文需要将他们分成固定的块长度,考虑到输入的数据长度的问题,所以加解密的填充有好几种:

  • 无填充,就是直接对明文进行加密

  • PKCS1。将数据长度分成密钥长度-11byte,比如密钥是1024bit,那么长度就是1024/8-11=117bytes,具体的格式:先填0,2,然后随机生成其他的byte,后面才是真正的数据

  • PKCS1_OAEP将数据长度分成密钥长度-41byte,比如密钥是1024bit,那么长度就是1024/8-41=77bytes,先填0,随机或者是固定的测试向量加20个bytes,然后加20个数字签名的数据,最后才是数据

  • SSLV23,将数据长度分成密钥长度-11byte,比如密钥是1024bit,那么长度就是1024/8-11=117bytes,具体的格式:先填0,2,填入8个3,填入一个’\0’,最后才是真正的数据。

在node-rsa模块中加解密默认使用 pkcs1_oaep ,如果有些js库使用pkcs1加密,请设置key.setOptions({encryptionScheme: 'pkcs1'})

const NodeRSA = require('node-rsa');

const key = new NodeRSA();
// key.setOptions({encryptionScheme: 'pkcs1'}) 
key.importKey(`-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDDBDlbS4dYbs27KFuO3ajDLZ12
RbFk5UpcEYuoTHmxs0n8/V3DL8FjdQJ3k0RSOGRPgFSQltSYEL+Er4GRmJ7nt0+b
lkSdFTmTZ2disYD9Odf2L0jnI4L2DGbPN1OPUxMsbZUCZ1EmneC5WoTlPpH88s0r
h+dbC34qxGVNl3DpMwIDAQAB
-----END PUBLIC KEY-----`, 'pkcs8-public');
let encrypted = key.encrypt('Hello RSA!', 'base64');
console.log(encrypted);

const priKey = new NodeRSA();
// priKey.setOptions({encryptionScheme: 'pkcs1'}) 
priKey.importKey(`-----BEGIN RSA PRIVATE KEY-----
MIICXQIBAAKBgQDDBDlbS4dYbs27KFuO3ajDLZ12RbFk5UpcEYuoTHmxs0n8/V3D
L8FjdQJ3k0RSOGRPgFSQltSYEL+Er4GRmJ7nt0+blkSdFTmTZ2disYD9Odf2L0jn
I4L2DGbPN1OPUxMsbZUCZ1EmneC5WoTlPpH88s0rh+dbC34qxGVNl3DpMwIDAQAB
AoGBAIdkm12i5mijJPpXXpmlktFPDg9a+3oNJr8c/1TtI2AYFborPbmRojYmunvC
JqDeveXkNtHbpeWdWxoHr3EiAM+vralpcN8l2Lg8TKltBCiMsHUiLrTcAfBTEp1X
fCwUNNjNiB1AK3H7v0JHfGeQZqre8SWK1C8qjAjEi9x2DDLBAkEA86k3bj8fb566
yyH9ub4Jq/sOd/r8urXHXMbLTUUw+ysI5VdCQ3pdOnLQT2hee/kqGgKZqI+S+/Zp
khhj1F5RGwJBAMzkY0Lbj0xk0jxTOb9xoVY5Qec4LkVLae/zpLRIMAZi/ZjYWP7n
UOvIe8lKTtRXW/a5NyFjBePxD84Os8h4YckCQQDAJjfCRyEhQwmHW3zdV1IFP+y/
DTz0eJmJPnPgsanYyK0xPsjQsdSHXTeNB39LQMjEzjwiw2ZkMIQ8Y+OF/AL1AkAP
ibplVZU2a+btoDoe5JUhntH6oO4RXzi3c7in21mZAmTM9Is7OXuPhfKtPy9fNwjI
Wx9tLr9BnARg0gicSVTRAkAx/lM1JEu0ETIvt6GJiXGXCi95Pj88uD6DCrEzl0oi
OUVnq5h/vXpTmEjGmNzluC8bwNWxIQp61isBtdMvYChO
-----END RSA PRIVATE KEY-----`, 'pkcs1')
var decrypted = priKey.decrypt(encrypted, 'utf8');
console.log(decrypted);

修改git全部已提交的用户名和邮箱

原文

#!/bin/sh

git filter-branch -f --env-filter '
OLD_EMAIL="liaodh@jpush.com"
CORRECT_NAME="laopo001"
CORRECT_EMAIL="353272497@qq.com"
if [ "$GIT_COMMITTER_EMAIL" = "$OLD_EMAIL" ]
then
    export GIT_COMMITTER_NAME="$CORRECT_NAME"
    export GIT_COMMITTER_EMAIL="$CORRECT_EMAIL"
fi
if [ "$GIT_AUTHOR_EMAIL" = "$OLD_EMAIL" ]
then
    export GIT_AUTHOR_NAME="$CORRECT_NAME"
    export GIT_AUTHOR_EMAIL="$CORRECT_EMAIL"
fi
' --tag-name-filter cat -- --branches --tags


git push --force --tags origin 'refs/heads/*'