JavaScript性能陷阱小结_javascript技巧_脚本之家

简言之,闭包是产生一个没有被释放资源的栈区。换言之,就是一个不可控的内存空间占用,如果与事件相关联,JS的垃圾回收机制也不会去触碰该区域。
例如:我们有个项目需要实现在一个div中有上百个热点区域,类似淘宝店铺广告位自定义,那么按照传统的做法,我们会如下做一个最典型的闭包使用的实例,目的是改变this的作用域,在其处理函数内部调用其他属于该作用域的方法或属性。
复制代码 代码如下: var apply = function()
{ this.div = document.getElementById; this.hot =
this.div.getElementsByTagName; for(var i=0; i复制代码 代码如下: var
apply = function() { this.div = document.getElementById;
this.div.onclick = function { return function() { var _event =
arguments.callee.caller.arguments[0]; var target = _event.target ||
_event.srcElement; if(target.tagName == “a”) me.edit; else return
false; } }; } apply.prototype = { edit: function { } }
现在,我们只关心容器元素是何物,而不用关心他的内部有多少个a,他们是否发生改变等。性能的差别是显然的。
10来分钟随便写写,有点混乱,希望对一些朋友有用,如有差错之处,还望各位指点。
auntion / 2011-11-15 mail Auntion@gmail.com QQ 82874972
原创文章,转载请留下此部分信息

1.避免使用eval或者Function构造函数 2.避免使用with
3.不要在性能要求关键的函数中使用try-catch-finally 4.避免使用全局变量
5.避免在性能要求关键的函数中使用for-in 6.使用字符串累加计算风格
7.原操作会比函数调用快 8.设置setTimeout 时传递函数名而不是字符串
9.避免在对象中使用不需要的DOM引用 10.最清晰的目标速度,最小化作用域链
11.试着在脚本中少使用注释,避免使用长变量名
12.在当前作用域存储应用的外部变量 13.使用变量缓存值
1.避免使用eval或者Function构造函数
使用eval或者Function构造函数的代价是非常昂贵的,每次都需要脚本引擎转换源代码到可执行代码。
此外,使用eval处理字符串必须在运行时解释。 运行缓慢的代码: 复制代码 代码如下: function addMethod(object,
property, code) { object[property] = new Function; } addMethod(myObj,
‘methodName’, ‘this.localVar=foo’); 运行更快的代码: 复制代码 代码如下: function addMethod(object,
property, func) { object[property] = func; } addMethod(myObj,
‘methodName’, function () { ‘this.localVar=foo’; }); 2.避免使用with
尽管很方便,with需要附加的查找引用时间,因为它在编译的时候并不知道作用域的上下没。
运行缓慢的代码: 复制代码 代码如下: with
{ foo = ‘Value of foo property of object’; bar = ‘Value of bar property
of object’; } 运行更快的代码: 复制代码
代码如下: var myObj = test.object; myObj.foo = ‘Value of foo property of
object’; myObj.bar = ‘Value of bar property of object’;
3.不要在性能要求关键的函数中使用try-catch-finally
try-catch-finally在运行时每次都会在当前作用域创建一个新的变量,用于分配语句执行的异常。
异常处理应该在脚本的高层完成,在异常不是很频繁发生的地方,比如一个循环体的外面。
如果可能,尽量完全避免使用try-catch-finally。 运行缓慢的代码: 复制代码 代码如下: var object = [‘foo’,
‘bar’], i; for (i = 0; i < object.length; i++) { try { // do
something that throws an exception } catch { // handle exception } }
运行更快的代码: 复制代码 代码如下: var
object = [‘foo’, ‘bar’], i; try { for (i = 0; i < object.length;
i++) { // do something } } catch { // handle exception }
4.避免使用全局变量
如果你在一个函数或者其它作用域中使用全局变量,脚本引擎需要遍历整个作用域去查找他们。
全局作用域中的变量在脚本的生命周期里都存在,然后局部范围的会在局部范围失去的时候被销毁。
运行缓慢的代码: 复制代码 代码如下: var
i, str = ”; function globalScope() { for { str += i; // here we
reference i and str in global scope which is slow } } globalScope();
运行更快的代码: 复制代码 代码如下:
function localScope() { var i, str = ”; for { str += i; // i and str in
local scope which is faster } } localScope();
5.避免在性能要求关键的函数中使用for-in
for-in循环需要脚本引擎建立一张所有可枚举属性的列表,并检查是否与先前的重复。
如果你的for循环作用域中的代码没有修改数组,可以预先计算好数组的长度用于在for循环中迭代数组。
运行缓慢的代码: 复制代码 代码如下: var
sum = 0; for { sum += arr[i]; } 运行更快的代码: 复制代码 代码如下: var sum = 0; for (var i = 0,
len = arr.length; i < len; i++) { sum += arr[i]; }
6.使用字符串累加计算风格
使用+运算会在内存中创建一个新的字符串并把连接的值赋给它。仅仅是将这个结果赋值给一个变量。
为了避免连接结果的中间变量,可以使用+=来直接赋值结果。 运行缓慢的代码:
复制代码 代码如下: a += ‘x’ + ‘y’;
运行更快的代码: 复制代码 代码如下: a +=
‘x’; a += ‘y’; 7.原操作会比函数调用快
可以考虑在性能要求关键的循环和函数中使用可以替代的原操作。
运行缓慢的代码: 复制代码 代码如下: var
min = Math.min; 运行更快的代码: 复制代码
代码如下: var min = a < b ? a : b; arr[arr.length] = val;
8.设置setTimeout 时传递函数名而不是字符串
如果你传递一个字符串到setTimeout中,字符串将会被eval计算而导致缓慢。
使用一个匿名函数包装来代替,这样在编译的时候就可以被解释和优化。
运行缓慢的代码: setInterval(‘doSomethingPeriodically;
setTimeOut(‘doSomethingAfterFiveSeconds; 运行更快的代码: 复制代码 代码如下:
setInterval(doSomethingPeriodically, 1000);
setTimeOut(doSomethingAfterFiveSeconds, 5000);
9.避免在对象中使用不需要的DOM引用 不要这么做: 复制代码 代码如下: var car = new Object();
car.color = “red”; car.type = “sedan” 更好的一种形式: 复制代码 代码如下: var car = { color : “red”;
type : “sedan” } 10.最清晰的目标速度,最小化作用域链 低效率方法:
复制代码 代码如下: var url =
location.href; 一种高效形式: 复制代码
代码如下: var url = window.location.href;
11.试着在脚本中少使用注释,避免使用长变量名
尽可能的保证注释少或者避免使用注释,特别是在函数,循环以及数组中。
注释不必要的减缓脚本执行并且增加了文件大小。比如: 不建议的形式: 复制代码 代码如下: function someFunction() {
var person_full_name=”somename”; /* stores the full name*/ }
更好的写法: 复制代码 代码如下: function
someFunction() { var name=”somename”; }
12.在当前作用域存储应用的外部变量
当一个函数被执行的运行上下问被穿件,一个活动的对象会包含所有局部变量会被推到上下文链的前面。
在作用域链中,最慢的是清楚的识别标识符,意味着局部变量是最快的。存储频繁使用的外部变量读和写都会明显的加快。这对于全局变量和其他深层次的标识符查找特别明显。
同样,在当前作用域中的变量比对象像属性的访问速度快。 运行缓慢的代码:
复制代码 代码如下: function doSomething {
var divs = document.getElementsByTagName, text = [‘foo’, /* … n …
*/, ‘bar’]; for (var i = 0, l = divs.length; i < l; i++) {
divs[i].innerHTML = text[i]; } } 运行更快的代码: 复制代码 代码如下: function doSomethingFaster {
var doc = document, divs = doc.getElementsByTagName, text = [‘foo’, /*
… n … */, ‘bar’]; for (var i = 0, l = divs.length; i < l; i++)
{ divs[i].innerHTML = text[i]; } }
如果你需要访问一个元素在一个大的循环中,使用一个本地的DOM访问会更快。
运行更快的代码: 复制代码 代码如下:
function doSomethingElseFaster() { var get =
document.getElementsByTagName; for (var i = 0, i < 100000; i++) {
get; } } 13.使用变量缓存值 在做重复工作的地方使用局部变量缓存值。
下面的一组例子表明了存储值到局部变量的广泛意义。
例子1.计算执行前在循环体内使用变量存储数学函数 错误的方法: 复制代码 代码如下: var d=35; for { y +=
Math.sin*10; } 更好的处理: 复制代码
代码如下: var d = 55; var math_sind = Math.sin*10; for { y +=
math_sind; } 例子2.保存数组的长度在循环中使用 糟糕的处理:
数组的长度每次都会被重复计算 复制代码
代码如下: for (var i = 0; i < arr.length; i++) { // do something }
更好的改进: 更好的方法是保存数组的长度 复制代码 代码如下: for (var i = 0, len =
arr.length; i < len; i++) { // do something }
总的来说,如果已经做了一次,我们就不需要重复的做不必要的工作。例如,作用域或者函数中多次使用到计算的一个表达式的值,保存到变量可以使它多次被使用,否则我们会过头的声明一个变量并赋值然后只适用一次。所以请记住这些。
补充说明:第2点 顺便说一下JS主要不是编译的是解释的.
虽说不影响表达,但学术还是严谨点好. 第6点这是不是格式搞乱了? a += ‘x’ +
‘y’; 运行更快的代码: a += ‘x’; a += ‘y’;
9.避免在对象中使用不需要的DOM引用 new Object也是DOM引用?
这个应该是说不要轻意使用new Object, 以及new Function() 一般情况使用
{…}, […], f = function{…} 即: 这和上面那一点应该说的是一回事.
最后补充一个少用 位运算,
因为js的所有数值运算最后都是全部转得浮点来算的..所以位运算可能反而更慢.

JavaScript事件代理一般用于以下情况:

对于初学者来说,理解Javascript闭包还是比较困难的,而撰写此文的目的就是用最通俗的文字揭开Javascript闭包的真实面目,让初学者理解起来更加容易一些。
一、什么是闭包?
“官方”的解释是:闭包是一个拥有许多变量和绑定了这些变量的环境的表达式,因而这些变量也是该表达式的一部分。相信很少有人能直接看懂这句话,因为他描述的太学术。
其实这句话通俗的来说就是:JavaScript中所有的function都是一个闭包。不过一般来说,嵌套的function所产生的闭包更为强大,也是大部分时候我们所谓的“闭包”。看下面这段代码:
复制代码 代码如下: function a() { var i =
0; function b; } return b; } var c = a; 这段代码有两个特点: 1.
函数b嵌套在函数a内部。 2. 函数a返回函数b。 这样在执行完var
c=a()后,变量c实际上是指向了函数b,b中用到了变量i,再执行c()后就会弹出一个窗口显示i的值。这段代码其实就创建了一个闭包,为什么?因为函数a外的变量c引用了函数a内的函数b,就是说:
当函数a的内部函数b被函数a外的一个变量引用的时候,就创建了一个我们通常所谓的“闭包”。让我们说的更透彻一些。所谓“闭包”,就是在构造函数体内定义另外的函数作为目标对象的方法函数,而这个对象的方法函数反过来引用外层外层函数体中的临时变量。
这使得只要目标对象在生存期内始终能保持其方法,就能间接保持原构造函数体当时用到的临时变量值。尽管最开始的构造函数调用已经结束,临时变量的名称也都消失了,但在目标对象的方法内却始终能引用到该变量的值,而且该值只能通这种方法来访问。
即使再次调用相同的构造函数,但只会生成新对象和方法,新的临时变量只是对应新的值,和上次那次调用的是各自独立的。为了更深刻的理解闭包,下面让我们继续探索闭包的作用和效果。
二、闭包有什么作用和效果?
简而言之,闭包的作用就是在a执行完并返回后,闭包使得Javascript的垃圾回收机制GC不会收回a所占用的资源,因为a的内部函数b的执行需要依赖a中的变量。这是对闭包作用的非常直白的描述,不专业也不严谨,但大概意思就是这样,理解闭包需要循序渐进的过程。
在上面的例子中,由于闭包的存在使得函数a返回后,a中的i始终存在,这样每次执行c(),i都是自加1后alert出i的值。那么我们来想象另一种情况,如果a返回的不是函数b,情况就完全不同了。
因为a执行完后,b没有被返回给a的外界,只是被a所引用,而此时a也只会被b引用,因此函数a和b互相引用但又不被外界打扰,函数a和b就会被GC回收。(关于Javascript的垃圾回收机制将在后面详细介绍)
三、闭包的微观世界
如果要更加深入的了解闭包以及函数a和嵌套函数b的关系,我们需要引入另外几个概念:函数的执行环境、活动对象、作用域、作用域链。以函数a从定义到执行的过程为例阐述这几个概念。
1.
当定义函数a的时候,js解释器会将函数a的作用域链设置为定义a时a所在的“环境”,如果a是一个全局函数,则scope
chain中只有window对象。 2. 当执行函数a的时候,a会进入相应的执行环境。 3.
在创建执行环境的过程中,首先会为a添加一个scope属性,即a的作用域,其值就为第1步中的scope
chain。即a.scope=a的作用域链。 4.
然后执行环境会创建一个活动对象。活动对象也是一个拥有属性的对象,但它不具有原型而且不能通过JavaScript代码直接访问。创建完活动对象后,把活动对象添加到a的作用域链的最顶端。此时a的作用域链包含了两个对象:a的活动对象和window对象。
5.
下一步是在活动对象上添加一个arguments属性,它保存着调用函数a时所传递的参数。
6.
最后把所有函数a的形参和内部的函数b的引用也添加到a的活动对象上。在这一步中,完成了函数b的的定义,因此如同第3步,函数b的作用域链被设置为b所被定义的环境,即a的作用域。
到此,整个函数a从定义到执行的步骤就完成了。此时a返回函数b的引用给c,又函数b的作用域链包含了对函数a的活动对象的引用,也就是说b可以访问到a中定义的所有变量和函数。函数b被c引用,函数b又依赖函数a,因此函数a在返回后不会被GC回收。
当函数b执行的时候亦会像以上步骤一样。因此,执行时b的作用域链包含了3个对象:b的活动对象、a的活动对象和window对象,当在函数b中访问一个变量的时候,搜索顺序是:
1.
先搜索自身的活动对象,如果存在则返回,如果不存在将继续搜索函数a的活动对象,依次查找,直到找到为止。
2.
如果函数b存在prototype原型对象,则在查找完自身的活动对象后先查找自身的原型对象,再继续查找。这就是Javascript中的变量查找机制。

1.
事件注册在祖先级元素上,代理其子级元素。可以减少事件注册数量,节约内存开销,提高性能。

  1. 如果整个作用域链上都无法找到,则返回undefined。
    小结,本段中提到了两个重要的词语:函数的定义与执行。文中提到函数的作用域是在定义函数时候就已经确定,而不是在执行的时候确定。用一段代码来说明这个问题:
    复制代码 代码如下: function f { var g =
    function () { return x; } return g; } var h = f;
    这段代码中变量h指向了f中的那个匿名函数。
    ◆假设函数h的作用域是在执行alert确定的,那么此时h的作用域链是:h的活动对象->alert的活动对象->window对象。
    ◆假设函数h的作用域实在定义时确定的,就是说h指向的那个匿名函数在定义的时候就已经确定了作用域。那么在执行的时候,h的作用域链为:h的活动对象->f的活动对象->window对象。
    如果第一种假设成立,那输出值就是undefined;如果第二种假设成立,输出值则为1。
    运行结果证明了第2个假设是正确的,说明函数的作用域确实是在定义这个函数的时候就已经确定了。
    四、闭包的应用场景 1.
    保护函数内的变量安全。以最开始的例子为例,函数a中i只有函数b才能访问,而无法通过其他途径访问到,因此保护了i的安全性。
    2.
    在内存中维持一个变量。依然如前例,由于闭包,函数a中i的一直存在于内存中,因此每次执行c(),都会给i自加1。
    3.
    通过保护变量的安全实现JS私有属性和私有方法推荐阅读:
    复制代码 代码如下: function Constructor {
    var that = this; var membername = value; function membername {…} }
    以上3点是闭包最基本的应用场景,很多经典案例都源于此。
    五、Javascript的垃圾回收机制
    在Javascript中,如果一个对象不再被引用,那么这个对象就会被GC回收。如果两个对象互相引用,而不再被第3者所引用,那么这两个互相引用的对象也会被回收。因为函数a被b引用,b又被a外的c引用,这就是为什么函数a执行后不会被回收的原因。
    六、结语
    理解JavaScript的闭包是迈向高级JS程序员的必经之路,理解了其解释和运行机制才能写出更为安全和优雅的代码。
  1. 对js动态添加的子元素可自动绑定事件。

之前一直用各种js库的事件代理,如 jQuery,非常方便实用。今天尝试用原生 js
实现该功能。

var addEvent =  { if (document.addEventListener) { return function (element, type, handler) { element.addEventListener; }; } else if  { return function (element, type, handler) { element.attachEvent('on' + type, function () { handler.apply; }); }; } else { return function (element, type, handler) { element['on' + type] = function () { return handler.apply; }; }; }})(),getClassElements = function (parentElement, classname) { var all, element, classArr = [], classElements = []; if (parentElement.getElementsByClassName) { return parentElement.getElementsByClassName; } else { all = parentElement.getElementsByTagName; for (var i = 0, len = all.length; i < len; i++) { element = all[i]; classArr = element && element.className && element.className.split { for (var j = 0; j < classArr.length; j++) { if (classArr[j] === classname) { classElements.push; } } } } return classElements; }},delegate = function () { // 参数:element, type, [selector,] handler var args = arguments, element = args[0], type = args[1], handler; if  { handler = args[2]; return addEvent(element, type, handler); } if  { selector = args[2]; handler = args[3]; return addEvent(element, type, function  { var event = event || window.event, target = event.target || event.srcElement, quickExpr = /^(?:[a-zA-Z]*#|[a-zA-Z]*\.$/, match, idElement, elements, tagName, count = 0, len; if (typeof selector === 'string') { match = quickExpr.exec { // #ID selector if  { idElement = document.getElementById; tagName = match[0].slice(0, match[0].indexOf; // tag selector } else if  { elements = element.getElementsByTagName; // .class selector } else if  { elements = getClassElements; tagName = match[0].slice(0, match[0].indexOf; } } if  { if ( tagName ? tagName === idElement.nodeName.toLowerCase() && target === idElement : target === idElement ) { return handler.apply; } } else if  { for (len = elements.length; count < len; count++) { if ( tagName ? tagName === elements[count].nodeName.toLowerCase() && target === elements[count] : target === elements[count] ) { return handler.apply(elements[count], arguments); } } } } }); }};

主要是用 apply 改变 this 的指向

handler.apply;handler.apply(elements[count], arguments);

#outer {padding: 50px; background-color: lightpink;}#inner {padding: 30px; background-color: aliceblue;}#paragraph1, #paragraph3 {background-color: cadetblue}

outer inner paragraph1 paragraph2 span paragraph3 

var outer = document.getElementById;delegate(outer, 'click', function () { console.log;

delegate(outer, 'click', 'p', function () { console.log; //点击 paragraph1 元素,输出其id为 "paragraph1"});

模仿 jQuery 的风格,优化代码:

 { var $ = function  { return new _$; }; var _$ = function  { this.element = element && element.nodeType === 1 ? element : document; }; _$.prototype = { constructor: _$, addEvent: function (type, handler, useCapture) { var element = this.element; if (document.addEventListener) { element.addEventListener(type, handler, (useCapture ? useCapture : false)); } else if  { element.attachEvent('on' + type, function () { handler.apply; }); } else { element['on' + type] = function () { return handler.apply; }; } return this; }, getClassElements: function  { var element = this.element, all, ele, classArr = [], classElements = []; if (element.getElementsByClassName) { return element.getElementsByClassName; } else { all = element.getElementsByTagName; for (var i = 0, len = all.length; i < len; i++) { ele = all[i]; classArr = ele && ele.className && ele.className.split { for (var j = 0; j < classArr.length; j++) { if (classArr[j] === classname) { classElements.push; } } } } return classElements; } }, delegate: function () { //参数:type, [selector,] handler var self = this, element = this.element, type = arguments[0], handler; if (arguments.length === 2) { handler = arguments[1]; return self.addEvent; } else if (arguments.length === 3) { selector = arguments[1]; handler = arguments[2]; return self.addEvent(type, function  { var event = event || window.event, target = event.target || event.srcElement, quickExpr = /^(?:[a-zA-Z]*#|[a-zA-Z]*\.$/, match, idElement, elements, tagName, count = 0, len; if (typeof selector === 'string') { match = quickExpr.exec { // #ID selector if  { idElement = document.getElementById; tagName = match[0].slice(0, match[0].indexOf; // tag selector } else if  { elements = element.getElementsByTagName; // .class selector } else if  { elements = self.getClassElements; tagName = match[0].slice(0, match[0].indexOf; } } if  { if ( tagName ? tagName === idElement.nodeName.toLowerCase() && target === idElement ? target === idElement ) { return handler.apply; } } else if  { for (len = elements.length; count < len; count++) { if ( tagName ? tagName === elements[count].nodeName.toLowerCase() && target === elements[count] : target === elements[count] ) { return handler.apply(elements[count], arguments); } } } } }); } } }; window.$ = $; return $;};

var outer = document.getElementById.delegate('click', '.parag', function  { console.log;

以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,同时也希望多多支持脚本之家!

相关文章