前端开发规范系列文章之Javascript Tips and Tricks,本意是写成常用代码收集、常用技巧整理的文章,感觉“常用代码大全”太土、“实用代码整理”有失偏颇,“提示与技巧”不够稳重,所以使用常用的英语说法,相信广大程序员都懂得。
Javascript美妙之处,需要我们静静体会,慢慢吸收,然后让代码在您指下曼舞。整理的这些代码我们称之为妙味,请大家细品。
博主会不断更新本文,方便大家阅读起见,我们采用倒序更新的方式,把最新更新的放最上方。
我们知道IE和标准浏览器在事件处理方面有很大不同,所以我们在使用的时候需要首先进行统一化处理,统一处理时有两种方式,shim和polyfill。
shim将新的api引入新的环境中,例如下面的代码使用addEvent和removeEvent来添加删除事件。
// 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等。
// EventListener | CC0 | github.com/jonathantneal/EventListener
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的时候就交换两个数组项的顺序,否则就不交换。如下代码所示。
yourArray.sort(function() { return 0.5 - Math.random() });
但是这种方式执行效率比较低,我还需探索更为高效的置乱方式。
/* 添加原型方法的方式 */
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;
};
这种方式已经比较高效,但是可控性不强,不能指定随机方法,我们又进行了改进。
/* 带参数的置乱数组原型方法 * 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; //返回置乱后数组
}
es5中具有删除空格的原生方法String.trimLeft()、 String.trimRight()和 String.trim(),这些方法在标准浏览器中兼容性良好,trim方法在ie 9+兼容良好,trimLeft和trimRight在IE中不兼容,不兼容的浏览器我们需要使用polyfill。
/* 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"
有的时候我们想知道某个元素是否具备某属性,我们可以使用该函数检测。
//检测属性
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中,==和===都表示相等,但是==会在需要的时候进行类型转换,而===则不会进行类型转换,会比较数据类型和数据数值,比==执行速度快。
[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
首先来看段代码,大家先猜下运行结果。
var a = 1;
function go(){
console.log(a);
var a = 2;
}
go();
运行结果为: undefined,你猜对了吗?我们接下来看看为啥?
悬挂,也即所有函数体内的变量声明(注意,仅仅是声明)都将被提到函数体开头进行。所以上面这段代码实际上是这样执行的。
var a;
a = 1;
function go(){
var a;
console.log(a);
a = 2;
}
go();
立即调用函数表达式(IIFE, Immediately Invoked Function Expressions)是Javascript里面常用特性,我们可以利用它“避免污染全局变量”、“解决闭包冲突”、“只执行一次的函数”、“减少重复创建比较大的对象的开销(常见在一些工具函数内部,保存正则对象,数组,长字符串等对象”等。
/*简化版的IIFE*/
(function(){
//您的代码
})();
模拟块作用域,避免污染全局变量,常见的插件即是如此。
/* jquery1.9.0的写法 */
(function( window, undefined ) {
//非常长的代码
})( window );
解决闭包冲突,我们知道闭包可以让“函数内部所定义的函数持有外层函数的执行环境”,然而也有可能有些问题,例如下面的代码。
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解决这个问题,修正过的代码如下。
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。
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可以这么写。
/*把IIFE和逻辑运算符配合使用,检测是否需要运行polyfill*/
this.Element &&
Element.prototype.attachEvent && !Element.prototype.addEventListener &&
(function () {
//polyfill
}
使用&&和||条件运算符,我们可以达到简化操作的目的,来看下面代码。
/* 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