JavaScript的妙与乐系列文章主要是展示一些JavaScript上面比较好玩一点的特性和一些有用的技巧,里面很多内容都是我曾经在项目中使用过的一些内容(当然,未必所有技巧的使用频率都很高_)。

本篇文章主要是探讨一些辅助函数的书写优化。

函数优化

JavaScript中由于浏览器的兼容性差异,导致了在操纵DOM相关的代码中常常会有所差异,比如最经典的给DOM绑定事件

//dom
var dom = document.getElementById('example');
//event
function clickEvent () {
console.log('click!');
} //IE9以上及Chrome、Firefox
dom.addEventListener('click', clickEvent, false); //IE
dom.attachEvent('onclick', clickEvent); //全部通用
dom.onclick = clickEvent;

可以看出给DOM绑定事件可以有三种实现方式,而我们会依次采用addEventListener/attachEvent/onclick这样的排序去绑定事件。聪明的我们可能就会立即想到我们应该要用一个通用函数来处理,毕竟谁也不想每次绑定事件都要判断一次浏览器特性。

function BindEvent (dom, event, handle, ex) {
if ('addEventListener' in dom) {
dom.addEventListener(event, handle, ex || false);
} else if ('attachEvent' in dom) {
dom.attachEvent('on' + event, handle);
} else {
dom['on' + event] = handle;
}
}

这看起来非常棒,使用起来也不错。

var dom = document.getElementById('example');
BindEvent(dom, 'click', function () {
alert('hey!');
});

现实情况往往会比想象中复杂那么一点点,页面上不可能只有一两个dom,在一些SPA中甚至会成千上万,来让我们尝试一下优化。其实对于浏览器的特性侦测只需要执行一次即可,而没有必要每次绑定事件的时候都去判断,我们不妨将BindEvent改成立即执行函数

var BindEvent = (function () {
if ('addEventListener' in document) {
return function (dom, event, handle, ex) {
dom.addEventListener(event, handle, ex || false);
}
} else if ('attachEvent' in document) {
return function (dom, event, handle) {
dom.attachEvent('on' + event, handle);
}
} else {
return function (dom, event, handle) {
dom['on' + event] = handle;
}
}
})();

这样BindEvent就会在加载的时候直接采用了最符合当前浏览器情况的实现方式。最后让我们来写一些测试代码对比一下效率,对于我们页面上最可能出现的情况(有1000个DOM)。这里我们采用Chrome提供的console.profile函数来做性能分析

function GenerateDOM (start, end) {
for (; start < end; start++) {
var dom = document.createElement('div');
dom.id = 'd' + start;
dom.innerHTML = 'I\'m d' + start + ' !';
document.body.appendChild(dom);
}
} GenerateDOM(0, 1000); function BindDOMEvent (doms, event, handle, ex) {
for (var i = 0; i < doms.length; i++) {
BindEvent(doms[i], event, handle, ex);
}
} var doms = document.getElementsByTagName('div'); console.profile();
BindDOMEvent(doms, 'click', function () {
alert('hey! ' + this.id);
});
console.profileEnd();

当你在页面按F12打开console点击Profiles的tab你可以看到Chrome提供的分析结果。而我们暂时只需要分析BindDOMEvent的耗时即可。如下图:

以下是我分别各执行两种版本10次后得出的综合分析结果,可以看出避免重复判断后,性能平均快了0.7ms,而且优化后基本能够维持在2.1ms(快了约1.27倍

原始版本

3.3 / 3.4 / 3.3 / 2.3 / 3.5 / 2.3 / 3.4 / 3.3 / 2.2 / 3.3

avg: 3.03 max: 3.5 min: 2.2

优化后版本

2.1 / 2.1 / 2.1 / 3.4 / 2.1 / 2.2 / 2.2 / 3.2 / 2.2 / 2.1

avg: 2.37 max: 3.4 min: 2.1

所以,我们可以直观的判断出,当有类似重复判断的函数,不妨观察分析一下是否能够将该判断只进行一次。这样已经能够给我们的性能带来一定的提升。

优化之路永无止境

对于大多数的项目来说,类似BindEvent这样的函数一般都会封装成一个Library以便共用代码。而这样的公用Library常常是每个页面都会默认加载。

<script src="/public/helper.js"></script>

但是实际上每个页面所用到的功能仅仅是Library里面的一小部分,例如我们刚刚所写的BindEvent。我们回头看一看实现代码。

var BindEvent = (function () {
if ('addEventListener' in document) {
return function (dom, event, handle, ex) {
dom.addEventListener(event, handle, ex || false);
}
} else if ('attachEvent' in document) {
return function (dom, event, handle) {
dom.attachEvent('on' + event, handle);
}
} else {
return function (dom, event, handle) {
dom['on' + event] = handle;
}
}
})();

这里有什么可以优化的点呢?在于我们不确定这个页面是否会用到这个function的时候,我们都默认帮他初始化好了,这里面会有一定的性能损耗。毕竟一个Library当中这样的方法会有N个,当N个函数都在页面加载的时候进行初始化,对于整个页面加载速度方面的影响不容小瞧。

那怎么去做优化呢?惰性加载,只有当有调用方去触发这个function的时候我们才去做初始化。实现代码如下:

var BindEvent = function (dom, event, handle, ex) {
if ('addEventListener' in document) {
BindEvent = function (dom, event, handle, ex) {
dom.addEventListener(event, handle, ex || false);
}
} else if ('attachEvent' in document) {
BindEvent = function (dom, event, handle) {
dom.attachEvent('on' + event, handle);
}
} else {
BindEvent = function (dom, event, handle) {
dom['on' + event] = handle;
}
}
BindEvent(dom, event, handle, ex);
};

这样当BindEvent被调用了一次之后,我们的BindEvent函数就会被替换成符合浏览器特性的实现方式,之后的所有调用均是调用最优结果。最后我们再进行同样地测试看一下对比

3.2 / 2.1 / 2.2 / 3.2 / 3.2 / 2.1 / 3.1 / 2.1 / 3.2 / 3.2

avg: 2.76 max: 3.2 min: 2.1

可以看到平均耗时是2.75而之前是2.37,性能仅仅是略为减慢而已(约是1.16倍),影响并不是非常大,可是对于页面加载的影响呢?

来,让我们用console.time来做一下对比测试,看一下两种实现方式对于页面加载的影响。

console.time('event1');
var BindEvent = (function () {
if ('addEventListener' in document) {
return function (dom, event, handle, ex) {
dom.addEventListener(event, handle, ex || false);
}
} else if ('attachEvent' in document) {
return function (dom, event, handle) {
dom.attachEvent('on' + event, handle);
}
} else {
return function (dom, event, handle) {
dom['on' + event] = handle;
}
}
})();
console.timeEnd('event1'); console.time('event2');
var BindEvent2 = function (dom, event, handle, ex) {
if ('addEventListener' in document) {
BindEvent = function (dom, event, handle, ex) {
dom.addEventListener(event, handle, ex || false);
}
} else if ('attachEvent' in document) {
BindEvent = function (dom, event, handle) {
dom.attachEvent('on' + event, handle);
}
} else {
BindEvent = function (dom, event, handle) {
dom['on' + event] = handle;
}
}
BindEvent(dom, event, handle, ex);
};
console.timeEnd('event2');

测试结果中前面的运行毫秒是BindEvent而后面的则是BindEvent2

0.038 / 0.007

0.041 / 0.006

0.031 / 0.007

0.053 / 0.009

0.041 / 0.007

BindEvent平均耗时: 0.0408BindEvent2平均耗时: 0.00702

前者约是后者的5.81

通过整体评测可以看得出来对于一个能够采用惰性加载的函数,通过这样的一个方式,能够大大减少页面加载时间,同时不会对执行时的运行效率产生太大的影响。

结论

  • 对于需要重复判断的条件看能否提取出来只判断一次

    • 如果不能,也可以选择保存一个bool值变量来做,而不要进行过于复杂的判断
  • 对于不是页面必须的函数,可以采用惰性加载方式来实现

怎么样?你学会了这个技巧了么?

console.profile与console.time的文档 需要自备梯子- -!

立即执行函数(immediately-invoked function expression IIFE) 的介绍

JavaScript的妙与乐(一)之 函数优化的更多相关文章

  1. 我自己的Javascript 库,封装了一些常用函数 Kingwell.js

    我自己的Javascript 库,封装了一些常用函数 Kingwell.js 博客分类: Javascript javascript 库javascript库  现在Javascript库海量,流行的 ...

  2. 博文推荐】Javascript中bind、call、apply函数用法

    [博文推荐]Javascript中bind.call.apply函数用法 2015-03-02 09:22 菜鸟浮出水 51CTO博客 字号:T | T 最近一直在用 js 写游戏服务器,我也接触 j ...

  3. JavaScript字符串插入、删除、替换函数

    JavaScript字符串插入.删除.替换函数 说明: 以下函数中前两个函数取出查找字符串的前一部分和后一部分,以用于其他函数.注意,调用一次 replaceString(mainStr,search ...

  4. 使用 JavaScript 实现名为 flatten(input) 的函数,可以将传入的 input 对象(Object 或者 Array)进行扁平化处理并返回结果

    请使用 JavaScript 实现名为 flatten(input) 的函数,可以将传入的 input 对象(Object 或者 Array)进行扁平化处理并返回结果.具体效果如下: const in ...

  5. 用JavaScript写一个类似PHP print_r的函数

    PHP print_r的函数很好用,可以用来打印数组.对象等的结构与数据,可惜JavaScript并没有原生提供类似的函数.不过我们可以试着自己来实现这个函数,下面提供一些方法与思路. 方法一 fun ...

  6. javascript进阶课程--第三章--匿名函数和闭包

    javascript进阶课程--第三章--匿名函数和闭包 一.总结 二.学习要点 掌握匿名函数和闭包的应用 三.匿名函数和闭包 匿名函数 没有函数名字的函数 单独的匿名函数是无法运行和调用的 可以把匿 ...

  7. JavaScript深入浅出第1课:箭头函数中的this究竟是什么鬼?

    <JavaScript 深入浅出>系列: JavaScript 深入浅出第 1 课:箭头函数中的 this 究竟是什么鬼? JavaScript 深入浅出第 2 课:函数是一等公民是什么意 ...

  8. javascript中,一个js中的函数,第一句var _this = this;为什么要这样做?

    javascript中,一个js中的函数,第一句var _this = this;为什么要这样做? 下面是源码: 下面这段代码是常用的网站首页,自动切换span或者tabbar来变更List显示内容的 ...

  9. JavaScript学习总结(四)function函数部分

    转自:http://segmentfault.com/a/1190000000660786 概念 函数是由事件驱动的或者当它被调用时执行的可重复使用的代码块. js 支持两种函数:一类是语言内部的函数 ...

随机推荐

  1. 使用TSQL查询和更新 JSON 数据

    JSON是一个非常流行的,用于数据交换的文本数据(textual data)格式,主要用于Web和移动应用程序中.JSON 使用“键/值对”(Key:Value pair)存储数据,能够表示嵌套键值对 ...

  2. Linux平台 Oracle 10gR2(10.2.0.5)RAC安装 Part3:db安装和升级

    Linux平台 Oracle 10gR2(10.2.0.5)RAC安装 Part3:db安装和升级 环境:OEL 5.7 + Oracle 10.2.0.5 RAC 5.安装Database软件 5. ...

  3. 一看就懂的ReactJs入门教程-精华版

    现在最热门的前端框架有AngularJS.React.Bootstrap等.自从接触了ReactJS,ReactJs的虚拟DOM(Virtual DOM)和组件化的开发深深的吸引了我,下面来跟我一起领 ...

  4. 我为什么要写LeetCode的博客?

    # 增强学习成果 有一个研究成果,在学习中传授他人知识和讨论是最高效的做法,而看书则是最低效的做法(具体研究成果没找到地址).我写LeetCode博客主要目的是增强学习成果.当然,我也想出名,然而不知 ...

  5. OpenCASCADE BRep Projection

    OpenCASCADE BRep Projection eryar@163.com 一网友发邮件问我下图所示的效果如何在OpenCASCADE中实现,我的想法是先构造出螺旋线,再将螺旋线投影到面上. ...

  6. 23种设计模式--工厂模式-Factory Pattern

    一.工厂模式的介绍       工厂模式让我们相到的就是工厂,那么生活中的工厂是生产产品的,在代码中的工厂是生产实例的,在直白一点就是生产实例的类,代码中我们常用new关键字,那么这个new出来的实例 ...

  7. 从零开始编写自己的C#框架(28)——建模、架构与框架

    文章写到这里,我一直在犹豫是继续写针对中小型框架的设计还是写些框架设计上的进阶方面的内容?对于中小型系统来说,只要将前面的内容进行一下细化,写上二三十章具体开发上的细节,来说明这个通用框架怎么开发的就 ...

  8. 【NLP】Python NLTK获取文本语料和词汇资源

    Python NLTK 获取文本语料和词汇资源 作者:白宁超 2016年11月7日13:15:24 摘要:NLTK是由宾夕法尼亚大学计算机和信息科学使用python语言实现的一种自然语言工具包,其收集 ...

  9. WebApi基于Token和签名的验证

    最近一段时间在学习WebApi,涉及到验证部分的一些知识觉得自己并不是太懂,所以来博客园看了几篇博文,发现一篇讲的特别好的,读了几遍茅塞顿开(都闪开,我要装逼了),刚开始读有些地方不理解,所以想了很久 ...

  10. WinForm设置控件焦点focus

    winform窗口打开后文本框的默认焦点设置,进入窗口后默认聚焦到某个文本框,两种方法: ①设置tabindex 把该文本框属性里的tabIndex设为0,焦点就默认在这个文本框里了. ②Winfor ...