Coder Social home page Coder Social logo

demacial's Introduction

demacial's People

Contributors

wulang8353 avatar gitbook-bot avatar

Stargazers

 avatar

Watchers

James Cloos avatar  avatar

demacial's Issues

3 浏览器渲染过程-从输入url到页面加载完成发生了什么?

3 浏览器渲染过程-从输入url到页面加载完成发生了什么?

单独把这一点拿出来是因为这是一道经典的面试题,浏览器渲染过程包含在了题目之中。这道题没有一个标准的答案,从前端的角度,我认为起码需要回答以下几点:

1、浏览器的地址栏输入URL并按下回车

2、浏览器查找当前URL是否存在缓存,并比较缓存是否过期

3、DNS解析URL对应的IP

4、根据IP建立TCP连接(三次握手)

5、HTTP发起请求,浏览器接收HTTP响应

6、渲染页面,构建DOM树

7、关闭TCP连接(四次挥手)

3.1 浏览器的地址栏输入URL并按下回车

Web浏览器通过URL从Web服务器请求页面, 一个网页地址实例:

http://www.runoob.com/html/html-tutorial.html 

语法规则如下:

scheme://host.domain:port/path/filename

说明:

  • scheme 协议, 比如 http、https、ftp
  • domain 域名, 比如 runoob.com
  • port 端口号, http默认80, https默认443
  • host 主机名, http 的默认主机是 www
  • path 定义在服务器上文件的路径
  • filename 资源

这个属于基本知识了,其中还涉及到了同源策略、跨域、XSS与CRXF等,这里先不介绍了。

3.2 浏览器查找当前URL是否存在缓存,并比较缓存是否过期

根据是否需要重新向服务器发起请求来分类,可以将缓存类型分为强制缓存对比缓存。

  • 1、强制缓存: cache-control,Expires

Expires是一个绝对时间,即服务器时间。浏览器会检查当前时间,如果没有到服务器时间,说明还没有过期,直接使用缓存文件。但是该方法存在一个问题:服务器时间与客户端本地时间可能不一致,就无法判断文件的有效期,因此该字段已经很少使用。

cache-control中的 max-age 保存一个相对时间。例如Cache-Control: max-age = 484200,表示浏览器收到文件后,在484200s内缓存有效,在这段时间内,发出的请求都会直接使用缓存。 如果同时存在 cache-control 和 Expires,浏览器总是优先使用 cache-control。

default

  • 2、对比缓存:last-modified,Etag

Last-Modified/If-Modified-Since

上面说的当有效期过后,检查服务端文件是否更新的第一种方式,要配合Cache-Control使用。

last-modify1

按下 ctrl+r 刷新,ctrl+r会默认跳过 max-age 和 Expires 的检验直接去向服务器发送请求

last-modify2

last-modified 第一次请求资源时,服务器返回该字段,表示最后一次更新的时间。下一次浏览器请求资源时,请求中会发送if-modified-since 字段。服务器用自己本地 Last-modified 时间与if-modified-since 时间比较,如果不一致则认为缓存已过期并返回新资源给浏览器;如果时间一致则发送 304 状态码,让浏览器继续使用缓存。

Etag/If-None-Match

ETag并不是文件的版本号,而是一串可以代表该文件唯一的字符串(文件的索引节,大小和最后修改时间进行Hash后得到的)

当客户端发现和服务器约定的直接读取缓存的时间过了,就在请求中发送If-None-Match选项,值即为上次请求后响应头的ETag值,该值在服务端和服务端代表该文件唯一的字符串对比(如果服务端该文件改变了,该值就会变),如果相同,则相应HTTP304,客户端直接读取缓存,如果不相同,HTTP200,下载正确的数据,更新ETag值。

etag

如果两者同时存在,If-None-Match/ETag优先,忽略Last-Modified/If-Modified-Since

HTTP1.1中 ETag 的出现主要是为了解决几个 Last-Modified 比较难解决的问题:

  • Last-Modified 标注的最后修改只能精确到秒级,如果某些文件在1秒钟以内,被修改多次的话,它将不能准确标注文件的修改时间

  • 如果某些文件会被定期生成,但有时内容并没有任何变化(仅仅改变了时间),但 Last-Modified 却改变了,导致文件没法使用缓存

  • 有可能存在服务器没有准确获取文件修改时间,或者与代理服务器时间不一致等情形

无法被浏览器缓存的请求:

  • HTTP响应头中不包含:Cache-Control/Expires
  • Last-Modified/Etag; 非 HTTP:
  • 需要根据 Cookie,认证信息等决定输入内容的动态请求是不能被缓存的
  • 经过 HTTPS 安全加密的请求
  • POST 请求无法被缓存

default

通过上表可以看到,在按 F5 进行刷新的时候,会忽略 Expires/Cache-Control 的设置,会再次发送请求去服务器请求,而 Last-Modified/Etag 还是有效的,服务器会根据情况判断返回 304 还是 200;而当使用 Ctrl+F5 进行强制刷新的时候,只是所有的缓存机制都将失效,重新从服务器拉去资源

3.3 DNS解析URL对应的IP

当在浏览器中输入一个地址时,例如 www.baidu.com, 其实不是百度网站真正意义上的网络地址。

互联网上每一台计算机的唯一标识是它的IP地址,但是IP地址并不方便记忆。用户更喜欢用方便记忆的网址去寻找互联网上的其它计算机,也就是上面提到的百度的网址。

可以这样理解, 整个网络相当于一个非常巨大的电话簿。我们在打电话的时候首先要找到名字,然后再去拨打对应的电话号码。

例如 www.baidu.com 这个域名就相当于名字,而如何找到对应的电话号码呢? DNS域名解析的过程实际是将域名还原为IP地址的过程,这样就能找到电话号码进行通信了。

DNS域名解析的过程

首先浏览器先检查本地hosts文件是否有这个网址映射关系,如果有就调用这个IP地址映射,完成域名解析。

如果没找到则会查找本地DNS解析器缓存,如果查找到则返回。

如果还是没有找到则会查找本地DNS服务器,如果查找到则返回。

最后迭代查询,按根域服务器 ->顶级域,.cn->第二层域,hb.cn ->子域,www.hb.cn的顺序找到IP地址。

dns1

3.4 根据IP建立TCP连接(三次握手)

通过DNS域名解析后拿到IP地址,在获取到IP地址后,便会开始建立一次连接,这是由TCP协议完成的。

主要通过三次握手进行连接。

》 第一次握手: 建立连接时,客户端发送syn包(syn=j)到服务器,并进入SYN_SENT状态,等待服务器确认; 

》 第二次握手: 服务器收到syn包,必须确认客户的SYN(ack=j+1),同时自己也发送一个SYN包(syn=k),即SYN+ACK包,此时服务器进入SYN_RECV状态;

》 第三次握手: 客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=k+1),此包发送完毕,客户端和服务器进入ESTABLISHED(TCP连接成功)状态,完成三次握手。

dns2

从图中就能看出为什么是三次握手而不是二次。因为要确保客户端与服务器双方的发送和接收的功能均正常。

default

  • 第一次握手,客户端传到服务器,建立连接

  • 第二次握手,服务器传到客户端

    服务器接收到客户端的消息,并发送消息到客户端,落脚点在客户端

    说明服务器能够正常接收消息,所以客户端的发送正常,服务器的接收也正常 -> 服务器 接收 √ | 客户端 发送

  • 第三次握手,客户端传到服务器

    客户端接收到服务器的消息,并发送消息到服务器,落脚点在服务器

    说明客户端能够正常接收消息,所以服务器的发送正常,客户端的接收也正常 -> 客户端 发送接收 √ | 服务器 接收发送

3.5 HTTP发起请求,浏览器接收HTTP响应

HTTP请求报文是由三部分组成: 请求行, 请求报头和请求正文。

default

这里就不用说太多,平常开发调试中应该是经常遇的到,就讲讲状态码吧:

  • 1xx:指示信息–表示请求已接收,继续处理

  • 2xx:成功–表示请求已被成功接收、理解、接受

    • 204:无内容。服务器成功处理,但未返回内容
  • 3xx:重定向–要完成请求必须进行更进一步的操作

    • 301:永久移动。请求的资源已被永久的移动到新URI,会返回新的URI,并且自动定向到该地址

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

    • 304: 未修改。所请求的资源未修改,服务器返回此状态码时,不会返回任何资源。 缓存经常遇到。

  • 4xx:客户端错误–请求有语法错误或请求无法实现

    • 401:请求身份认证,需要验证

    • 405:请求中的方法被禁止,接口中限定了请求方法

    • 415:服务器无法处理请求附带的媒体格式,一般是MIME

  • 5xx:服务器端错误–服务器未能实现合法的请求

    • 503:服务器暂时的无法处理客户端的请求,服务器未开启

    • 504:网管超时,一般是服务器方法有问题

3.6 渲染页面,构建DOM树

浏览器拿到内容后,渲染大概可以划分成以下几个步骤:

  • 1、DOM树:从上到下解析HTML文档建立DOM树
  • 2、CSSOM树:加载解析样式生成CSSOM树
  • 3、执行JavaScript:加载并执行JavaScript代码
  • 4、构建渲染树(render tree):根据DOM树和CSSOM树,生成渲染树(render tree)
  • 5、布局(layout):根据渲染树将节点树的每一个节点布局在屏幕上的正确位置
  • 6、绘制(painting):遍历渲染树绘制所有节点,为每一个节点适用对应的样式,这一过程是通过UI后端模块完成

dom

如果 DOM 或 CSSOM 被修改,以上过程需要重复执行,这样才能计算出哪些像素需要在屏幕上进行重新渲染。实际页面中,CSS 与 JavaScript 往往会多次修改 DOM 和 CSSOM。

HTML页面加载和解析流程具体一点来说:

1、解析HTML结构。发现 ``<head>`` 标签中有一个 ``<link> `` 标签引用外部CSS文件

2、浏览器又发出CSS文件的请求,服务器返回这个CSS文件

3、浏览器继续载入html中 ``<body>`` 部分的代码,并且CSS文件就位,开始渲染页面了

4、浏览器在代码中发现一个 ``<img>`` 标签引用了一张图片,向服务器发出请求。此时浏览器不会等到图片下载完,而是继续渲染后面的代码

5、服务器返回图片文件,由于图片占用了一定面积,影响了后面段落的排布,因此浏览器需要回过头来重新渲染这部分代码

6、浏览器发现了一个包含一行Javascript代码的 ``<script>`` 标签,解析并运行

7、Javascript命令浏览器隐藏掉掉某个DOM元素,浏览器重新渲染这部分代码

8、解析至</html>的到来,整个流程结束

defer与async

由上可知,script标签的存在会阻塞渲染,但浏览器总是并行加载资源,当浏览器遇到script时候:

1、<script src="script.js"></script>

没有defer和async,浏览器会立即加载并执行脚本,此时会将渲染挂起,页面会有延迟的感觉,所以一般会将其放在</body>之前

2、<script src="script.js" async></script>

有async,加载和渲染后续文档元素的过程和script.js的加载与执行并行进行,不一定要按照JS的顺序执行

3、<script src="script.js" defer></script>

有defer。加载后续文档元素的过程和script.js的加载并行,但是和script.js的执行要在所有元素解析完成之后, DOMContentLoaded事件(DOM树构建完成)之前完成,并且是按照JS的顺序执行

defer

3.7 关闭TCP连接(四次挥手)

这一过程可以由客户端或者服务器任一方来触发,流程如下:

default

第一次挥手:浏览器发完数据后,发送FIN请求断开连接。

第二次挥手:服务器发送ACK表示同意

第三次挥手:但考虑到服务器可能还有数据要发送,那么就等服务器发送数据结束后再发送FIN,表示断开连接

第四次挥手:浏览器需要返回ACK表示同意

为什么是四次挥手?

客户端发送 FIN 仅仅表示不再发送数据,但是还能接受数据。服务器发送ack表示知道你准备关闭,但是我这还没发送完。直到服务器也发送了 FIN 表示不再发送数据,同意现在关闭连接。浏览器接收后发送ack到服务器,整个连接关闭。

抽象理解成:

客户端:"老服同志,我不想讲了,要关闭连接了哈" -> FIN、ack

服务器:“你个老客,你要走了啊? 等回,我还没说完” -> ack

(服务器啰嗦了一会,看到没回应觉得没意思)

服务器: “算了,不说了,没意思,回见了您” -> FIN

客户端:“回见” -> ack

如何处理二进制数据流:Blob对象

blob 对象

应用背景

一直以来,JS都没有比较好的可以直接处理二进制的方法。而Blob的存在可以通过JS直接操作二进制数据。 Blob对象可以看做是存放二进制数据的容器,此外还可以通过Blob设置二进制数据的MIME类型。

一个Blob对象就是一个包含有只读原始数据的类文件对象。Blob对象中的数据并不一定得是JavaScript中的原生形式。File接口基于Blob,继承了Blob的功能,并且扩展支持了用户计算机上的本地文件

Blob对象

属性

var blob = new Blob(dataArr, option)
  • dataArr:数组,包含了要添加到Blob对象中的数据,数据可以是任意多个ArrayBuffer,ArrayBufferView, Blob,或者 DOMString对象
  • opt:对象,用于设置Blob对象的属性(如:MIME类型)

1、创建一个有DOMString的Blob对象

domstring

2、创建一个有ArrayBuffer的Blob对象

arraybuffer

方法 Bolb.slice()

此方法返回一个新的Blob对象,包含了原Blob对象中指定范围内的数据

Blob.slice(start:number, end:number, contentType:string)
  • start:开始索引,默认为0
  • end:截取结束索引(不包括end)
  • contentType:新Blob的MIME类型,默认为空字符串

slice

应用场景

通过url下载文件

思路: window.URL对象可以为Blob对象生成一个网络地址,结合a标签的download属性,可以实现点击url下载文件

download("download.txt", "download-file");

function download(fileName, url) {
  var blob = new Blob([url]); \\ 创建Blob对象,可以指定MIME
  if (window.navigator.msSaveOrOpenBlob) { \\ 针对IE浏览器打开Blob URL连接
    navigator.msSaveBlob(blob, fileName);  \\ msSaveOrOpenBlob 提高保存和打开选项,而msSaveBlob只提供保存选项
  } else {
    var link = document.createElement("a");
    link.download = fileName; \\ 下载的文件名称
    link.innerHTML = fileName; \\ 强行指定文件内容
    link.href = window.URL.createObjectURL(blob); \\ 创建Blob连接
    document.getElementsByTagName("body")[0].appendChild(link); 
    \\ link.click(); 浏览器会直接打开文件
    \\ window.URL.revokeObjectURL(link.href); 只需要访问一次,一旦已经访问到了,这个对象URL就不再需要了,就被释放掉
  }
}

分片上传大文件

通过Blob.slice方法,可以将大文件分片,轮循向后台提交各文件片段,即可实现文件的分片上传。

分片上传逻辑如下:
1、获取要上传文件的File对象,根据chunk(每片大小)对文件进行分片
2、通过post方**循上传每片文件,其中url中拼接querystring用于描述当前上传的文件信息
3、post body中存放本次要上传的二进制数据片段,接口每次返回offset,用于执行下次上传

initUpload();

//初始化上传
function initUpload() {
    var chunk = 100 * 1024;   //每片大小
    var input = document.getElementById("file");    //input file
    input.onchange = function (e) {
        var file = this.files[0];
        var query = {};
        var chunks = [];
        if (!!file) {
            var start = 0;
            //文件分片
            for (var i = 0; i < Math.ceil(file.size / chunk); i++) {
                var end = start + chunk;
                chunks[i] = file.slice(start , end);
                start = end;
            }
            
            // 采用post方法上传文件
            // url query上拼接以下参数,用于记录上传偏移
            // post body中存放本次要上传的二进制数据
            query = {
                fileSize: file.size,
                dataSize: chunk,
                nextOffset: 0
            }

            upload(chunks, query, successPerUpload);
        }
    }
}

// 执行上传
function upload(chunks, query, cb) {
    var queryStr = Object.getOwnPropertyNames(query).map(key => {
        return key + "=" + query[key];
    }).join("&");
    var xhr = new XMLHttpRequest();
    xhr.open("POST", "http://xxxx/opload?" + queryStr);
    xhr.overrideMimeType("application/octet-stream");
    
    //获取post body中二进制数据
    var index = Math.floor(query.nextOffset / query.dataSize);
    getFileBinary(chunks[index], function (binary) {
        if (xhr.sendAsBinary) {
            xhr.sendAsBinary(binary);
        } else {
            xhr.send(binary);
        }

    });

    xhr.onreadystatechange = function (e) {
        if (xhr.readyState === 4) {
            if (xhr.status === 200) {
                var resp = JSON.parse(xhr.responseText);
                // 接口返回nextoffset
                // resp = {
                //     isFinish:false,
                //     offset:100*1024
                // }
                if (typeof cb === "function") {
                    cb.call(this, resp, chunks, query)
                }
            }
        }
    }
}

// 每片上传成功后执行
function successPerUpload(resp, chunks, query) {
    if (resp.isFinish === true) {
        alert("上传成功");
    } else {
        //未上传完毕
        query.offset = resp.offset;
        upload(chunks, query, successPerUpload);
    }
}

// 获取文件二进制数据
function getFileBinary(file, cb) {
    var reader = new FileReader();
    reader.readAsArrayBuffer(file);
    reader.onload = function (e) {
        if (typeof cb === "function") {
            cb.call(this, this.result);
        }
    }
}

通过url显示图片

思路: img的src属性及background的url属性,都可以通过接收图片的网络地址或base64来显示图片,所以可以把图片转化为Blob对象,生成URL。

2 JS单线程与浏览器多进程

2 JS单线程与浏览器多进程

2.1 JS是单线程

单线程:一个进程中只有一个线程。程序顺序执行,前面的执行完,才会执行后面的程序。

问题1、JavaScript为什么是单线程?

举个栗子,在页面中直接alert('hello'),只要不关闭这个对话框,后续的JS代码就不会再执行。单线程就是这样一步一步的顺次执行,前面不执行完,后面不会执行。也就是说,在具体的某一时刻,只有一段代码在执行

问题2、为什么有时候感觉JavaScript是多线程?

先说答案:JS单线程通过执行浏览器的任务队列,从而在感觉上可以处理各种触发事件

  • 任务队列(事件队列)

    单线程意味所有任务需要排队,前一个任务结束,才会执行后一个。假设任务耗时很长,后一个任务得一直等着。

    但比如一些异步操作(Ajax请求)得到结果是需要一定时间的,所以CPU总不能在等待结果的这段时间内一直闲着。

    于是,所有任务可以分成两种,一种是同步任务(synchronous),另一种是异步任务(asynchronous)。

    同步任务 指的是,在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务

    异步任务 指的是,不进入主线程、而进入任务队列的任务。通过任务队列去通知js引擎主线程,告诉主线程某个异步任务可以执行了,该任务才会进入js引擎主线程执行。

总结一下:

1、所有同步任务都在主线程上执行,形成一个执行栈

2、主线程之外,还存在一个"任务队列"。只要异步任务有了运行结果,就在"任务队列"之中放置一个该异步任务对应的事件。执行顺序是先进先出。

3、一旦"执行栈"中的所有同步任务执行完毕,系统就会读取"任务队列",看看里面有哪些事件以及对应的异步任务,将其放入执行栈,开始执行任务队列中的事件。

4、主线程不断重复上面的第三步。

default

只要主线程中所有的同步任务结束完了,就会去读取"任务队列"的异步任务(按照事件的触发顺序,比如鼠标点击了、定时器设定的时间到了、HTTP 请求的状态变更),这也就是JavaScript的运行机制。

2.2 浏览器的多进程与多线程

2.2.1 浏览器的多进程

以谷歌浏览器为例,每打开一个Tab页,就相当于创建了一个独立的浏览器进程,可以在Chrome的任务管理器中(更多工具 -> 任务管理器,或者shift + esc)看到有多个进程

注意:在这里浏览器应该也有自己的优化机制,有时候打开多个tab页后,可以在Chrome任务管理器中看到,有些进程被合并了。浏览器有时会将多个进程合并(譬如打开多个空白标签页后,会发现多个空白标签页被合并成了一个进程)

default

强化记忆:在浏览器中打开一个网页相当于新起了一个进程(进程内有自己的多线程),所以浏览器多进程的。

知道了浏览器是多进程后,再来看看它到底包含哪些其他的进程

  • Browser进程:浏览器的主进程(负责协调、主控),只有一个。作用有
    • 负责浏览器界面显示,与用户交互。如前进,后退等
    • 负责各个页面的管理,创建和销毁其他进程
    • 将Renderer进程得到的内存中的Bitmap,绘制到用户界面上
    • 网络资源的管理,下载等
  • 第三方插件进程:每种类型的插件对应一个进程,仅当使用该插件时才创建
  • GPU进程:最多只有一个,用于3D绘制等
  • 浏览器渲染进程(浏览器内核)(Renderer进程,内部是多线程的):默认每个Tab页面一个进程,互不影响。主要作用为页面渲染,脚本执行,事件处理等

2.2.2 浏览器的多线程

上面介绍了浏览器是多进程的,那么,对于前端操作来说,最终要的是什么呢?答案是浏览器渲染进程,想要了解深入了解就先得要了解浏览器的基础结构,主要包括如下几个部分:

1、用户界面(User Interface):用户所看到及与之交互的功能组件,如地址栏,返回前进按钮,标签页等,可以理解成除去页面内容显示之外的其他各部分的组合。

2、浏览器引擎(Browser engine): 负责在用户界面与渲染引擎之间指令传输

3、渲染引擎(Rendering engine):负责显示内容,通过解析用户请求的内容(如HTML以及相关CSS)然后返回解析后的内容并显示

4、网络(Networking):负责处理网络相关的事务,如HTTP请求等

5、UI后端(UI backend):负责绘制提示框等浏览器组件,公开通用接口,其底层使用的是操作系统的用户接口

6、JavaScript解释器(JavaScript interpreter):负责解析和执行JavaScript代码

7、数据存储(Data storage):负责持久存储诸如cookie和缓存等应用数据,是一个完整但轻便的浏览器数据库

default

渲染引擎被称为浏览器内核,也就是所说的渲染进程。可以这样理解,页面的渲染,JS的执行,事件的循环,都在这个进程内进行。

各大主要浏览器使用内核也是有差别的,大致可以分为以下几类:

Trident内核: IE
Webkit内核:Chrome,Safari
Gecko内核:FireFox

请牢记,浏览器的渲染进程是多线程的

那么接下来看看它都包含了哪些线程

  • GUI渲染线程

    • 负责渲染浏览器界面,解析HTML,CSS,构建DOM树和渲染树,布局和绘制等。
    • 重绘(Repaint)或回流(reflow),该线程就会执行
    • GUI渲染线程与JS引擎线程是互斥,当JS引擎执行时GUI线程会被挂起,GUI的更新会被保存在一个队列中,等到JS引擎空闲时立即被执行。
  • JS引擎线程

    • 负责处理Javascript脚本程序。(例如V8引擎)
    • JS引擎负责同步任务,同时也一直等待着任务队列中的异步任务
    • 一个Tab页中无论什么时候都只有一个JS线程在运行JS程序
  • 定时触发器线程

    • setInterval与setTimeout
    • 浏览器定时计数器并不是由JS引擎计数的
    • 因此通过单独的定时触发器线程来计时并触发定时(计时完毕后,添加到任务队列中,等待JS引擎空闲后执行)
  • http请求线程

    • 在XMLHttpRequest连接后,浏览器新开的一个线程
    • 检测到状态变更时,如果设置有回调函数,异步http请求线程就产生状态变更事件,将这个回调再放入任务队列中。再由JS引擎执行
  • 事件触发线程

    • 当JS引擎执行定时器、鼠标点击、AJAX异步请求等,会将对应任务添加到事件触发线程中
    • 当对应事件触发条件被触发时,该线程会把事件添加到任务队列的队尾,等待JS引擎的处理
    • 由于JS是单线程,所以这些待处理队列中的事件都得排队等待JS引擎处理(当JS引擎空闲时才会去执行)

default

总结一下:

由此可知,浏览器是多进程的,而其中的渲染进程是多线程的

2.2.3 主进程和渲染进程的通信过程

看到这里,首先,应该对浏览器内的进程和线程都有一定理解了。那么进程与渲染进程间是如何通信的呢?

根据浏览器的任务管理所示,可以看到有一个浏览器任务,这就是主进程;其他则是打开了Tab页面的渲染进程,比如百度github等。

default

通信过程如下:

1、主进程(Browser)进程根据用户操作,首先需要通过网络请求下载资源获取页面内容,随后将该下载任务传递给渲染进程(Render进程)

2、渲染进程的Renderer接口收到消息,解析后交给其中的GUI渲染线程,然后开始渲染

3、渲染线程接收请求,加载网页并渲染网页,这其中可能需要主进程获取其他资源例如图片、音频等,也可能会存在JS引擎线程操作DOM

5、最后渲染进程将结果传递给主进程,主进程接收到结果并将结果绘制出来

渲染引擎被称为浏览器内核,也就是所说的渲染进程。可以这样理解,页面的渲染,JS的执行,事件的循环,都在这个进程内进行。 既然渲染进程主要影响了浏览器页面内容的显示,那么接下来就看看进程中各线程的关系。

2.2.4 互斥的线程

首先回顾一下,渲染进程中的有哪些线程:

  • GUI渲染线程
  • JS引擎线程
  • 定时触发器线程
  • http请求线程
  • 事件触发线程

GUI渲染线程与JS引擎线程互斥,这里借用一下阮一峰老师在阮一峰《JavaScript 运行机制详解:再谈Event Loop》的例子:假定JavaScript同时有两个线程,一个线程在某个DOM节点上添加内容,另一个线程删除了这个节点,这时浏览器应该以哪个线程为准?

因此,为了避免由于JS线程和GUI线程同时运行而造成的渲染线程前后获得的元素数据不一致,浏览器设置GUI渲染线程与JS引擎为互斥的关系,当JS引擎执行时GUI线程会被挂起,当JS引擎线程空闲时,保存在一个队列中的GUI线程会被立刻执行。由此可见,JS如果执行时间过长就会阻塞页面,所以要尽量避免JS执行时间过长,导致页面渲染速度慢而产生卡顿的感觉。

WebWorker与SharedWorker

大量的JS计算无疑会对页面渲染的效率产生影响,那么如何高效的处理这种密集型计算? HTML5支持了Web Worker

Web Worker为Web内容在后台线程中运行脚本提供了一种简单的方法。线程可以执行任务而不干扰用户界面

一个worker是使用一个构造函数创建的一个对象(e.g. Worker()) 运行一个命名的JavaScript文件 

这个文件包含将在工作线程中运行的代码; workers 运行在另一个全局上下文中,不同于当前的window

因此,使用 window快捷方式获取当前全局的范围 (而不是self) 在一个 Worker 内将返回错误

简单理解:

  • 创建Worker时,JS引擎向浏览器申请开一个子线程(子线程是浏览器开的,而且不能操作DOM)

  • JS引擎线程与worker线程间通过特定的方式通信(postMessage API)

  • Worker线程专门处理耗时的工作,而且并不会影响JS引擎线程,当计算结束后传给主线程即可

使用教程可参考阮一峰《Web Worker 使用教程》

WebWorker只属于某个页面,由于每一个Tab页就是一个render进程,每个render进程都会有独立的Worker线程来运行JS。

SharedWorker是浏览器所有页面共享的,不能采用与Worker同样的方式实现,因为它不隶属于某个Render进程,可以为多个Render进程共享使用。

SharedWorker由独立的进程管理,WebWorker只是属于render进程下的一个线程。

参考资料:

阮一峰《JavaScript 运行机制详解:再谈Event Loop》

不知名前端《从浏览器多进程到JS单线程》

进程与线程,JS是什么运行机制

1 进程和线程

1 进程和线程

1.1 进程和线程概念理解

default

工厂:假设CPU是一个有很多车间的工厂、但工厂的电力有限,一次只能供一个车间使用,也就是说,一个车间开工,其他车间必须停工。

车间:进程就好比工厂的车间,所代表需要处理的单个任务,某个时间段,CPU运行一个进程,其他进程就不能运行。

工人:车间需要工人, 所以车间里的工人就好比线程,协同完成一个任务。即一个进程中可以包含多个线程。

房间:车间会有比如装配室、加工室等独立的房间,都可以让工人自由出入。这些房间相当于内存空间,这象征一个进程(车间)的内存空间(房间)是共享的,每个线程(工人)都可以使用这些共享内存。

独卫:有人的地方就会有厕所,当厕所里面有人的时候,其他人就必须在外面憋着。说明一个线程使用共享内存时,其他线程必须等待他使用完。

上厕所记得关门:先到的人锁上门,后来的人看到锁知道里面有人后,就在门口排队,等里面的人用完开锁再进去,这就叫“互斥锁”,防止多个线程同时读写某一块内存。

下班换衣服咯:假设有n个更衣室,墙上挂了n把钥匙,进去的人就取一把钥匙,出来时再把钥匙挂回原处。没有钥匙就得排队等着。这种做法叫做"信号量",由于某些内存区域只能供给固定数目的线程使用,这样就可以保证多个线程不会互相冲突。

default

搬文阮一峰老师-进程与线程的一个简单解释

总结一下:

  • 任务是由人完成的,所以CPU的基本调度单位是线程

  • 进程表示程序(任务)的一次执行,也是CPU资源分配的最小单位(要人干活总得有地方吧)

  • 进程间相互独立,同一进程的各线程共享内存

  • 互斥锁和信号量区别就在于资源个数,互斥锁是信号量在n=1的特殊情况

1.2 进程和线程的深入理解

场景想象:

周末晚上在家玩电脑,想听听歌打开了网易云音乐放了首《单身的人不要听情歌》

这时微信显示有消息,发现是一个妹子找你聊天说心情不太好.

你激动的写了一段文字发了过去,又觉得这样不太主动,换了首《慢慢喜欢你》,刻意压低声音发了一段语音过去。

妹子听了很感动然后觉得你人不错想和你视频,然后你马上穿上背心,向双手唾了唾抹在头发上就接收了视频邀请,

美妙的夜晚于是就拉开了帷幕~~~

从上面yy的场景中可以知道:

1、进程:网易云音乐、微信
2、线程:歌曲搜索、对话框弹出、文字传输、语音传输...

进程就是我们打开的一个软件,而在软件运行的过程中,多个工作(线程)协同才能支撑软件完成整个运行

比如快播播放器,而快播里面的那些功能,如搜索、播放、暂停。

有没有发现,其实进程和线程是CPU工作时间段的描述

1、说到这需要讲讲电脑的组成,可以大概的将电脑的构成包括:CPU+RAM+配置(显卡、光驱、显示屏、键盘等等)。 但电脑的运行实际上是CPU和寄存器以及RAM之间的事情

2、CPU执行速度太快了,当多个任务要执行的时候,在CPU看来就是轮流着“宠幸”

3、现在有个程序要执行了,等着CPU"宠幸"。再此之前,是不是环境得准备好啊,鲜花啊、蜡烛啊。不对不对,是比如显卡啊、声卡啊都得就位就差与CPU金风玉露一相逢了。

4、除了CPU以外所有的就构成了这个程序的执行环境,即程序的上下文环境。当程序执行结束 or 分配给它的CPU执行时间用完,程序就被切换出去,只能咬着白手绢等待下一次CPU的临幸了。

5、在被切换出去之前,最后一步是要保存上下文环境,原因很简单,因为想让CPU知道,还是熟悉的配方、熟悉的味道。

整个过程串起来就是:

先加载程序A的上下文,然后执行A,结束后保存程序A的上下文。继续调入下一个程序B的上下文,执行B,结束后保存...

》 进程是包含上下文环境切换与程序执行的时间总和 = CPU加载上下文 + CPU执行 + CPU保存上下文

CPU执行肯定不会只有一个逻辑,肯定会有多个分支和程序段,类似于程序A的执行需要分成A1,A2,A3等多个部分。所以当CPU执行程序A的时候,整个过程等价于:

CPU加载程序A上下文环境 -> CPU开始执行A1小段,A2小段,A3小段...直到执行完毕 -> 执行结束CPU保存程序A的上下环境

》 A1,A2,A3 就是线程,它们共享了进程(程序A)的上下文环境,是一种更为细分的CPU时间段

所以, 进程和线程是CPU工作时间段的描述,区别是其执行时间的颗粒大小不同

总结一下:

  • 进程:一段程序的执行,是CPU资源分配的最小单位

  • 线程:支撑这段程序完整执行的多项工作,是CPU的基本调度单位

  • 进程和线程是CPU工作时间段的描述,区别是其执行时间的颗粒大小不同

JavaScript加密浅浅了解

前端数据加密

前端传输的数据一般有几种加密做法:

  • js加密后传输
  • 浏览器插件加密传输
  • HTTPS 传输

js加密一般分为两种手段:

一、是使用数据摘要进行数据杂凑 ( 哈希加密 )

二、使用非对称加密算法对明文进行加密 ( 密钥加密 )

严格来讲, 第一种方式并非加密而是一种信息摘要的过程.

哈希加密与密钥加密

default

可见哈希与密钥是两个不同的东西, 其不同点在于:

  • 哈希算法: 用于数据摘要, 通过 hash 函数生成相同长度的文本, 其加密文本长度与明文文本有关. 哈希加密是不可逆的

  • 密钥加密: 分为对称加密与非对称加密

    非对称加密算法, 加密和解密的密钥是不同的, 分为私钥和公钥. 常用的 RSA 就是一种非对称加密算法.

    对称加密算法, 加密和解密公用同一个密钥, 如 AES/DES.

    从性能上来说, 非对称加密相对于对称加密要慢很多. 在HTTPS中, 认证过程使用了非对称加密, 非认证过程使用了对称加密.

前端加密的意义

在HTTP协议下, 数据是明文传输, 在传输过程中可以通过网络直接获取信息, 具有安全隐患; 另一方面在非加密的传输过程中,攻击者可更改数据或插入恶意的代码等。

HTTPS 的诞生就是为了解决中间人攻击的问题. 但如果仍然使用 HTTP 协议, 前端加密的作用即在数据发送前将数据进行哈希或使用公钥加密。如果数据被中间人获取,拿到的则不再是明文.

如何加密

1、哈希加密

首先回顾下后端通过哈希算法如何验证用户: 如果前端传过来的是明文, 那么在用户在注册时, 将哈希算法也存入数据库. 待用户登录后,将密码进行哈希后和数据库对应的数据比对, 若一致则说明密码正确 ( 密码+哈希 )

为了避免攻击者窃取前后端加密生成的字典,我们需要加盐,并不定期更新盐值:

前端加密后使用定期刷新的 Salt 再一次哈希生成密文,将该密文发送到后端, 数据库中的密码哈希值再与 Salt 哈希,两者进行密文比对. ( 密码+Salt+哈希 )

由于攻击者仍可以使用加密后的密码进行直接登陆,所以固定的 Salt 并不安全. 动态 Salt 有很多方法,可以是动态的 Token,也可以直接使用现成的验证码, 或者图形验证码

动态Salt加密过程:

前端先将密码哈希, 然后和用户输入的验证码进行哈希, 得到的结果作为密码字段发送给服务器. 服务器先确认验证码正确,然后再进行密码验证,否则直接返回验证码错误信息

hash

两种加密方式对比

1、哈希加密

先来理解两个概念:

撞库: 由于用户在不同网站使用的是相同的帐号密码, 收集已泄露的用户和密码信息,生成对应的字典表,尝试批量登陆其他网站后,得到一系列可以登录的账号

拖库: 指网站遭到入侵后,黑客窃取其数据库

js哈希加密保证了密码在传输过程中的资料安全, 因为验证码是动态的, 即使攻击者拿到了数据也无法重放. 图形化验证码更是增加了难度. 另一方面该实践大大增加了撞库的成本

使用一些前端加密手段,可以加大拖库后的数据破解的难度. 但是之前介绍的验证码方法不具有这样的功能,因为数据库存的仍是哈希后的明文密码后的结果,那么攻击者可以直接窃取数据库进行暴力破解

从两个方面分别去思考如何进一步提升性能

  • 降低暴力破解速度

    暴力破解即是使用相同的算法把常用的字符组跑一遍

    数据被暴力破解出来的时间与哈希算法速度负相关. 也就是说, 哈希算法越快, 破解的时间越快.

    例如 MD5 加密一次耗费1微秒,那么攻击者一秒钟就可以猜大约 100万 个词组. 所以为了减慢破解速度,我们需要增加破解的时间,一个直接的方法就是使用更慢的算法,我们可以把常用的MD5算法替换为 bcrypt,PBKDF2 等慢算法

  • 前端加密结果影响数据库的数据存储

    由于慢的算法会增加服务器计算压力,可以把一部分哈希工作分到前端,即减慢了加密速度,减轻了服务器压力,也顺带完成了前端加密的工作

哈希加密速度减慢一定程度会降低用户体验,这也是一部分站点未启用 https 的原因之一。但是因为我们的前端加密只会用在不常使用的登录和注册上,所以不会影响网站整体的体验

2、非对称加密

因为可以在前端窃取数据,一旦密钥失窃就相当于明文数据了, 所以使用对称加密是不安全的.

非对称加密原理很简单,前后端共用一套加密解密算法,前端使用公钥对数据加密,后端使用私钥将数据解密为明文。中间攻击人拿到密文,如果没有私钥的话是没办法破解的。

这里介绍 jsencrypt 插件实现前端参数加密,jsencypt具体的使用参考 jsencrypt

思路: 拿到公钥和私钥. 利用公钥RSA加密, 私钥解密

1 创建密钥对JKS格式keystore:
keytool -genkey -v -alias test -keyalg RSA -keystore test.jks

2 将JKS格式keystore转换成PKCS12证书文件:
keytool -importkeystore -srckeystore test.jks -destkeystore test.p12 -srcstoretype JKS -deststoretype PKCS12

3 使用OpenSSL工具从PKCS12证书文件导出密钥对:
openssl pkcs12 -in test.p12 -nocerts -nodes -out test.key

4 从密钥对中提取出公钥:
openssl rsa -in test.key -pubout -out test_public.pem

拿到公钥test_public.pem后,在cat test_public.pem查看这个公钥内容,内容是base64格式的

  const jse  = new JSEncrypt() // 创建该对象

  jsencrypt.setPublicKey(this.publicKey)  // 设置公钥

  let enPassword = jse.encrypt(this.password) // 对密码加密

总结: 在没有 https 的情况下,使用 JavaScript 非对称加密或者插件加密通讯是比较好的替代方法

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.