全称:Asynchronous Javascript And XML。异步传输+js+xml。
var xhr = new XMLHttpRequest();
xhr.open("get", "example.php", true); \/\/最后一个参数表示是否异步
xhr.send();\/\/参数为作为请求主体发送的数据,没有就null
收到响应后,响应数据自动填充XHR对象的属性,有responseText、responseXML、status、statusText,异步的话有readyState属性,它的改变会触发readystatechange 事件。
readyState中0表示未初始化,没调用open()方法,1表示启动,调用open()未调用send(),2发送,调用send()未收到响应,3接收到部分响应数据,4接收到全部响应数据。
使用setRequestHeader()方法可以设置自定义的请求头部信息。这个方法接受两个参数:头部字段的名称和头部字段的值。要成功发送请求头部信息,必须在调用open()方法之后且调用send()方法之前调用setRequestHeader()。调用XHR 对象的getResponseHeader() 方法并传入头部字段名称,可以取得相应的响应头部信息。而调用getAllResponseHeaders()方法则可以取得一个包含所有头部信息的长字符串。
get通常请求用于向服务器查询信息,具有幂等性。一般将查询字符串参数追加到URL 的末尾,以便将信息发送给服务器。查询字符串中每个参数的名称和值都必须使用encodeURIComponent()进行编码,且用&号分开。
post通常用于向服务器发送应该被保存的数据。通过创建FormData对象,可以序列化表单以及创建与表单格式相同的数据(用于通过XHR 传输)。使用FormData 的方便之处体现在不必明确地在XHR 对象上设置请求头部。XHR 对象能够识别传入的数据类型是FormData 的实例,并配置适当的头部信息。或者在open()后使用 setRequestHeader() 来添加 HTTP 头。然后在 send() 方法中规定您希望发送的数据: `xhr.setRequestHeader("Content-type","application\/x-www-form-urlencoded"); xhr.send("fname=Bill&lname=Gates");\/\/字符串
xhr.setRequestHeader("Content-Type", "application\/json;charset=UTF-8"); xhr.send(JSON.stringify({name:"John Rambo", time:"2pm"}));\/\/json`
与 POST 相比,GET 更简单也更快,并且在大部分情况下都能用。然而,在以下情况中,请使用 POST 请求: 无法使用缓存文件(更新服务器上的文件或数据库) 向服务器发送大量数据(POST 没有数据量限制) 发送包含未知字符的用户输入时,POST 比 GET 更稳定也更可靠。
loadstart:在接收到响应数据的第一个字节时触发。
progress:在接收响应期间持续不断地触发。
error:在请求发生错误时触发。事件处理程序会接收到一个event 对象,其target 属性是XHR 对象,但包含着三个额外的属性:lengthComputable(进度是否可用的布尔值)、position(表示已经接收的字节数) 和totalSize(根据Content-Length 响应头部确定的预期字节数)。
abort:在因为调用abort()方法而终止连接时触发。
load:在接收到完整的响应数据时触发。
loadend:在通信完成或者触发error、abort 或load 事件后触发。
缺点:ajax不支持浏览器back按钮。安全问题 AJAX暴露了与服务器交互的细节。对搜索引擎的支持比较弱。破坏了程序的异常机制。不容易调试。
IE缓存问题:在IE浏览器下,如果请求的方法是GET,并且请求的URL不变,那么这个请求的结果就会被缓存。解决这个问题的办法可以通过在URL末尾添加上随机的时间戳参数('t'= + new Date().getTime())或者:open('GET','demo.php?rand=+Math.random()',true);
Ajax请求的页面历史记录状态问题可以通过锚点来记录状态,location.hash。让浏览器记录Ajax请求时页面状态的变化。还可以通过HTML5的history.pushState,来实现浏览器地址栏的无刷新改变
问题来源——同源策略,协议相同、域名相同、端口相同。 一个源可以向另一个不同源发送数据,但不可以从另一个源读取数据。其目的是防止某个文档或脚本从多个不同源装载。同源策略仅仅阻止了第三方站点读取来自其他站点的内容,但是却没有防止这些第三方站点向其他站点发出请求(CSRF攻击)。
CORS 背后的基本思想,就是使用自定义的HTTP 头部让浏览器与服务器进行沟通,从而决定请求或响应是应该成功,还是应该失败。
发送请求时给它附加一个额外的Origin 头部,其中包含请求页面的源信息(协议、域名和端口),以便服务器根据这个头部信息来决定是否给予响应。服务器设置Access-Control-Allow-Origin,请求和响应都不包含cookie 信息。跨域XHR 对象也有一些限制,不能使用setRequestHeader()设置自定义头部、 不能发送和接收cookie、调用getAllResponseHeaders()方法总会返回空字符串。
JSONP 由两部分组成:回调函数和数据。回调函数是当响应到来时应该在页面中调用的函数。回调函数的名字一般是在请求中指定的。通过动态<script>元素来使用,使用时可以为src 属性指定一个跨域URL。 动态插入script标签,通过script标签引入一个js文件,这个js文件载入成功后会执行我们在url参数中指定的函数,并且会把我们需要的json数据作为参数传入。 问题:安全问题,难以判断请求是否失败。
封装一个函数:
function getJsonp(options) {
var callbackName = options.callbackName;
var url = options.url;
var scriptElem = document.createElement('script');
scriptElem.setAttribute('src', url + '?callback=' + callbackName);
scriptElem.onload = function(e) {
delete window[callbackName];
this.parentNode.removeChild(this);
};
scriptElem.onerror = function(e) {
console.log(e, 'load error');
delete window[callbackName];
this.parentNode.removeChild(this);
};
//设置回调函数
window[callbackName] = options.success;
// 添加
document.querySelector('head').appendChild(scriptElem);
}
window.name 在一个窗口(标签)的生命周期之内是共享的, 当对一个页面设置 window.name 后,即使改变该页面 url,window.name 也不会被重写。 利用这点就可以传输一些数据。
可以父窗口打开一个不同源的子窗口,该网页信息写入window.name,然后子窗口调回到主窗口同域的代理页面网址( 因为要同源才可以获取到window.name ),就可以读取window.name信息了。结合iframe可以直接读取信息。
跨子域。 把两个同主域,不同子域的文件的 document.domain 设置成为页面本身或者更高一级的域名。缺点:安全问题,不适合页面较多的网站。
监听hashchange事件,子页面可以通过 parent. location.hash给父页面传数据,父页面可以改变iframe子页面的src来给子页面传数据。
使用postMessage(data,url)来发送数据, 调用postMessage方法的window对象是指要接收消息的那一个window对象。通过message事件,监听对方的消息,message事件对象的属性有source、origin、data,接收消息的窗口可以用event.source引用发送消息的窗口,而event.origin可以过滤掉不是发送给本窗口的消息。可以通过它来读写其他窗口的localStorage.
以上后四种方法经常配合iframe使用, iframe主要的两个API就是contentWindow,和contentDocument ,前者用于获取iframe的window对象,后者 获取iframe的document对象。
CSRF 跨站请求伪造
对于未被授权系统有权访问某个资源的情况,我们称之为CSRF(Cross-Site Request Forgery,跨站点请求伪造)。未被授权系统会伪装自己,让处理请求的服务器认为它是合法的。
请注意,下列措施对防范CSRF 攻击不起作用:要求发送POST 而不是GET 请求——很容易改变。 检查来源URL 以确定是否可信——来源记录很容易伪造。 基于cookie 信息进行验证——同样很容易伪造。
原理:会话是由cookie唯一标识的,并且该cookie是由浏览器自动发送的。根源在于Web的身份验证机制虽然可以向目标站点保证一个请求来自于某个用户的浏览器,但是却无法保证该请求的确是那个用户发出的或者是经过那个用户批准的(Web的隐式身份验证机制)。
理解:攻击者盗用了你的身份,以你的名义发送恶意请求。CSRF能够做的事情包括:以你名义发送邮件,发消息,盗取你的账号,甚至于购买商品,虚拟货币转账......造成的问题包括:个人隐私泄露以及财产安全。
要完成一次CSRF攻击,受害者必须依次完成两个步骤:
防御:
1.在请求地址中添加 token 并验证:先服务器端要以某种策略生成随机字符串,作为令牌(token),保存在 Session 里。然后在发出请求的页面,把该令牌以隐藏域一类的形式,与其他信息一并发出。在接收请求的页面,把接收到的信息中的令牌与 Session 中的令牌比较,只有一致的时候才处理请求,处理完成后清理session中的值,否则返回 HTTP 403 拒绝请求或者要求用户重新登陆验证身份
2.在 HTTP 头中自定义属性并验证:这种方法也是使用 token 并进行验证,和上一种方法不同的是,这里并不是把 token 以参数的形式置于 HTTP 请求之中,而是把它放到 HTTP 头中自定义的属性里。通过 XMLHttpRequest 这个类,可以一次性给所有该类请求加上 csrftoken 这个 HTTP 头属性,并把 token 值放入其中。这样解决了上种方法在请求中加入 token 的不便,同时,通过 XMLHttpRequest 请求的地址不会被记录到浏览器的地址栏,也不用担心 token 会透过 Referer 泄露到其他网站中去。
然而这种方法的局限性非常大。XMLHttpRequest 请求通常用于 Ajax 方法中对于页面局部的异步刷新,并非所有的请求都适合用这个类来发起,而且通过该类请求得到的页面不能被浏览器所记录下,从而进行前进,后退,刷新,收藏等操作,给用户带来不便。另外,对于没有进行 CSRF 防护的遗留系统来说,要采用这种方法来进行防护,要把所有请求都改为 XMLHttpRequest 请求,这样几乎是要重写整个网站,这代价无疑是不能接受的。
3.验证码
4.良好的API设计,最好遵循Restful风格。GET操作只是获取资源,不能操作资源,它具备幂等性。如果需要对资源进行操作,请使用POST请求,当然你如果想使用PUT和DELETE,可以使用POST来模拟。
5.验证 HTTP Referer 字段:根据 HTTP 协议,在 HTTP 头中有一个字段叫 Referer,它记录了该 HTTP 请求的来源地址。缺点:依赖浏览器,黑客可能可以修改
Xss(cross-site scripting)通常是指,攻击者向一个站点注入恶意代码(通常为JavaScript代码或html标签),以攻击该站点的其它用户(即攻击的最终目标并非站点本身)。比如:攻击者在论坛中放一个看似安全的链接,骗取用户点击后,窃取cookie中的用户私密信息;或者攻击者在论坛中加一个恶意表单,当用户提交表单的时候,却把信息传送到攻击者的服务器中,而不是用户原本以为的信任站点。
攻击者使被攻击者在浏览器中执行脚本后,如果需要收集来自被攻击者的数据(如cookie或其他敏感信息),可以自行架设一个网站,让被攻击者通过JavaScript等方式把收集好的数据作为参数提交,随后以数据库等形式记录在攻击者自己的服务器上。
当来自用户的不可信数据在没有验证以及反射回浏览器而没有进行编码或转义的情况下进行了处理, 导致浏览器引擎执行了代码。\
反射型XSS:简单地把用户输入“反射”给浏览器,往往需要用户点击一个恶意链接才能攻击成功。
存储型XSS:把用户输入数据存储在服务器端(恶意js代码的博客)
DOM Based XSS:修改页面DOM节点形成的XSS
常用的XSS攻击手段和目的有:
XSS防范方法
原则: 不相信客户输入的数据,验证输入并基于语境和按照正确顺序转义不可信数据。
<script>, <iframe> , < for <, > for >, " for
首先,避免直接在cookie 中泄露用户隐私,例如email、密码等等。
其次,通过使cookie 和系统ip 绑定来降低cookie 泄露后的危险。这样攻击者得到的cookie 没有实际价值,不可能拿来重放。
尽量采用POST 而非GET 提交表单
XSS是获取信息,不需要提前知道其他用户页面的代码和数据包。CSRF是代替用户完成指定的动作,需要知道其他用户页面的代码和数据包。
CSRF和XSS攻击的区别在于,XSS攻击需要JavaScript,而CSRF攻击不需要;XSS攻击要求站点接受恶意代码,而对于CSRF攻击来说,恶意代码位于第三方站点上。过滤用户的输入可以防止恶意代码注入到某个站点,但是它无阻止法恶意代码在第三方站点上运行。由于恶意代码可以在第三方站点上运行,所以防御XSS攻击的措施无法保护站点不受CSRF攻击的危害。如果站点具有XSS攻击漏洞,那么它也有CSRF攻击漏洞。但是,即使站点针对XSS攻击采取了全面保护,却仍然面临CSRF攻击的威胁。
通过将数据注入网络应用程序,然后被用于 SQL 请求来操作。数据通常来自类似网页表单的不可信来源。不过,数据也可能来自包括数据库本身在内的其他来源。如果攻击成功,SQL 注入攻击能够操纵受攻击的 SQL 请求,从而进行非程序员意愿的数据库操作。
就是通过把SQL命令插入到Web表单递交或输入域名或页面请求的查询字符串,最终达到欺骗服务器执行恶意的SQL命令。
总的来说有以下几点:
1.永远不要信任用户的输入,要对用户的输入进行校验,可以通过正则表达式,或限制长度,对单引号和双"-"进行转换等。检查变量数据类型和格式,过滤特殊符号。
2.永远不要使用动态拼装SQL,可以使用参数化的SQL或者直接使用存储过程进行数据查询存取。
3.永远不要使用管理员权限的数据库连接,为每个应用使用单独的权限有限的数据库连接。
4.不要把机密信息明文存放,请加密或者hash掉密码和敏感的信息
JSON 是一个轻量级的数据格式,语法可以包含简单值、对象、数组。和JS对象\/数组字面量相比,JSON对象\/数组没有声明变量且没有末尾分号,JSON对象属性必须加双引号。
stringify方法用于序列化, 除了要序列化的js对象还有两个参数:过滤器(数组或函数),是否保留缩进的选项(数值表示空格数或者字符串),对象上定义toJSON()可以作为函数过滤器的补充,会首先被调用。
JSON.parse()方法也可以接收另一个参数,该参数是一个函数,将在每个键值对儿上调用。、
判断是否离线:navigator.onLine属性,Online,Offline事件。
如何缓存:使用描述文件manifest file.<html manifest = '/offline.manifest'>.
文件列出需要下载和缓存的资源。
最初用于客户端存储会话信息,可以用个HTTP头添加Set-Cookie来将cookie发送给服务器。存在客户端计算机上,每个域的cookie总数有限。cookie由名称、值、域、路径、失效时间、安全标志等信息构成。 cookie的最大大约为4096字节,为了兼容性,一般不能超过4095字节。
优点:极高的扩展性和可用性
1.通过良好的编程,控制保存在cookie中的session对象的大小。
2.通过加密和安全传输技术(SSL),减少cookie被破解的可能性。
3.只在cookie中存放不敏感数据,即使被盗也不会有重大损失。
4.控制cookie的生命期,使之不会永远有效。偷盗者很可能拿到一个过期的cookie。
缺点:
1.Cookie
数量和长度的限制。每个domain最多只能有20条cookie,每个cookie长度不能超过4KB,否则会被截掉.
2.安全性问题。如果cookie被人拦截了,那人就可以取得所有的session信息。即使加密也与事无补,因为拦截者并不需要知道cookie的意义,他只要原样转发cookie就可以达到目的了。
3.有些状态不可能保存在客户端。例如,为了防止重复提交表单,我们需要在服务器端保存一个计数器。如果我们把这个计数器保存在客户端,那么它起不到任何作用
如果静态文件都放在主域名下,那静态文件请求的时候都带有的cookie的数据提交给server的,非常浪费流量, 所以不如隔离开。
因为cookie有域的限制,因此不能跨域提交请求,故使用非主要域名的时候,请求头中就不会带有cookie数据, 这样可以降低请求头的大小,降低请求时间,从而达到降低整体请求延时的目的。
同时这种方式不会将cookie传入Web Server,也减少了Web Server对cookie的处理分析环节, 提高了webserver的http请求的解析速度。
sessionStorage,localStorage,区别是前者只保存到浏览器关闭。方法有clear(),getItem(name),key(index),removeItem(name),setItem(name,value)
storage事件,对Storage 对象进行任何修改,都会在文档上触发storage 事件。事件的event对象由以下属性:domain,key,newValue,oldValue
cookie是网站为了标示用户身份而储存在用户本地终端(Client Side)上的数据(通常经过加密)。 cookie数据始终在同源的http请求中携带(即使不需要),会在浏览器和服务器间来回传递。 sessionStorage和localStorage不会自动把数据发给服务器,仅在本地保存。
但是cookie也是不可或缺的:cookie的作用是与服务器进行交互,作为HTTP规范的一部分而存在 ,而Web Storage仅仅是为了在本地“存储”数据而生。
告诉浏览器某些JavaScript 代码将要执行动画。这样浏览器可以在运行某些代码后进行适当的优化。
接收一个参数,即在重绘屏幕前调用的一个函数。这个函数负责改变下一次重绘时的DOM样式。
回调函数只会被传入一个DOMHighResTimeStamp
参数,这个参数指示当前被 requestAnimationFrame 序列化的函数队列被触发的时间 。
返回requestID
是一个长整型非零值,作为一个唯一的标识符.你可以将该值作为参数传给window.cancelAnimationFrame()
来取消这个回调函数。
MV*框架的思维方式是:以模型为中心,DOM操作只是附加
Model-View-Presenter(MVP) and Model-View-ViewModel(MVVM)模式,实际上就是将职责重分配,都是为了将数据,视图与业务逻辑的拆分。
MVC:View 传送指令到 Controller,Controller 完成业务逻辑后,要求 Model 改变状态,Model 将新的数据发送到 View,用户得到反馈,所有通信都是单向的。
MVVM:组成部分Model(数据访问层)、View(UI界面)、ViewModel(是View的抽象,负责View与Model之间信息转换,将View的Command传送到Model),Angular它采用双向绑定(data-binding):View的变动,自动反映在 ViewModel,反之亦然。
如果我们来设计Angular这么一个前端框架,应当如何入手呢?很显然,逻辑的控制必须使用JavaScript,一个框架,最本质的事情在于它的逻辑处理方式。 我们的界面为什么可以多姿多彩?因为有HTML和CSS,注意到这两种东西都是配置式的写法,参照后端的依赖注入,如果把这两者视为跟Spring框架中一些XML等同的配置文件,思路就豁然开朗了。
与后端不同的是,充当前端逻辑工具的JavaScript不能做入口,必须挂在HTML里才能运行,所以出现了一个怪异的状况:逻辑要先挂在配置文件(HTML)上,先由另外的容器(浏览器或者Hybird的壳)把配置文件加载起来,然后才能从某个入口开始执行逻辑。好消息是,过了这一步,逻辑层就开始大放异彩了。
从这个时候开始,框架就启动了,它要做哪些事情呢?
这些是主线流程,还有一些支线,比如:
SPA的一个典型特征就是部分加载,界面的部件化也是其中比较重要的一环。界面片段在动态请求得到之后,借助模版引擎之类的技术,经过某种转换,放置到主界面相应的地方。所以,从这个角度来看,HTML的组件化非常容易理解,那就是界面的片段化和模板化。
JavaScript这个部分有好几个发展阶段。
JavaScript组件化的目标是什么呢,是清晰的职责,松耦合,便于单元测试和重复利用。这里的松耦合不仅体现在js代码之间,也体现在js跟 DOM之间的关系,所以像Angular这样的框架会有directive的概念,把DOM操作限制到这类代码中,其他任何js代码不操作DOM。
如上图所示,总的原则是先分层次,层内再作切分。这么做的话,不再存在之前那种端到端组件了,使用起来没有原先那么方便,但在另外很多方面比较好。
这方面,业界也有很多探索,比如LESS,SASS,Stylus等。为什么CSS也要做组件化呢?传统的CSS是一种扁平的文本结构,变更成本较 高,比如说想要把结构从松散改紧凑,需要改动很多。如果把实际使用的CSS只当作输出结果,而另外有一种适合变更的方式当作中间过程,这就好多了。比如 说,我们把一些东西定义成变量,每个细节元素使用这些变量,当需要整体变更的时候,只需修改这些变量然后重新生成一下就可以了。
工厂模式的主要好处就是可以消除对象间的耦合,通过使用工程方法而不是new关键字。将所有实例化的代码集中在一个位置防止代码重复。解决了重复实例化的问题 ,但还有一个问题,那就是识别问题,因为根本无法搞清楚他们到底是哪个对象的实例。
使用构造函数的方法 ,即解决了重复实例化的问题 ,又解决了对象识别的问题,该模式与工厂模式的不同之处在于:
1) 单例: 任意对象都是单例,无须特别处理
var obj = {name: 'michaelqin', age: 30};
2) 工厂: 就是同样形式参数返回不同的实例
function Person() { this.name = 'Person1'; }
function Animal() { this.name = 'Animal1'; }
function Factory() {}
Factory.prototype.getInstance = function(className) {
return eval('new ' + className + '()');
}
var factory = new Factory();
var obj1 = factory.getInstance('Person');
var obj2 = factory.getInstance('Animal');
console.log(obj1.name); // Person1
console.log(obj2.name); // Animal1
3) 代理: 就是新建个类调用老类的接口,包一下
function Person() { }
Person.prototype.sayName = function() { console.log('michaelqin'); }
Person.prototype.sayAge = function() { console.log(30); }
function PersonProxy() {
this.person = new Person();
var that = this;
this.callMethod = function(functionName) {
console.log('before proxy:', functionName);
that.person[functionName](); // 代理
console.log('after proxy:', functionName);
}
}
var pp = new PersonProxy();
pp.callMethod('sayName'); // 代理调用Person的方法sayName()
pp.callMethod('sayAge'); // 代理调用Person的方法sayAge()
4) 观察者: 就是事件模式,比如按钮的onclick这样的应用.
function Publisher() {
this.listeners = [];
}
Publisher.prototype = {
'addListener': function(listener) {
this.listeners.push(listener);
},
'removeListener': function(listener) {
delete this.listeners[listener];
},
'notify': function(obj) {
for(var i = 0; i < this.listeners.length; i++) {
var listener = this.listeners[i];
if (typeof listener !== 'undefined') {
listener.process(obj);
}
}
}
}; // 发布者
function Subscriber() {
}
Subscriber.prototype = {
'process': function(obj) {
console.log(obj);
}
}; // 订阅者
var publisher = new Publisher();
publisher.addListener(new Subscriber());
publisher.addListener(new Subscriber());
publisher.notify({name: 'michaelqin', ageo: 30}); // 发布一个对象到所有订阅者
publisher.notify('2 subscribers will both perform process'); // 发布一个字符串到所有订阅者
对普通的网站有一个统一的思路,就是尽量向前端优化、减少数据库操作、减少磁盘IO。
减少HTTP请求
DNS查找缓存
减少重定向(重定向:把用户从一个url重新路由到另一个url,最常见301,302;url结尾用\/)
并行请求
压缩:压缩组件用gzip,压缩HTML文档、样式表、脚本,减少响应数据量;Vary:Accept-Encoding
缓存
按需加载
前端模块化
样式表放在顶部,将脚本放在底部
移除重复脚本
图片预加载
使用内容发布网络 (CDN)
代码层面:
移动端性能优化:
尽量使用css3动画,开启硬件加速。适当使用touch事件代替click事件。避免使用css3渐变阴影效果。
减少 JavaScript 对性能的影响有以下几种方法:
<script>
标签放到页面底部,也就是</body>
闭合标签之前,这能确保在脚本执行前页面已经完成了渲染。<script>
标签越少,加载也就越快,响应也越迅速。无论是外链脚本还是内嵌脚本都是如此。网站重构:在不改变外部行为的前提下,简化结构、添加可读性,而在网站前端保持一致的行为。也就是说是在不改变UI的情况下,对网站进行优化,在扩展的同时保持一致的UI。
对于传统的网站来说重构通常是:
深层次的网站重构应该考虑的方面:
通常来说对于速度的优化也包含在重构中
1、浏览器会开启一个线程来处理这个请求,对 URL 分析判断如果是 http 协议就按照 Web 方式来处理;
2、调用浏览器内核中的对应方法,比如 WebView 中的 loadUrl 方法;
3、通过DNS解析获取网址的IP地址,设置 UA 等信息发出第二个GET请求;
4、进行HTTP协议会话,客户端发送报头(请求报头);
5、进入到web服务器上的 Web Server,如 Apache、Tomcat、Node.JS 等服务器;
6、进入部署好的后端应用,如 PHP、Java、JavaScript、Python 等,找到对应的请求处理;
7、处理结束回馈报头,此处如果浏览器访问过,缓存上有对应资源,会与服务器最后修改时间对比,一致则返回304;
8、浏览器开始下载html文档(响应报头,状态码200),同时使用缓存;
9、文档树建立,根据标记请求所需指定MIME类型的文件(比如css、js),同时设置了cookie;
10、页面开始渲染DOM,JS根据DOM API操作DOM,执行事件绑定等,页面显示完成。
简洁版:
查找浏览器缓存
DNS解析、查找该域名对应的IP地址、重定向(301)、发出第二个GET请求
进行HTTP协议会话
客户端发送报头(请求报头)
服务器回馈报头(响应报头)
html文档开始下载
浏览器对加载到的资源(HTML、JS、CSS等)进行语法解析,建立相应的内部数据结构(如HTML的DOM)
载入解析到的资源文件,渲染页面,完成。
浏览器从URL 中解析出服务器的主机名,并将服务器的主机名转换成服务器的IP 地址(先查找浏览器是否缓存了url,若有则不需要再DNS解析)
浏览器将端口号(如果有的话)从URL 中解析出来;
浏览器建立一条与Web 服务器的TCP 连接;
浏览器向服务器发送一条HTTP 请求报文;
服务器向浏览器回送一条HTTP 响应报文(包括状态码如HTTP\/1.1 200 OK 、头和响应体),处理结束回馈报头,200的HTTP响应状态表示一个正确的响应,下载html文档;此处如果浏览器访问过,缓存上有对应资源,会与服务器最后修改时间对比,一致则返回304;
Web服务器向浏览器发送数据:Web服务器向浏览器发送头信息后,它会发送一个空白行来表示头信息的发送到此为结束,接着,它就以Content-Type应答头信息所描述的格式发送用户所请求的实际数据。
Web服务器关闭TCP连接:若connection 模式为close,则服务器主动关闭TCP 连接,客户端被动关闭连接,释放TCP 连接;若connection 模式为keepalive,则该连接会保持一段时间,在该时间内可以继续接收请求
TCP四次挥手:
1字头:消息。这一类型的状态码,代表请求已被接受,需要继续处理。
2字头:成功。这一类型的状态码,代表请求已成功被服务器接收、理解、并接受。
3字头:重定向。这类状态码代表需要客户端采取进一步的操作才能完成请求。
4字头:客户端错误。这类状态码代表了客户端看起来可能发生错误,妨碍了服务器的处理。
5字头:服务器错误。这类状态码代表了服务器在处理请求的过程中有错误或者异常状态发生
你能讲讲304缓存的原理吗
服务器首先产生ETag,服务器可在稍后使用它来判断页面是否已经被修改。本质上,客户端通过将该记号传回服务器要求服务器验证其(客户端)缓存。
客户端请求一个页面(A)。 服务器返回页面A,并在给A加上一个ETag。 客户端展现该页面,并将页面连同ETag一起缓存。 客户再次请求页面A,并将上次请求时服务器返回的ETag一起传递给服务器。 服务器检查该ETag,并判断出该页面自上次客户端请求之后还未被修改,直接返回响应304(未修改——Not Modified)和一个空的响应体。
当发送一个服务器请求时,浏览器首先会进行缓存过期判断。浏览器根据缓存过期时间判断缓存文件是否过期。
情景一:若没有过期,则不向服务器发送请求,直接使用缓存中的结果,此时我们在浏览器控制台中可以看到 200 OK(from cache) ,此时的情况就是完全使用缓存,浏览器和服务器没有任何交互的。
情景二:若已过期,则向服务器发送请求,此时请求中会带上①中设置的文件修改时间,和Etag
然后,进行资源更新判断。服务器根据浏览器传过来的文件修改时间,判断自浏览器上一次请求之后,文件是不是没有被修改过;根据Etag,判断文件内容自上一次请求之后,有没有发生变化
情形一:若两种判断的结论都是文件没有被修改过,则服务器就不给浏览器发index.html的内容了,直接告诉它,文件没有被修改过,你用你那边的缓存吧—— 304 Not Modified,此时浏览器就会从本地缓存中获取index.html的内容。此时的情况叫协议缓存,浏览器和服务器之间有一次请求交互。
情形二:若修改时间和文件内容判断有任意一个没有通过,则服务器会受理此次请求,之后的操作同①
① 只有get请求会被缓存,post请求不会
对于文件的请求,有时候http的请求效率会很慢,怎么办呢?
balabla 那就使用文件合并了,比如将多个小文件合并成大文件,或者将该嵌入的内容嵌入到html,css sprites等等,减少http请求数
Etag由服务器端生成,客户端通过If-Match或者说If-None-Match这个条件判断请求来验证资源是否修改。常见的是使用If-None-Match。请求一个文件的流程可能如下:
====第一次请求===
1.客户端发起 HTTP GET 请求一个文件;
2.服务器处理请求,返回文件内容和一堆Header,当然包括Etag(例如"2e681a-6-5d044840")(假设服务器支持Etag生成和已经开启了Etag).状态码200 ====第二次请求===
客户端发起 HTTP GET 请求一个文件,注意这个时候客户端同时发送一个If-None-Match头,这个头的内容就是第一次请求时服务器返回的Etag:2e681a-6-5d0448402.服务器判断发送过来的Etag和计算出来的Etag匹配,因此If-None-Match为False,不返回200,返回304,客户端继续使用本地缓存;流程很简单,问题是,如果服务器又设置了Cache-Control:max-age和Expires呢,怎么办 答案是同时使用,也就是说在完全匹配If-Modified-Since和If-None-Match即检查完修改时间和Etag之后,服务器才能返回304.(不要陷入到底使用谁的问题怪圈)
为什么使用Etag请求头?Etag 主要为了解决 Last-Modified 无法解决的一些问题。
HTTP协议通常承载于TCP协议之上,有时也承载于TLS或SSL协议层之上,这个时候,就成了我们常说的HTTPS。
默认HTTP的端口号为80,HTTPS的端口号为443。
为什么HTTPS安全
因为网络请求需要中间有很多的服务器路由器的转发。中间的节点都可能篡改信息,而如果使用HTTPS,密钥在你和终点站才有。https之所以比http安全,是因为他利用ssl\/tls协议传输。它包含证书,卸载,流量转发,负载均衡,页面适配,浏览器适配,refer传递等。保障了传输过程的安全性
一般情况下是指私钥用于对数据进行签名,公钥用于对签名进行验证; HTTP网站在浏览器端用公钥加密敏感数据,然后在服务器端再用私钥解密。
简单来说,浏览器会解析HTML
生成DOM Tree
,其次会根据CSS生成CSS Rule Tree,而javascript
又可以根据DOM API
操作DOM
,执行事件绑定等,页面显示完成。
文档树建立,根据标记请求所需指定MIME类型的文件(比如css、js),同时设置了cookie。
浏览器的内核分别是什么?
介绍一下你对浏览器内核的理解?
通常所谓的浏览器内核也就是浏览器所采用的渲染引擎,渲染引擎决定了浏览器如何显示网页的内容以及页面的格式信息。负责取得网页的内容(HTML、XML、图像等等)、整理讯息(例如加入CSS等),以及计算网页的显示方式,然后会输出至显示器或打印机。浏览器内核的不同对于网页的语法解释会有不同,所以渲染的效果也不相同。所有网页浏览器、电子邮件客户端以及其它需要编辑、显示网络内容的应用程序都需要内核。
解析HTML;构建DOM树并解析css样式;DOM树与CSS样式进行附着构造渲染树;布局;绘制
渲染引擎一开始会从网络层获取请求文档的内容,内容的大小一般限制在 8000 个块以内。
渲染引擎将开始解析 HTML 文档,并将各标记逐个转化成“内容树”上的 DOM 节点。同时也会解析外部 CSS 文件以及样式元素中的样式数据。HTML 中这些带有视觉指令的样式信息将用于创建另一个树结构:渲染树。渲染树包含多个带有视觉属性(如颜色和尺寸)的矩形。这些矩形的排列顺序就是它们将在屏幕上显示的顺序。
渲染树构建完毕之后,进入“布局”处理阶段,也就是为每个节点分配一个应出现在屏幕上的确切坐标。下一个阶段是绘制 —— 渲染引擎会遍历渲染树,由用户界面后端层将每个节点绘制出来。
需要着重指出的是,这是一个渐进的过程。为达到更好的用户体验,渲染引擎会力求尽快将内容显示在屏幕上。它不必等到整个 HTML 文档解析完毕之后,就会开始构建渲染树和设置布局。在不断接收和处理来自网络的其余内容的同时,渲染引擎会将部分内容解析并显示出来。
其实浏览器加载显示html的顺序是按下面的顺序进行的:
Firefox处理下载和渲染顺序大体相同,只是在细微之处有些差别,例如:iframe的渲染。
JS的加载
不能并行下载和解析(阻塞下载)
网络的模型是同步的。网页作者希望解析器遇到<script>
标记时立即解析并执行脚本。文档的解析将停止,直到脚本执行完毕。如果脚本是外部的,那么解析过程会停止,直到从网络同步抓取资源完成后再继续。此模型已经使用了多年,也在 HTML4 和 HTML5 规范中进行了指定。作者也可以将脚本标注为“defer”,这样它就不会停止文档解析,而是等到解析结束才执行。HTML5 增加了一个选项,可将脚本标记为异步,以便由其他线程解析和执行。
样式表
另一方面,样式表有着不同的模型。理论上来说,应用样式表不会更改 DOM 树,因此似乎没有必要等待样式表并停止文档解析。但这涉及到一个问题,就是脚本在文档解析阶段会请求样式信息。如果当时还没有加载和解析样式,脚本就会获得错误的回复,这样显然会产生很多问题。这看上去是一个非典型案例,但事实上非常普遍。Firefox 在样式表加载和解析的过程中,会禁止所有脚本。而对于 WebKit 而言,仅当脚本尝试访问的样式属性可能受尚未加载的样式表影响时,它才会禁止该脚本。
页面加载的时候,遇到外部css文件,浏览器另外发出一个请求,来获取css文件;遇到图片资源,浏览器也会另外发出一个请求,获取图片资源;这是异步请求。并不会影响html文档的加载,但是当文档加载过程中遇到外部js文件,html文档会挂起渲染的线程,不仅要等js文件加载完成,还要等待解析执行完毕,才可以恢复html文档的渲染。
原因:执行js可能会修改DOM;这意味着在js执行完之前,后续所有资源的加载都是没有必要的,这是js阻塞后续资源加载的根本原因。
解决办法:①可以将js文件放在之前。②js文件可以用window.onload事件来触发
1)外部样式会阻塞后续脚本执行,直到外部样式加载并解析完毕。
2)外部样式不会阻塞后续外部脚本的加载,但会阻塞外部脚本的执行。
3)如果后续外部脚本含有async属性(IE下为defer),则外部样式不会阻塞该脚本的加载与执行
4)对于js动态创建的link标签不会阻塞其后动态创建的script的加载与执行,不管script标签是否具有async属性,但对于其他非动态创建的script,以上三条结论仍适用
http:\/\/www.html5rocks.com\/zh\/tutorials\/internals\/howbrowserswork\/#The_main_flow
采用无阻塞下载 JavaScript 脚本的方法:
<script>
标签的 defer 属性(仅适用于 IE 和 Firefox 3.5 以上版本),还有async属性<script>
元素来下载并执行代码;.