javascript编程技巧与优化
DESCRIPTION
TRANSCRIPT
问题 1
• Javascript 对于你来说是?
• Javascript 是 PHP 程序员最头痛的事
问题 2
• 为什么要把 Javascript 放在 </body> 之前?
• IE 经典错误“ operation aborted”
• 不阻塞页面解析
问题 3
• Javascript 为什么这么令人崩溃?
• 严苛的浏览器环境
• 变态的兼容问题,各种不同的内核、引擎
• 单线程,阻塞也得靠浏览器
技巧
• If 、 switch 、 for 语句都用 {} 括起来
• js 文件几个注意的地方:
1. 统一用 UTF-8 编码 2. 文件头和尾都写上 “ ;” 3. 所有注释使用 /**/ 4. 文件头写上 document.domain =
"56.com"; 方便跨域使用
• typeof (null) == 'object' 是 true
• var a;• a == undefined 是 true
• null == undefined 是 true• null === undefined 则是 false
• 字符串连接优先级高于算术运算:• 1 + '1' + 1 = '111’• '2' - 1 - 1 = 0
• switch 语句很快
• 把发生几率最高的 case 放在前面
• HTML5 是个无底洞
• js 没有函数重载概念: function foo(a) { alert(1); } function foo(a, b) { alert(2); } var f = foo(100); // alert 2
• var a = '1';• var b = {n:'kim'};• var c = a; // 值复制• var d = b; // 引用复制
• 初始化对象尽量使用字面量语法:
var obj = { "name": "kim", "age": 18 };
• RegExp 构造函数中的反斜杠需要双重转义:
// 比如匹配 2.5 、 3.4 等小数 var e = /\d.\d/; var e = new RegExp("\\d.\\d");
• 递归函数的定义: var factorial = (function f(num){ if (num <= 1) { return 1; } else { return num * f(num - 1); } });
• 理解 this 对象 : 当前的环境对象:
window.name = 'win'; var obj = {name: "kim"}; function myEnv() { alert(this.name); } myEnv.call(window); // win myEnv.call(obj) // kim
• 取 a-b 中间的随机数公式:
• var rand = Math.floor(Math.random() * b + a);
• prototype :定义对象的祖宗。
• 如果不了解 prototype ,那就别使用它,一般工厂模式或者单例模式就够用了
• 标准单例定义模式:
var singleton = function() { var private = 'hey'; function somePrivateFunc() { return 'hi'; } var public = { “name”: “kim”, getPrivate: function() { alert(private + somePrivateFunc()); } } return public; }(); var s = singleton; s.getPrivate();
• 跨浏览器取得窗口左上角位置:
• var left = (window.screenLeft ? window.screenLeft : window.screenX);• var top = (window.screenTop ? window.screenTop : window.screenY);
• url 跳转的几种方式:
window.location = 'http://example.com/'; location.href = 'http://example.com/'; location.assign('http://example.com/'); // 用 replace 的区别是取代 history 中的记
录 location.replace('http://example.com/');
• 用 location 页内跳转:
• // url 变成 http://example.com/#hash1 location.hash = '#hash1';• // url 变成 http://example.com/user/#hash1 location.pathname = 'user';• // url 变成 http://www.56.com/user/#hash1 location.hostname = 'www.56.com';
• 除非是紧急 bug ,或者小范围的修改,否则不要用浏览器检测,而应该用功能检测:
// 正确 if (document.all) { // do something with document.all } // 错误 if (browser.isIE) { // do something with document.all } else { // do nothing with document.all }
• 获取 html 元素:
document.documentElement document.firstChild document.childNodes[0]
• 2 个子域页面共享 js 对象:
• document.domain = “example.com”
• document.write 输出 script 需注意:
• document.write("<script type=\"test/javascript\" src=\"objcmt.js\">" + "<\/script>");
• 屏幕滚动到该元素: div.scrollIntoView();
• 站内 js 的兼容测试:必须按实际情况,根据各浏览器的访问数据为依据
• 访问 iframe 中的 document :
• var iframe = document.getElementById('ifrm');• var ifrmdoc = iframe.contentDocument || iframe.contentWindow.document;
• 谨记 DOM 事件流:
• 捕捉阶段:• document -> html -> body -> ... -> div
• 冒泡阶段:• div -> ... -> body -> html -> document
• setTimeout 高级定时器:
f = setTimeout(function () { objcmt.ajaxFlvHeartbeat(); f = setTimeout(arguments.callee, 180000); }, 180000);
• IE 经典错误:
• “ 缺少标识符、字符串或数字”,一般就是多了个逗号
• IE 经典错误:
• “ 无效字符”,转义前面缺了引号,或者字符串中的引号缺了转义
• 判断参数是否传入:
function foo(param1, param2) { if (param1) { // bad } if (typeof param1 != 'undefined') { // good, go on } if (param2 instanceof Function) { // better, go on do with function } }
• instanceof, typeof 等等都不是 100% 正确的,最实际的做法是直接判断该对象,或者该方法是否可用,比如 :
if (document.all) { // do something with document.all }
• 不要用各种 javascript 模板技术,从个人经验来看,这些模板技术只会添乱
• 不要“添加、修改、重定义”已有实例或对象的属性和方法
• 两者的区别?• var obj1 = {"name": "kim"}• var obj1 = {name: "kim"}
• JSONP 的原理:
function myCallback(rep) { alert(rep); } var script = document.createElement('script'); script.src = "http://comment.56.com/api/api.php?callback=myCallback"; document.body.appendChild(script);
// api.php <?php if (isset($_GET['callback'])) { echo htmlspecialchars($_GET['callback'] . '()'); } exit(); ?>
• 尽量少用框架,或者基于框架开发的组件
• 网站流量统计:嵌入 js 方式的统计,比服务器日志更准确,因为客户端有缓存。
• 用 <object> 先缓存 js,再执行:
<script type="text/javascript"> // 先cache js,再执行 var myJs = "http://example.com/myJs.js"; function cachejs(script_filename){ var cache = document.createElement('object'); cache.data = script_filename; cache.width = 0; cache.height = 0; document.body.appendChild(cache); } function loadjs(script_filename) { var script = document.createElement('script'); script.setAttribute('type', 'text/javascript'); script.setAttribute('src', script_filename); script.setAttribute('id', 'script_id'); script_id = document.getElementById('script_id'); if(script_id){ document.getElementsByTagName('head')[0].removeChild(script_id); } document.getElementsByTagName('head')[0].appendChild(script); } cachejs(myJs); loadjs(myJs); </script>
• 使用 jQuery 深度复制来重置对象,比如要在其它地方获取一个纯净的 oriObj 对象:
var pureObj = jQuery.extend(true, {}, oriObj); function someFunc() { oriObj = jQuery.extend(true, {}, pureObj); } someFunc(); // deal with oriObj
• 减少 js 依赖的方法:
1. 尽量不依赖别的 js 文件,插件或者引用都可以直接加到文件中,作为本地代码
2. 使用本地存储来解耦,比如 cookie3. 尽量不使用全局变量和全局函数,如必要,可以通过统一使
用一个全局变量来实现,比如: var myGlobal = { GLOBAL_CONSTANTS: "coco", "globalName": "kim", "globalFunc": function() { alert('haha'); } }
• 函数节流: // 睡一会儿 function sleep(milliSeconds){ var startTime = new Date().getTime(); while (new Date().getTime() < startTime + milliSeconds); }
var throttle = function(fn, delay) { // 定时阀 var timer = null; // 返回创建的函数 return function() { var that = this, args = arguments; clearTimeout(timer); timer = setTimeout(function() { fn.apply(that, args); }, delay); }; };
i = 0; var myFunc = function() { // 每 1.5秒加一点内容 sleep(1500); document.body.innerHTML += i + '<br />'; i++; }
window.onresize = throttle(myFunc, 100);
性能
• 尽量使用原生 javascript 方法
• 用逗号一次定义多个变量: var a = 1, b = 'hi', c = [1,3];
• 初始化数组或对象都可以只用一条语句:
var arr = new Array(); arr[0] = 1; arr[1] = 2; // 完全等同以下: var arr = [1, 2];
var obj = new Object(); obj.name = 'kim'; obj.age = 3; // 完全等同于一条字面量定义: var obj = { "name": 'kim', "age": 3 };
• 乘除 2 的操作都可以用位运算代替,比如: var i = 2 * 4; // 等价于 var i = 2 << 2;
• 三元运算符 “ ?:” 更简洁更快: var min = Math.min(a, b); // 慢 var min = a < b ? a : b; // 快
• 直接数组追加比 push 要快: arr.push(v); // 慢 arr[arr.length] = v; // 快
• 字符串连接: // 慢,创建了一个临时字符串 str += 'x' + 'y'; // 更快,更少内存 str += 'x'; str += 'y';
• 尽量不要用 for-in 来遍历对象。• for-in 会为对象的所有可枚举的属性创建一
个 list ,并且在进行枚举之前检查重复属性。
• 最快的数组循环: var i = arr.length - 1; if (i > -1) { do { // do something with arr[i] } while (--i >= 0); }
• 尽量不用 with 语句,容易出 bug 。• with 延长了作用域, js 编译时识别不了这
个附加作用域的,通常都可以直接用 obj.property 来代替。
• new String 的应用场景,如果需要多次调用字符串对象的属性时:
// 共创建 21 个变量,每次调用都会创建一个临时字符串变量 var s = '0123456789'; for (var i = 0; i < s.length; i++) { s.charAt(i); }
// 只创建一个对象 var s = new String('0123456789'); for (var i = 0; i < s.length; i++) { s.charAt(i); }
• 闭包:延长了作用域而且常驻内存。
• 对象、对象中的对象、闭包,在不用的时候就设为 null ,等待内存回收,特别是当对象是 Dom 元素的时候。
obj1.obj2 = new Foo(); obj1.dom3 = document.getElementById('id1'); // some operations ... obj1.obj2 = null; obj1.dom3 = null; obj1 = null;
• 尽量不用全局变量和全局函数:
1. 全局变量实际上是 2 个作用域,一个是当前 js 脚本,一个 windows 。
2. 在整个 js 运行周期内都占用内存,并不像局部变量,用完就可以被回收
• 函数内使用全局变量,尽量把全局变量变成局部变量,或者通过参数传入函数内部,以保证最短作用域链。
var foo = 'foo'; function bar() { var local = foo, dm = document; alert(local); dm.write(local); dm.write(local); }
• try-catch 是低性能的,切忌用在循环里,“变量 e” 是个额外开销
for (var i = 0; i < obj.length; i++) { try { test[obj[i]].property = somevalue; } catch(e) { ... } } 应该写在外面: try { for (var i = 0; i < obj.length; i++) { test[obj[i]].property = somevalue; } } catch(e) { ... }
• 更好的实践是根本不用 try-catch ,基本上大多数情况下都可以用 if 来代替。
• js 这种弱类型的语言,用 if 能解决很多问题。
• setTimeout 和 setInterval 应该直接传入 function :
setTimeout("alert('hihi')", 10); // 慢 setTimeout(function() { alert(‘hihi’); }, 10); // 快
• getElementById 是最快的,任何时候都是开发首选。
• 所有类型的 selector 都是可以用指定 id 来替代的。
• 尽量不使用 HTMLCollection 对象。• 比如
getElementByTagName 、 getElementsByName 、 document.images 、 document.forms 、jQuery('div') 等。
• innerHTML 和 outerHTML 的特点:
1. 很快,但不是所有元素都支持。 2. 需手动清除 innerHTML 里面 js 对象和属性绑定,优化内存
• 避免重复设置 innerHTML ,比如:
// 错误 for (var i = 0; i < 10; i++) { div.innerHTML += 'hi'; } // 正确 var htmltext = ''; for (var i = 0; i < 10; i++) { htmltext += 'hi'; } div.innerHTML(htmltext);
• 任何情况下,遍历节点都是性能杀手,应坚决避免,比如:
document.createNodeIterator
• eval == evil // 不解释
• 和 css 解耦:
1. 尽可能少的修改元素 style 属性 2. 不在 css 中使用 expression 3. 尽量通过修改 className 来修改样式 4. 通过 cssText 属性来设置样式值
// 进行了 4次重新渲染 el.style.color = 'red; el.style.height = '100px'; el.style.fontSize = '12px'; el.style.backgroundColor = 'white';
• 尽量不在元素中直接绑定事件处理,事件处理程序的本质:
<input onclick="alert(this)" />
1. 开辟内存,创建一个函数 2. 函数内部封装了对该对象的引用,即
this 对象 3. 创建了局部变量 event 事件对象 4. 每定义一个函数处理程序就是对 DOM 的
一次访问
• 解决 inline script 阻塞页面的方法,排名先后:
1. window.onload :并行下载,渲染无阻 2. setTimeout :并行下载,部分浏览器渲染
无阻 3. 移到底部:并行下载,但是渲染还是阻塞 4. defer :并行下载,部分浏览器支持
• 设置元素的 position 为 absolute 或 fixed ,可以使元素从 DOM 树结构中脱离出来独立的存在。
• 在元素的 position 为 static 和 relative 时,元素处于 DOM 树结构当中,当对元素的某个操作需要重新渲染时,浏览器会渲染整个页面。
• 页面重复渲染,即 reflow 和 repaint 发生的情景:
1. DOM 元素的添加、修改(内容)、删除 (Reflow + Repaint)2. 应用新的样式,或者修改任何影响元素外观属性的时候
(Reflow + Repaint)3. 仅修改 DOM 元素的字体颜色( Repaint)4. Resize 浏览器窗口、滚动页面( Reflow)5. 读取元素的部分属性( offsetLeft 、 offsetTop 、 offsetHeight 、 offsetWidth 、 scrollTop/Left/Width/Height 、 clientTop/Left/Width/Height 、 getComputedStyle() 、 currentStyle(in IE))(是的,读取属性也会引起 repaint!)
• Reflow :简单的说,就是位置、大小变了• Repaint :简单地说,就是字体、颜色变了
• 减少 reflow/repaint :
// 将元素的 display 设为 "none" ,完成修改后再把 display 改为原来的值
var dv = document.getElementById('divId'); dv.style.display = 'none'; dv.innerHTML += 'hi '; dv.innerHTML += 'kim'; dv.style.color = 'red'; dv.style.display = 'block';
• 减少 reflow/repaint :
// 使用 DocumentFragment 一次性创建多个 DOM节点 var span1 = document.createElement('span'); var span2 = document.createElement('span'); span1.appendChild(document.createTextNode("hi")); span2.appendChild(document.createTextNode("kim"));
var fragment = document.createDocumentFragment(); fragment.appendChild(span1); fragment.appendChild(span2);
var div = document.getElementById("divContainer"); div.appendChild(fragment);
• 层与渲染的关系:
// 创建了 2层 var newWidth = aDiv.offsetWidth + 10; // Read aDiv.style.width = newWidth + 'px'; // Write var newHeight = aDiv.offsetHeight + 10; // Read ,清空了 reflow 队列,导致新增一层
aDiv.style.height = newHeight + 'px'; // Write
// 只建了 1层 var newWidth = aDiv.offsetWidth + 10; // Read var newHeight = aDiv.offsetHeight + 10; // Read aDiv.style.width = newWidth + 'px'; // Write aDiv.style.height = newHeight + 'px'; // Write
• Javascript 终极优化 - 事件代理和委托 1. 减少 DOM 加载时间 2. 减少内存使用 3. 响应最快
/* 举一个实际应用中的例子:评论删除、回复、顶 */ "initListBindings": function() { var that = this; /* 代理 click 时间 */ jq('#' + config.cmtTarget).bind('click', function(event){ event.stopPropagation(); /* 停止冒泡 */ var target = jq(event.target); /* 获取正在访问的对象 */
switch (target.attr('class')) { case 'cmt_delete_link': /* 如果点击的是删除链接 */ that.openDelCommentPopup(); /* 打开删除对话框 */ break; case 'cmt_reply_link': /* 如果点击的是回复链接 */ that.openReplyPopup(); /* 打开回复框 */ break; case 'cmt_agree_link_em': /* 如果点击的是顶 */ target = target.parent(); case 'cmt_agree_link': /* 如果点击的是顶链接 */ that.ajaxIncNbAgree(event.target, ids[1], ids[2]); /* 执行“顶”动作 */ event.preventDefault(); // IE6 getJSON bug hack break; default: break; } }); },
实例吐槽
Javascript 优化无止境
Faq
谢谢!