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

深入

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

声明

前端开发whqet,关注前端开发,分享相关资源。csdn专家博客,王海庆希望能对您有所帮助,限于作者水平有限,出错难免,欢迎拍砖!

欢迎任何形式的转载,烦请注明装载,保留本段文字。

本文原文链接,http://blog.csdn.net/whqet/article/details/43865127

欢迎大家访问独立博客http://whqet.github.io

时间: 2024-08-26 00:42:09

Javascript Tips & Tricks的相关文章

45 Useful JavaScript Tips, Tricks and Best Practices(有用的JavaScript技巧,技巧和最佳实践)

As you know, JavaScript is the number one programming language in the world, the language of the web, of mobile hybrid apps (like PhoneGap or Appcelerator), of the server side (like NodeJS or Wakanda) and has many other implementations. It’s also the

JavaScript Tips

# JavaScript Tips -------------------------- 1. return false - event.preventDefault();  //阻止默认行为 P.S 阻止a标签的跳转 - event.stopPropagation(); //阻止事件冒泡 2. reset 事实上很少需要用到reset,多数需求为返回上一页面 P.S 注册时不打算注册了,而不是想要把之前填的很多内容清空 3. form1.elements[0] or name 使用方便,当然当

Productivity tips, tricks and hacks for academics (2015 edition)

Productivity tips, tricks and hacks for academics (2015 edition) Contents Jump to: My philosophy: Optimize transaction costs. Don't work from home. Eliminate temptation to waste time. Salvage dead time with technology. Get rid of your TV. Taming emai

iOS 7 Development Tips, Tricks &amp; Hacks

iOS 7 Development Tips, Tricks & Hacks September 18, 2013 Like with any new iOS version there are a bunch of new tricks and hacks to work out. Here are a few things that weren't immediately obvious to me, it's in no way a complete set, just things th

JavaScript tips:window.onload与$(document).ready()的差异

1.window.onload在$(document).ready()之后执行 原因:二者触发与否,都是依据document.readyState这个属性. (1)document.readyState的值:uninitialized.loading.interactive.complete(这四个值,依据时间先后排序). (2)当其值为interactive时,会触发documentContentLoaded事件,JQuery.ready()就是对documentContentLoaded事件

javascript tips and snippets

数字化的Date js中,Date是内置的一种数据类型,虽然直接输出的date格式适合人们阅读,但是对于机器程序来说却有些不方便: var now=new Date(); console.log(now);//Wed Nov 25 2015 22:02:17 GMT+0800 (中国标准时间) 怎么转换为便于js程序来操作的形式呢?很简单只要在new关键字前面加上一个+即可: var now=+new Date(); console.log(now);//1448460291345 javasc

JavaScript Tips: 原型链 prototype

当Bar.prototype = new Foo(); $(function () { var test = new Bar(); console.log(test);//Bar test.method();//method log console.log(test.foo);//Hello World console.log(test.value);//42 var footest = new Foo(); console.log(footest);//Foo console.log(foot

JavaScript tips:数组去重

1.实现目标:数组去重 2.实现思路: (1)创建新数组. (2)遍历原数组,判断当前被遍历元素是否存在于新数组,如果存在于新数组,则判断当前被遍历元素是重复的:如果不存在于新数组,则判断当前被遍历元素不是重复的,则将其压入新数组. (3)遍历完毕原数组后,返回新数组. 3.具体实现:根据对当前元素是否重复的判断方法不同,有四种不同的具体实现. (1)利用hash表保存被遍历元素是否重复的状态. 1 function unique( arr ) { 2 var n = {}, //hash表,用

Office 365 - SharePoint Tips &amp; Tricks

1. Recycle Bin 地址: //管理员 /_layouts/15/AdminRecycleBin.aspx //普通用户 /_layouts/15/RecycleBin.aspx 2.