Javascrit Tips & Tricks

前端开发规范系列文章之Javascript Tips and Tricks,本意是写成常用代码收集、常用技巧整理的文章,感觉“常用代码大全”太土、“实用代码整理”有失偏颇,“提示与技巧”不够稳重,所以使用常用的英语说法,相信广大程序员都懂得。

##妙味
Javascript美妙之处,需要我们静静体会,慢慢吸收,然后让代码在您指下曼舞。整理的这些代码我们称之为妙味,请大家细品。
博主会不断更新本文,方便大家阅读起见,我们采用倒序更新的方式,把最新更新的放最上方

事件处理

我们知道IE和标准浏览器在事件处理方面有很大不同,所以我们在使用的时候需要首先进行统一化处理,统一处理时有两种方式,shim和polyfill。
shim将新的api引入新的环境中,例如下面的代码使用addEvent和removeEvent来添加删除事件。

1
// Shim for DOM Events for IE7-
// http://www.quirksmode.org/blog/archives/2005/10/_and_the_winner_1.html
// Use addEvent(object, event, handler) instead of object.addEventListener(event, handler)
window.addEvent = function(obj, type, fn) {
	if (obj.addEventListener) {
		obj.addEventListener(type, fn, false);
	} else if (obj.attachEvent) {
		obj["e" + type + fn] = fn;
		obj[type + fn] = function() {
			var e = window.event;
			e.currentTarget = obj;
			e.preventDefault = function() {
				e.returnValue = false;
			};
			e.stopPropagation = function() {
				e.cancelBubble = true;
			};
			e.target = e.srcElement;
			e.timeStamp = Date.now();
			obj["e" + type + fn].call(this, e);
		};
		obj.attachEvent("on" + type, obj[type + fn]);
	}
};

window.removeEvent = function(obj, type, fn) {
	if (obj.removeEventListener) {
		obj.removeEventListener(type, fn, false);
	} else if (obj.detachEvent) {
		obj.detachEvent("on" + type, obj[type + fn]);
		obj[type + fn] = null;
		obj["e" + type + fn] = null;
	}
};

polyfill让旧浏览器支持新功能,使用方式和标准浏览器一样,例如下面的EventListener polyfill在IE9-浏览器上实现addEventListener、removeEventListener、dispatchEvent和customEvent等。

1
//
this.Element && Element.prototype.attachEvent && !Element.prototype.addEventListener && (function () {
	function addToPrototype(name, method) {
		Window.prototype[name] = HTMLDocument.prototype[name] = Element.prototype[name] = method;
	}
	// add
	addToPrototype("addEventListener", function (type, listener) {
		var
		target = this,
		listeners = target.addEventListener.listeners = target.addEventListener.listeners || {},
		typeListeners = listeners[type] = listeners[type] || [];

		// if no events exist, attach the listener
		if (!typeListeners.length) {
			target.attachEvent("on" + type, typeListeners.event = function (event) {
				var documentElement = target.document && target.document.documentElement || target.documentElement || { scrollLeft: 0, scrollTop: 0 };

				// polyfill w3c properties and methods
				event.currentTarget = target;
				event.pageX = event.clientX + documentElement.scrollLeft;
				event.pageY = event.clientY + documentElement.scrollTop;
				event.preventDefault = function () { event.returnValue = false };
				event.relatedTarget = event.fromElement || null;
				event.stopImmediatePropagation = function () { immediatePropagation = false; event.cancelBubble = true };
				event.stopPropagation = function () { event.cancelBubble = true };
				event.target = event.srcElement || target;
				event.timeStamp = +new Date;

				// create an cached list of the master events list (to protect this loop from breaking when an event is removed)
				for (var i = 0, typeListenersCache = [].concat(typeListeners), typeListenerCache, immediatePropagation = true; immediatePropagation && (typeListenerCache = typeListenersCache[i]); ++i) {
					// check to see if the cached event still exists in the master events list
					for (var ii = 0, typeListener; typeListener = typeListeners[ii]; ++ii) {
						if (typeListener == typeListenerCache) {
							typeListener.call(target, event);

							break;
						}
					}
				}
			});
		}

		// add the event to the master event list
		typeListeners.push(listener);
	});

	// remove
	addToPrototype("removeEventListener", function (type, listener) {
		var
		target = this,
		listeners = target.addEventListener.listeners = target.addEventListener.listeners || {},
		typeListeners = listeners[type] = listeners[type] || [];

		// remove the newest matching event from the master event list
		for (var i = typeListeners.length - 1, typeListener; typeListener = typeListeners[i]; --i) {
			if (typeListener == listener) {
				typeListeners.splice(i, 1);

				break;
			}
		}

		// if no events exist, detach the listener
		if (!typeListeners.length && typeListeners.event) {
			target.detachEvent("on" + type, typeListeners.event);
		}
	});

	// dispatch
	addToPrototype("dispatchEvent", function (eventObject) {
		var
		target = this,
		type = eventObject.type,
		listeners = target.addEventListener.listeners = target.addEventListener.listeners || {},
		typeListeners = listeners[type] = listeners[type] || [];

		try {
			return target.fireEvent("on" + type, eventObject);
		} catch (error) {
			if (typeListeners.event) {
				typeListeners.event(eventObject);
			}

			return;
		}
	});

	// CustomEvent
	Object.defineProperty(Window.prototype, "CustomEvent", {
		get: function () {
			var self = this;

			return function CustomEvent(type, eventInitDict) {
				var event = self.document.createEventObject(), key;

				event.type = type;
				for (key in eventInitDict) {
					if (key == 'cancelable'){
						event.returnValue = !eventInitDict.cancelable;
					} else if (key == 'bubbles'){
						event.cancelBubble = !eventInitDict.bubbles;
					} else if (key == 'detail'){
						event.detail = eventInitDict.detail;
					}
				}
				return event;
			};
		}
	});

	// ready
	function ready(event) {
		if (ready.interval && document.body) {
			ready.interval = clearInterval(ready.interval);

			document.dispatchEvent(new CustomEvent("DOMContentLoaded"));
		}
	}

	ready.interval = setInterval(ready, 1);

	window.addEventListener("load", ready);
})();

!this.CustomEvent && (function() {
	// CustomEvent for browsers which don't natively support the Constructor method
	window.CustomEvent = function CustomEvent(type, eventInitDict) {
		var event;
		eventInitDict = eventInitDict || {bubbles: false, cancelable: false, detail: undefined};

		try {
			event = document.createEvent('CustomEvent');
			event.initCustomEvent(type, eventInitDict.bubbles, eventInitDict.cancelable, eventInitDict.detail);
		} catch (error) {
			// for browsers which don't support CustomEvent at all, we use a regular event instead
			event = document.createEvent('Event');
			event.initEvent(type, eventInitDict.bubbles, eventInitDict.cancelable);
			event.detail = eventInitDict.detail;
		}

		return event;
	};
})();

置乱数组

Javascript中置乱数组的一种方式,sort方法可以接受一个 函数为参数,当函数返回值为1的时候就交换两个数组项的顺序,否则就不交换。如下代码所示。

1
yourArray.sort(function() { return 0.5 - Math.random() });

但是这种方式执行效率比较低,我还需探索更为高效的置乱方式。

1
2
3
4
5
/* 添加原型方法的方式 */
Array.prototype.shuffle = Array.prototype.shuffle || function(){
for(var j, x, i = this.length; i; j = parseInt(Math.random() * i), x = this[--i], this[i] = this[j], this[j] = x);
return this;
};

这种方式已经比较高效,但是可控性不强,不能指定随机方法,我们又进行了改进。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/* 带参数的置乱数组原型方法
* copy默认值为false,copy为true时 ,返回置乱的数组,不影响数组本身
* rng为置乱函数,默认为Math.random
* */

Array.prototype.shuffle = Array.prototype.shuffle || function(copy, rng){
var that = this, //保证原数组不受影响
i = this.length, //循环指针,初始值为数组长度
r, //随机数
t; //交换时的临时变量

copy = copy || false; //参数默认值
rng = rng || Math.random;
copy&&(that=this.slice()); //copy为true时,生成新的数组that
while(i){
r = Math.floor(rng() * i); //生成随机数
t=that[--i]; //交换数值
that[i]=that[r];
that[r]=t;
}
return that; //返回置乱后数组
}

删除字符串中的空格(Trim String)

es5中具有删除空格的原生方法String.trimLeft()、 String.trimRight()和 String.trim(),这些方法在标准浏览器中兼容性良好,trim方法在ie 9+兼容良好,trimLeft和trimRight在IE中不兼容,不兼容的浏览器我们需要使用polyfill。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/* e5中trim方法的polyfill */
String.prototype.trim = String.prototype.trim || function () {
return this.replace(/^\s+|\s+$/g, "");
};
String.prototype.trimLeft = String.prototype.trimLeft || function () {
return this.replace(/^\s+/, "");
};
String.prototype.trimRight = String.prototype.trimRight || function () {
return this.replace(/\s+$/, "");
};
String.prototype.trimFull = String.prototype.trimFull || function () {
return this.replace(/(?:(?:^|\n)\s+|\s+(?:$|\n))/g, "").replace(/\s+/g, " ");
};

//使用
" hello world ".trimRight(); //" hello world"
" hello world ".trimLeft(); //"hello world "
" hello world ".trim(); //"hello world"
" hello world ".trimFull(); //"hello world"

检测html标签属性

有的时候我们想知道某个元素是否具备某属性,我们可以使用该函数检测。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//检测属性
function elementSupportsAttribute(element, attribute) {
var test = document.createElement(element);
if (attribute in test) {
return true;
} else {
return false;
}
}
//简化版检测属性
function elementSupportsAttribute(element, attribute) {
return !!(attribute in document.createElement(element));
};
//使用,检测textarea元素的placeholder属性
elementSupportsAttribute("textarea", "placeholder")?alert("ok"):alert("not");

尽量使用“===”

在javascript中,==和===都表示相等,但是==会在需要的时候进行类型转换,而===则不会进行类型转换,会比较数据类型和数据数值,比==执行速度快。

1
[10] === 10    // is false
[10] ==  10    // is true
'10' === 10    // is false
'10' ==  10    // is true
 []  === 0     // is false
 []  ==  0     // is true
 ''  === false // is false 
 ''  ==  false // is true but true == "a" is false

悬挂(Hoisting)

首先来看段代码,大家先猜下运行结果。

1
2
3
4
5
6
var a = 1;
function go(){
console.log(a);
var a = 2;
}
go();

运行结果为: undefined,你猜对了吗?我们接下来看看为啥?
悬挂,也即所有函数体内的变量声明(注意,仅仅是声明)都将被提到函数体开头进行。所以上面这段代码实际上是这样执行的。

1
2
3
4
5
6
7
8
var a;
a = 1;
function go(){
var a;
console.log(a);
a = 2;
}
go();

立即调用函数表达式(IIFE)

立即调用函数表达式(IIFE, Immediately Invoked Function Expressions)是Javascript里面常用特性,我们可以利用它“避免污染全局变量”、“解决闭包冲突”、“只执行一次的函数”、“减少重复创建比较大的对象的开销(常见在一些工具函数内部,保存正则对象,数组,长字符串等对象”等。

1
2
3
4
/*简化版的IIFE*/
(function(){
//您的代码
})();

模拟块作用域,避免污染全局变量,常见的插件即是如此。

1
2
3
4
/* jquery1.9.0的写法 */
(function( window, undefined ) {
//非常长的代码
})( window );

解决闭包冲突,我们知道闭包可以让“函数内部所定义的函数持有外层函数的执行环境”,然而也有可能有些问题,例如下面的代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var f1 = function() {
var res = [];
var fun = null;
for(var i = 0; i < 10; i++) {
fun = function() { console.log(i);};//产生闭包
res.push(fun);
}

return res;
}
// 会输出10个10,而不是预期的0 1 2 3 4 5 6 7 8 9
var res = f1();
for(var i = 0; i < res.length; i++) {
res[i]();
}

我们可以使用IIFE解决这个问题,修正过的代码如下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
var f1 = function() {
var res = [];
for(var i = 0; i < 10; i++) {
// 添加一个IIFE,立即执行
(function(index) {
fun = function() {console.log(index);};
res.push(fun);
})(i);
}

return res;
}

// 输出结果为0 1 2 3 4 5 6 7 8 9
var res = f1();
for(var i = 0; i < res.length; i++) {
res[i]();
}

模拟单例,javascript中我们可以使用IIFE实现OOP。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
var counter = (function(){
var i = 0;
return {
get: function(){
return i;
},
set: function( val ){
i = val;
},
increment: function() {
return ++i;
}
};
}());

counter.get(); // 0
counter.set( 3 );
counter.increment(); // 4
counter.increment(); // 5

配合逻辑运算符使用,例如addEventListener的polyfill可以这么写。

1
2
3
4
5
6
/*把IIFE和逻辑运算符配合使用,检测是否需要运行polyfill*/
this.Element &&
Element.prototype.attachEvent && !Element.prototype.addEventListener &&
(function () {
//polyfill
}

逻辑运算符妙用

使用&&和||条件运算符,我们可以达到简化操作的目的,来看下面代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
/* code one */
if (!theTitle) {
theTitle = "Untitled Document";
}
//使用||
theTitle = theTitle || "Untitled Document";

/* code two */
function isAdult(age) {
if (age && age > 17) {
return true;
}
else {
return false;
}
}
//使用 &&
function isAdult(age) {
return age && age > 17 ;
}

/* code three*/
if (userName) {
logIn (userName);
}
else {
signUp ();
}
//混合使用&&、||
userName && logIn (userName) || signUp ();

/*code four*/
var userID;
if (userName && userName.loggedIn) {
userID = userName.id;
}
else {
userID = null;
}
//使用&&、||
var userID = userName && userName.loggedIn && userName.id

##深入
本文的写作过程大量参考了以下文章,大家可以仔细阅读下面文章获得更深的体会。

##声明
前端开发whqet,关注前端开发,分享相关资源。csdn专家博客,王海庆希望能对您有所帮助,限于作者水平有限,出错难免,欢迎拍砖!
欢迎任何形式的转载,烦请注明装载,保留本段文字。
本文原文链接,http://blog.csdn.net/whqet/article/details/43865127
欢迎大家访问独立博客http://whqet.github.io