Web前端入门必知——浏览器基础知识

浏览器的主要功能:javascript

是将用户选择的web资源呈现出来,它须要从服务器请求资源,并将其显示在浏览器窗口中,资源的格式一般是HTML,也包括PDF、image及其余格式。用户用URI(Uniform Resource Identifier统一资源标识符)来指定所请求资源的位置。css

浏览器的主要组件包括:html

  1. 用户界面 - 包括地址栏、后退/前进按钮、书签目录等,也就是你所看到的除了用来显示你所请求页面的主窗口以外的其余部分。前端

  2. 浏览器引擎 - 用来查询及操做渲染引擎的接口。html5

  3. 渲染引擎 - 用来显示请求的内容,例如,若是请求内容为html,它负责解析html及css,并将解析后的结果显示出来。java

  4. 网络 - 用来完成网络调用,例如http请求,它具备平台无关的接口,能够在不一样平台上工做。jquery

  5. UI后端 - 用来绘制相似组合选择框及对话框等基本组件,具备不特定于某个平台的通用接口,底层使用操做系统的用户接口。css3

  6. JS解释器 - 用来解释执行JS代码。web

  7. 数据存储 - 属于持久层,浏览器须要在硬盘中保存相似cookie的各类数据,HTML5定义了web database技术,这是一种轻量级完整的客户端存储技术。面试


1. 浏览器输入URL到显示页面发生了什么?

老问题,你们面试的时候应该都被问过这种问题,网上的答案千篇一概,咱们来更深刻的了解一下。

1.1 在浏览器中输入url

用户输入url,例如http://www.feng.com。其中http为协议,www.feng.com为网络地址,及指出须要的资源在哪台计算机上。通常网络地址能够为域名或IP地址,此处为域名。使用域名是为了方便记忆,一串数字哦咱们很容易会记错,可是为了让计算机理解这个地址还须要把它解析为IP地址。

1.2 查看浏览器缓存

若是访问过该url,会先进入浏览器缓存中查询是否有要请求的文件(浏览器缓存是在本地保存资源副本)。

当浏览器发现请求的资源已经在浏览器缓存中存有副本,它会拦截请求,返回该资源的副本,并直接结束请求,而不会再去源服务器从新下载。若是缓存查找失败,就会进入网络请求过程了。

在network中会标注该请求是在服务器中请求的仍是浏览器缓存中的。

一条域名的DNS记录会在本地有两种缓存:浏览器缓存和操做系统(OS)缓存。

1.2.1 浏览器缓存 – 浏览器会缓存DNS记录一段时间。通常是2分钟到30分钟不等。查找浏览器缓存时会按顺序查找: Service Worker-->Memory Cache-->Disk Cache-->Push Cache。

Service Worker:

是运行在浏览器背后的独立线程,通常能够用来实现缓存功能。使用 Service Worker的话,传输协议必须为 HTTPS。由于 Service Worker 中涉及到请求拦截,因此必须使用 HTTPS 协议来保障安全。Service Worker 的缓存与浏览器其余内建的缓存机制不一样,它可让咱们自由控制缓存哪些文件、如何匹配缓存、如何读取缓存,而且缓存是持续性的。

Memory Cache:

内存中的缓存,主要包含的是当前中页面中已经抓取到的资源,例如页面上已经下载的样式、脚本、图片等。读取内存中的数据确定比磁盘快,内存缓存虽然读取高效,但是缓存持续性很短,会随着进程的释放而释放。一旦咱们关闭 Tab 页面,内存中的缓存也就被释放了。

Disk Cache:

存储在硬盘中的缓存,读取速度慢点,可是什么都能存储到磁盘中,比之 Memory Cache 胜在容量和存储时效性上。

在全部浏览器缓存中,Disk Cache 覆盖面基本是最大的。它会根据 HTTP Herder 中的字段判断哪些资源须要缓存,哪些资源能够不请求直接使用,哪些资源已通过期须要从新请求。而且即便在跨站点的状况下,相同地址的资源一旦被硬盘缓存下来,就不会再次去请求数据。绝大部分的缓存都来自 Disk Cache。

Push Cache:

Push Cache(推送缓存)是 HTTP/2 中的内容,当以上三种缓存都没有命中时,它才会被使用。它只在会话(Session)中存在,一旦会话结束就被释放,而且缓存时间也很短暂,在Chrome浏览器中只有5分钟左右,同时它也并不是严格执行HTTP头中的缓存指令。

1.2.2系统缓存 – 若是在浏览器缓存里没有找到须要的记录,浏览器会作一个系统调用得到系统缓存中的记录(windows里是gethostbyname)。

1.2.3 路由器缓存** – 接着,前面的查询请求发向路由器,它通常会有本身的DNS缓存。

1.2.4 ISP DNS 缓存** – 接下来要check的就是ISP缓存DNS的服务器。在这通常都能找到相应的缓存记录。

1.2.5 递归搜索** – 你的ISP的DNS服务器从跟域名服务器开始进行递归搜索,从.com顶级域名服务器到Facebook的域名服务器。通常DNS服务器的缓存中会有.com域名服务器中的域名,因此到顶级服务器的匹配过程不是那么必要了。

1.3 DNS域名解析

若是没有访问过该url,就会进行DNS域名解析了。

IP地址和域名同样都是用来作网络标识的,域名和 IP 地址是一一对应的映射关系。

DNS:Domain Name System域名系统(基于RFC规范解释),是万维网上做为域名和IP地址相互映射的一个分布式数据库,可以使用户更方便的访问互联网,而不用去记住可以被机器直接读取的IP数串。

DNS解析过程:

1.3.1 用户主机上运行着DNS的客户端,就是咱们的PC机或者手机客户端运行着DNS客户端。

1.3.2 浏览器将接收到的url中抽取出域名字段,就是访问的主机名,好比www.feng.com, 并将这个主机名传送给DNS应用的客户端.

1.3.3 DNS客户机端向DNS服务器端发送一份查询报文,报文中包含着要访问的主机名字段(中间包括一些列缓存查询以及分布式DNS集群的工做)。

1.3.4 该DNS客户机最终会收到一份回答报文,其中包含有该主机名对应的IP地址。

1.3.5 一旦该浏览器收到来自DNS的IP地址,就能够向该IP地址定位的HTTP服务器发起TCP链接。

1.4 获取端口号

可能域名下有多个端口号,对应着不一样的网络功能,因此在DNS解析以后,浏览器还会获取端口号。

1.5 创建TCP链接

TCP链接,就是耳熟能详的三次握手好朋友,四次挥手是路人。

TCP链接过程:

1.5.1 服务端经过socket,bind和listen准备好接受外来的链接,此时服务端状态为Listen。

1.5.2 客户端经过调用connect来发起主动链接,致使客户端TCP发送一个SYN(同步)字节,告诉服务器客户将在(待创建的)链接中发送的数据的初始序列号,客户端状态为SYN_SENT。

1.5.3 服务器确认(ACK)客户的SYN,并本身也发送一个SYN,它包含服务器将在同一链接中发送数据的初始序列号。

1.5.4 客户端确认服务的ACK和SYN,向服务器发送ACK,客户端状态ESTABLISHED。

1.5.5 服务器接收ACK,服务器状态ESABLISHED。

1.6 HTTP请求

既然咱们握手成功了,链接到了Web服务器,浏览器会根据解析到的IP地址和端口号发起HTTP请求。

1.6.1 http协议向服务器发送请求,发送请求的过程当中,浏览器会向Web服务器以Stream(流)的形式传输数据,告诉Web服务器要访问服务器里面的哪一个Web应用下的Web资源。

1.6.2 服务器接收到浏览器传输的数据后,开始解析接收到的数据,服务器解析请求里面的内容时知道客户端浏览器要访问的是应用里面的哪这个Web资源,而后服务器就去读取这个Web资源里面的内容,将读到的内容再以Stream(流)的形式传输给浏览器。

1.7 关闭TCP

TCP链接停止过程:

1.7.1 某端首先调用close,成为主动关闭端,向另外一端发送FIN分节,表示数据发送完毕,此时主动关闭端状态FIN_WAIT_1;

1.7.2 接收到FIN的是被动关闭端,FIN由TCP确认,先向主动关闭端发送ACK,做为一个文件结束符传递给接收端应用进程(放在已排队等候该应用进程接收到的任何其余数据以后),由于FIN的接收意味着接收端应用进程在相应链接无额外数据可接收,接收端状态CLOSE_WAIT;主动关闭端接收到ACK状态变为FIN_WAIT_2;

1.7.3 一段时间后,接收端接收到这个文件结束符的应用进程调用close关闭套接字,向主动关闭端发送FIN,接收端状态为LAST_ACK;

1.7.4 主动关闭端确认FIN,状态变为TIME_WAIT,并向接收端发送ACK,接收端接收到ACK关闭TCP,而主动关闭端一段时间后也关闭TCP;

1.8 浏览器渲染

当浏览器得到一个html文件时,会自上而下的加载,并在加载过程当中进行解析渲染。

解析:

1. 浏览器会将HTML解析成一个DOM树,DOM 树的构建过程是一个深度遍历过程:当前节点的全部子节点都构建好后才会去构建当前节点的下一个兄弟节点。

2. 将CSS解析成 CSS Rule Tree 。

3. 根据DOM树和CSSOM来构造 Rendering Tree。注意:Rendering Tree 渲染树并不等同于 DOM 树,由于一些像 Header 或 display:none 的东西就不必放在渲染树中了。

4. 有了Render Tree,浏览器已经能知道网页中有哪些节点、各个节点的CSS定义以及他们的从属关系。下一步操做称之为Layout,顾名思义就是计算出每一个节点在屏幕中的位置。

  1. 再下一步就是绘制,即遍历render树,并使用UI后端层绘制每一个节点

渲染:

1. 接收服务器返回html文件。

2. 浏览器开始载入html代码,发现<head>标签内有一个<link>标签引用外部CSS文件,浏览器又发出CSS文件的请求,服务器返回这个CSS文件。

3. 浏览器继续载入html中<body>部分的代码,而且CSS文件已经拿到手了,能够开始渲染页面了。

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

5. 服务器返回图片文件,因为图片占用了必定面积,影响了后面段落的排布,所以浏览器须要回过头来从新渲染这部分代码。

6. 浏览器发现了一个包含一行Javascript代码的<script>标签,赶快运行它。

7. Javascript脚本执行了这条语句,它命令浏览器隐藏掉代码中的某个<div> (style.display=”none”)。忽然少了这么一个元素,浏览器不得不从新渲染这部分代码。

8. 终于等到了</html>的到来,浏览器泪流满面。

9. 等等,还没完,用户点了一下界面中的“换肤”按钮,Javascript让浏览器换了一下<link>标签的CSS路径。

10. 浏览器召集了在座的各位<div><span><ul><li>们,“大伙儿收拾收拾行李,咱得从新来过……”,浏览器向服务器请求了新的CSS文件,从新渲染页面。

2. 浏览器是如何解析代码的?

上面已经描述了大概,咱们深刻的了解一下,了解以后能够考虑考虑咱们怎么写代码能够给浏览器减小点工做量。

2.1 解析HTML

HTML的解析是逐行解析。

浏览器的渲染引擎会解析HTML文档并把标签转换成内容树中的DOM节点。

它会解析style元素和外部文件中的样式数据。样式数据和HTML中的显示控制将共同用来建立另外一棵树——渲染树。

渲染引擎会尝试尽快的把内容显示出来。它不会等到全部HTML都被解析完才建立并布局渲染树。它会 在处理后续内容的同时把处理过的局部内容先展现出来。

浏览器的解析器一般把工做分给两个组件——分词程序负责把输入切分红合法符号序列,解析程序负责按照句法规则分析文档结构和构建句法树。词法分析器知道如何过滤像空格,换行之类的无关字符。

解析器输出的树是由DOM元素和属性节点组成的。

DOM与标签几乎有着一一对应的关系,以下面的标签

<html>
    <body>
        <p>
            Hello 枫
        </p>
        <div> <img src="feng.png"/></div>
    </body>
</html>

会被转换成如的DOM树:

2.2 解析CSS

CSS选择器的读取顺序是从右向左。

#molly div.haha span{color:#f00}

如上面的代码,浏览器会按照从右向左的顺序去读取选择器。

先找到span而后顺着往上找到class为“haha”的div再找到id为“molly”的元素。

成功匹配到则加入结果集,若是直到根元素html都没有匹配,则再也不遍历这条路径,从下一个span开始重复这个过程。

整个过程会造成一条符合规则的索引树,树由上至下的节点是规则中从右向左的一个个选择符匹配的节点。

若是从左向右的顺序读取,在执行到左边的分支后发现没有相对应标签匹配,则会回溯到上一个节点再继续遍历,直到找到或者没有相匹配的标签才结束。

若是有100个甚至1000个分支的时候会消耗不少性能。反之从右向左查找极大的缩小的查找范围从而提升了性能。

这就解释了为何id选择器大于类选择器,类选择器大于元素选择器。

2.3 解析JS

在浏览器中有一个js解析器的工具,专门用来解析咱们的js代码。

当浏览器遇到js代码时,立马召唤“js解析器”出来工做。

解析器会找到js当中的全部变量、函数、参数等等,而且把变量赋值为未定义(undefined)。

把函数取出来成为一个函数块,而后存放到仓库当中。这件事情作完了以后才开始逐行解析代码(由上向下,由左向右),而后再去和仓库进行匹配。

<script>
alert(a);   //undefined
var a = 1;
alert(a);   //1
</script>

<script>
a = 1;
alert(a);
//这个时候会运行报错!
//这时候a并非一个变量,解析器找不到,仓库里面并无a
</script>

再看一下这段代码

<script>
    alert(a);    //function a(){alert(4)}
    var a = 1;
    alert(a);    //1
    function a(){alert(2)}
    alert(a);    //1
    var a = 3;
    alert(a);    //3
    function a(){alert(4)}
    alert(a);    //3
</script>

在js预解析的时候,在遇到变量和函数重名的时候,只会保留函数块。在逐行解析代码的时候表达式(+、-、*、/、%、++、–、 参数 ……)会改变仓库里对应的值。

咱们来了解一个词“做用域”,如今把这个词拆分一下。 做用:读、写操做 域:空间、范围、区域… 连起来就是可以进行读写操做的一个区域。 “域”:函数、json、……都是做为一块做用域。 全局变量、局部变量、全局函数 一段 也是一块域。在域解析的时候,也是由上向下开始解析。这就解释了为何引用的外部公共js文件(好比:jquery)应该放到自定义js上边的缘由。

再来看一下这段代码

<script>
    var a = 1;
    function fn(){
        alert(a);    //undefined
        var a = 2;
    }
    fn();
    alert(a);    //1
</script>

继续跟踪一下解析器的解析过程:首先函数fn()外部的a是一个全局变量,fn()里面的a是一个局部变量。fn()函数同时是一个做用域,只要是做用域,就得作预解析和逐行解析的步骤。因此第一个alert打印的是fn()做用域的仓库指向的变量a,即为undefined。第二个alert打印的是全局的变量a,即为1。

接下来继续看代码,基本雷同的代码,我改变其中一小个地方。

<script>
    var a = 1;
    function fn(){
        alert(a);    //1
        a = 2;
    }
    fn();
    alert(a);    //2
</script>

看到这里当解析到fn()的时候,发现里面并无任何变量,因此也就不往仓库里面存什么,此时的仓库里面是空的,啥也没有。可是这个时候解析并无结束,而是从函数里面向外开始找,找到全局的变量a。此时打印的正式全局变量a的值。

这里就涉及到一个做用域链的问题。整个解析过程像是一条链子同样。由上向下,由里到外。局部可以读写全局,全局没法读写局部。

来,继续看代码,基本雷同的代码,我再次改变其中一小个地方。

<script>
    var a = 1;
    function fn(a){
        alert(a);    //undefined
        a = 2;
    }
    fn();
    alert(a);    //1
</script>

千万不能忘了,在预解析的时候浏览器除了要找变量和函数以外还须要找一些参数,而且赋值为未定义。因此这里的fn(a)至关于fn(var a),这个时候的逻辑就和第一段实例代码同样了。

继续搞事情,继续看代码,基本雷同的代码,我再次改变其中一小个地方。

<script>
    var a = 1;
    function fn(a){
        alert(a);    //1
        a = 2;
    }
    fn(a);
    alert(a);    //1
</script>

当代码执行到fn(a);的时候调用的fn()函数而且把全局变量a做为参数传递进去。

此时打印的天然是1,要记住function fn(a)至关于function fn(var a),因此这时候a=2;改变的是局部变量a,并无影响到全局变量a,因此第二次打印的依然是1。

3. 浏览器的垃圾回收机制

因为字符串、对象和数组没有固定大小,全部当他们的大小已知时,才能对他们进行动态的存储分配。JavaScript程序每次建立字符串、数组或对象时,解释器都必须分配内存来存储那个实体。只要像这样动态地分配了内存,最终都要释放这些内存以便他们可以被再用,不然,JavaScript的解释器将会消耗完系统中全部可用的内存,形成系统崩溃。

JavaScript的解释器能够检测到什么时候程序再也不使用一个对象了,当他肯定了一个对象是无用的时候,他就知道再也不须要这个对象,能够把它所占用的内存释放掉了。

var a = "before";
var b = "override a";
var a = b; //重写a

这段代码运行以后,“before”这个字符串失去了引用(以前是被a引用),系统检测到这个事实以后,就会释放该字符串的存储空间以便这些空间能够被再利用。

浏览器一般用采用的垃圾回收有两种方法:标记清除、引用计数。

3.1 标记清除

这是javascript中最经常使用的垃圾回收方式。当变量进入执行环境是,就标记这个变量为“进入环境”。从逻辑上讲,永远不能释放进入环境的变量所占用的内存,由于只要执行流进入相应的环境,就可能会用到他们。当变量离开环境时,则将其标记为“离开环境”。

垃圾收集器在运行的时候会给存储在内存中的全部变量都加上标记。而后,它会去掉环境中的变量以及被环境中的变量引用的标记。而在此以后再被加上标记的变量将被视为准备删除的变量,缘由是环境中的变量已经没法访问到这些变量了。最后。垃圾收集器完成内存清除工做,销毁那些带标记的值,并回收他们所占用的内存空间。

当对象,没法从根对象沿着引用遍历到,即不可达(unreachable),进行清除。对于上面的例子,fn() 里面的 a 和 b 在函数执行完毕后,就不能经过外面的上下文进行访问了,因此就能够清除了。

这是当前主流的GC算法,V8里面就是用这种。

无论是高级语言,仍是低级语言。内存的管理都是:分配内存使用内存(读或写)释放内存前两步,你们都没有太大异议。关键是释放内存这一步,各类语言都有本身的垃圾回收(garbage collection, 简称GC)机制。

在大部分的应用场景:一个新建立的对象,生命周期一般很短。因此,V8里面,GC处理分为两大类:新生代和老生代。

新生代的堆空间为1M~8M,并且被平分红两份(to-space和from-space),一般一个新建立的对象,内存被分配在新生代。当to-space满的时候,to-space和form-space交换位置(此时,to空,from满),并执行GC。若是一个对象被判定为,未被引用,就清除;有被引用,逃逸次数+1(若是此时逃逸次数为2,就移入老生代,不然移入to-space)。

老生代的堆空间大,GC不适合像新生代那样,用平分红两个space这种空间换时间的方式。老生代的垃圾回收,分两个阶段:标记、清理(有Sweeping和Compacting这两种方式)。

标记,采用3色标记:黑、白、灰。步骤以下:

GC开始,因此对象标记为白色。

根对象标记为黑色,并开始遍历其子节点(引用的对象)。

当前被遍历的节点,标记为灰色,被放入一个叫 marking bitmap 的栈。在栈中,把当前被遍历的节点,标记为黑色,并出栈,同时,把它的子节点(若是有的话)标记为灰色,并压入栈。(大对象比较特殊,这里不展开)

当全部对象被遍历完后,就只剩下黑和白。经过Sweeping或Compacting的方式,清理掉白色,完成GC。

3.2 引用计次

引用计数的含义是跟踪记录每一个值被引用的次数。当声明了一个变量并将一个引用类型赋值给该变量时,则这个值的引用次数就是1。相反,若是包含对这个值引用的变量又取得了另一个值,则这个值的引用次数就减1。当这个引用次数变成0时,则说明没有办法再访问这个值了,于是就能够将其所占的内存空间给收回来。这样,垃圾收集器下次再运行时,它就会释放那些引用次数为0的值所占的内存。

可是用这种方法存在着一个问题,下面来看看代码:

function problem() {
    var objA = new Object();
    var objB = new Object();

    objA.someOtherObject = objB;
    objB.anotherObject = objA;
}

  在这个例子中,objA和objB经过各自的属性相互引用;也就是说这两个对象的引用次数都是2。在采用引用计数的策略中,因为函数执行以后,这两个对象都离开了做用域,函数执行完成以后,objA和objB还将会继续存在,由于他们的引用次数永远不会是0。这样的相互引用若是说很大量的存在就会致使大量的内存泄露。

大多数浏览器已经放弃了这种回收方式。

4. 浏览器的本地存储

若是我问你,浏览器中的缓存有哪些,我相信绝大部分人会说有三种:cookie,sessionStorage,localStorage。

可是诶,我不知为何你们都叫这三个为缓存,他们叫缓存,咱们上面提到的Memory Cache等cache也叫缓存,不是很乱吗,并且浏览器把他们归到了storage里面,storage翻译过来为存储。

还有一点,这里有五种:Cookies、Local Storage、Session Storage、WebSQL 和 IndexedDB。

4.1 cookie

Cookies 是最先的本地存储,是浏览器提供的功能,而且对服务器和 JS 开放,这意味着咱们能够经过服务器端和客户端保存 Cookies。不过能够存储的数据总量大小只有 4KB,若是超过了这个限制就会忽略,无法进行保存。

HTTP协议自己是无状态的。什么是无状态呢,即服务器没法判断用户身份。Cookie其实是一小段的文本信息(key-value格式)。客户端向服务器发起请求,若是服务器须要记录该用户状态,就使用response向客户端浏览器颁发一个Cookie。客户端浏览器会把Cookie保存起来。当浏览器再请求该网站时,浏览器把请求的网址连同该Cookie一同提交给服务器。服务器检查该Cookie,以此来辨认用户状态。

4.2 Local Storage Session Storage

Local Storage 与 Session Storage 都属于 Web Storage。Web Storage 和 Cookies 相似,区别在于它有更大容量的存储。其中 Local Storage 是持久化的本地存储,除非咱们主动删除数据,不然会一直存储在本地。Session Storage 只存在于 Session 会话中,也就是说只有在同一个 Session 的页面才能使用,当 Session 会话结束后,数据也会自动释放掉。

4.3 cookie Local Storage Session Storage比较

通常在咱们面试的时候,面试官都会问cookie Local Storage Session Storage之间有什么区别。

特性

Cookie

Local Storage

Session Storage

数据的生命期

可设置失效时间,默认是关闭浏览器后失效

除非被显式清除,不然永久保存

会话级存储,仅在当前会话下有效,会话结束,关闭页面或浏览器后被清除

存放数据大小

4KB左右

5MB~10MB(浏览器不一样,状况不一样)

5MB~10MB(浏览器不一样,状况不一样)

与服务器端通讯

每次都会携带在HTTP头中,若是使用cookie保存过多数据会带来性能和安全问题

仅在客户端(即浏览器)中保存,不参与和服务器的通讯

仅在客户端(即浏览器)中保存,不参与和服务器的通讯

易用性

源生的Cookie接口不友好,开发者须要根据需求封装

源生接口良好,亦可再次封装来对Object和Array有更好的支持

源生接口良好,亦可再次封装来对Object和Array有更好的支持

应用场景

用户登陆时,保存服务器返回的一段加密过的惟一辨识单一用户的code,用以判断当前用户登陆状态,或者以前电商网站用来保存用户的购物车信息。

Local Storage能够替代Cookie完成用户购物车信息的前端保存功能,同时能够看成HTML5游戏的本地数据的存储空间。

当页面存在多表单的状况下,能够利用Session Storage实现表单页拆分,优化用户体验。

注意

不要将系统敏感的数据保存到Cookie,Local Storage,Session Storage中,防止XSS注入的风险。由于XSS注入能够经过控制台对你的属性值进行修改,具体能够参考我写的另外一篇博客,前端黑客技术。

4.4 WebSQL

WebSQL 与 IndexedDB 都是最新的 HTML5 本地缓存技术,相比于 Local Storage 和 Session Storage 来讲,存储功能更强大,支持的数据类型也更多,好比图片、视频等。

WebSQL 更准确的说是 WebSQL DB API,它是一种操做本地数据库的网页 API 接口,经过 API 能够完成客户端数据库的操做。当咱们使用 WebSQL 的时候,能够方便地用 SQL 来对数据进行增删改查。而这些浏览器客户端,好比 Chrome 和 Safari 会用 SQLite 实现本地存储,微信就采用了 SQLite 做为本地聊天记录的存储。

4.5 IndexedDB

IndexedDB就是浏览器提供的本地数据库,它能够被网页脚本建立和操做。它能够存储大量数据,提供了查找接口,可以创建索引。可是不属于关系型数据库(不支持SQL查询语句,更相似于NoSQL数据库)。

IndexedDB的特色:

  1. 键值对存储:IndexedDB内部采用对象仓库(object store)存放数据。全部类型的数据均可以直接存入,包括JavaScript对象。对象仓库中,数据以“键值对”的形式保存。每个数据记录都有对应的主键,主键是惟一的,若是重复会抛出一个错误。

  2. 异步:IndexedDB操做不会锁死浏览器,用户依然能够进行其余操做,与Local Storage的同步操做不一样,异步的设计是为了大量数据的读写,拖慢页面的表现,下降用户体验。

  3. 支持事务:IndexedDB支持事务(transaction),这意味着一些列操做步骤中,只要有某个步骤出现异常,则整个事务就会消失,数据库回滚到事务发生以前的状态,不存在只写一部分数据的状况。

  4. 同源限制:IndexedDB受到同源限制,每个数据库对应建立它的域名。网页只能访问自身域名下的数据库,而不可以访问跨域的IndexedDB数据库。

  5. 存储空间大:IndexedDB的存储空间通常很多于250MB,或者更大。

  6. 支持二进制存储:IndexedDB不只能够储存字符串,还能够储存二进制数据(ArrayBuffer对象和Blob对象)。

事务是数据库的概念,事务( transaction)是访问并可能操做各类数据项的一个数据库操做序列,这些操做要么所有执行,要么所有不执行,是一个不可分割的工做单位。事务由事务开始与事务结束之间执行的所有数据库操做组成。

5. 浏览器的线程

咱们日常使用JavaScript的时候都知道js是单线程的,而浏览器,则是多线程的。

5.1 CPU

CPU是计算机的核心,其负责承担计算机的计算任务。这里咱们比喻为一个工厂。

5.2 进程

进程是一个具备必定独立功能的程序在一个数据集上的一次动态执行的过程,是操做系统进行资源分配和调度的一个独立单位,是应用程序运行的载体。

咱们这里将进程比喻为工厂的车间,它表明CPU所能处理的单个任务。任一时刻,CPU老是运行一个进程,其余进程处于非运行状态。

5.3 线程

线程是程序执行中一个单一的顺序控制流程,是程序执行流的最小单元。

这里把线程比喻一个车间的工人,即一个车间能够容许由多个工人协同完成一个任务。

5.4 浏览器的多线程

浏览器内核是多线程,在内核控制下各线程相互配合以保持同步,一个浏览器一般由如下常驻线程组成:

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

5.4.1 GUI渲染线程

GUI渲染线程负责渲染浏览器界面HTML元素,解析HTML,CSS,构建DOM树和RenderObject树,布局和绘制等。

当界面须要重绘(Repaint)或因为某种操做引起回流(重排)(reflow)时,该线程就会执行。

在Javascript引擎运行脚本期间,GUI渲染线程都是处于挂起状态的,也就是说被”冻结”了,GUI更新会被保存在一个队列中等到JS引擎空闲时当即被执行。

5.4.2 JavaScript引擎线程

JavaScript引擎,也能够称为JS内核,主要负责处理Javascript脚本程序,例如V8引擎。

JS引擎一直等待着任务队列中任务的到来,而后加以处理,一个Tab页(renderer进程)中不管何时都只有一个JS线程在运行JS程序(单线程)。

注意:GUI渲染线程和JavaScript引擎线程互斥。

因为JavaScript是可操纵DOM的,若是在修改这些元素属性同时渲染界面(即JavaScript线程和UI线程同时运行),那么渲染线程先后得到的元素数据就可能不一致了。

所以为了防止渲染出现不可预期的结果,浏览器设置GUI渲染线程与JavaScript引擎为互斥的关系,当JavaScript引擎执行时GUI线程会被挂起,GUI更新会被保存在一个队列中等到引擎线程空闲时当即被执行。

若是JS执行的时间过长,这样就会形成页面的渲染不连贯,致使页面渲染加载阻塞的感受。

5.4.3 事件触发线程

当一个事件被触发时该线程会把事件添加到待处理队列的队尾,等待JS引擎的处理。

这些事件能够是当前执行的代码块如定时任务、也可来自浏览器内核的其余线程如鼠标点击、AJAX异步请求等,但因为JS的单线程关系全部这些事件都得排队等待JS引擎处理。

5.4.4 定时触发器线程

setIntervalsetTimeout所在线程

浏览器定时计数器并非由JavaScript引擎计数的, 由于JavaScript引擎是单线程的, 若是处于阻塞线程状态就会影响记计时的准确。

经过单独线程来计时并触发定时(计时完毕后,添加到事件队列中,等待JS引擎空闲后执行)

注意,W3C在HTML标准中规定,规定要求setTimeout中低于4ms的时间间隔算为4ms。

5.4.5 异步http请求线程

在XMLHttpRequest在链接后是经过浏览器新开一个线程请求。

将检测到状态变动时,若是设置有回调函数,异步线程就产生状态变动事件,将这个回调再放入事件队列中,再由JavaScript引擎执行。

6. 浏览器的兼容

浏览器的兼容问题一直是一个让人很头痛的问题,前段时间我还在为qiankun兼容IE弄得焦头烂额。

6.1 为何咱们的代码在浏览器中会出现兼容问题?

由于不一样的浏览器对同一段代码有不一样的解析,形成页面显示效果不统一的状况。在大多数状况下,咱们的需求是,不管用户用什么浏览器来查看咱们的网站或者登录咱们的系统,都应该是统一的显示效果。

版本越高的浏览器,支持的特性越多,咱们用的某个插件使用的特性可能高版本的浏览器支持,低版本的不支持。

6.2 咱们要怎么解决呢?

6.2.1 CSS Hack

在CSS方面,咱们可使用CSS Hack。CSS hack是经过在CSS样式中加入一些特殊的符号也就是浏览器前缀,让不一样的浏览器识别不一样的符号(什么样的浏览器识别什么样的符号是有标准的,CSS hack就是让你记住这个标准),以达到应用不一样的CSS样式的目的。

6.2.2 polyfill

在JS方面,咱们可使用polyfill。polyfill 是一段代码(或者插件),提供了那些开发者们但愿浏览器原生提供支持的功能。程序库先检查浏览器是否支持某个API,若是不支持则加载对应的 polyfill。好比,html5的storage。不一样浏览器,不一样版本,有些支持,有些不支持。其实polyfill就是shim的一种。

Shim 指的是在一个旧的环境中模拟出一个新 API ,并且仅靠旧环境中已有的手段实现,以便全部的浏览
器具备相同的行为。

6.2.3 PostCSS

PostCSS是一个利用JS插件来对CSS进行转换的工具,这些插件很是强大,强大到无所不能。其中,Autoprefixer就是众多PostCSS插件中最流行的一个。

Autoprefixer能够自动帮咱们加上浏览器前缀。

6.2.4 Modernizr.js

Modernizr.js十分的强大,既能给老版本浏览器打补丁,又能保证新浏览器渐进加强的用户体验。

Modernizr默认作的事情不多,除了(在你选择的状况下)给不支持html5的标签的浏览器,如IE6,7,8追加一点由Remy Sharp开发的html5垫片脚本,使其识别

、等html5元素以外,它主要作的就是浏览器功能检测。所以,它知道浏览器是否支持各类html5和css3特性。

7. 最后

最近断断续续整理了一些阿里、腾讯、字节等等大厂的面试题,目的是想了解一下大厂招聘的技术热点,不断提高学习。

其中包含HTML、CSS、JavaScript、服务端与网络、Vue、浏览器等等,免费分享给你们,还在持续整理收集整理中,有须要的朋友 点击这里免费领取题目+解析PDF。

篇幅有限,仅展现部份内容

相关文章
相关标签/搜索