Javascript Tips & Tricks

前端开发规范系列文章之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

深入

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

  • 45 Useful JavaScript Tips, Tricks and Best Practices
  • 12 Simple (Yet Powerful) JavaScript Tips
  • 13 JavaScript Tips & Tricks
  • 21 JavaScript Tips And Tricks For JavaScript Developers
  • CSS-Tricks Javascript Snippets
  • JavaScript Snippets
  • JavaScript Snippets
  • 5 Array Methods That You Should Be Using Now
  • JS-Tricks
  • ten-useful-javascript-tips-best-practices
  • javascript-performance-tips-tricks
  • Superpower your JavaScript with 10 quick tips - For Beginners

声明

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

你可能感兴趣的:(JavaScript,规范,前端开发,tips,Tricks)