CSS和JS阻塞
哪些地方会出现js阻塞?
- js 的阻塞特性:所有浏览器在下载 JS 的时候,会阻止一切其他活动,比如其他资源的下载,内容的呈现等等。直到 JS 下载、解析、执行完毕后才开始继续并行下载其他资源并呈现内容。为了提高用户体验,新一代浏览器都支持并行下载 JS,但是 JS 下载仍然会阻塞其它资源的下载(例如.图片,css文件等)。
- 由于浏览器为了防止出现 JS 修改 DOM 树,需要重新构建 DOM 树的情况,所以就会阻塞其他的下载和呈现。
- 嵌入 JS 会阻塞所有内容的呈现,而外部 JS 只会阻塞其后内容的显示,2 种方式都会阻塞其后资源的下载。也就是说外部样式不会阻塞外部脚本的加载,但会阻塞外部脚本的执行。
CSS 怎么会阻塞加载?
- CSS 本来是可以并行下载的,在什么情况下会出现阻塞加载了(在测试观察中,IE6 下 CSS 都是阻塞加载)
- 当 CSS 后面跟着嵌入的 JS 的时候,该 CSS 就会出现阻塞后面资源下载的情况。而当把嵌入 JS 放到 CSS 前面,就不会出现阻塞的情况了。
- 根本原因:因为浏览器会维持 html 中 css 和 js 的顺序,样式表必须在嵌入的 JS 执行前先加载、解析完。而嵌入的 JS 会阻塞后面的资源加载,所以就会出现上面 CSS 阻塞下载的情况。
嵌入JS应该放在什么位置?
- 放在底部,虽然放在底部照样会阻塞所有呈现,但不会阻塞资源下载。
- 如果嵌入JS放在head中,请把嵌入JS放在CSS头部。
- 使用 defer(只支持IE)
- 不要在嵌入的JS中调用运行时间较长的函数,如果一定要用,可以用 setTimeout 来调用
Javascript无阻塞加载具体方式
-
将脚本放在底部。还是放在head中,用以保证在js加载前,能加载出正常显示的页面。<script>标签放在前。
-
阻塞脚本:由于每个<script>标签下载时阻塞页面解析过程,所以限制页面的<script>总数也可以改善性能。适用于内联脚本和外部脚本。
-
非阻塞脚本:等页面完成加载后,再加载js代码。也就是,在 window.onload 事件发出后开始下载代码。
-
defer属性:支持IE4和fierfox3.5更高版本浏览器
-
动态脚本元素:文档对象模型(DOM)允许你使用js动态创建HTML的几乎全部文档内容。代码如下:
<script> var script=document.createElement("script"); script.type="text/javascript"; script.src="file.js"; document.getElementsByTagName("head")[0].appendChild(script); </script>
此技术的重点在于:无论在何处启动下载,文件额下载和运行都不会阻塞其他页面处理过程,即使在head里(除了用于下载文件的 http 链接)。
Javascript垃圾回收方法
标记清除(mark and sweep)
- 这是JavaScript最常见的垃圾回收方式,当变量进入执行环境的时候,比如函数中声明一个变量,垃圾回收器将其标记为“进入环境”,当变量离开环境的时候(函数执行结束)将其标记为“离开环境”。
- 垃圾回收器会在运行的时候给存储在内存中的所有变量加上标记,然后去掉环境中的变量以及被环境中变量所引用的变量(闭包),在这些完成之后仍存在标记的就是要删除的变量了
引用计数(reference counting)
- 在低版本IE中经常会出现内存泄露,很多时候就是因为其采用引用计数方式进行垃圾回收。引用计数的策略是跟踪记录每个值被使用的次数,当声明了一个 变量并将一个引用类型赋值给该变量的时候这个值的引用次数就加1,如果该变量的值变成了另外一个,则这个值得引用次数减1,当这个值的引用次数变为0的时 候,说明没有变量在使用,这个值没法被访问了,因此可以将其占用的空间回收,这样垃圾回收器会在运行的时候清理掉引用次数为0的值占用的空间。
- 在IE中虽然JavaScript对象通过标记清除的方式进行垃圾回收,但BOM与DOM对象却是通过引用计数回收垃圾的,也就是说只要涉及BOM及DOM就会出现循环引用问题。
常见web安全及防护原理
sql注入原理
就是通过把SQL命令插入到Web表单递交或输入域名或页面请求的查询字符串,最终达到欺骗服务器执行恶意的SQL命令。
总的来说有以下几点:
- 永远不要信任用户的输入,要对用户的输入进行校验,可以通过正则表达式,或限制长度,对单引号和双"-"进行转换等。
- 永远不要使用动态拼装SQL,可以使用参数化的SQL或者直接使用存储过程进行数据查询存取。
- 永远不要使用管理员权限的数据库连接,为每个应用使用单独的权限有限的数据库连接。
- 不要把机密信息明文存放,请加密或者hash掉密码和敏感的信息。
XSS原理及防范
Xss(cross-site scripting)攻击指的是攻击者往Web页面里插入恶意 html标签或者javascript代码。
比如:攻击者在论坛中放一个,看似安全的链接,骗取用户点击后,窃取cookie中的用户私密信息;或者攻击者在论坛中加一个恶意表单,当用户提交表单的时候,却把信息传送到攻击者的服务器中,而不是用户原本以为的信任站点。
XSS防范方法
- 首先代码里对用户输入的地方和变量都需要仔细检查长度和对”<”,”>”,”;”,”’”等字符做过滤;其次任何内容写到页面之前都必须加以encode,避免不小心把html tag 弄出来。这一个层面做好,至少可以堵住超过一半的XSS 攻击。
- 避免直接在cookie 中泄露用户隐私,例如email、密码等等。
- 通过使cookie 和系统ip 绑定来降低cookie 泄露后的危险。这样攻击者得到的cookie 没有实际价值,不可能拿来重放。
- 如果网站不需要再浏览器端对cookie 进行操作,可以在Set-Cookie末尾加上HttpOnly 来防止javascript 代码直接获取cookie 。
- 尽量采用POST 而非GET 提交表单
CSRF的防御
- 服务端的CSRF方式方法很多样,但总的思想都是一致的,就是在客户端页面增加伪随机数。
- 通过验证码的方法
XSS与CSRF有什么区别吗
- XSS是获取信息,不需要提前知道其他用户页面的代码和数据包。
- CSRF是代替用户完成指定的动作,需要知道其他用户页面的代码和数据包。
要完成一次CSRF攻击,受害者必须依次完成两个步骤:
- 登录受信任网站A,并在本地生成Cookie。
- 在不登出A的情况下,访问危险网站B。
Etag
什么是Etag
当发送一个服务器请求时,浏览器首先会进行缓存过期判断。浏览器根据缓存过期时间判断缓存文件是否过期。
- 情景一:若没有过期,则不向服务器发送请求,直接使用缓存中的结果,此时我们在浏览器控制台中可以看到 200 OK(from cache) ,此时的情况就是完全使用缓存,浏览器和服务器没有任何交互的。
- 情景二:若已过期,则向服务器发送请求,此时请求中会带上①中设置的文件修改时间,和Etag
然后,进行资源更新判断。服务器根据浏览器传过来的文件修改时间,判断自浏览器上一次请求之后,文件是不是没有被修改过;根据Etag,判断文件内容自上一次请求之后,有没有发生变化
- 情形一:若两种判断的结论都是文件没有被修改过,则服务器就不给浏览器发index.html的内容了,直接告诉它,文件没有被修改过,你用你那边的缓存吧—— 304 Not Modified,此时浏览器就会从本地缓存中获取index.html的内容。此时的情况叫协议缓存,浏览器和服务器之间有一次请求交互。
- 情形二:若修改时间和文件内容判断有任意一个没有通过,则服务器会受理此次请求,之后的操作同①
① 只有get请求会被缓存,post请求不会
ETag应用
Etag由服务器端生成,客户端通过If-Match或者说If-None-Match这个条件判断请求来验证资源是否修改。常见的是使用If-None-Match。请求一个文件的流程可能如下:
- 第一次请求
- 客户端发起 HTTP GET 请求一个文件;
- 服务器处理请求,返回文件内容和一堆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
- 网页的缓存是由HTTP消息头中的“Cache-control”来控制的,常见的取值有private、no-cache、max-age、must-revalidate等,默认为private。
- Expires 头部字段提供一个日期和时间,响应在该日期和时间后被认为失效。允许客户端在这个时间之前不去检查(发请求),等同max-age的效果。但是如果同时存在,则被Cache-Control的max-age覆盖。
- Expires = “Expires” “:” HTTP-date
例如:
Expires: Thu, 01 Dec 1994 16:00:00 GMT (必须是GMT格式)
如果把它设置为-1,则表示立即过期
Expires 和 max-age 都可以用来指定文档的过期时间,但是二者有一些细微差别 - Expires在HTTP/1.0中已经定义,Cache-Control:max-age在HTTP/1.1中才有定义,为了向下兼容,仅使用max-age不够
- Expires指定一个绝对的过期时间(GMT格式),这么做会导致至少2个问题:
2.1客户端和服务器时间不同步导致Expires的配置出现问题。
2.2很容易在配置后忘记具体的过期时间,导致过期来临出现浪涌现象 - max-age 指定的是从文档被访问后的存活时间,这个时间是个相对值(比如:3600s),相对的是文档第一次被请求时服务器记录的Request_time(请求时间)
- Expires 指定的时间可以是相对文件的最后访问时间(Atime)或者修改时间(MTime),而max-age相对对的是文档的请求时间(Atime)
- 如果值为 no-cache,那么每次都会访问服务器。如果值为max-age,则在过期之前不会重复访问服务器。
304缓存的原理
服务器首先产生ETag,服务器可在稍后使用它来判断页面是否已经被修改。本质上,客户端通过将该记号传回服务器要求服务器验证其(客户端)缓存。
304是HTTP状态码,服务器用来标识这个文件没修改,不返回内容,浏览器在接收到个状态码后,会使用浏览器已缓存的文件
客户端请求一个页面(A)。 服务器返回页面A,并在给A加上一个ETag。 客户端展现该页面,并将页面连同ETag一起缓存。 客户再次请求页面A,并将上次请求时服务器返回的ETag一起传递给服务器。 服务器检查该ETag,并判断出该页面自上次客户端请求之后还未被修改,直接返回响应304(未修改——Not Modified)和一个空的响应体。
哪些操作会造成内存泄漏
- 内存泄漏指任何对象在您不再拥有或需要它之后仍然存在。
- 垃圾回收器定期扫描对象,并计算引用了每个对象的其他对象的数量。如果一个对象的引用数量为 0(没有其他对象引用过该对象),或对该对象的惟一引用是循环的,那么该对象的内存即可回收。
- setTimeout 的第一个参数使用字符串而非函数的话,会引发内存泄漏。
- 闭包、控制台日志、循环(在两个对象彼此引用且彼此保留时,就会产生一个循环)
快速排序的思想并实现一个快排
"快速排序"的思想很简单,整个排序过程只需要三步:
-
在数据集之中,找一个基准点
-
建立两个数组,分别存储左边和右边的数组
-
利用递归进行下次比较
<script type="text/javascript"> function quickSort(arr){ if(arr.length<=1){ return arr;//如果数组只有一个数,就直接返回; } var num = Math.floor(arr.length/2);//找到中间数的索引值,如果是浮点数,则向下取整 var numValue = arr.splice(num,1);//找到中间数的值 var left = []; var right = []; for(var i=0;i<arr.length;i++){ if(arr[i]<numValue){ left.push(arr[i]);//基准点的左边的数传到左边数组 } else{ right.push(arr[i]);//基准点的右边的数传到右边数组 } } return quickSort(left).concat([numValue],quickSort(right));//递归不断重复比较 } alert(quickSort([32,45,37,16,2,87]));//弹出“2,16,32,37,45,87” </script>
异步加载和延迟加载
- 异步加载的方案: 动态插入 script 标签
- 通过 ajax 去获取 js 代码,然后通过 eval 执行
- script 标签上添加 defer 或者 async 属性
- 创建并插入 iframe,让它异步执行 js
- 延迟加载:有些 js 代码并不是页面初始化的时候就立刻需要的,而稍后的某些情况才需要的
ie 各版本和 chrome 可以并行下载多少个资源
- IE6 2 个并发
- iE7 升级之后的 6 个并发,之后版本也是 6 个
- Firefox,chrome 也是6个
webSocket如何兼容低浏览器
- Adobe Flash Socket
- ActiveX HTMLFile (IE)
- 基于 multipart 编码发送 XHR
- 基于长轮询的 XHR
WEB应用从服务器主动推送Data到客户端有那些方式
- html5提供的Websocket
- 不可见的iframe
- WebSocket通过Flash
- XHR长时间连接
- XHR Multipart Streaming
- <script>标签的长时间连接(可跨域)
检测浏览器版本版本有哪些方式
功能检测、userAgent特征检测
比如:navigator.userAgent
“Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_2) AppleWebKit/537.36
(KHTML, like Gecko) Chrome/41.0.2272.101 Safari/537.36”
对MVC和MVVM的理解
MVC
View 传送指令到 Controller
Controller 完成业务逻辑后,要求 Model 改变状态
Model 将新的数据发送到 View,用户得到反馈
所有通信都是单向的。
MVVC
Angular它采用双向绑定(data-binding):View的变动,自动反映在 ViewModel,反之亦然。
组成部分Model、View、ViewModel
View:UI界面
ViewModel:它是View的抽象,负责View与Model之间信息转换,将View的Command传送到Model;
Model:数据访问层
为什么利用多个域名来存储网站资源会更有效
- CDN缓存更方便
- 突破浏览器并发限制
- 节约cookie带宽
- 节约主域名的连接数,优化页面响应速度
- 防止不必要的安全问题
有哪些地方会有缓存处理
在css/js代码上线之后开发人员经常会优化性能,从用户刷新网页开始,一次js请求一般情况下有哪些地方会有缓存处理?
答案:dns缓存,cdn缓存,浏览器缓存,服务器缓存。
同源策略
同源策略,所谓同源,是指只有在域名、协议名、端口号都相同。不同源的客户端脚本在没有明确授权的情况下,是不能够读写对方的资源的。
只有满足同源的脚本才可以获取资源,虽然这样确实保证了网络上的安全性,但是另一方面又限制了资源之间的互相利用,比如我们的AJAX,
AJAX也是通过url来获取数据,同样也会受到同源策略的限制。
为什么要有同源限制
比如一个黑客程序,他利用Iframe把真正的银行登录页面嵌到他的页面上,当你使用真实的用户名,密码登录时,他的页面就可以通过Javascript读取到你的表单中input中的内容,这样用户名,密码就轻松到手了。
大概定义为,一段脚本只能读取来自于同一来源的窗口和文档的属性,这里的同一来源指的是主机名、协议和端口号的组合
网页验证码的作用(为了解决什么安全问题)
区分用户是计算机还是人的公共全自动程序。可以防止恶意破解密码、刷票、论坛灌水;
有效防止黑客对某一个特定注册用户用特定程序暴力破解方式进行不断的登陆尝试。
什么是FOUC(无样式内容闪烁)?你如何来避免 FOUC?
FOUC - Flash Of Unstyled Content 文档样式闪烁
<style type=“text/css” media=“all”>@import “…/fouc.css”;</style>而引用CSS文件的@import就是造成这个问题的罪魁祸首。IE会先加载整个HTML文档的DOM,然后再去导入外部的CSS文件,因此,在页面DOM加载完成到CSS导入完成中间会有一段时间页面上的内容是没有样式的,这段时间的长短跟网速,电脑速度都有关系。
解决方法:只要在<head>之间加入一个<link>或者<script>元素就可以了。
document.write和 innerHTML的区别
- document.write是直接写入到页面的内容流,如果在写之前没有调用document.open,浏览器会自动调用open。每次写完关闭之后重新调用该函数,会导致页面被重写。
- innerHTML则是DOM页面元素的一个属性,代表该元素的html内容。你可以精确到某一个具体的元素来进行更改。如果想修改document的内容,则需要修改document.documentElement.innerElement。
- innerHTML很多情况下都优于document.write,其原因在于其允许更精确的控制要刷新页面的那一个部分。
- document.write是直接将内容写入页面的内容刘,会导致页面全部重绘,innerHTML将内容写入某个DOM节点,不会导致页面全部重绘
原生JS的window.onload与Jquery的$(document).ready(function(){})有什么不同?如何用原生JS实现Jq的ready方法?
-
window.onload()方法是必须等到页面内包括图片的所有元素加载完毕后才能执行。
-
$(document).ready()是DOM结构绘制完毕后就执行,不必等到加载完毕。
function ready(fn){ if(document.addEventListener) { //标准浏览器 document.addEventListener('DOMContentLoaded', function() { //注销事件, 避免反复触发 document.removeEventListener('DOMContentLoaded',arguments.callee, false); fn(); //执行函数 }, false); }else if(document.attachEvent) { //IE document.attachEvent('onreadystatechange', function() { if(document.readyState == 'complete') { document.detachEvent('onreadystatechange', arguments.callee); fn(); //函数执行 } }); } };
prototype 和 proto function,object和 object function
主要三个问题
- prototype 和 proto
- function 和 object
- new 到底发生了什么
prototype 和 proto
在 JS 中,常常让我们感到困惑的地方,就是 prototype 和 proto 到底是干嘛的prototype 和 proto function,object和 object function
-
proto 就是 Javascript中 所谓的原型 (这里,我们还是拿具体的例子来说明吧)
function A (name) { // 这里是一个构造函数 thia.name = name } var Aobj = { // 这里是一个 对对象字面量 name: '' } console.dir(A) console.dir(Aobj)
这里我们可以很明显的看到 ,构造函数的**_proto_属性指向了function()**
对象字面量的**_proto_属性指向了Object**
为什么指向的是不一样的呢?
确实是不一样的, 因为构造函数本身也是一个函数, 所以它的原型指向 function()
而对象字面量是一个对象, 那么他的原型肯定是指向Object
扩展思考,如果是一个数组对象,那么它的**_proto_**会指向什么?
const arr = [112,22,3333]
console.dir(arr)
这里的**_proto_**就指向了 Array[0]
总结:一个对象的**_proto_属性和自己的内部属性[[Prototype]]**指向一个相同的值 (通常称这个值为原型)
注意:firefox、chrome等浏览器把对象内部属性**[[Prototype]]用_proto_的形式暴露了出来.(老版本的IE并不支持 _proto_ ,IE11中已经加上了_proto_**属性)
- prototype : 看看上面的截图,你会发现只有构造函数中有这个玩意儿, 对的。 prototype确实是在function 中特有的。别的对象类型中都不会有的属性。
我们在看这个function对象属性的时候就会发现这么一个prototype的属性,它的值是一个Object。点开这个 obj 我们就会发现,这个 obj 的 constructor 属性 指向了这个构造函数本身。
为什么在 javascript 中,函数对象的 prototype 属性的 constructor 指向是 函数本身?
function 和 object
再看一个例子
function B(name) {
this.name = name
this.getName = function() {
console.log(this.name)
}
var c = 'test'
console.log(c)
}
var b = new B('testb') // test
console.log(b) // B: { name: 'testb',getName: function() {} }
B('testc') // test
看到上面的 输出 是不是觉得又很诧异了。
确实, 为什么 在 new 的时候, 构造函数居然执行了一次。
同样, 在非严格模式下, 我们直接执行构造函数, B(‘testc’) 相当于:
// window.name = ‘testc’
// window.getName = function() { console.log(this.name) }
思考:
我们的函数B既可以直接执行,又可以new一下返回一个对象。
当我们执行 var b = new B(‘testb’) 的时候发生了什么?
function和object到底是什么关系,new的时候发生了什么?**
MDN 上的介绍是这样的说的:
对于 var b = new B(‘testb’); // javascript 实际上执行的是:
var o = new Object(); // 生成一个新的对象 b 这里可以约等于 var b = {}
o._proto_ = B.prototype;
// 这里就是函数对象中独有的 prototype 属性。这个独有的 prototype 属性 包含了一个 constructor 属性方法,指向的就是构造函数, 也就是 这里的function B(name) {}
B.call(o);
// 注意:由于 call 的使用将这里this是指向o , 所以就可以把this.name/getName 强行的绑定到o上。同时,需要注意的一点就是, 这里的构造函数执行了一遍, 只不过是将 this 指向的属性和方法,都强行的给新创建的这个 o 对象绑定了一遍。
var b = o;
// 把这个 o 返回给了 b 。 从而完成了 var b = new B(‘testb’) 的过程
我们来看看 call 是干嘛用的
关于 call 的使用说明
var o1 = {
name: '111',
getName: function() {
console.log(this.name)
}
}
var o2 = {
name: '222'
}
o1.getName.call(o2) // 222
所以,这个时候我们反过头来看看这个 new 的对象都有哪些属性和方法。
我们可以来做一个小实验,来证明下,我们以上所说的东西。
function A (name) { // 这里是一个构造函数
this.name = name
}
var o = {}
o.__proto__ = A.prototype
A.call(o)
var a = o
var b = new A()
console.log(a)
console.log(b)
果然和我们想象 的一模一样。
至于 js 为什么要把新建对象的 原型 指向构造函数的 prototype 属性。
因为 通过 new 方法来创建的 obj 。肯定是需要一个标记来找到自己的 构造器函数,所以,为了让整个 程序结构看上去合理。我们需要把新建 对象的原型 指向构造函数的 prototype 属性。
总结:在 javascript 中 prototype 和 proto 到底有什么区别。
prototype 是 面向 构造函数,来思考, proto 是 面向 实例化 后 的对象 来思考就对了。
最后再给一个例子,我们经常会在开发中用到的例子。
var Person = function(){}
Person.prototype.sayName = function() {
alert('my name is xxx')
}
Person.prototype.age = 12
var p = new Person()
p.sayName()
当我们 实例化 之后, 在我们 去执行 p.sayName() 的 时候,我们就会去 this 内部去 查找,这里就是 构造函数 Person 内部去找。
可是 没找到啊。只是一个 空函数, 怎么办呢?这个时候 就会沿着 原型链向上追溯, 但是如何 追溯呢?
这里就要用到 _proto_ 属性 来作为追溯的桥梁。
因为,实例化对象的 _proto_ 属性 指向的就是 构造函数的 prototype 属性所对应的对象