1、JS 中的内存管理
1)不管什么程序语言,内存生命周期基本是一致的:
- 分配你所需要的内存
- 使用分配到的内存(读, 写)
- 不需要时将其释放/归还
在 C语言中, 有专门的内存管理接口, 像malloc()
和 free()
. 而在 JS 中, 没有专门的内存管理接口, 所有的内存管理都是"自动"的. JS 在创建变量时, 自动分配内存, 并在不使用的时候, 自动释放. 这种"自动"的内存回收, 造成了很多 JS 开发并不关心内存回收, 实际上, 这是错误的.尽管自动 GC 很方便, 但是我们不知道GC 什么时候会进行. 这意味着如果我们在使用过程中使用了大量的内存, 而 GC 没有运行的情况下, 或者 GC 无法回收这些内存的情况下, 程序就有可能假死, 这个就需要我们在程序中手动做一些操作来触发内存回收.
2)常见的内存泄露案例
- 全局变量
function foo(arg) { bar = "some text"; }
在 JS 中处理未被声明的变量, 上述范例中的 bar
时, 会把bar
, 定义到全局对象中, 在浏览器中就是 window
上. 在页面中的全局变量, 只有当页面被关闭后才会被销毁. 所以这种写法就会造成内存泄露, 当然在这个例子中泄露的只是一个简单的字符串, 但是在实际的代码中, 往往情况会更加糟糕.
function foo() { this.var1 = "potential accidental global"; } // foo 被调用时, this 指向全局变量(window) foo();
在这种情况下调用foo
, this被指向了全局变量window
, 意外的创建了全局变量。我们谈到了一些意外情况下定义的全局变量, 代码中也有一些我们明确定义的全局变量. 如果使用这些全局变量用来暂存大量的数据, 记得在使用后, 对其重新赋值为 null.
- 未销毁的定时器和回调函数
在很多库中, 如果使用了观察者模式, 都会提供回调方法, 来调用一些回调函数. 要记得回收这些回调函数. 举一个 setInterval的例子.
var serverData = loadData(); setInterval(function() { var renderer = document.getElementById('renderer'); if(renderer) { renderer.innerHTML = JSON.stringify(serverData); } }, 5000); // 每 5 秒调用一次
如果后续 renderer
元素被移除, 整个定时器实际上没有任何作用. 但如果你没有回收定时器, 整个定时器依然有效, 不但定时器无法被内存回收, 定时器函数中的依赖也无法回收. 在这个案例中的 serverData
也无法被回收.
- 闭包
在 JS 开发中, 我们会经常用到闭包, 一个内部函数, 有权访问包含其的外部函数中的变量.
- DOM 引用
很多时候, 我们对 Dom 的操作, 会把 Dom 的引用保存在一个数组或者 Map 中.
var elements = { image: document.getElementById('image') }; function doStuff() { elements.image.src = 'http://example.com/image_name.png'; } function removeImage() { document.body.removeChild(document.getElementById('image')); // 这个时候我们对于 #image 仍然有一个引用, Image 元素, 仍然无法被内存回收. }
上述案例中, 即使我们对于 image 元素进行了移除, 但是仍然有对 image 元素的引用, 依然无法对其进行内存回收.另外需要注意的一个点是, 对于一个 Dom 树的叶子节点的引用. 举个例子: 如果我们引用了一个表格中的td
元素, 一旦在 Dom 中删除了整个表格, 我们直观的觉得内存回收应该回收除了被引用的 td
外的其他元素. 但是事实上, 这个td
元素是整个表格的一个子元素, 并保留对于其父元素的引用. 这就会导致对于整个表格, 都无法进行内存回收. 所以我们要小心处理对于 Dom 元素的引用.
2、XSS攻击
XSS攻击全称跨站脚本攻击,是为不和层叠样式表(Cascading Style Sheets, CSS)的缩写混淆,故将跨站脚本攻击缩写为XSS,XSS是一种在web应用中的计算机安全漏洞,它允许恶意web用户将代码植入到提供给其它用户使用的页面中。XSS漏洞按照攻击利用手法的不同分为反射式漏洞和存储式漏洞(该类型是应用最为广泛而且有可能影响到Web服务器自身安全的漏洞)
1)express
Express是目前最流行的基于Node.js的Web开发框架,可以快速地搭建一个完整功能的网站。通过应用生成器工具 express-generator
可以快速创建一个应用的骨架,express-generator
包含了 express
命令行工具。
在当前目录创建一个web应用,采用
ejs 模板引擎(EJS 是一套简单的模板语言,帮你利用普通的 JavaScript 代码生成 HTML 页面。)
npm install express-generator -g express -e ./
cnpm i
npm start
在浏览器访问localhost:3000即可访问页面
2)反射型xss演示
发出请求时,XSS代码出现在url中,作为输入提交到服务器端,服务器端解析后响应,XSS代码随响应内容一起传回给浏览器,最后浏览器解析执行XSS代码。这个过程像一次反射,故叫反射型XSS。
// req.query:获取url的search参数 router.get('/', function(req, res, next) { res.render('index', { title: 'Express', xss: req.query.xss }); });
<h1><%= title %></h1> <p>Welcome to <%= title %></p> <p><%- xss %></p>
浏览器访问http://localhost:3000/?xss=<strong>123</strong>,页面渲染结果
补充ejs语法:<%=(直接输出数据到模板)、<%-(输出转义后的数据到模板)
eg:下方浏览器将xss攻击拦截,如果攻击成功,因为注入的图片地址不存在,页面会弹框1
router.get('/', function(req, res, next) { res.set('X-XSS-Protection', 0); //关闭xss攻击拦截 res.render('index', { title: 'Express', xss: req.query.xss }); });
再访问该链接,结果如下(自动触发xss攻击)
又如访问链接http://localhost:3000/?xss=<span onclick="alert('点我')">点我</span>(引诱触发xss攻击)
还可以通过iframe植入广告、进行csrf攻击,如访问链接http://localhost:3000/?xss=<iframe src="http://baidu.com"></iframe>,
3)存储型xss演示
存储型XSS和反射型的差别仅在于,提交的代码会存储在服务器端(数据库、内存、文件系统等),下次请求目标页面时不用再提交XSS代码。
4)XSS防御措施:编码、过滤、校正
- 编码:对用户输入的数据进行HTML Entity编码
- 过滤:移除用户上传的DOM属性如onerror、onclick等,移除用户上传的Style、Script、Iframe节点等(除非有特殊业务需求)
如果XSS攻击注入body{display:none !important}该样式,会出现空白页面
- 校正:避免直接对HTML Entity解码;使用DOM Parse转换,校正不配对的DOM标签
3、ie8兼容
1)在ie8下点击a标签有虚线框,设置样式outline:0
4、jquery源码
1)任何库与框架设计的第一个要点就是解决命名空间与变量污染的问题。jQuery就是利用了JavaScript函数作用域的特性,采用立即调用表达式包裹了自身的方法来解决这个问题。
(function(window, undefined) { var jQuery = function() {} // ... window.jQuery = window.$ = jQuery; })(window);
全局变量是魔鬼, 匿名函数可以有效的保证在页面上写入JavaScript,而不会造成全局变量的污染,通过小括号,让其加载的时候立即初始化,这样就形成了一个单例模式的效果从而只会执行一次。
2)原型prototype
// 类一: function ajQuery() { this.name = 'jQuery'; this.sayName = function(){ return this.name } } var a = new ajQuery() var b = new ajQuery() // 类二: function ajQuery() { this.name = 'jQuery' } ajQuery.prototype = { sayName: function() { return this.name } } var a = new ajQuery() var b = new ajQuery()
类一与类二产生的结构几乎是一样的,而本质区别就是:类二new产生的a、b两个实例对象共享了原型的sayName方法,这样的好处节省了内存空间,类一则是要为每一个实例复制sayName方法,每个方法属性都占用一定的内存的空间,所以如果把所有属性方法都声明在构造函数中,就会无形的增大很多开销,这些实例化的对象的属性一模一样,都是对this的引用来处理。除此之外类一的所有方法都是拷贝到当前实例对象上。类二则是要通过scope连接到原型链上查找,这样就无形之中要多一层作用域链的查找了。
jQuery对象的构建如果在性能上考虑,所以就必须采用原型式的结构
3)方法链式调用
jQuery的核心理念是Write less,Do more(写的更少,做的更多),那么链式方法的设计与这个核心理念不谋而合。这种设计其实就是一种Internal DSL。
4)插件接口
基于插件接口设计的好处也是颇多的,其中一个最重要的好处是把扩展的功能从主体框架中剥离出去,降低了框架的复杂度。在软件设计中插件接口的提供把独立的功能与框架以一种很宽松的方式松耦合。
jQuery插件的开发分为两种:一种是挂在jQuery命名空间下的全局函数,也可称为静态方法;另一种是jQuery对象级别的方法,即挂在jQuery原型下的方法,这样通过选择器获取的jQuery对象实例也能共享该方法。
// jQuery jQuery.extend({ data:function(){}, removeData:function(){} }) // jQuery原型 jQuery.fn.extend({ data:function(){}, removeData:function(){} })
通过extend()函数可以方便快速的扩展功能,不会破坏jQuery的原型结构。
5)each迭代器
jQuery的each方法从使用上就要分2种情况:$.each()函数、$(selector).each()
$.each()函数可用于迭代任何集合,无论是“名/值”对象(JavaScript对象)或数组。在迭代数组的情况下,回调函数每次传递一个数组索引和相应的数组值作为参数。jQuery的实例方法最终也是调用的静态方法。
jQuery.fn = jQuery.prototype = { each: function(callback, args) { return jQuery.each(this, callback, args); } }
6)观察者模式
观察者也被叫作订阅者(Subscriber),它指向被观察的对象,即被观察者(Publisher 或 subject)。当事件发生时,被观察者(Publisher)就会通知观察者(subscriber)。
观察者的使用场合就是:当一个对象的改变需要同时改变其它对象,并且它不知道具体有多少对象需要改变的时候,就应该考虑使用观察者模式。
7) 为什么排版引擎解析 CSS 选择器时一定要从右往左析
如果正向解析,例如「div div p em」,我们首先就要检查当前元素到 html 的整条路径,找到最上层的 div,再往下找,如果遇到不匹配就必须回到最上层那个 div。再往下找,如果遇到不匹配就必须回到最上层那个 div,往下再去匹配选择器中的第一个 div,回溯若干次才能确定匹配与否,效率很低。
8)Sizzle选择器
Sizzle,作为一个独立全新的选择器引擎,出现在jQuery 1.3版本之后,并被John Resig作为一个开源的项目,可以用于其他框架:Mool、Dojo、YUI等。
简单讲,通常在文档初次加载时,浏览器引擎会解析HTML文档来构建DOM树,之后根据DOM元素的几何属性构建一棵用于渲染的树。渲染树的每个节点都有大小和边距等属性,类似于盒子模型(由于隐藏元素不需要显示,渲染树中并不包含DOM树中隐藏的元素)。
'div > div.aaron input[name=ttt]'
是一个相对复杂的选择器,这样的结构在不支持高级API的浏览器中是无法直接通过获取的,那么所有选择器的库就必须要干的一件事,把复杂选择器按照一样的设计规则,分解成浏览器原始API能够识别的结构,然后通过其他的方法找个这个结构。
9)jQuery 遍历
jQuery 遍历,意为“移动”,用于根据其相对于其他元素的关系来“查找”(或选取)HTML 元素。以某项选择开始,并沿着这个选择移动,直到抵达您期望的元素为止。
遍历的接口我们可以归为几大类:祖先、 同胞兄弟、 后代、 过滤
遍历的接口很多都是具有相似或者说是一类的处理功能,那么这种接口的设计我们不能就事论事的一个一个去实现,这样代码就会显得非常累赘也不容易维护。丰富的接口可以让高层的设计更为简单,但是框架内部的却要简练。那么针对这种类似功能的接口,jQuery内部就会做更多的抽象处理了。
迭代器是一个框架的重要设计。我们经常需要提供一种方法顺序的用来处理聚合对象中各个元素,而又不暴露该对象的内部,这也是设计模式中的迭代器模式。迭代器除了单纯的遍历,在jQuery内部的运用最多的就是接口的抽象合并,相同功能的代码功能合并处理。
jQuery.each({ parent: function(elem) {}, parents: function(elem) {}, nextAll: function(elem) {}, prevAll: function(elem) {}, ................ }, function(name, fn) {// ... });
可以看出上面代码方法,针对相同的功能,节约了大量的代码空间。
10)节点操作
.domManip()是jQuery DOM操作的核心函数。
文档碎片DocumentFragment:参考标准的描述,DocumentFragment是一个轻量级的文档对象,能够提取部分文档的树或创建一个新的文档片段,换句话说有文档缓存的作用。
createDocumentFragment作用:多次使用节点方法(如:appendChild)绘制页面,每次都要刷新页面一次。效率也就大打折扣了,而使用document.createDocumentFragment()创建一个文档碎片,把所有的新结点附加在其上,然后把文档碎片的内容一次性添加到document中,这也就只需要一次页面刷新就可以了。在所有节点类型中,只有DocumentFragment在文档中没有对应的标记。DOM规定文档片段(documentfragment)是一种“轻量级”的文档,可以包含和控制节点,但不会像完整的文档那样占用额外资源。虽然不能把文档片段直接添加到文档中,但可以将它作为一个“仓库”来使用,即可以在里面保存将来可能会添加到文档中的节点。
5、CSRF
参考:https://www.imooc.com/article/38820
CSRF(Cross-site request forgery)跨站请求伪造,通常缩写为CSRF或者XSRF,是一种对网站的恶意利用。尽管听起来像跨站脚本(XSS),但它与XSS非常不同,XSS利用站点内的信任用户,而CSRF则通过伪装来自受信任用户的请求来利用受信任的网站。与XSS攻击相比,CSRF攻击往往不大流行(因此对其进行防范的资源也相当稀少)和难以防范,所以被认为比XSS更具危险性。
CSRF的防御
1.在表单里增加Hash值,以认证这确实是用户发送的请求,然后在服务器端进行Hash值验证。
2.验证码:每次的用户提交都需要用户在表单中填写一个图片上的随机字符串。
3.修改,增加重要信息,比如密码,个人信息的操作,尽量使用post。避免使用get把信息暴露在url上面。