前端面试九

时间:2024-04-01 07:12:09

41、请解释事件代理

一篇写的很好的博客:https://blog.****.net/majian_1987/article/details/8591385

事件代理的定义:

把一个或者多个元素的事件委托到它的父元素或者更外层元素上,这主要得益于浏览器的事件冒泡机制

 

事件代理的好处:

1)减少内存消耗,动态绑定事件

2)其中事件的参数event中的event.target是指触发事件的最具体的元素,而currentTarget是绑定事件的对象

 

例子:一个ul中有10个li,实现点击对应的li 输出对应的下标

<!DOCTYPE html>
<html>
<head>
  <title></title>
</head>
<body>
  <ul>
    <li>1</li>
    <li>2</li>
    <li>3</li>
    <li>4</li>
    <li>5</li>
    <li>6</li>
  </ul>
  <script type="text/javascript">
    // 注意:document.getElementsByTagName('ul')获取到的是一个伪数组,这时需要加上[0]才表示获取到对象
    var parent = document.getElementsByTagName('ul')[0]; 
    var list = document.getElementsByTagName('ul li'); 
    if( window.addEventListener ){
        parent.addEventListener('click', function(event){ 
              var ev = event; 
              var target = ev.target; 
              if( target.nodeName.toLowerCase() == 'li' ){ 
                  console.log(target.innerText); //target.innerText
              } 
        });
    }else if( window.attachEvent ){//ie6  ie7  ie8不支持addEventListener
        parent.attachEvent('onclick', function(event){ 
              var ev = window.event; 
              var target = ev.srcElemnt; 
              if( target.nodeName.toLowerCase() == 'li' ){ 
                  console.log(target.innerText); //target.innerText
              } 
        });
    }
   
  </script>
</body>
</html> 

为父节点添加一个click事件,当子节点被点击的时候,click事件会从子节点开始向上冒泡。父节点捕获到事件之后,通过判断ev.target.nodeName/ev.srcElement.nodeName来判断是否为我们需要处理的节点。并且通过ev.target/ev.srcElement拿到了被点击的Li节点。从而可以获取到相应的信息,并作处理。

 

动态创建10个li,并且每点击一个li输出其对应的索引,事件绑定使用事件代理

<!DOCTYPE html>
<html>
  <head>
    <title></title>
  </head>
  <body>
    <ul>
    </ul>
    <script type="text/javascript">
      let ul = document.getElementsByTagName('ul')[0];
      let str = '';
      for(let i=0;i<10;i++){
        str += `<li index='${i+1}'>${i}</li>`;
      }
      ul.innerHTML = str;
      ul.addEventListener('click',function(event){
        let target = event.target;
        let index = target.getAttribute('index');
        console.log(index);
      },false);
    </script>
  </body>
</html> 

 

html的 字符编码有哪些

通过下面的语句设置,字符集手册     http://www.w3school.com.cn/tags/html_ref_charactersets.asp

如果页面没有设置成utf-8那么,注释的中文就有可能会成为乱码

// Unicode 字符编码,国际标准编码
<meta charset='utf-8'>
// 繁体中文
<meta charset='gbk'>
// 一般用于简体中文
<meta charset='gb2312'>
<meta charset='ISO-8859-1'>

 

13、HTTP请求/响应报文结构

HTTP请求报文的组成:由四个部分组成:请求行、请求头部、空行、请求数据

1)请求行:请求行由请求方法字段、URL字段和HTTP协议版本字段3个字段组成,它们用空格分隔。比如 GET /data/info.html HTTP/1.1

 

13、HTTP有哪些头部信息(HTTP报文首部):首部分为五个主要的类型,包括通用首部、请求首部、响应首部、实体首部和扩展首部

2)请求头部信息:

Accept:浏览器能够处理的的内容类型

Accept-Encoding:浏览器能够处理的压缩编码

Accept-Language:浏览器当前设置的语言

Accept-Charset:浏览器能够显示的字符集

Cookie:当前页面设置的任何Cookie

Host:发出请求的页面所在的域

Referer:发出请求的页面的URI

User-Agent:浏览器的用户代理字符串

Connection:浏览器与服务器之间连接的类型

 

3)空行-----通过一个空行,告诉服务器请求头部到此为止

4)请求数据-----若方法字段是GET,则此项为空,没有数据;若方法字段是POST,则通常来说此处放置的就是要提交的数据

 

HTTP响应报文由三部分组成:响应行、响应头、响应体

1)响应行-----一般由协议版本、状态码及其描述组成 比如 HTTP/1.1 200 OK

2)响应头部------用于描述服务器的基本信息,以及数据的描述,服务器通过这些数据的描述信息,可以通知客户端如何处理等一会儿它回送的数据,设置HTTP响应头往往和状态码结合起来

响应头部信息

Allow:服务器支持哪些请求方法(如GET、POST等)

Date:表示消息发送的时间,时间的描述格式由rfc822定义

Server:服务器名字

Connection:浏览器与服务器之间连接的类型

Content-Type:文档的MIME类型---( (Multipurpose Internet Mail Extensions) 是描述消息内容类型的因特网标准, 消息能包含文本、图像、音频、视频以及其他应用程序专用的数据)

Content-Length:表示内容长度

Content-Encoding:文档的编码(Encode)方法

Cache-Control:控制HTTP缓存

Expires:缓存的有效期

Etag:资源的标志

Last-Modifined:资源上次修改的时间

3)响应体:就是响应的消息体,如果是纯数据就是返回纯数据,如果请求的是HTML页面,那么返回的就是HTML代码,如果是JS就是JS代码,如此之类

 

浏览器渲染原理

一个页面从输入url 到页面加载显示完成,这个过程中都发生什么了

42、输入网址到网页显示的过程发生了什么?(一次完整的HTTP事务是一个怎样的过程)

从用户在地址栏上输入网址到网页显示给用户,这中间发生的流程大概如下面所述:

1) 在客户端浏览器地址栏上输入网址URL

2)浏览器会先查看浏览器缓存--系统缓存--路由缓存,如有存在缓存,就直接显示。如果没有,接着第三步

3) 经过DNS(域名服务器)解析,获得域名对应的WEB服务器的IP地址(域名解析用到的协议ARP----地址解析协议

4) 客户端浏览器与WEB服务器建立TCP(传输控制协议)连接

5) 连接成功构,客户端浏览器向对应IP地址的WEB服务器发送相应的HTTP或HTTPS请求

6) WEB服务器响应请求,返回指定的URL数据或错误信息;如果设定重定向,则重定向到新的URL地址

7) 客户端浏览器下载数据解析HTML源文件,生成DOM树

8)浏览器解析css样式生成render  tree,js交互,最终生成构建树

9) 分析页面中的超链接显示在当前页面重复以上过程直至没有超链接需要发送,从而完成页面全部显示

 

 详细讲解的地址:https://blog.****.net/donggx/article/details/71402871?locationNum=1&fps=1

(一次完整的HTTP事务是一个怎样的过程----------客户端和服务端建立连接;客户端发送请求,服务端响应客户端请求并发送回数据客户端和服务端断开链接这四个过程

域名解析 --> 发起TCP的3次握手 --> 建立TCP连接后发起http请求 --> 服务器响应http请求,浏览器得到html代码 --> 浏览器解析html代码,并请求html代码中的资源(如js、css、图片等) --> 浏览器对页面进行渲染呈现给用户

 

56、HTTP的keeep-alive的作用 ?TCP的keeep-alive的作用

详情    https://mp.****.net/postedit/78135322

 

56、HTTP协议可以用UDP协议实现吗?

HTTP是一个基于TCP的协议。不过,目前,有人正在研究基于TCP+UDP混合的HTTP协议 

HTTP协议是建立在请求/响应模型上的

首先,由客户建立一条与服务器的TCP链接,并发送一个请求到服务器,请求中包含请求方法、URI、协议版本以及 相关的MIME样式的消息

然后,服务器响应一个状态行,包含消息的协议版本、一个成功和失败码以及相关的MIME式样的消息

 HTTP/1.0为每一次HTTP的请求/响应建立一条新的TCP链接,因此一个包含HTML内容和图片的页面将需要建立多次的短期的TCP链接。一次TCP链接的建立将需要3次握手。 另 外,为了获得适当的传输速度,则需要TCP花费额外的回路链接时间(RTT)。每一次链接的建立需要这种经常性的开销,而其并不带有实际有用的数据,只是 保证链接的可靠性,因此HTTP/1.1提出了可持续链接的实现方法。HTTP/1.1将只建立一次TCP的链接而重复地使用它传输一系列的请求/响应消 息,因此减少了链接建立的次数和经常性的链接开销。

 

 

56、对HTTP2.0的了解

1)增加了头压缩(header compression),从而减少流量传输

2)可以给请求添加优先级

3)所有的HTTP请求都建立在一个TCP请求上,实现多路复用

4)引入了“服务端推(server push)”的概念,它允许服务端在客户端需要数据之前,就主动地将数据发送到客户端缓存中,从而提高性能

5)提供更多的加密支持

 

47、TCP和UDP的区别

TCP协议是一种面向连接的、可靠的、基于字节流的传输层通信协议,UDP协议是一种无连接的、不可靠的、基于报文的传输层通信协议。他们的区别主要有以下几点:

1)TCP面向连接;UDP是无连接的

面向连接

a)连接是对状态的保持,在客户端和服务器端都维护一个变量,这个变量维护现在数据传输的状态

b)通信双方在通信前,建立一条通信线路,其有3个过程:建立连接(TCP的三次握手)、使用连接、释放连接

无连接:

通信双方不需要事先建立一条通信线路,而是把每个带有目的地址的包(报文分组)送到线路上,由系统自主选定路线进行传输

 

2)TCP提供可靠的服务(通过TCP连接传送的数据,无差错、不丢失、不重复、按序到达);UDP尽最大努力交付,不保证可靠交付

TCP的可靠性体现在:三次握手、流量控制、拥塞控制、失序数据重排序, 丢弃重复数据、数据报校验、应答机制、超时重发等

3)TCP基于字节流(实际上是TCP把数据看成一连串无结构的字节流);UDP是基于报文的协议

4)TCP可以保证接收方收到的数据与发送方发送的数据完全一致,保证数据不丢失、无差错、不重复;UDP则可能存在丢包,不保证数据的正确性

5)每一条TCP连接只能是点到点的; UDP支持一对一、一对多、多对一、多对多的交互通信

6)TCP的逻辑通信信道是全双工的可靠信道;UDP则是不可靠信道

7)TCP的首部开销20字节;UDP的首部开销8字节

8)TCP要求系统资源较多,UDP较少

一些网络协议   https://blog.****.net/tangxiujiang/article/details/79573339

 

43、为什么有时候UDP比TCP更有优势

1)TCP设计过于冗余,速度难以进一步提升。TCP为了实现网络通信的可靠性,使用了复杂的拥塞控制算法,建立了繁琐的握手过程以及重传策略。由于TCP内置在系统协议栈中,极难对其进行改进

2)网速的提升,使得UDP协议以其简单、传输快的优势,在越来越多场景下取代了TCP,如网页浏览、流媒体、实时游戏、物联网 。网速的提升给UDP稳定性提供可靠网络保障 ,网络环境变好,网络传输延迟、稳定性也随之改善,UDP的丢包率变小,如果再使用应用层重传,能够完全确保传输的可靠性

 网页浏览: 使用UDP协议有三个优点 ------ 精简握手过程,减少网络通信往返次数; 能够对TLS加解密过程进行优化; 收发快速,无阻塞

采用UDP有3个关键点: 网络带宽需求较小,而实时性要求高; 大部分应用无需维持连接; 需要低功耗

 

43、常用的http状态码的解释

状态码的作用:借助状态码,用户可以知道服务器端是正常处理了请求,还是出现了什么错误

1xx:信息性状态码,表示服务器已接收了客户端请求,客户端可继续发送请求

100 Continue:客户端继续发送请求

101 Switching Protocols:服务器已经理解客户端的请求,并通过upgrad消息头,通知客户端采用不同的协议来完成请求

 

2xx:成功状态码,表示服务器已成功接收到请求并进行处理

200 OK: 表示客户端请求成功,服务器正常返回信息

201  Created:表示客户端请求成功,服务器创建了新的资源

202  Accepted:服务器已经接收请求,但尚未处理

204 No Content :表示客户端请求成功,但不返回任何实体的主体部分

206 Partial Content: 成功执行了一个范围(Range)请求

 

3xx:重定向状态码,表示服务器要求客户端重定向

301 Moved Permanently :永久性重定向,响应报文的Location首部应该有该资源的新URL

302 moved temporaroly 临时性重定向,响应报文的Location首部给出的URL用来临时定位资源

303 See Other 请求的资源存在着另一个URI,客户端应使用GET方法定向获取请求的资源

304 Not Modified 服务器内容没有更新,可以直接读取浏览器缓存(协商缓存)

307 Temporary Redirect 临时重定向。与302 Found含义一样。302禁止POST变换为GET,但实际使用时并不一定,307则更多浏览器可能会遵循这一标准,但也依赖于浏览器具体实现

 

共同点:301,302对用户来说没有区别,他们看到效果只是一个跳转,浏览器中旧的URL变成了新的URL。页面跳到了这个新的url指向的地方

301和302的差异

301:301重定向是永久性重定向,旧地址的资源已经被永久的移除了,被请求的资源已永久移动到新位置。搜索引擎在抓取新的内容的同时,也将旧的网址替换为重定向之后的网址。

302:302重定向只是暂时的重定向,请求的资源现在临时从不同的URI响应请求搜索引擎会抓取新的内容,而保留旧的地址因为服务器返回302,所以,搜索引擎认为新的网址是暂时的。由于这样的重定向是临时的,客户端应当继续向原有地址发送以后的请求。只有在Cache-Control或Expires中进行了指定的情况下,这个响应才是可缓存的。

 

301的使用场景

1)更换域名,如:域名到期不想续费、发现更合适网站的域名 
2)在搜索引擎的搜索结果中出现了不带www的域名,而带www的域名却没有收录,这个时候可以用301重定向来告诉搜索引擎目标的域名是哪一个 
3)空间服务器不稳定,换空间的时候

302的使用场景

尽量使用301跳转,防止网址劫持

从网址A 做一个302 重定向到网址B 时,主机服务器的隐含意思是网址A 随时有可能改主意,重新显示本身的内容或转向其他的地方。大部分的搜索引擎在大部分情况下,当收到302 重定向时,一般只要去抓取目标网址就可以了,也就是说网址B。如果搜索引擎在遇到302 转向时,百分之百的都抓取目标网址B 的话,就不用担心网址URL 劫持了。问题就在于,有的时候搜索引擎,尤其是Google并不能总是抓取目标网址。比如说,有的时候A 网址很短,但是它做了一个302 重定向到B 网址,而B 网址是一个很长的乱七八糟的URL 网址,甚至还有可能包含一些问号之类的参数。很自然的,A 网址更加友好,而B 网址既难看,又不友好。这时Google 很有可能会仍然显示网址A。由于搜索引擎排名算法在遇到302 重定向的时候,并不能像人一样的去准确判定哪一个网址更适当,这就造成了网址URL 劫持的可能性。也就是说,一个不道德的人在他自己的网址A 做一个302 重定向到你的网址B出于某种原因, Google 搜索结果所显示的仍然是网址A,但是所用的网页内容却是你的网址B 上的内容,这种情况就叫做网址URL 劫持。你辛辛苦苦所写的内容就这样被别人偷走了。302 重定向所造成的网址URL 劫持现象,已经存在一段时间了

 

 

什么时候进行重定向?

1)网站调整(如改变网页目录结构); 
2)网页被移到一个新地址; 
3)网页扩展名改变(如应用需要把.php改成.Html或.shtml)

为什么要进行重定向?---

1)这种情况下,如果不做重定向,则用户收藏夹或搜索引擎数据库中旧地址只能让访问到一个404错误页面信息

2)某些注册了多个域名的网站,需要通过重定向,让访问这些域名的用户,自动跳转到主站点

 

 

转载地址         https://blog.****.net/baidu_30000217/article/details/71189314

 

重定向的状态码和前端实现重定向的方法有哪些?前端实现重定向的优缺点?

重定向的状态码:301   302

页面重定向是指在打开某个页面的时候,希望页面跳转到另一个指定的页面

 

前端实现重定向(页面的刷新和跳转)的方法:可以通过HTML或者JS方式实现

 

页面刷新:location.reload()     history.go(0)     location.assign(location.href)   location.replace(location.href)

 

html方式

1)通过<meta>标签实现

<meta http-equiv="refresh" content="0; URL=http://www.labangme.com"> 

js方式

通过window.location接口---window.location 对象表示窗口中当前显示的文档的 Web 地址

window.location对象的属性

// 属性
//   http:80//example.com:1234/test.htm#part2
window.location.href----设置或返回完整的 URL
window.location.protocol---设置或返回当前URL的协议
window.location.host-----设置或返回主机名和当前 URL 的端口号
window.location.hostname---设置或返回当前 URL 的主机名
window.location.port-----设置或返回当前 URL 的端口号
window.location.pathname---设置或返回当前 URL 的路径部分
window.location.search----置或返回从问号 (?) 开始的 URL(查询部分)
window.location.hasah-----设置或返回从井号 (#) 开始的 URL(锚)

window.location对象的方法

// 方法
reload()
assing()
replace()

//没有参数或者是false,它就会用HTTP头 If-Modified-Since 来检测服务器上的文档是否已改变
// 如果文档已改变,reload() 会再次从服务器下载该文档
// 如果文档未改变,则该方法将从缓存中装载文档
// 这与用户单击浏览器的刷新按钮的效果是完全一样的
// 如果把该方法的参数设置为true,那么无论文档的最后修改日期是什么,它都会绕过缓存,
// 从服务器上重新下载该文档。这与单击浏览器的刷新按钮时按住Shift健的效果是完全一样
window.location.reload()----重新加载当前文档
// 当一个 Location 对象被转换成字符串,href 属性的值被返回。这意味着你可以使用表达式 
// location 来替代 location.href
即window.location = location.href = 'url'



// 装载一个新文档而无须为它创建一个新的历史记录,也就是说,在浏览器的历史列表中,新
// 文档将替换当前文档
//不能通过“前进”和“后退”来访问已经被替换的URL。 
window.location.replace(newURL)----用新的文档替换当前文档



window.location.assign(URL)---可加载一个新的文档

 

location属性是兼容所有浏览器的,在实现页面跳转的时候比较靠谱

1)window.location

<script type="text/javascript">
    window.location = "http://www.labangme.com";
</script>

2)window.location.href 

<script type="text/javascript">
    window.location.href = "http://www.labangme.com";
</script>

3)window.location.replace

<script type="text/javascript">
    window.location.replace("http://www.labangme.com"); 
</script>

通过window.history接口--对象包含用户(在浏览器窗口中)访问过的 URL

属性

length----返回浏览器历史列表中的 URL 数量
// 方法
back() //加载 history 列表中的前一个 URL
forward() //加载 history 列表中的下一个 URL
go(num) //加载 history 列表中的某个具体页面

1)window.history

<script language="javascript"> 
   alert("返回"); 
   window.history.back(-1); 
</script>


history.back() - 与在浏览器点击后退按钮相同
history.forward() - 与在浏览器中点击按钮向前相同

 

通过window.navigate接口---window.navigator 对象包含有关访问者浏览器的信息

这个方法只是针对IE的,其他浏览器并不适用,因此不推荐使用

<script language="javascript"> 
    window.navigate("http://shanghepinpai.com"); 
</script> 

 

4xx:客户端错误状态码,表示客户端的请求有非法内容

400 Bad Request: 表示客户端请求有语法错误,不能被服务器所理解.

比如,使用post方法,type=file的input的value不能为空,如果为空的话,服务器读不到这个值,那么就会报400错误

401 Unauthonzed: 表示请求未经授权,该状态代码必须与 WWW-Authenticate 报头域一起使用

403 Forbidden :表示服务器收到请求,但是拒绝提供服务,通常会在响应正文中给出不提供服务的原因

404 Not Found :请求的资源不存在,例如,输入了错误的URL

 

5xx:服务器错误状态码,表示服务器未能正常处理客户端的请求而出现意外错误。

500 Internel Server Error: 表示服务器发生不可预期的错误,导致无法完成客户端的请求

这个有可能是因为在界面中的input输入框的输入值形式和服务器那边的定义形式不一样,然后就会报500

503 Service Unavailable: 表示服务器当前不能够处理客户端的请求,在一段时间之后,服务器可能会恢复正常

 

 

44、http请求中,哪些字段可以设置缓存

HTTP控制缓存的字段主要包括:Cache-Control/PragmaExpires(缓存资源的有效期),Last-Modified/Etag(资源上次修改的时间/资源的标志)

1)Cache-Control/Pragma---指定所有缓存机制在整个请求/响应链必须服从的指令

 用于指定所有缓存机制在整个请求/响应链必须服从的指令,如果知道该页面是否为缓存,不仅可以控制浏览器,还可以控制和HTTP相关的缓存或代理服务器。它可以指定下列可选值:
    1、Public:所有内容都将被缓存,在响应头中设置
    2、Private:内容只缓存在私有缓存中,在响应头中设置
    3、no-cache:所有内容都不会被缓存,在请求头和响应头中设置
    4、no-store:所有内容都不会被缓存在缓存或Internet临时文件中,在响应头中设置
    5、must-revalidation/proxy-revalidation:如果缓存的内容失效,请求必须发送到服务  器/代理以进行重新验证,在请求头中设置
    6、max-age=xxx:缓存的内容将在xxx秒后失效,这个选项只在HTTP1.1中可用,和Last-Modified一起使用时优先级较高,在响应头中设置

    Cache-Control请求字段可以被个浏览器很好的支持,而且优先级也比较高,它和其他一些请求字段(如Expires)同时使用时,Cache-Control会覆盖其他字段

    Pragma字段的作用和Cache-Control类似,最常用的Pragma:no-cache,他和Cache-cache的作用是一致的

 

2)Expires--设置缓存失效的时间

Expires通常的使用格式是Expires:Sat,25 Feb 2012 12:22:17 GMT,后面跟着一个日期和时间,超过这个时间后,缓存的内容将失效。浏览器在发送请求之前检查这个页面的字段,看该页面是否已经过期了,如果过期,就向服务端重新发起请求

 

3)Last-Modifined/Etag---

  Last-Modified字段一般用于表示一个服务器上的资源的最后修改时间,资源可以是静态资源,也可以是动态内容,  通过这个最后修改时间可以判断当前请求的资源是否是最新的。
一般服务端会在响应头中返回一个Last-Modified字段,告诉浏览器这个页面的最后修改时间, 如Last-Modified:Sat, 25 Feb 2012 12:55:04 GMT,浏览器再次请求时在请求头中增加If-Modified-Since:Sat, 25 Feb 2012 12:55:04 GMT字段,询问当前缓存的页面是否是最新。如果是最新的就返回304,告诉浏览器是最新的,服务器也不会传输最新的数据
   

Etag字段的作用和Last-Modified字段作用相同,这个字段的作用是让服务端每个页面分配一个唯一的编号,然后通过这个编号来区分当前页面是否最新。这种方式比较灵活,但是当后端的服务器有多台时比较难以处理

 

45、为什么在浏览器中缓存了资源,在下次请求该资源的时候就不会向服务器发送请求了,而是直接读取缓存的资源

a) 当资源第一次被访问的时候,http返回200的状态码,并在头部携带上当前资源的一些描述信息,如

Last-Modified  // 指示最后修改的时间
Etag           // 指示资源的状态唯一标识
Expires       // 指示资源在浏览器缓存中的过期时间

b) 接着浏览器会将文件缓存到Cache目录下,并同时保存文件上述信息

c) 当第二次请求该文件时,浏览器会先检查Cache目录下是否含有该文件,如果,并且还没到Expires设置的时间,即文件还没有过期,那么此时浏览器将直接从Cache目录中读取文件,而不再发送请求(强缓存)。如果文件此时已经过期,则浏览器会发送一次HTTP请求到WebServer,并在头部携带上当前文件的如下信息:

If-Modified-Since Thu, 26 Nov 2009 13:50:19 GMT
If-None-Match "8fb8b-14-4794674acdcc0"

即把If-Modified-Since(上一次修改的时间),If-None-Match(上一次请求返回的Etag值)一起发送给服务器

d) 服务器在接收到这个请求的时候,先解析Header里头的信息,然后校验该头部信息。如果该文件从上次时间到现在都没有过修改或者Etag信息没有变化,则服务端将直接返回一个304的状态,而不再返回文件资源,304表示:资源没有被修改过,可以直接读取浏览器缓存(协商缓存)。如果文件修改过了,就会将文件资源返回,并带上新文件状态信息。

这样,就能够很大程度上减少网络带宽以及提升用户的浏览器体验。

 

76、304缓存的原理(协商缓存)

状态码304

304是HTTP状态码,客户端有缓存的文档,发出一个条件性的请求(提供If-Modified-Since头,标识客户端缓存修改时间),服务器告诉客户端,原来缓存的文档还可以继续使用

304缓存的具体过程

1)客户端请求一个资源时,发现自己缓存的文件有Last Modified,那么在请求中会包含If Modified Since,这个时间就是缓存文件最后修改的时间

2)如果请求中包含 If Modified Since,就说明已经有缓存在客户端。服务器根据这个时间当前请求文件的修改时间,就可以确定是否返回304还是200

3)于静态文件,例如:CSS、图片,服务器会自动完成 时间的比较,完成缓存或者更新。返回304则表明,该资源从上次缓存到现在并没有修改过,条件请求可以确保客户端资源是最新的同时,避免每次都请求相同资源给服务器带来的性能问题

4)对于动态页面往往没有Last Modified信息,浏览器和代理服务器不会做缓存,每次请求的时候都会完成一次200的请求。

5)如果要对动态页面做缓存加速,那么服务器要在响应报文的头部添加Last Modified定义,再根据请求报文中的If Modified Since来决定返回200还是304,如果返回304,则返回的只是一个响应报文的头部不包含主题部分,从而大大降低带宽的消耗,提高用户体验

 

浏览器如何判断缓存是否过期

1)根据相应报文的Cache-Control和Expires这两个属性(记录了资源过期的时间),在该日期前的所有对资源的请求都会直接使用浏览器缓存

2)两个属性都存在时Cache-Control优先级较高

3)Cache-Control中的max-age指示资源的过期时间

4)Expires中的设置的过期时间是针对服务器而言,客户端和服务器端的时间相差可能很大,使用Cache-Control的max-age字段来代替比较好。

 

服务端如何判断资源失效

1)Etag:资源在服务器的唯一标识(生成规则由服务器决定),缓存过期时发现资源带有Etag声明,则会在请求头部带上If-None-Match,服务器收到请求后,会与被请求资源的相应校验串对比,决定返回200或304

2)Last-modified:当缓存过期时,发现资源具有Last-Modified声明,则在请求头部带上If-Modified-Since,服务器收到相应请求后,会将这个信息与请求资源最后修改时间进行对比,如果资源修改时间较新,则说明资源被改动了,则返回200状态码和相应资源,若最后修改时间较旧,说明资源无新修改,则返回304状态码,浏览器直接继续使用所保存的cache


注意:服务器优先判断Etag值是否改变,没改变则再判断Last-Modified是否为最新的修改,因为Etag是资源在服务端的唯一标识

1)当客户端请求的资源是第一次请求的时候, 服务器会返回200状态码,并在返回的头部加上当前资源的一些信息,如

Last-Modified:上次修改的时间
Etag:当前资源的唯一标志
Expires:资源在浏览器缓存失效的时间

2)浏览器会将当前资源保存在Cache目录下以及上述信息。

3)当浏览器第二次访问该资源的时候,浏览器会先检查Cache目录下是否有该资源,如果有的话并且缓存时间没有失效,则直接读取Cache目录下的资源。如果失效,那么会向服务器发送一次请求,服务器接收到请求后,会先解析header头部的Etag,并校验头部的信息,如果自上次修改到现在请求的资源都没有修改过或Etag没有变化,服务器直接返回304状态码,如果有变化,则服务器返回修改后的资源并带上当前资源的一些描述信息

 

为什么有Last-modifined还有Etag

1)Last-Modified标注的时间能精确到秒

2)服务器可能没有准确获取到文件修改时间

3)如果文件被定期生成,当有时内容没有什么变化,但Last-Modified改变了,导致文件没法使用缓存
 

200 OK(from cache)   和   304 Not Modified的区别

1)200 OK不会向服务器发送请求,本地的资源没有过期,直接使用本地缓存文件
2)304 Not Modified则向服务器发送请求,如果服务器认为浏览器的缓存还能使用,则返回304
一般手动刷新时,都会向服务器询问本地缓存是否能使用,返回304

 

 

133、get和post的区别

提交数据的形式

1)get把参数拼接到URL中的查询字符串中(放在HTTP head中),在地址栏可以看到传输的数据,以?分割URL和传输数据,参数之间以&相连

2)post通过request body来传输数据

 

提交数据的大小

get传送的数据量较小,post传送的数据量较大

一个比较实际的问题是:GET方法可能会产生很长的URL,或许会超过某些浏览器与服务器对URL长度的限制

当然,如果URL不直接提供给用户,而是提供给程序调用,这时的长度就只受Web服务器影响了

在get中,不同浏览器对get的数据量大小的限定:

IE:2083字符
FireFox:65536字符
Safari:80,000字符
Opera:190,000字符
Google:8182字符

在get中,不同服务器对get的数据量大小的限定

Apache:8192字符
IIS:16,384字符

POST方法长度限制

理论上讲,POST是没有大小限制的。HTTP协议规范也没有进行大小限制,起限制作用的是服务器的处理程序的处理能力

 

 

提交数据的安全

 get安全性非常低(数据明文存放,会受到CSRF攻击,将请求数据存放在请求头中,也就是拼接到url的后面),post安全性较高(数据传输可明可密,请求数据存放在请求体中),原因如下:

(1) 登录页面有可能被浏览器缓存

(2) 其他人查看浏览器的历史纪录,那么别人就可 以拿到你的账号和密码了

(3)当遇上跨站的攻击时,安全性的表现更差了

 

在服务端解析get请求的数据常用url模块中的url.parse(req.url,true)反序列化

在服务端解析post请求的数据常用querystring模块中的req.on('data',(data)=>{str += data;})

req.on('end',()=>{POST = querystring.parse(str)})

 

47、HTTP的请求方法,什么情况下只能使用POST?

GET和POST两种方法都是将数据送到服务器。HTTP标准包含这两种方法是为了达到不同的目的 

POST用于创建资源,资源的内容会被编入HTTP请示的内容中。例如,处理订货表单、在数据库中加入新数据行

 

 

POST可以向服务器发送修改请求从而修改服务器中的内容,比方说,我们要在论坛上回贴、在博客上评论,这就要用到Post了,当然它也是可以仅仅获取数据

符合下面任意一个条件,使用POST方法

1)请求的结果有持续性的副作用,例如,数据库内添加新的数据行、更新服务器上的文件或数据库

2)向服务器发送大量数据,若使用GET方法,可能会让URL过长,而POST 没有数据量限制

3)用户需要对交互产生的结果负责

 

GET用于获取信息,表单提交的数据,只是帮助服务器获取、查询数据,也就是说它不会修改服务器上的数据,从这点来讲,它是数据安全的,而稍后会提到的Post它是可以修改数据的,所以这也是两者差别之一了

客户端与服务端的交互像是一个提问(如查询操作、搜索操作、读操作

符合下面任意一个条件,使用GET方法

1)请求是为了查找资源,HTML表单数据仅用来帮助搜索

2) 请求结果无持续性的副作用

3) 收集的数据及HTML表单内的输入字段名称的总长不超过1024个字符

 

47、HTTPS的具体流程

HTTPS是在HTTP的基础上和ssl/tls证书结合起来的一种协议,保证了传输过程中的安全性,减少了被恶意劫持的可能,很好的解决了解决了http的三个缺点(被监听、被篡改、被伪装

对称加密:加密的**和解密的**相同(MD5)

非对称加密:非对称加密将**分为公钥和私钥,公钥可以公开,私钥需要保密,客户端公钥加密的数据,服务端可以通过私钥来解密

RSA非对称加密:RSA分为公钥和私钥,从私钥可以生成公钥,但是不能通过公钥生成私钥。公钥加密过的信息,只有私钥能解开,私钥加密的信息,只有公钥能解开

具体流程:---先非对称加密后面再对称加密

1)通过TCP三次握手建立连接

2)客户端先向服务器发出加密通信的请求:

    a)包括客户端支持的加密通信协议版本(TLS 1.0)

    b)客户端生成的随机数random1(稍后用于生成"对话**")、

    c)支持的加密方法(RSA公钥加密)

    d) 支持的压缩方法

3)服务器收到请求,然后响应

     a)确认使用的加密通信协议版本(TLS 1.0版本)。如果浏览器与服务器支持的版本不一致,服务器关闭加密通信

     b)一个服务器生成的随机数random2,稍后用于生成"对话**"

     c)确认使用的加密方法,比如RSA公钥加密

     d) 返回服务器证书(证书中有公钥

4)客户端收到证书之后,通过CA验证证书的安全性,如果通过则会随机生成一个随机数PreMaster secret,用证书中公钥对其加密,发送到服务端

5)服务端接受到这个用公钥加密后的随机数后,会用私钥对其解密,得到真正的随机数PreMaster secret,然后根据radom1radom2、pre-master secret通过一定的算法得出session Key和MAC算法秘钥,作为后面对称加密的私钥。同时客户端也会使用radom1、radom2、pre-master secret,和同样的算法生成session Key和MAC算法的秘钥。

6)客户端在接收到加密后的数据,使用私钥(即生成的随机值)对数据进行解密并且将解析数据的结果呈现给客户

7)SSL加密建立

 

 

 

48、抓包工具

抓包工具:是拦截、查看网络数据包内容的软件,抓取发送给服务器的请求,观察下它的请求时间还有发送内容等等,有时候,
可能还会用到这个去观察某个页面下载组件消耗时间太长找出原因,要开发做性能调优

 

web调试工具firebug,达到通用的强大的抓包工具wireshark,fiddler,使用fiddler原因如下:

a)Firebug虽然可以抓包,但是对于分析http请求的详细信息,不够强大模拟http请求的功能也不够,且firebug常常是需要“无刷新修改”,如果刷新了页面,所有的修改都不会保存。

b)Wireshark是通用的抓包工具,但是比较庞大,对于只需要抓取http请求的应用来说,似乎有些大材小用。

c)Httpwatch也是比较常用的http抓包工具,但是只支持IE和firefox浏览器(其他浏览器可能会有相应的插件),对于想要调试chrome浏览器的http请求,似乎稍显无力

d)Fiddler是一个使用本地 127.0.0.1:8888 的 HTTP 代理,任何能够设置 HTTP 代理为 127.0.0.1:8888 的浏览器和应用程序都可以使用 Fiddler

 

1)Fiddler:

a)Fiddler是位于客户端和服务器端的HTTP代理,也是目前最常用的http抓包工具之一 

b)它能够记录客户端和服务器之间的所有HTTP请求,可以针对特定的HTTP请求,分析请求数据、设置断点、调试web应用、修改请求的数据,修改服务器返回的数据,功能非常强大,是web调试的利器

c)Fiddler支持所有可以设置http代理为127.0.0.1:8888的浏览器和应用程序

 

抓取http协议具体用法:

https://blog.****.net/ohmygirl/article/details/17846199   详情1

https://blog.****.net/ohmygirl/article/details/17849983    详情2

https://blog.****.net/ohmygirl/article/details/17855031     详情3

 

抓取httpshttps://blog.****.net/qq_15283475/article/details/62224149    详情

 

 

 

48、控制流和数据流

1)数据流--------描述程序运行过程数据的流转方式及其行为状态

a)MVC模型中,Model层的本质就是“数据”,数据在MVC的各个构成要素流转并且在不同的层次扮演着不同的角色

b)程序运行起来之后,我们会发现正是由于数据的流转,才使得原本孤立和静态的元素形成了互动。因此,我们可以得出结论——真正贯穿MVC框架并且将MVC的各个模块黏合在一起的是数据。数据作为黏合剂,构成了模块与模块间的互动载体,把MVC真正融合在了一起。

因此我们可以看到在MVC模型中,Model层实际上是一个动态元素它作为数据载体流转于程序之间,并在不同的程序模块中表现出不同的行为状态,这就是形成数据流的本质

 

数据载体的选择

Model层,我们对于数据的关注点主要有两个:数据存储和数据传输。 因而Model层总是以“属性对象模式”作为观察角度进行建模的。在这种情况下,数据(Model)所扮演的是一个载体的角色

所谓载体,它首先必须具备一定的数据结构不同的Web层框架有不同的数据结构和数据形式,比如数据载体有Map、FormBean、POJO

 

2)控制流----控制程序逻辑执行的先后顺序控制流实际上是数据流融入控制层之后形成的逻辑处理和程序跳转的结果

控制层的四大职责

  • 控制层负责请求数据的接收
  • 控制层负责业务逻辑的处理
  • 控制层负责响应数据的收集
  • 控制层负责响应流程的控制

其中,控制层的核心职责处理业务逻辑

 

46、有一个实时更新的数据,如何将其显示(具体实现方式)

1)ajax短连接:客户端每隔一定时间发一次请求,服务器收到请求后会立刻返回结果,不管有没有新数据。

2)ajax长连接:客户端发送一次请求,服务器端收到请求后查询有没有新数据,如果没有新数据就阻塞这个请求,直到有新数据或者超时为止。客户端每次收到请求返回结果后立刻再发一次请求。comet貌似就是这个原理

3)Comet:它是对ajax的进一步扩展,让服务器几乎能够实时的向客户端推送数据。实现Comet的手段主要有两个:长轮询和HTTP流。所有浏览器都支持长轮询,只有部分浏览器原生支持HTTP流。

4)SSE:(Servert-Sent Events,服务器发送事件)是一种实现Comet交互的浏览器API。既支持长轮询,也支持HTTP流。

3)WebSocket:是一种与服务器进行全双工、长连接通信的信道。与其他方案不同,它不使用HTTP协议,而使用一种自定义的协议。这种协议专门为快速传输小数据设计。

 

8、Web应用从服务器主动推送Data到客户端有那些方式

a)  AJAX 轮询(long-polling)方式

实现方式简单,前端浏览器都支持。但是这种方式会有非常严重的问题,客户端不断的向服务器发起请求,导致服务器资源浪费,且会加重网络负载

传统的Ajax轮询方式:客户端定时向服务器端发送请求,服务端接收到请求后马上回应,不管数据是否有效。

Ajax长轮询:它是Ajax轮询的升级版,客户端向服务器端发送请求,服务端接收到请求后,保持连接,检查数据是否有更新,有的话返回信息并断开连接;数据没有更新的话,就一直和客户端保持连接,不返回信息。等到了服务端设置的超时的时间之后,还是没有检查到数据更新,那么服务端就会返回超时消息。客户端这边,不管请求成功还是失败,或者请求超时,都会再次向服务端发送请求。这一过程,在这一页面在浏览器打开期间,都会循环不断。

function checkStatus(){
	var url = '填写具体的请求地址';
	var data = {用POST请求方式,则需要一些一些数据};//GET方法不需要数据data变量
	//这里使用jquery中封装的方法$.ajax()
	$.ajax({
		url:url,
		data:data,
		type:'POST',
		datatype:'json',
		timeout:200000,
		success:function(res){
			//todo
			checkStatus();
		},
		error:fucntion(res){
			//tode
			checkStatus();
		},
		complete:fucntion(XMLHttpRequest,statusText){
			if(statusText==='timeout'){
				//todo
				checkStatus();
			}
		}
	});
}

b) comet方式:comet是一种用于web的推送技术,能够让服务器实时的将更新的信息传送到客户端,而不需要客户端发出请求。

目前实现comet方式的方法有两种:

一:长轮询:长轮询 (long polling) 是在打开一条连接以后保持,等待服务器推送来数据再关闭,可以采用HTTP长轮询和XHR长轮询两种方式。

HTTP长轮询+JSONP长轮询:把 script 标签附加到页面上以让脚本执行。服务器会挂起连接直到有事件发生,接着把脚本内容发送回浏览器,然后重新打开另一个 script 标签来获取下一个事件,从而实现长轮询的模型。

XHR长轮询:客户端发送一个到服务器端的 AJAX 请求,然后等待响应;服务器端需要一些特定的功能允许请求被挂起,只要一有事件发生,服务器端就会在挂起的请求中送回响应并关闭该请求。客户端 JavaScript 响应处理函数在处理完服务器返回的信息后,再次发出请求,重新建立连接;如此循环。

 

二、<iframe>标签:一种 HTML 标记, 通过在 HTML 页面里嵌入一个隐蔵帧,然后将这个隐蔵帧的 SRC 属性设为对一个长连接的请求,服务器端就能源源不断地往客户端输入数据

 

c)  HTML5新引入的WebSocket,可以实现服务器主动发送数据至网页端,它和HTTP一样,是一个基于HTTP的应用层协议,跑的是TCP,即依赖 HTTP 协议进行一次握手 ,握手成功后,数据就直接从 TCP 通道传输,与 HTTP 无关了。所以本质上还是个长连接,双向通信,意味着服务器端和客户端可以同时发送并响应请求,而不再像HTTP的请求和响应模式。

 

Comet介绍

定义

1)Comet是Alex Russell(是js框架DoJo的创始人)发明的一个词,指的是一种更高级的Ajax技术(经常有人称为“服务器推送”)

2)Ajax是一种从页面向服务器请求数据的技术,而Comet则是一种服务器向页面推送数据的技术

3)让服务器几乎能够实时的向客户端推送数据

 

2种实现方式:长轮询和HTTP流

1)长轮询:页面发起一个到服务器的请求,然后服务器一直保持连接打开,直到有数据发送或者达到超时。发送完数据之后,浏览器关闭连接,随即又发起一个到服务器的新请求。这一过程在页面打开期间一直持续不断

2)HTTP流:流不同于轮询,因为在页面的整个生命周期内只使用一个HTTP连接。具体来说,就是浏览器向服务器发送一个请求,服务器保持连接打开,然后周期性的向浏览器发送数据

 

WebSocket的定义:

WebSocket 是Web应用程序的传输协议,它提供了双向的,按序到达的数据流。他是一个Html5协议,WebSocket的连接是持久的,他通过在客户端和服务器之间保持双工连接,服务器的更新可以被及时推送给客户端,而不需要客户端以一定时间间隔去轮询

 

为什么需要WebSocket?

● HTTP 协议是一种无状态的、无连接的、单向应用层协议

● 它采用了请求/响应模型

● 通信请求只能由客户端发起,服务端对请求做出应答处理

● 这种通信模型有一个弊端:HTTP 协议无法实现服务器主动向客户端发起消息

● 这种单向请求的特点,注定了如果服务器有连续的状态变化,客户端要获知就非常麻烦

● 大多数 Web 应用程序将通过频繁的异步JavaScript和XML(AJAX)请求实现长轮询

● 轮询的效率低,非常浪费资源(因为必须不停连接,或者 HTTP 连接始终打开)

因此WebSocket应运而生,需要一种可以保持连接、进行全双工通信的协议

● WebSocket 连接允许客户端和服务器之间进行全双工通信,以便任一方都可以通过建立的连接将数据推送到另一端● WebSocket 只需要建立一次连接,就可以一直保持连接状态,这相比于轮询方式的不停建立连接显然效率要大大提高

 

WebSocket的应用场景

实时性要求高的场景:社交聊天、弹幕、多玩家游戏、协同编辑、股票基金实时报价、体育实况更新、视频会议/聊天、基于位置的应用、在线教育、智能家居等需要高实时的场景

 

WebSocket表示当前状态的readyState属性,属性值如下:

WebSocket.OPENING(0):正在连接
WebSocket.OPEN(1):已经建立连接
WebSocket.CLOSING(2):正在关闭连接
WebSocket.CLOSE(3):已经关闭连接

WebSocket发送数据:send(任意字符串),复杂数据需要将数据系列化,如下面所示:

var message ={
   time:new Date(),
    text:'hi'
}
socket.send(JSON.stringify(message));

WebSocket接收数据:服务器向客户端发送信息,触发message()事件,把数据存在event.data中

socket.onmessage=function(event){
  var data = event.data;
}

WebSocket其他事件

open():在成功建立连接时触发

error():在发生错误时触发,连接不能持续

close():在连接关闭时触发

 

44、SSE(Server-Sent  Event,服务器发送事件)是围绕只读Comet交互推出的API或者模式。

1)SSE API用于创建到服务器的单向连接,服务器通过这个连接可以发送任意数量的数据

2)服务器响应的MIME类型必须是text/event-stream,而且是浏览器中的javascript API能解析格式输出

3)SSE支持短轮询、长轮询、HTTP流,能在断开连接时自动确定何时重新连接

 

SSE API----SSE的javascript API

预定新的事件流,首先创建新的EventSource对象,并传进一个入口点,传进的url须与创建对象的页面同源

var source = new EventSource('event.php')

EventSource的实例有一个readyState属性

//readyState属性的值
0-----表示正在连接到服务器
1-----表示打开了连接
2-----表示关闭了连接

EventSource的实例有3个事件

1)open:在建立连接时触发
2)message:在从服务器接收到新事件时触发
3)close:在无法建立连接时触发

source.onmessage = function(event){
   var data = event.data
}


// 默认情况下,EventSource对象保持与服务器的活连接,如果断开还会重新连接,意味着SSE适合长轮询和HTTP流
//强制断开连接并不重新连接的方法
source.close()

 

44、链式写法的原理

原理:定义一个类,然后再类的原型对象上定义方法并且方法的返回值为this对象,然后再用工厂模式定义一个对象,之后就可以采用链式写法了。

jQuery里面为了实现链式调用而在方法中返回this对象

var obj={};
obj.a=function(){
    console.log("a")
    return this
}
obj.b=function(){
    console.log("n")
    return this
}
obj.a().b();  //链式调用

链式写法的优点:

节约JS代码;所返回的都是同一个对象,可以提高代码的效率

// 类 +  原型方法  + 方法返回值为this对象 +  工厂模式 
//定义一个JS类 
function Demo() { 
} 
//扩展它的prototype对象,每个prototype函数的返回值是this对象
Demo.prototype ={ 
	setName:function (name) { 
		this.name = name; 
		console.log(this.name);
		return this; 
	}, 

	getName:function () { 
		console.log(this.name);
		return this.name; 
	}, 
	setAge:function (age) { 
		this.age = age; 
		console.log(this.age);
		return this; 
	} 
}; 

////工厂函数 
function D() { 
	return new Demo(); 
} 

//去实现可链式的调用 
D().setName("CJ").setAge(18).setName();

45、js书写一个函数,实现sum(a,b)的输出结果和sum(1)(2)的结果一样(这里用的是闭包实现,最后面返回的是一个数字),不具备通用性

返回一个对象,而不是一个数值,这里需要对toString()和valueOf()进行重写,这样的话,大部分场景是适用的,还有很多边缘条件都还没有考虑到

function sum(){
    //参数为1个的时候,对参数进行累加,利用闭包对就结果进行保存
    if(arguments.length==1){
        let sum2 = arguments[0];
        var suum = function (y){
            sum2 +=y;
            return suum; 
        }

        // 利用console.log()会调用valueOf(),所以需要对累加函数重写valueOf函数
        suum.valueOf = function(){
          return sum2;
        }

        //返回求和的变量
        return suum; 
    }else{
        // 如果传进来的参数不是1个就对其进行求和
        let sum1 = 0;
        for(var i = 0;i<arguments.length;i++){
           sum1 += arguments[i]; 
        }
        return sum1;
    } 
} 
console.log(sum());
console.log(sum(2));
console.log(sum(2)(3)(4));

用函数式编程的理念来书写,这种方法是最全面的,可以针对多种书写形式输出相同的结果,但是下面的写法只是针对形参个数确定写的

// 把一批参数分多次传递这个操作叫curry
function curry(fn) {
    var slice = [].slice;
    var len = fn.length;  //形参个数

    return function curried() {
        var args = slice.call(arguments);
        if (args.length >= len) {
            return fn.apply(null, args);
        }

        return function () {
            return curried.apply(null, args.concat(slice.call(arguments)));
        };
    };
}

var add = curry(function (a, b, c, d) {
    return a + b + c + d;
});

console.log(add(1)(2)(3)(4)); // 10
console.log(add(1, 2, 3)(4)); // 10
console.log(add(1,2)(3,4)); // 10
let curry = (fn)=>{
	if( typeof(fn) !=='function' ){
		return Error('error');
	}

	let len = fn.length;
	return function curried(...args){
		if ( args.length<len ){
			return function(...arg){
				return curried.apply(null,args.concat(arg));
			}
		}
		return fn.apply(null,args);//调用参数的时机是当实际传参个数为形参个数的时候调用函数参数

	}
}

let sum = curry(function(a,b){
	return a+b;
});

console.log(sum(1)(2));

这里也是用函数柯里化实现的,可以针对不同的参数个数进行调用,缺点就是要求值的时候需要调用一次无参的函数

const curry = function(fn){
    let params = [];   //对参数进行保留,用闭包对参数进行保留
    return function curring(...args){
        if(args==[] || args.length==0){ //调用fn的时机是不传参的时候
            return fn.apply(this,params);
        }else{
            params = params.concat(args);
            return curring;
        }
    }
}

const cost = (...args)=>{
    let result = 0;
    args.forEach((val)=> result += val);
    return result;
}

let sum = curry(cost);
console.log(sum(0)(1)(2)());

 

 

js书写一个递归函数,主要是考擦arguments和arguments.callee的使用,arguments.callee表示调用正在执行的函数

function factorial(num){
	if(num<=1){
		return 1;
	}else{
		return num * arguments.callee(num-1);
		//上面这个是用arguments.callee调用正在执行的函数,和下面的语句其实一个道理
		//return num*factorial(num-1);
	}
}
console.log(4+"阶乘是: "+factorial(4));//24

 

45、为什么在浏览器中缓存了资源,在下次请求该资源的时候就不会向服务器发送请求了,而是直接读取缓存的资源

a) 当资源第一次被访问的时候,http返回200的状态码,并在头部携带上当前资源的一些描述信息,如

Last-Modified  // 指示最后修改的时间
Etag           // 指示资源的状态唯一标识
Expires       // 指示资源在浏览器缓存中的过期时间

b) 接着浏览器会将文件缓存到Cache目录下,并同时保存文件上述信息

c) 当第二次请求该文件时,浏览器会先检查Cache目录下是否含有该文件,如果,并且还没到Expires设置的时间,即文件还没有过期,那么此时浏览器将直接从Cache目录中读取文件,而不再发送请求(强缓存)。如果文件此时已经过期,则浏览器会发送一次HTTP请求到WebServer,并在头部携带上当前文件的如下信息:

If-Modified-Since Thu, 26 Nov 2009 13:50:19 GMT
If-None-Match "8fb8b-14-4794674acdcc0"

即把If-Modified-Since(上一次修改的时间),If-None-Match(上一次请求返回的Etag值)一起发送给服务器

d) 服务器在接收到这个请求的时候,先解析Header里头的信息,然后校验该头部信息。如果该文件从上次时间到现在都没有过修改或者Etag信息没有变化,则服务端将直接返回一个304的状态,而不再返回文件资源,304表示:资源没有被修改过,可以直接读取浏览器缓存(协商缓存)。如果文件修改过了,就会将文件资源返回,并带上新文件状态信息。

这样,就能够很大程度上减少网络带宽以及提升用户的浏览器体验。

 

45、负载均衡

当系统面临大量用户访问时,负载过高的时候,通常会使用增加服务器的数量来进行横向的扩展,使用集群或者负载均衡提供整个系统的处理能力

https://kb.cnblogs.com/page/559213/   详情

https://blog.****.net/mengdonghui123456/article/details/53981976  负载均衡技术

 

45、CDN的介绍

CDN的定义:内容分发网络

CDN是一个经部署策略的整体系统,包分布式存储、负载均衡、网络请求的重定向、内容管理,将源站内容分发至最接近用户的节点,使用户可就近取得所需内容,提高用户访问的响应速度和成功率。解决因分布、带宽、服务器性能带来的访问延迟问题,适用于站点加速、点播、直播等场景

最简单的CDN网络由一个DNS服务器和几台缓存服务器组成:

1) 当用户点击网站页面上的内容URL,经过本地DNS系统解析,DNS系统会最终将域名的解析权交给CNAME指向的CDN专用DNS服务器

2) CDN的DNS服务器将CDN的全局负载均衡设备IP地址返回用户

3) 用户向CDN的全局负载均衡设备发起URL访问请求

4) CDN全局负载均衡设备根据用户IP地址,以及用户请求的内容URL,选择一台用户所属区域的区域负载均衡设备,告诉用户向这台设备发起请求

5) 区域负载均衡设备会为用户选择一台合适的缓存服务器提供服务,选择的依据包括:根据用户IP地址,判断哪一台服务器距用户最近;根据用户所请求的URL中携带的内容名称,判断哪一台服务器上有用户所需内容;查询各个服务器当前的负载情况,判断哪一台服务器尚有服务能力。基于以上这些条件的综合分析之后,区域负载均衡设备会向全局负载均衡设备返回一台缓存服务器的IP地址

6) 全局负载均衡设备把缓存服务器的IP地址返回给用户

7) 用户向缓存服务器发起请求,缓存服务器响应用户请求,将用户所需内容传送到用户终端。如果这台缓存服务器上并没有用户想要的内容,而区域均衡设备依然将它分配给了用户,那么这台服务器就要向它的上一级缓存服务器请求内容,直至追溯到网站的源服务器将内容拉到本地。

 

CDN的实现原理

 

用户访问没有使用CDN缓存的网站的过程如下:

前端面试九

a) 用户向浏览器提供要访问的域名

b) 浏览器调用域名解析函数库域名进行解析,以得到此域名对应的IP地址

c) 浏览器使用所得到的IP地址,向域名的服务主机发出数据访问请求

d) 浏览器根据域名主机返回的数据,显示网页的内容。

通过以上四个步骤,浏览器完成从用户处接收用户要访问的域名到从域名服务主机处获取数据的整个过程。

 

CDN网络是在用户和服务器之间增加Cache层,如何将用户的请求引导到Cache上获得源服务器的数据,主要是通过接管DNS实现。

下面让我们看看访问使用CDN缓存后的网站的过程:

前端面试九

 

1) 用户向浏览器提供要访问的域名;

2) 浏览器调用域名解析库对域名进行解析,由于CDN对域名解析过程进行了调整,所以解析函数库一般得到的是该域名对应的CNAME记录,为了得到实际IP地址,浏览器需要再次对获得的CNAME域名进行解析以得到实际的IP地址;在此过程中,使用的全局负载均衡DNS解析,如根据地理位置信息解析对应的IP地址,使得用户能就近访问。

3) 此次解析得到CDN缓存服务器的IP地址,浏览器在得到实际的IP地址以后,向缓存服务器发出访问请求;

4) 缓存服务器根据浏览器提供的要访问的域名,通过Cache内部专用DNS解析得到此域名的实际IP地址,再由缓存服务器向此实际IP地址提交访问请求;

5) 缓存服务器从实际IP地址得得到内容以后,一方面在本地进行保存,以备以后使用,另一方面把获取的数据返回给客户端,完成数据服务过程;

6) 客户端得到由缓存服务器返回的数据以后显示出来并完成整个浏览的数据请求过程。

通过以上的分析我们可以得到,为了实现既要对普通用户透明(即加入缓存以后用户客户端无需进行任何设置,直接使用被加速网站原有的域名即可访问,又要在为指定的网站提供加速服务的同时降低对ICP的影响,只要修改整个访问过程中的域名解析部分,以实现透明的加速服务,下面是CDN网络实现的具体操作过程。

 

CDN网络实现的具体操作过程:

1) 作为ICP,只需要把域名解释权交给CDN运营商,其他方面不需要进行任何的修改;操作时,ICP修改自己域名的解析记录,一般用cname方式指向CDN网络Cache服务器的地址。

2)、作为CDN运营商,首先需要为ICP的域名提供公开的解析,为了实现sortlist,一般是把ICP的域名解释结果指向一个CNAME记录;

3)、当需要进行sortlist时,CDN运营商可以利用DNS对CNAME指向的域名解析过程进行特殊处理,使DNS服务器在接收到客户端请求时可以根据客户端的IP地址,返回相同域名的不同IP地址;

4)、由于从cname获得的IP地址,并且带有hostname信息,请求到达Cache之后,Cache必须知道源服务器的IP地址,所以在CDN运营商内部维护一个内部DNS服务器,用于解释用户所访问的域名的真实IP地址;

5)、在维护内部DNS服务器时,还需要维护一台授权服务器,控制哪些域名可以进行缓存,而哪些又不进行缓存,以免发生开放代理的情况。

 

为什么要使用CDN(使用CDN有什么好处)

1)加速网站的访问

2) 实现跨运营商、跨地域的全网覆盖

互联不互通、区域ISP地域局限、出口带宽受限制等种种因素都造成了网站的区域性无法访问。CDN加速可以覆盖全球的线路,通过和运营商合作,部署IDC资源,在全国骨干节点商,合理部署CDN边缘分发存储节点,充分利用带宽资源,平衡源站流量。阿里云在国内有500+节点,海外300+节点,覆盖主流国家和地区不是问题,可以确保CDN服务的稳定和快速

3) 保障你的网站安全

CDN的负载均衡和分布式存储技术,可以加强网站的可靠性,相当无无形中给你的网站添加了一把保护伞,应对绝大部分的互联网攻击事件。防攻击系统也能避免网站遭到恶意攻击。

4)异地备援

当某个服务器发生意外故障时,系统将会调用其他临近的健康服务器节点进行服务,进而提供接近100%的可靠性,这就让你的网站可以做到永不宕机

5) 节约成本投入

使用CDN加速可以实现网站的全国铺设,你根据不用考虑购买服务器与后续的托管运维,服务器之间镜像同步,也不用为了管理维护技术人员而烦恼,节省了人力、精力和财力

6) 让你更专注业务本身

CDN加速厂商一般都会提供一站式服务,业务不仅限于CDN,还有配套的云存储、大数据服务、视频云服务等,而且一般会提供7x24运维监控支持,保证网络随时畅通,你可以放心使用。并且将更多的精力投入到发展自身的核心业务之上

 

CDN适用场景

 

1)网站站点/应用加速

站点或者应用中大量静态资源的加速分发,建议将站点内容进行动静分离,动态文件可以结合云服务器ECS,静态资源如各类型图片、html、css、js文件等,建议结合 对象存储OSS 存储海量静态资源,可以有效加速内容加载速度,轻松搞定网站图片、短视频等内容分发

2)视音频点播/大文件下载分发加速

支持各类文件的下载、分发,支持在线点播加速业务,如mp4、flv视频文件或者平均单个文件大小在20M以上,主要的业务场景是视音频点播、大文件下载(如安装包下载)等,建议搭配对象存储OSS使用,可提升回源速度,节约近2/3回源带宽成本。

3)视频直播加速(内测中)

视频流媒体直播服务,支持媒资存储、切片转码、访问鉴权、内容分发加速一体化解决方案。结合弹性伸缩服务,及时调整服务器带宽,应对突发访问流量;结合媒体转码服务,享受高速稳定的并行转码,且任务规模无缝扩展。目前CDN直播加速已服务内部用户测试并优化,即将上线,敬请期待

4)移动应用加速

移动APP更新文件(apk文件)分发,移动APP内图片、页面、短视频、UGC等内容的优化加速分发。提供httpDNS服务,避免DNS劫持并获得实时精确的DNS解析结果,有效缩短用户访问时间,提升用户体验。

 

 

关于CDN的一些常见名词

 

 

1)Origin Server源站

做CDN 之前的客户真正请求的服务器

2)User访问者

也就是要访问网站的网民

3)Last Mile最后一公里

也就是网民到他所访问到的 CDN 服务器之间的路径

4)域名

域名是Internet网络上的一个服务器或一个网络系统的名字,全世界,没有重复的域名

5)CNAME记录

它是一个别名记录( Canonical Name );当 DNS 系统在查询 CNAME 左面的名称的时候,都会转向 CNAME 右面的名称再进行查询,一直追踪到最后的 PTR 或 A 名称,成功查询后才会做出回应,否则失败

6)CNAME域名

CDN的域名加速需要用到CNAME记录,在阿里云控制台配置完成CDN加速后,您会得到一个加速后的域名,称之为CNAME域名(该域名一定是*.*http://kunlun.com), 用户需要将自己的域名作CNAME指向这个*.*http://kunlun.com的域名后,域名解析的工作就正式转向阿里云,该域名所有的请求都将转向阿里云CDN的节点

7)DNS

DNS即Domain Name System,是域名解析服务的意思。它在互联网的作用是:把域名转换成为网络可以识别的ip地址。人们习惯记忆域名,但机器间互相只认IP地址,域名与IP地址之间是一一对应的,它们之间的转换工作称为域名解析,域名解析需要由专门的域名解析服务器来完成,整个过程是自动进行的。比如:上网时输入的百度一下,你就知道会自动转换成为220.181.112.143

8)边缘节点

也称CDN节点、Cache节点等;是相对于网络的复杂结构而提出的一个概念,指距离最终用户接入具有较少的中间环节的网络节点,对最终接入用户有较好的响应能力和连接速度。其作用是将访问量较大的网页内容和对象保存在服务器前端的专用cache设备上,以此来提高网站访问的速度和质量

9)cache

cache高速缓冲存储器一种特殊的存储器子系统,其中复制了频繁使用的数据以利于快速访问。存储器的高速缓冲存储器存储了频繁访问的RAM位置的内容及这些数据项的存储地址。当处理器引用存储器中的某地址时,高速缓冲存储器便检查是否存有该地址。如果存有该地址,则将数据返回处理器;如果没有保存该地址,则进行常规的存储器访问。因为高速缓冲存储器总是比主RAM存储器速度快,所以当RAM的访问速度低于微处理器的速度时,常使用高速缓冲存储器。

 

 

45、js数组排序

使用Array.sort()对数组元素排序

var arr = [5,3,2,9];
arr.sort(function(a,b){return a-b});
console.log(arr);  //[2,3,5,9]

 

1) 冒泡排序:升序 ,最坏的情况下是O(n^2)

 

// 冒泡排序的思想:相邻元素两两比较,最外层循环表示两两比较的趟数,
// 内层循环实现两两比较和交换,一趟比较下来可实现找到最大或最小的元素
// 升序排序
function bubelSort( arr ){
    if( !Array.isArray(arr) ){
        return '输入的不是一个数组类型';
    }else if( arr == false ){
        return '输入的是一个空的数组';
    }


    var len = arr.length;
    var i = 0;
    var j = 0;
    var temp;
    for( ;i<len-1;i++ ){
        // 比较趟数
        for( ;j<len-1-i;j++ ){
            // 两两比较,符合条件交换位置,实现一趟比较下来找到最大(小)元素
            if( arr[j] > arr[j+1] ){
                temp = arr[j+1];
                arr[j+1] = arr[j];
                arr[j] = temp;
            }
        }
    }
    return arr;
}


var arr = [3,1,5,3];
console.log(bubelSort(arr));

2) 快速排序:分而治之的算法,O( nlog(n) ),优于归并排序

// 从数组的中间拿一个值,然后通过这个值挨个和数组里面的值
// 进行比较,如果大于的放一边,小于的放一边,然后把这些合
// 并,再进行比较,如此反复即可,利用递归的思想
// 这里实现快速升序排序
var arr = [3,1,4,2,5,21,6,15,63];
function quickSort(arr){
    if(!Array.isArray(arr)){
        return '输入的不是一个数组';
    }

    // 如果只有一位,就没有必要比较
    if(arr.length<=1){
        return arr;
    }
    // 获取中间值的索引
    var midIndex = Math.floor(arr.length/2);
    // 截取中间值
    var midElement = arr.splice(midIndex,1);
    // 小于中间值放这里面
    var left = [];
    // 大于的放着里面
    var right = [];
    for(var i=0;i<arr.length;i++){
        // 判断是否大于
        if(midElement>arr[i]){
            left.push(arr[i]);
        }else{
            right.push(arr[i]);
        }
    }
    // 通过递归,上一轮比较好的数组合并,并且再次进行比较。
    return quickSort(left).concat(midElement,quickSort(right));
}
console.log(quickSort(arr));

3) 归并排序:自上而下的递归,分而治之,O( nlog(n) )。基本思路:先递归的分解数列,在合并数列。

归并算法特别适合将两个有序的数组组合

// 归并排序,升序
function mergeSort( arr ){
    if( !Array.isArray(arr) ){
        return '输入的不是一个数组';
    }else if( arr == false ){
        return '输入的是一个空的数组';
    }

    var len = arr.length;
    // 递归结束的条件,数组只剩下一个元素的时候
    if( len <2 ){
        return arr;
    }

    // 从中间开始,将一个数组分成两个数组
    var midIndex = Math.floor(len/2),
        left = arr.slice(0,midIndex),
        right = arr.slice(midIndex);


    // 调用合并函数
    return merge( mergeSort(left),mergeSort(right) );
}


function merge( left,right ){
    var arr = [];
    // 两个数组都不为空的时候
    while( left.length && right.length ){
        // 取两个数组的第一个元素作比较,小的放进arr里面
        if( left[0]<right[0] ){
            arr.push(left.shift());
        }else{
            arr.push(right.shift());
        }
    }




    // 如果left数组还没有取完的话,需要将left剩余的元素放进arr中
    while(left.length){
        arr.push(left.shift());
    }




    // 如果right数组还没有取完的话,需要将right剩余的元素放进arr中
    while(right.length){
        arr.push(right.shift());
    }




    return arr;
}




var arr = [6,1,3];
console.log(mergeSort(arr));

 

4) 选择排序,O( n^2 )

// 选择排序的思想:最外层i循环是比较的趟数
// 内层j循环是用于找到最值元素的索引号index
// 然后如果i!=index,则将这两个下标表示的元素进行交换
// 这里是升序
function selectSort(arr){
    if( !Array.isArray(arr) ){
        console.log('输入的不是一个数组');
        return '输入的不是一个数组';
    }

    var len = arr.length;
    var tempVal,tempIndex;


    if(len==1){
        // 数组长度为1就不需要排序了
        return arr;
    }else if( arr == false ){
        console.log('输入的是一个空的数组');
        return '输入的是一个空的数组';
    }

    for( var i=0;i<len-1;i++ ){
        // 比较的趟数
        tempIndex = i;       //从i的下标开始
        for( var j = i+1;j<len;j++ ){
            // 两两比较,每趟都找到最小值的下标
            if( arr[tempIndex] > arr[j]){
                tempIndex = j;
            }
        }
        // 判断每趟的最小值与i对应的值是否一样,不一样则交换
        if( tempIndex!=i ){
            tempVal = arr[i];
            arr[i] = arr[tempIndex];
            arr[tempIndex] = tempVal
        }
    }
    return arr;
}

var arr=[6,2,3];
console.log(selectSort(arr));

 

5) 插入排序 (o(n^2))

将一组数据分成两组,分别将其称为有序组与待插入组。每次从待插入组中取出一个元素,与有序组的元素进行比较,并找到合适的位置,将该元素插到有序组当中。就这样,每次插入一个元素,有序组增加,待插入组减少。直到待插入组元素个数为0。当然,插入过程中涉及到了元素的移动。

为了排序方便,我们一般将数据第一个元素视为有序组,其他均为待插入组。

// 插入排序,升序
// 分为两个数组:一个有序数组
//(刚开始默认将第一个元素作为有序数组),另一个无序数组
function insertionSort(arr) {
    var len = arr.length;
    var preIndex, curElement;
    for (var i = 1; i < len; i++) {
        preIndex = i - 1;
        curElement = arr[i];
        while(preIndex >= 0 && arr[preIndex] > curElement) {//将无序数组的第一个元素和有序数组的所有元素比较
            arr[preIndex+1] = arr[preIndex]; //移位
            preIndex--;  //找到要插入的的位置
        }
        arr[preIndex+1] = curElement; //插入元素
    }
    return arr;
}
var arr = [6,1,3];
console.log(insertionSort(arr));

 

 

6) 堆排序可以说是一种利用堆的概念来排序的选择排序。分为两种方法:

堆排序是利用这种数据结构而设计的一种排序算法,堆排序是一种选择排序,它的最坏,最好,平均时间复杂度均为O(nlogn),它也是不稳定排序


大顶堆:每个节点的值都大于或等于其子节点的值,在堆排序算法中用于升序排列,大顶堆的根节点是最大值

 

小顶堆:每个节点的值都小于或等于其子节点的值,在堆排序算法中用于降序排列,小顶堆的根节点是最小值

大顶堆:arr[i] >= arr[2i+1] && arr[i] >= arr[2i+2]  

小顶堆:arr[i] <= arr[2i+1] && arr[i] <= arr[2i+2]

 

https://www.cnblogs.com/chengxiao/p/6129630.html    详解

思路:

a.将无序序列构建成一个堆,根据升序降序需求选择大顶堆或小顶堆,升序排列选择大顶堆排序

b.将堆顶元素与末尾元素交换,将最大元素"沉"到数组末端

c.重新调整结构,使其满足堆定义,然后继续交换堆顶元素与当前末尾元素,反复执行调整+交换步骤,直到整个序列有序

详细讲解的链接:https://www.cnblogs.com/chengxiao/p/6129630.html

// 堆排序:升序
// 选择排序的变种
var len;    //因为声明的多个函数都需要数据长度,所以把len设置成为全局变量

function swap(arr, i, j) {
    var temp = arr[i];
    arr[i] = arr[j];
    arr[j] = temp;
}

function heapify(arr, i) {     //堆调整,也就是进行一层的节点调整,父节点和子节点的比较大小,然后对位置进行调整
    var left = 2 * i + 1,
        right = 2 * i + 2,
        largest = i;          //父子节点比较,保存最大值的索引

    //父节点要和他的两个子节点比较大小
    // 父节点和左孩子节点比较
    if (left < len && arr[left] > arr[largest]) {
        largest = left;
    }

    // 父节点和右孩子节点比较
    if (right < len && arr[right] > arr[largest]) {
        largest = right;
    }

    if (largest != i) {
        swap(arr, i, largest);
        heapify(arr, largest);     //递归调用,没有结束条件?????
    }
}

function buildMaxHeap(arr) {   //建立大顶堆,根节点是最大值,节点的左右孩子小于等于节点值
    len = arr.length;
    for (var i = Math.floor(len/2); i >= 0; i--) { //从最后一个非叶子节点开始
        heapify(arr, i);
    }
}

function heapSort(arr) {
    buildMaxHeap(arr);  //建立大顶堆只需要调用一次

    for (var i = arr.length-1; i > 0; i--) {  //进行交换的次数,也是交换的索引
        swap(arr, 0, i);    //堆顶元素和相应倒数末端进行交换
        len--;              //从数组中排除掉已经找到实际位置的元素
        heapify(arr, 0);    //继续调整堆 
    }
    return arr;
}

var arr = [6,1,3];
console.log(heapSort(arr));

 

7) 计数排序:计数排序的核心在于将输入的数据值转化为键存储在额外开辟的数组空间中;作为一种线性时间复杂度的排序,计数排序要求输入的数据必须是有确定范围的整数。

非基于比较的排序算法,它的优势在于在对一定范围内的整数排序时,它的复杂度为Ο(n+k)(其中k是整数的范围),快于任何比较排序算法。牺牲空间换取时间的做法,当O(k)>O(n*log(n))的时候其效率反而不如基于比较的排序

假设数序列中小于元素a的个数为n,则直接把a放到第n+1个位置上。当存在几个相同的元素时要做适当的调整,因为不能把所有的元素放到同一个位置上。计数排序假设输入的元素都是0到k之间的整数。

// 计数排序,升序
// 数组中的元素作为另一个空间的索引,利用flag去判断是否有这个元素,
// 有则将开辟空间的 索引放进源数组中
function countSort(arr,maxVal){//arr--输入的数组,maxValue---数组中元素最大值
    if(!Array.isArray(arr)){
        return '输入的不是一个数组';
    }else if( arr == false ){
        return '输入的是一个空的数组';
    }else if( arguments.length !=2 ){
        return '输入参数不符合规定的个数';
    }


    var newArr = new Array(maxVal+1),//另外开辟的一个空间
        newIndex = 0,
        arrLen = arr.length,
        newArrLen = maxVal+1;


    for(var i =0;i<arrLen;i++){
        newArr[arr[i]] = 1;  //将原数组中的元素作为newArr的索引,并将这个索引对应的元素的值标记为1
    }


    for( var j=0; j < newArrLen;j++){
        if( newArr[j] == 1 ){ //检查开辟的空间newArr中元素值为1表示该元素是原数组元素,将其添加
            arr[newIndex++] = j;
            newArr[j]--;
        }
    }
    return arr;
}


var arr = [6,1,3];
console.log(countSort(arr,6));

8) 桶排序:

 

9)基数排序:

var counter = [];
function radixSort(arr, maxDigit) {
    var mod = 10;
    var dev = 1;
    for (var i = 0; i < maxDigit; i++, dev *= 10, mod *= 10) {
        for(var j = 0; j < arr.length; j++) {
            var bucket = parseInt((arr[j] % mod) / dev);
            if(counter[bucket]==null) {
                counter[bucket] = [];
            }
            counter[bucket].push(arr[j]);
        }
        var pos = 0;
        for(var j = 0; j < counter.length; j++) {
            var value = null;
            if(counter[j]!=null) {
                while ((value = counter[j].shift()) != null) {
                      arr[pos++] = value;
                }
          }
        }
    }
    return arr;
}

var arr = [6,1,3];
console.log(radixSort(arr,6));

 

10) 基数排序: