一、取URL中的参数
function getParameterByName(name) { var match = RegExp('[?&]' + name + '=([^&]*)') .exec(window.location.search); return match && decodeURIComponent(match[1].replace(/\+/g, ' ')); }
二、正则分组
var testStr="<div><img src='/a.jpg' alt='' /><span>test</span><img src='/b.jpg' alt='' /><span>TTest</span><img src='/c.png' alt='' /></div>"; var reg=/<img\ssrc='(.*?)'\s+alt=''\s*\/>/g; var match=reg.exec(testStr),results=[]; while(match != null){ results.push(match[1]); match=reg.exec(testStr); } console.log(results); /* Array ["/a.jpg", "/b.jpg", "/c.png"] */
三、为什么parseInt(1/0,19)的结果为18
1/0的结果是Infinity,所以parseInt(1/0,19)等同于parseInt("Infinity",19),而在19进制中:
19进制 10进制 -------------------- 0 0 1 1 2 2 3 3 4 4 5 5 6 6 7 7 8 8 9 9 a 10 b 11 c 12 d 13 e 14 f 15 g 16 h 17 i 18
i表示18,所以parseInt(1/0,19)的结果为18。
四、jQuery中获取设置checkbox选中状态
由于在jQuery1.6以后.attr("checked")的返回结果是 checked,所以一般用下面两种方法获取选中状态:
$("#checkboxID").is(":checked");
//jQuery 1.6 + $("#checkboxID").prop("checked");
选中checkbox:
//jQuery 1.6+ $("#checkboxID").prop("checked", true); $("#checkboxID").prop("checked", false); //jQuery 1.5 and below $('#checkboxID').attr('checked','checked') $('#checkboxID').removeAttr('checked')
五、jQuery中判断一个元素是否存在
if ($(selector).length)
六、用JavaScript对URL进行编码
var myUrl = "http://example.com/index.html?param=1&anotherParam=2"; var myOtherUrl = "http://example.com/index.html?url=" + encodeURIComponent(myUrl);
七、jQuery中event.preventDefault() 与 return false 的区别
//Demo1 event.preventDefault() $('a').click(function (e) { // custom handling here e.preventDefault(); }); //Demo2 return false $('a').click(function () { // custom handling here return false; };
jQuery中return false相当于同时调用e.preventDefault 和 e.stopPropagation。
要注意的是,在原生js中,return false仅仅相当于调用了e.preventDefault。
八、JavaScript检查一个字符串是否为空最简单的方法
if (strValue) { //do something }
九、用JavaScript添加和删除class
//Add Class document.getElementById("MyElement").className += " MyClass"; //Remove Class document.getElementById("MyElement").className = document.getElementById("MyElement").className.replace(/(?:^|\s)MyClass(?!\S)/,'');
十、在jQuery中取消一个ajax请求
var xhr = $.ajax({ type: "POST", url: "test.php", data: "name=test", success: function(msg){ alert( msg ); } }); //取消请求 xhr.abort()
要注意的是,在ajax请求未响应之前可以用xhr.abort()取消,但如果请求已经到达了服务器端,这样做的结果仅仅是让浏览器不再监听这个请求的响应,但服务器端仍然会进行处理。
十一、JavaScript删除数组中的项 delete vs splice
var myArray=["a","b","c"]; delete myArray[0]; for(var i=0,j=myArray.length;i<j;i++){ console.log(myArray[i]); /* undefined b c */ } var myArray2=["a","b","c"]; myArray2.splice(0,1); for(var i=0,j=myArray2.length;i<j;i++){ console.log(myArray2[i]); /* b c */ }
上面的代码已经说明区别了,一个是设置为undefined,一个是真正的删除了。
十二、JavaScript中16进制与10进制相互转换
var sHex=(255).toString(16);//ff var iNum=parseInt("ff",16);//255
十三、JavaScript多行字符串
如何在JavaScript中方便地写一个多行字符串呢,有三种方案,你自己选吧:
//one var testHtml="a"+ "b"+ "c"; //two var testHtml2="a\ b\ c"; //three var testHtml3=["a", "b", "c"].join("");
十四、JavaScript中!!操作符是什么
console.log(!!10);//true console.log(!!0);//false console.log(!!"abc");//true console.log(!!"");//false
简单地说就是把右侧的值转为bool值
十五、JavaScript实现endsWith
String.prototype.endsWith = function(suffix) { return this.indexOf(suffix, this.length - suffix.length) !== -1; }; //or function endsWith(str, suffix) { return str.indexOf(suffix, str.length - suffix.length) !== -1; }
十六、JavaScript中克隆对象
function clone(obj) { // Handle the 3 simple types, and null or undefined if (null == obj || "object" != typeof obj) return obj; // Handle Date if (obj instanceof Date) { var copy = new Date(); copy.setTime(obj.getTime()); return copy; } // Handle Array if (obj instanceof Array) { var copy = []; for (var i = 0, var len = obj.length; i < len; ++i) { copy[i] = clone(obj[i]); } return copy; } // Handle Object if (obj instanceof Object) { var copy = {}; for (var attr in obj) { if (obj.hasOwnProperty(attr)) copy[attr] = clone(obj[attr]); } return copy; } throw new Error("Unable to copy obj! Its type isn't supported."); }
十七、JavaScript字符与ASCII码间的转换
console.log("\n".charCodeAt(0));//10 console.log(String.fromCharCode(65));//A
十八、JavaScript中浮点数的相等判断不能用 ==
console.log(0.1+0.2 == 0.3);//false console.log(Math.abs(0.1+0.2 - 0.3) < 0.000001);//true
如上所示,浮点数相等判断要用差的绝对值小于某一个数来判断。至于原因可以参考这里:http://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html
十九、JavaScript中base64编码
var Base64 = { // private property _keyStr : "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=", // public method for encoding encode : function (input) { var output = ""; var chr1, chr2, chr3, enc1, enc2, enc3, enc4; var i = 0; input = Base64._utf8_encode(input); while (i < input.length) { chr1 = input.charCodeAt(i++); chr2 = input.charCodeAt(i++); chr3 = input.charCodeAt(i++); enc1 = chr1 >> 2; enc2 = ((chr1 & 3) << 4) | (chr2 >> 4); enc3 = ((chr2 & 15) << 2) | (chr3 >> 6); enc4 = chr3 & 63; if (isNaN(chr2)) { enc3 = enc4 = 64; } else if (isNaN(chr3)) { enc4 = 64; } output = output + this._keyStr.charAt(enc1) + this._keyStr.charAt(enc2) + this._keyStr.charAt(enc3) + this._keyStr.charAt(enc4); } return output; }, // public method for decoding decode : function (input) { var output = ""; var chr1, chr2, chr3; var enc1, enc2, enc3, enc4; var i = 0; input = input.replace(/[^A-Za-z0-9\+\/\=]/g, ""); while (i < input.length) { enc1 = this._keyStr.indexOf(input.charAt(i++)); enc2 = this._keyStr.indexOf(input.charAt(i++)); enc3 = this._keyStr.indexOf(input.charAt(i++)); enc4 = this._keyStr.indexOf(input.charAt(i++)); chr1 = (enc1 << 2) | (enc2 >> 4); chr2 = ((enc2 & 15) << 4) | (enc3 >> 2); chr3 = ((enc3 & 3) << 6) | enc4; output = output + String.fromCharCode(chr1); if (enc3 != 64) { output = output + String.fromCharCode(chr2); } if (enc4 != 64) { output = output + String.fromCharCode(chr3); } } output = Base64._utf8_decode(output); return output; }, // private method for UTF-8 encoding _utf8_encode : function (string) { string = string.replace(/\r\n/g,"\n"); var utftext = ""; for (var n = 0; n < string.length; n++) { var c = string.charCodeAt(n); if (c < 128) { utftext += String.fromCharCode(c); } else if((c > 127) && (c < 2048)) { utftext += String.fromCharCode((c >> 6) | 192); utftext += String.fromCharCode((c & 63) | 128); } else { utftext += String.fromCharCode((c >> 12) | 224); utftext += String.fromCharCode(((c >> 6) & 63) | 128); utftext += String.fromCharCode((c & 63) | 128); } } return utftext; }, // private method for UTF-8 decoding _utf8_decode : function (utftext) { var string = ""; var i = 0; var c = c1 = c2 = 0; while ( i < utftext.length ) { c = utftext.charCodeAt(i); if (c < 128) { string += String.fromCharCode(c); i++; } else if((c > 191) && (c < 224)) { c2 = utftext.charCodeAt(i+1); string += String.fromCharCode(((c & 31) << 6) | (c2 & 63)); i += 2; } else { c2 = utftext.charCodeAt(i+1); c3 = utftext.charCodeAt(i+2); string += String.fromCharCode(((c & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63)); i += 3; } } return string; } } //encode Base64.encode("Test"); //VGVzdA== //decode Base64.decode("VGVzdA=="); // Test
二十、jQuery中each跟map的区别
each跟map都可以用来遍历Array或Object,区别是each不改变原来的Array或Object,map是操作给定的Array或Object返回一个新Array或Object。Demo:
var items = [1,2,3,4]; $.each(items, function() { alert('this is ' + this);//alert 1,2,3,4 }); var newItems = $.map(items, function(i) { return i + 1; }); // newItems is [2,3,4,5]
map会占用更多的内存,所以如果只是遍历建议用each。
二十一、判断一个对象是否为数组
function isArray(obj){ return Object.prototype.toString.call(obj) == "[object Array]"; }
不能用instanceof 和 constructor来判断,原因参考:http://perfectionkills.com/instanceof-considered-harmful-or-how-to-write-a-robust-isarray/
二十二、通过原型继承创建一个新对象
function inherit(p){ if(!p){ throw TypeError("p is not an object or null"); } if(Object.create){ return Object.create(p); } var t=typeof p; if(t !== "object" && t !== "function"){ throw TypeError("p is not an object or null"); } function f(){}; f.prototype=p; return new f(); }
注意:这种方法不能处理参数为null的情况。
JavaScript中浮点数的相等判断不能用 ==
console.log(0.1+0.2 == 0.3);//false console.log(Math.abs(0.1+0.2 - 0.3) < 0.000001);//true
javascript继承写法
var Base = function () {
this.method1 = function () {
alert("method1");
}
}
var Second = function () {
this.method2 = function () {
alert("method2");
}
};
var Third = function () {
this.method3 = function () {
alert("method3");
}
};
extend(Base, Second);
extend(Base, Third);
var t = new Base();
t.method2();
function extend(base, inheritClass) {
inheritClass.call(base.prototype);
}
$.extend(t, {
method3: function () {
new Base().method3();
}
})
t.method3();
http://www.cnblogs.com/aaa6818162/archive/2012/06/04/2534839.html
//父类
function OrderBaseHd() {
//私有
add2 = function () {
alert("add2");
},
//公有
this.add = function () {
add2();
}
}
//子类
function StoreOrderHd() {
this.storeAdd = function () {
alert("storeAdd");
},
this.add = function () {
alert("StoreOrderHd:add");
}
}//继承 1
StoreOrderHd.prototype = new OrderBaseHd();
new StoreOrderHd().storeAdd();
new StoreOrderHd().add(); //方法重写
//继承 2
var StoreOrderHd2 = new OrderBaseHd();
$.extend(StoreOrderHd2, {
methodExtend: function () {
alert("i am extend");
}
})
StoreOrderHd2.methodExtend();
StoreOrderHd2.add();
匿名函数
(fuction(){
})();
包含fuction(){ }的第一个括号向脚本返回未命名的函数。
随后的一对括号立即执行返回的未命名函数。
1.作用域解析和闭包的概念
function initanchors(W3CEvent) {
for (var i=1 ; i<=3 ; i++ ) {
var anchor = document.getElementById('anchor' + i);
ADS.addEvent(anchor,'click',function() {
alert('My id is anchor' + i);
});
}
其结果不是我们想象的那样分别弹出 1,2,3
而全是4
因为i的值实际上是在单击事件发生时才从作用域链中取得的。
当单击事件发生时,initanchors方法已经执行完毕,因此i的值等于4.
具体来说 当click事件侦听器被调用并在它的内部作用域中查找大i的值时,结果没有找到
因此它会到包含自己的外部作用域即initanchors()函数中找。
而在initanchors()函数中i的值是4,所以它就从该处取得了这个值。
正确的做法:
把事件侦听器的注册转移到一个独立的函数中,并通过该函数的参数传递适当的值。
function registerListener(anchor, i) {
ADS.addEvent(anchor, 'click', function() {
alert('My id is anchor' + i);
});
}
function initAnchors(W3CEvent) {
for ( i=1 ; i<=5 ; i++ ) {
var anchor = document.getElementById('anchor'+i);
registerListener(anchor,i);
}
}
ADS.addEvent(window, 'load', initAnchors);
2 覆盖作用域中的对象
function override() {
// Override the alert function
var alert = function(message) {
window.alert('overridden:' + message);
};
alert('alert'); //alert 1
// Call the original alert from the override() scope
window.alert('window.alert'); //alert 2
}
override();
// Call the alert in the window scope
alert('alert from outside'); //alert 3
结果:
1'overridden 'alert'
2'window.alert'
3'alert from outside'
3对象
3.1添加静态方法
3.2添加公有方法
通过prototype属性
3.3通过私有和特权成员控制访问
// 构造函数
function myConstructor(message) {
this.myMessage = message;
// 私有属性
var separator = ' -';
var myOwner = this;
// 私有方法
function alertMessage() {
alert(myOwner.myMessage);
}
alertMessage();
// 特权方法(也是公有方法)
this.appendToMessage = function(string) {
this.myMessage += separator + string;
alertMessage();
}
}
// 公有方法
myConstructor.prototype.clearMessage = function(string) {
this.myMessage = '';
}
// 静态属性
myConstructor.name = 'Jeff';
// 静态方法
myConstructor.alertName = function() {
alert(this.name);
}
结论:
1 由于私有和特权成员在函数的内部,因此它们会被带到函数的每个实例中。
2公有的原型成员是对象的一部分,使用与每个实例
3静态成员只适合于对象的一个特殊实例。
4 call apply重新定义执行环境
function bindFunction(obj, func) {
return function() {
func.apply(obj,arguments);
};
};
initPage() {
function doubleCheck() {
this.message = 'Are you sure you want to leave?';
}
doubleCheck.prototype.sayGoodbye = function() {
return confirm(this.message);
}
var clickedLink = new doubleCheck();
var links = document.getElementsByTagName('a');
for (var i=0 ; i<links.length ; i++) {
// remember not to include the () on the funciton
// as you don’t want to execute it.
ADS.addEvent(
links[i],
'click',
ADS.bindFunction(clickedLink, clickedLink.sayGoodbye)
);
}
}
addEvent(window,'load',initPage);
5重载并非真正的重载
<script type="text/javascript">
function myfuction(a)
{
alert("aaaaaaaaaaa"+a);
}
function myfuction(a,b)
{
alert("a+b========="+a+b);
}
myfuction("xxxxxxxxxxxxx");
</script>
结果是 a+b=========xxxxxxxxxxxxx
结论:脚本在执行时不会顾及到函数及函数定义时的参数,而是直接使用在作用域链中最后定义的那个函数。
这意味着,相同名称的函数永远只存在一个实例。
6.迭代对象
<body>
<input id="Text1" type="text" name="aa"/>
<input id="Text2" type="text" name="aa"/>
</body>
<script type="text/javascript">
var dd=document.getElementsByName("aa");
for( i in dd)
{
if(!dd.hasOwnProperty(i)){continue;}
alert("hasOwnProperty:"+i);
}
for( i in dd)
{
alert("nohasOwnProperty:"+i);
}
//alert(dd.length);
for(var i=0;i<dd.length;i++)
{
alert("length:"+dd[i].value);
}
</script>
7 事件冒泡和事件捕获
事件冒泡:先捕获目标元素
事件捕获:先捕获目标元素的最高父级
w3c方式:
obj.addEventListener(type,fn,true/false)
如果是true即启用事件捕获取消冒泡,false则相反。
ie方式:就是冒泡。
阻止事件冒泡:
w3cDOM2调用stopPropation()方法
ie将事件的cancelBubble属性设置为true
/**
* 中断事件传播
*/
function stopPropagation(eventObject) {
eventObject = eventObject || getEventObject(eventObject);
if(eventObject.stopPropagation) {
eventObject.stopPropagation();
} else {
eventObject.cancelBubble = true;
}
}
取消默认动作:
w3cDOM2调用preventDefault()方法
ie将事件的returnValue属性设置为false
/**
* Prevents the default event in the event flow (such as following the href in an anchor).
*/
function preventDefault(eventObject) {
eventObject = eventObject || getEventObject(eventObject);
if(eventObject.preventDefault) {
eventObject.preventDefault();
} else {
eventObject.returnValue = false;
}
}
window['ADS']['preventDefault'] = preventDefault;
获取事件对象
/**
* Retrieves the event object in a cross-browser way.
*/
function getEventObject(W3CEvent) {
return W3CEvent || window.event;
}
window['ADS']['getEventObject'] = getEventObject;
访问事件的目标元素
/**
* 访问事件的目标元素
*/
function getTarget(eventObject) {
eventObject = eventObject || getEventObject(eventObject);
// Check if the target is W3C or MSIE
var target = eventObject.target || eventObject.scrElement;
// Reassign the target to the parent
// if it is a text node like in Safari
if (target==undefined)
{
if(target.nodeType == ADS.node.TEXT_NODE) {
target = node.parentNode;
}
}
return target;
}
window['ADS']['getTarget'] = getTarget;
8一种写法
var example=a||b;
只要a的值不是null,undenfined或false
a的值都会被赋给example
否则使用b的值。
一.Javascript代码执行效率
1. DOM
1.1 使用 DocumentFragment 优化多次 append
说明:添加多个 dom 元素时,先将元素 append 到 DocumentFragment 中,最后统一将 DocumentFragment 添加到页面。
该做法可以减少页面渲染 dom 元素的次数。经 IE 和 Fx 下测试,在 append1000 个元素时,效率能提高 10%-30% , Fx 下提升较为明显。
服用前:
服用后:
1.2 通过模板元素 clone ,替代 createElement
说明:通过一个模板 dom 对象 cloneNode ,效率比直接创建 element 高。性能提高不明显,约为 10% 左右。在低于 100 个元素 create 和 append 操作时,没有优势。
服用前:
服用后:
1.3 使用一次 innerHTML 赋值代替构建 dom 元素
说明:根据数据构建列表样式的时候,使用设置列表容器 innerHTML 的方式,比构建 dom 元素并 append 到页面中的方式,效率有数量级上的提高。
服用前:
服用后:
1.4 使用 firstChild 和 nextSibling 代替 childNodes 遍历 dom 元素服用前:
服用后:
2. 字符串
2.1 使用 Array 做为 StringBuffer ,代替字符串拼接的操作
说明: IE 在对字符串拼接的时候,会创建临时的 String 对象;经测试,在 IE 下,当拼接的字符串越来越大时,运行效率会急剧下降。 Fx 和 Opera 都 对字符串拼接操作进行了优化;经测试,在 Fx 下,使用 Array 的 join 方式执行时间约为直接字符串拼接的 1.4 倍。
服用前:
服用后:
3. 循环语句
3.1 将循环控制量保存到局部变量
说明:对数组和列表对象的遍历时,提前将 length 保存到局部变量中,避免在循环的每一步重复取值。
3.2 顺序无关的遍历时,用 while 替代 for
说明:该方法可以减少局部变量的使用。比起效率优化,更能直接看到的是字符数量的优化。该做法有程序员强迫症的嫌疑。
4. 条件分支
4.1 将条件分支,按可能性顺序从高到低排列
说明:可以减少解释器对条件的探测次数。
4.2 在同一条件子的多( >2 )条件分支时,使用 switch 优于 if
说明: switch 分支选择的效率高于 if ,在 IE 下尤为明显。 4 分支的测试, IE 下 switch 的执行时间约为 if 的一半。
4.3 使用三目运算符替代条件分支
服用前:
if (a > b) {
num = a;
} else {
num = b;
}
服用后:
num = a > b ? a : b;
5. 定时器
5.1 需要不断执行的时候,优先考虑使用 setInterval
说明: setTimeout 每一次都会初始化一个定时器。 setInterval 只会在开始的时候初始化一个定时器
5.2 使用 function 而不是 string
说明:如果把字符串作为 setTimeout 和 setInterval 的参数,浏览器会先用这个字符串构建一个 function 。
6. 其他
6.1 尽量不使用动态语法元素
说明: eval 、 Function 、 execScript 等语句会再次使用 javascript 解析引擎进行解析,需要消耗大量的执行时间。
6.2 重复使用的调用结果,事先保存到局部变量
说明:避免多次取值的调用开销。
6.3 使用直接量
说明:
6.4 避免使用 with
说明: with 虽然可以缩短代码量,但是会在运行时构造一个新的 scope 。
OperaDev 上还有这样的解释,使用 with 语句会使得解释器无法在语法解析阶段对代码进行优化。对此说法,无法验证。
6.5 巧用 || 和 && 布尔运算符
重要程度: ★★★
6.6 类型转换
说明:
1). 数字转换成字符串,应用 "" + 1 ,性能上: ("" +) > String() > .toString() > new String() ;
2). 浮点数转换成整型,不使用 parseInt() , parseInt() 是用于将字符串转换成数字,而不是浮点数和整型之间的转换,建议使用 Math.floor() 或者 Math.round()
3). 对于自定义的对象,推荐显式调用 toString() 。内部操作在尝试所有可能性之后,会尝试对象的 toString() 方法尝试能否转化为 String 。
二.内存管理
2.1 循环引用
说明:如果循环引用中包含 DOM 对象或者 ActiveX 对象,那么就会发生内存泄露。内存泄露的后果是在浏览器关闭前,即使是刷新页面,这部分内存不会被浏览器释放。
简单的循环引用:
但是通常不会出现这种情况。通常循环引用发生在为 dom 元素添加闭包作为 expendo 的时候。
如:
init 在执行的时候,当前上下文我们叫做 context 。这个时候, context 引用了 el , el 引用了 function , function 引用了 context 。这时候形成了一个循环引用。
下面 2 种方法可以解决循环引用:
1) 置空 dom 对象
将 el 置空, context 中不包含对 dom 对象的引用,从而打断循环应用。
如果我们需要将 dom 对象返回,可以用如下方法:
2) 构造新的 context
把 function 抽到新的 context 中,这样, function 的 context 就不包含对 el 的引用,从而打断循环引用。
2.2 通过 javascript 创建的 dom 对象,必须 append 到页面中
说明: IE 下,脚本创建的 dom 对象,如果没有 append 到页面中,刷新页面,这部分内存是不会回收的!
示例代码:
2.3 释放 dom 元素占用的内存
说明:
将 dom 元素的 innerHTML 设置为空字符串,可以释放其子元素占用的内存。
在 rich 应用中,用户也许会在一个页面上停留很长时间,可以使用该方法释放积累得越来越多的 dom 元素使用的内存。
2.4 释放 javascript 对象
说明:在 rich 应用中,随着实例化对象数量的增加,内存消耗会越来越大。所以应当及时释放对对象的引用,让 GC 能够回收这些内存控件。
对象: obj = null
对象属性: delete obj.myproperty
数组 item :使用数组的 splice 方法释放数组中不用的 item
2.5 避免 string 的隐式装箱
说明:对 string 的方法调用,比如 'xxx'.length ,浏览器会进行一个隐式的装箱操作,将字符串先转换成一个 String 对象。推荐对声明有可能使用 String 实例方法的字符串时,采用如下写法:
var myString = new String('Hello World');
目录
优化前后对比
优化前 |
优化后 |
代码混乱,同样功能的函数重复出现在多个地方。如果需要修改实现,需要找到所有的地方。牵一发而动全身 |
模块化,提取公共接口组织为库、结构清晰、方便代码重用、并且能够游戏防止变量污染问题。 |
JavaScript文件未压缩,size比较大加载消耗网络耗时,阻塞页面渲染
|
JavaScript公共库文件使用UglifyJS压缩: n Size比较小优化了网络加载时间 n 压缩混淆了代码,在一定程度上保护代码 |
使用时需要加载多个单独的JavaScript文件,增加了http请求数降低性能 |
对公共库合并压缩在减少size的同时,减少http请求数 |
缺乏文档(让后面的开发者对已有功能不清楚,这在一定程度上造成前面说的,同样功能的函数重复出现在多个地方) |
公共库中每个类、函数、属性都有说明文档 |
l 模块化(类编程):代码清晰、有效防止变量污染问题、代码重用方便扩展等;
l JavaScript压缩混淆:减少size优化加载时间,混淆保护代码;
l JavaScript文件合并:减少http请求优化网络耗时提升性能;
l 生成文档:方便公共库的使用,查找接口方便。
对于静态类来说JavaScript实现比较简单,使用Object直接量就已经够用了;但是要创建实例化、可继承经典的类需要做一番工作。因为JavaScript是基于原型的(prototype-based)编程语言,并没有包含内置类的实现(它没有访问控制符,它没有定义类的关键字class,它没有支持继承的extend或冒号,它也没有用来支持虚函数的virtual等),但是我们通过JavaScript可以轻易地模拟出经典的类。
根据宝宝JS公共接口的特性,它们不需要实例化,所以优化使用了该方式。下面以PetConfigParser为例介绍下实现方式:
PetConfigParser类 |
var PetConfigParser; if (!PetConfigParser) { PetConfigParser = {}; }
(function () { //private 变量、函数 /** * 宝宝所有配置字典,以【cate * 10000 + (lvl - 1) * 10 + dex - 1】为key * @attribute petDic * @type {Object} * @private */ var petDic = null; //宝宝字典
/** * 根据__pet_config构建一个Object字典,以cate、dex、lvl组合作为key * @method buildPetDic * @private * @return {void} */ function buildPetDic() { petDic = new Object(); for (var item in __pet_config) { var lvl = parseInt(__pet_config[item]['lvl']); var dex = parseInt(__pet_config[item]['dex']); var cate = parseInt(__pet_config[item]['cate']); var key = cate * 10000 + (lvl - 1) * 10 + dex; petDic[key] = __pet_config[item]; } }
//public 接口
/** * 根据宝宝id,读取__pet_config中对应宝宝的信息 * @method getPetById * @param {String/int} petId 宝宝id * @return {Object} pet 宝宝的所有静态信息,如{id:"300003289", lvl:"1", dex:"2", price:"200", life:"2592000", cate:"3", name:"飞天小使等级1熟练2", intro:"", skill:"护身符", skill1_prob:"30", skill2_prob:"0"} */ if (typeof PetConfigParser.getPetById !== 'function') { PetConfigParser.getPetById = function (petId) { var pet = ("undefined" == typeof (__pet_config)) ? null : __pet_config["pet_" + petId]; return pet; } } })(); |
这种方式利用了JavaScript匿名函数来创建私有作用域,这些私有作用域只能在内部访问。总结上述过程分为以下几个步骤:
1) 定义一个全局的变量(var PetConfigParser),注意变量首字母大写与普通变量区别;
2) 然后创建一个匿名函数并运行( (function () {/*xxxx*/ })(); ),在匿名函数内部创建局部变量和函数,它们只能在当前作用域中被访问到;
3) 全局变量(var PetConfigParser)可以在任何地方访问到,在匿名函数内部操作PetConfigParser添加静态函数。
使用实例:
$(function () {
DialogManager.init();
$('#showDialog').click(function () {
DialogManager.show("#msgBoxTest", "#closeId");
return false;
});
$('#cofirmBtn').click(function () {
DialogManager.hide();
return false;
});
})
JavaScript实现经典的类,总结有三种方法:
n 构造函数方式;
n 原型方式;
n 构造函数+原型的混合方式
构造函数用来初始化实例对象的属性和值。任何JavaScript函数都可以用作构造函数,构造函数必须使用new运算符作为前缀来创建新的实例。
构造函数方式 |
var Person = function (name) { this.name = name; this.sayName = function(){ alert(this.name); }; }
//实例化 var tyler = new Person("tylerzhu"); var saylor = new Person("saylorzhu"); tyler.sayName(); saylor.sayName(); //检查实例 alert(tyler instanceof Person); |
构造函数方式跟传统的面向对象语言是不是很相识!只不过是class关键字用function替换了。
注意:不要省略new否则Person(“tylerzhu”) //==>undefined。当使用new关键字来调用构造函数时,执行上下文(context)从全局对象(window)变成一个空的上下文,这个上下文代表了新生成的实例。因此,this关键子指向当前创建的实例。所以省略new时,没有进行上下文切换会在全局对象中查找name,没有找到而创建一个全局变量name返回undefined。
构造函数方式简单,但是存在一个浪费内存的问题。如上面的例子中实例化了两个对象tyler、saylor,表面上好像没什么问题,但是实际上对于每一个实例对象,sayName()方法都是一模一样的内容,每一次生成一个实例,都必须为重复的内容申请内容。
alert(tyler. sayName == saylor. sayName) 输出false!!!
Javascript中每一个构造函数都有一个prototype属性,指向另一个对象。这个对象的所有属性和方法,都会被构造函数的实例共享。
原型方式 |
var Person = function (name) { Person.prototype = name; Person.prototype.sayName = function(){ alert(this.name); } }
//实例化 var tyler = new Person("tylerzhu"); var saylor = new Person("saylorzhu"); tyler.sayName(); saylor.sayName();
//检查实例 alert(tyler instanceof Person); |
这时tyler、saylor实例的sayName方法,都是同一个内存地址(指向prototype对象),因此原型方法更节省内存。
但是看tyler.sayName();saylor.sayName();两者输出,会看出问题 —— 它们都输出“saylorzhu”。因为原型所有属性都共享,只要一个实例改变其他的都会跟着改变,所以实例化对象saylor覆盖了tyler。
构造函数方式可以为同一个类的每一个对象分配不同的内存,这很适合写类的时候设置属性;但是设置方法的时候我们就需要让同一个类的不同对象共享同一个内存了,写方法用原型的方式最好。所以写类的时候需要把构造方法和原型两种方式混合着用(很多类库提供的创建类的方法或框架的写类方式本质上都是:构造函数+原型)。
构造函数+原型 |
var Person = function (name) { Person.prototype = name; Person.prototype.sayName = function(){ alert(this.name); } }
//实例化 var tyler = new Person("tylerzhu"); var saylor = new Person("saylorzhu"); tyler.sayName(); saylor.sayName(); //检查实例 alert(tyler instanceof Person); |
这样即可通过构造函数构造不同name的人,对象实例也都共享sayName方法,不会造成内存浪费。
JavaScript代码压缩混淆的意义:简单的说就是为了减小js文件大小,去掉多余的注释和换行缩进等,使得下载起来更快,提高用户体验。
JavaScript压缩工具有很多,我推荐使用jQuery现在使用的工具UglifyJS(jQuery以前也使用过多种压缩工具,如Packer),因为它压缩性能很好。
“jQuery 1.5 发布的时候 john resig 大神说所用的代码优化程序从Google Closure切换到UglifyJS,新工具的压缩效果非常令人满意”
下面是官方性能对比:We’re still a lot better than YUI in terms of compression, though slightly slower. We’re still a lot faster than Closure, and compression after gzip is comparable.
File |
UglifyJS |
UglifyJS +gzip |
Closure |
Closure +gzip |
YUI |
YUI +gzip |
jquery-1.6.2.js |
91001 (0:01.59) |
31896 |
90678 (0:07.40) |
31979 |
101527 (0:01.82) |
34646 |
paper.js |
142023 (0:01.65) |
43334 |
134301 (0:07.42) |
42495 |
173383 (0:01.58) |
48785 |
prototype.js |
88544 (0:01.09) |
26680 |
86955 (0:06.97) |
26326 |
92130 (0:00.79) |
28624 |
thelib-full.js |
251939 (0:02.55) |
72535 |
249911 (0:09.05) |
72696 |
258869 (0:01.94) |
76584 |
UglifyJS是基于 NodeJS 的Javascript语法解析/压缩/格式化工具,所以我们要安装NodeJS。
N ode.js is a platform built on Chrome's JavaScript runtime for easily building fast, scalable network applications. Node.js uses an event-driven, non-blocking I/O model that makes it lightweight and efficient, perfect for data-intensive real-time applications that run across distributed devices.
JavaScript最早是运行在浏览器中,然而浏览器只是提供了一个上下文,它定义了使用JavaScript可以做什么,但并没有“说”太多关于JavaScript语言本身可以做什么。事实上,JavaScript是一门“完整”的语言: 它可以使用在不同的上下文中,其能力与其他同类语言相比有过之而无不及。Node.js事实上就是另外一种上下文,它允许在后端(脱离浏览器环境)运行JavaScript代码。
要实现在后台运行JavaScript代码,代码需要先被解释然后正确的执行。Node.js的原理正是如此,它使用了Google的V8虚拟机(Google的Chrome浏览器使用的JavaScript执行环境),来解释和执行JavaScript代码。
除此之外,伴随着Node.js的还有许多有用的模块,它们可以简化很多重复的劳作,比如向终端输出字符串。因此,Node.js事实上既是一个运行时环境,同时又是一个库。
l Windows下面直接下载exe文件执行即可。(http://nodejs.org/)
C:\Users\tyler>node -v
v0.8.2
l 设置代理(公司网络不设置代理无法下载,外网环境不需要)
“npm,全称是"node packagemanager",它是node包管理器,第三方的package全是通过npm去安装的。”
n 为npm设在代理
npm config set proxy=http://proxy.tencent.com:8080
n 为npm默认选择http方式,不选用https
npm config set registry http://registry.npmjs.org
l npm安装uglify-js
npm –g install uglify-js
l 验证安装是否成功
C:\Users\tyler>npm -v
1.1.36
uglifyjs [ 选项... ] [ 文件 ]
文件参数应该放在选项后面,uglifyjs 会读取文件中的javascript代码进行处理。如果你不指定输出的文件名,那么他会把处理后的内容输出到命令行中。
支持的选项 :
l -b 或 --beautify - 输出格式化代码,当传入该参数,下面的附加选项用于更美观的控制格式化:
n -i N 或 --indent N - 缩进级别(空格数量)
n -q 或 --quote-keys - 是否用引号引起字符串对象的键(默认只会引起不能被正确标志的键名)
l --ascii -默认 UglifyJS 不处理字符编码而直接输出 Unicode 字符,通过传入该参数将非ASCII编码的字符转化为\cXXXX的序列(输出总按照UTF8编码,但传入该选项能得到ASCII编码的输出)。
l -nm 或 --no-mangle - 不改变变量名称
l -ns 或 --no-squeeze - 不调用 ast_squeeze() 函数(该函数会做多种优化使得结果更小,可读性略有降低)
l -mt 或 --mangle-toplevel - 在顶级作用域打乱变量名称(默认不开启)
l --no-seqs - 当调用 ast_squeeze() 将会合并多个语句块为一个语句块,如 "a=10; b=20; foo()" 将被转换为 "a=10,b=20,foo()"
l --no-dead-code - 默认 UglifyJS 将会删除不被用到的代码,传入该参数禁用此功能。
l -nc 或 --no-copyright - 默认 uglifyjs 会在输出后的代码中添加版权信息等注释代码,传入该参数禁用此功能。
l -o 文件名 或 --output 文件名 - 指定输出文件名,如果不指定,则打印到标准输出(STDOUT)
l --overwrite - 如果传入的JS代码来自文件而不是标准输入,传入该参数,输出会覆盖该文件。
l --ast - 传入该参数会得到抽象的语法树而不是Javascript,对调试或了解内部代码很有用。
l -v 或 --verbose - 在标准错误输出一些信息(目前的版本仅输出操作用时)
l --extra - 开启附加优化,这些优化并未得到全面的测试。
l --unsafe - 开启其他附加优化,这些优化已知在特定情况下并不安全,目前仅支持:
l foo.toString() ==> foo+””
l --max-line-len (默认32K字节) - 在32K字节出增加换行符,传入0禁用此功能。
l --reserved-names - 一些类库会依赖一些变量,该参数指定的名称不会被混淆掉,多个用逗号隔开
下面是我们使用uglifyjs压缩,PetConfigParser.js的例子:
uglifyjs -nc -mt PetConfigParser.js > PetConfigParser.min.js
PetConfigParser.js压缩前后对比
规则1——减少HTTP请求(Minimize HTTP Requests)
Yahoo前端优化性能规则[5]
只有10%~20%的最终用户响应时间花在接收请求的HTML文档上,剩下的80%~90%时间都花在HTML文档所引用的所有组件(图片、脚本、样式表、Flash等)进行的HTTP请求上。因此,改善响应时间最简单的办法就是减少组件数量并由此减少HTTP请求数。
对公共库合并压缩在减少size的同时,减少http请求优化网络耗时提升性能。
YUIDoc 是一个基于 Node.js 的应用程序,用来根据 JavaScript 的注释中生成 API 文档,类似 JavaDoc、ASDoc,这也是当前 YUI 用来生成文档的工具。
l YUIDoc安装
与UglifyJS一样,YUIDoc也是基于Nodejs的一个应用程序,使用npm安装即可。
npm -g install yuidocjs.
校验安装是否成功
C:\Users\tyler>yuidoc -v
0.3.15
l 生成文档(一次性生成)
yuidoc .
一次性生成该目录及其子目录下所有JS的文档 默认在不配置的情况下会生成在当前目录的out目录中。
-o, --out <directory path> Path to put the generated files (defaults to ./out)
l 生成文档(实时生成)
YUIDoc还提供了一种实时文档生成的方式,有利于团队协作开发 比如在SVN上部署YUIDoc实时文档,递交到SVN的代码都会及时生成文档提供团队使用查阅
yuidoc --server
默认开放监听当前目录文件变动,开放3000端口 可以通过
http://127.0.0.1:3000/
来访问文档 如果3000端口被占用,也可以指定特定端口号
yuidoc --server 5000
来通过开放5000端口提供文档访问
要使用YUIDoc,那么所有注释都得安装YUIDoc的标准来,否则不能正确解析出文档。YUIDoc使用的标签和其它语言类同,比较容易理解。下面不详细说明每个标签,只列举几个例子,具体可参加官方文档。例如:
对PetConfigParser类进行注释:
/**
* 宝宝配置文件解析,及提供查询宝宝配置相关的操作方法<br/>
* 1. getPetById 根据宝宝id获取对应宝宝的信息<br/>
* 2. getPetName 根据宝宝的id,读取宝宝信息,然后拼接出宝宝的名字,如3+10天蝎宝宝<br/>
* 等等<br/>
* @author tylerzhu
* @class PetConfigParser
* @static
*/
对类中的变量进行注释:
/**
* 宝宝所有配置字典,以【cate * 10000 + (lvl - 1) * 10 + dex - 1】为key
* @attribute petDic
* @type {Object}
* @private
*/
对类中函数进行注释:
/**
* 根据宝宝id,读取__pet_config中对应宝宝的信息
* @method getPetById
* @param {String/int} petId 宝宝id
* @return {Object} pet 宝宝的所有静态信息,如{id:"300003289", lvl:"1", dex:"2", price:"200", life:"2592000", cate:"3", name:"飞天小使等级1熟练2", intro:"", skill:"护身符", skill1_prob:"30", skill2_prob:"0"}
*/
[1] NodeJS,http://nodejs.org/
[2] UglifyJS,https://github.com/mishoo/UglifyJS/
[3] 用UglifyJS解析/压缩/格式化你的Javascript,http://goo.gl/bwf8U
[4] Yahoo前端优化性能规则,http://goo.gl/nfEBg
[5] 用YUIDoc文档化JavaScript代码,http://goo.gl/5RJxn
[6] YUIDoc官方,http://yui.github.com/yuidoc/
作者:吴秦
出处:http://www.cnblogs.com/skynet/
本文基于署名 2.5 中国大陆许可协议发布,欢迎转载,演绎或用于商业目的,但是必须保留本文的署名