Javascript Tips & Tricks

原创 2015年02月20日 05:54:19

前端开发规范系列文章之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;                      //返回置乱后数组
}

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

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"

检测html标签属性

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

//检测属性
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

悬挂(Hoisting)

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

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)

立即调用函数表达式(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

版权声明:本文为博主原创文章,未经博主允许不得转载。

相关文章推荐

45 Useful JavaScript Tips, Tricks and Best Practices

By Saad Mousliki As you know, JavaScript is the number one programming language in the world, the...

Visual Studio .NET Tips and Tricks

  • 2007年07月22日 14:44
  • 2.78MB
  • 下载

训练CNN你需要知道的tricks/tips

Introduction本文假设阅读者有基本的NN基础,涉及的tips有一下几点: - data augmentation - pre-process on images - initia...

tips and tricks ultimate

  • 2008年07月19日 23:56
  • 8.98MB
  • 下载

25 iOS App Performance Tips & Tricks

This is a post by iOS Tutorial Team Member Marcelo Fabri, an iOS developer working at Movile. Chec...

101 Google Tricks Tips and Hacks

  • 2013年10月18日 22:37
  • 246KB
  • 下载
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:Javascript Tips & Tricks
举报原因:
原因补充:

(最多只允许输入30个字)