JavaScript核武库

以下是个人总结,也有一些是copy大神的,现在放到一起,方便以后查阅(有不对的地方,还望大家能够提出,我会尽快加以改正)。

!!强制转boolean

根据真值,假值 判断,返回truefalse假值: 0 “” null undefined false NaN

例如:
var nu=null;
var nul="";
var str="abcd";

console.log(!!nu)     // false;
console.log(!!nul)    // false;
console.log(!!str)    // true;

+obj 强制转Number

将对象强制转number,如果是纯数字的“88”字符串,可转为number

var a="88";
console.log(+a)   =>88
//但是如果是混合类型的字符串,则会转为NaN
var b="1606e";
console.log(+b)  => NaN

~~ 强制取整

~~ 12.34
 12
~~ 12.88
 12

不可靠的undefined 可靠的void 0

JavaScript中,假设我们想判断一个是否是 undefined,那么我们通常会这样写:

if(a === undefined){
  dosomething
}

//但是在javascript中,undefined是不可靠的

例如:
当undefined在函数内,并且是在局部变量是可以赋上值

function foo2(){
 var undefined=1;
 console.log(undefined)
}
foo2();  =>1;

但是当在函数内定义一个全局变量,并不能给赋上值

var undefined;
function foo2(){
undefined=1;
 console.log(undefined)
}
foo2()  // undefined
void 0或者 void (0):

最常见的用法是通过void 0 运算来获得 undefined,表达式为 0 时的运算开销最小:
那在以后需要判断值为undefined的时候,可以直接用void 0或者void (0)

字符串也是有length属性的!

我们知道所有的array都是有length,属性,就算事空数组,length 是0,那么字符串有没有呢?接下来我们来验证一下:

var str="sdfsd5565s6dfsd65sd6+d5fd5";
console.log(str.length)      // 26

结果是有的,所以我们在判断类型时,不能单纯拿有没有length属性来判断是不是数组了,我们可以用下面的方法:

var obj=[1,2] ;
console.log(toString.call(obj) === '[object Array]');

生成一个随机数组(创建数组,sort排序)

在项目中有时候我们需要一个随机打乱的数组,那么下面我们来实现以下:
先来创建一个数组:

var arr=[];
for(var i=0;i<10;i++){
   arr.push(i)
}
console.log(arr)    //  [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

接下来我们来打乱它:

arr.sort(()=>{
 return Math.random() - 0.5
})                 // [1, 0, 2, 3, 4, 6, 8, 5, 7, 9] 

第二种打乱方法:

arr.sort((a,b)=>{
   return a>Math.random()*10;
})               //  [1, 2, 0, 6, 4, 3, 8, 9, 7, 5]

我们以前的正常排序是这样的:

arr.sort(function(a,b){
   return b-a
});

解析:
先说正常的排序:
a,b表示数组中的任意两个元素,若return > 0 b前a后;reutrn < 0 a前b后;a=b时存在浏览器兼容 ,
a-b输出从小到大排序,b-a输出从大到小排序。
然后再说我们打乱的方法:
创建数组不用说,接下来就是用js的sort方法 来实现,Math.random()实现一个随机0-1之间的小数 然后再减去0.5,这时就会根据return比较后得到的值排,所以说就会生成不是正常从大到小或者从小到大的排序。

第二个打乱的方法同样是遵循sort的方法,将a,b传进去 然后和随机数做比较,关于比较的方法不太清楚。

去除前后、前、后 所有空格

这是专门为去除空格写的一套方法,适用于各种情况,所有空格,前后空格,前空格,后空格。

var strr=” 1 ad dertasdf sdfASDFDF DFG SDFG “
// type 1-所有空格 2-前后空格 3-前空格 4-后空格

function trim(str,type){
    switch (type){
        case 1:return str.replace(/\s+/g,"");
        case 2:return str.replace(/(^\s*)|(\s*$)/g, "");
        case 3:return str.replace(/(^\s*)/g, "");
        case 4:return str.replace(/(\s*$)/g, "");
        default:return str;
    }
}
console.log( trim(strr,1))      //  "1addertasdfsdfASDFDFDFGSDFG"

解析:
这个方法用的是正则的匹配格式,后面我会把正则单独拿出来总结一个系列,敬请期待!!!

\s : 空格符,Tab,换页符,换行符
  \S : 非\s的所有内容
  /g :  全局匹配
  ^  :  匹配在行首
  $  :  匹配在行尾
  +  : 重复次数>0
  *  : 重复次数>=0
  |  :  或者

replace(a,b): 方法用于在字符创中用一些字符替换另一些字符, 会传入两个值,将逗号前面的值a替换成逗号后面的值b

字母大小写切换(正则匹配,replace

这个方法主要是给一些需要大小写转换提供的方法,主要有首字母大写,首字母小写,大小写转换,全部转大写和全部转小写。

type:
  1:首字母大写
  2:首页母小写
  3:大小写转换
  4:全部大写
  5:全部小写

原始字符串:

var str="sdfwwerasfddffddeerAasdgFegqer";

function changeCase(str,type) {

   //这个函数是第三个大小写转换的方法
    function ToggleCase(str) {
        var itemText = ""
        str.split("").forEach(
                function (item) {
                 // 判断循环字符串中每个字符是否以a-z之间开头的并且重复大于0次
                    if (/^([a-z]+)/.test(item)) {
                    //  如果是小写,转换成大写
                        itemText += item.toUpperCase();
                    }
                //  判断循环字符串中每个字符是否以A-Z之间开头的并且重复大于0次
                    else if (/^([A-Z]+)/.test(item)) {
                   //   如果是大写,转换成小写
                        itemText += item.toLowerCase();
                    }
                    else{
                  //  如果都不符合,返回其本身
                        itemText += item;
                    }
                });
        return itemText;
    }
  //下面主要根据传入的type值来匹配各个场景
    switch (type) {
         //当匹配
        case 1:
            return str.replace(/^(\w)(\w+)/, function (v, v1, v2) {
                 //v=验证本身  v1=s ; v2=dfwwerasfddffddeerAasdgFegqer
                return v1.toUpperCase() + v2.toLowerCase();
            });
        case 2:
            return str.replace(/^(\w)(\w+)/, function (v, v1, v2) {
                //v=验证本身  v1=s ; v2=dfwwerasfddffddeerAasdgFegqer
                return v1.toLowerCase() + v2.toUpperCase();
            });
        case 3:
            return ToggleCase(str);
        case 4:
            return str.toUpperCase();
        case 5:
            return str.toLowerCase();
        default:
            return str;
    }

}

 console.log(changeCase(str,1)) =>SdfwwerasfddffddeerAasdgFegqer

解析:

split:用于把一个字符串分割成字符串数组
\w: 数字0-9或字母a-z及A-Z,或下划线
\W: 非\w,除以上的特殊符号等   
toUpperCase:转大写
toLowerCase:转小写
replace第二个参数可以是函数,函数的参数中,第一个是本身,第二个是正则匹配内容,第三个匹配剩下的内容

下面我们就通过小实验来验证一下:
网上有说replace是可以有4个参数的,但是我并没有验证到第四个代表的意义,前三个已经验证,第一个参数为验证本身,第二个正则匹配结果,第三为第二个匹配完剩下的值。

循环n次传入的字符串str为传入随意字符串,count为循环的次数

var str="abc";
 var number=555;

function repeatStr(str, count) {
    //声明一个空字符串,用来保存生成后的新字符串
    var text = '';
    //循环传入的count值,即循环的次数
    for (var i = 0; i < count; i++) {
       //循环一次就把字符串+到我们事先准备好的空字符串上
        text += str;
    }
    return text;
}

  console.log(repeatStr(str, 3))         // "abcabcabc"

  console.log(repeatStr(number, 3))      // "555555555"

解析:根据count循环的次数,在循环体内复制,return 返回+=后的值

查找字符串的A内容替换成B内容

let str="abacdasdfsd" function replaceAll(str,AFindText,ARepText){
   raRegExp = new RegExp(AFindText,"g");
   return str.replace(raRegExp,ARepText);
}
console.log(replaceAll(str,"a","x"))  // xbxcdxsdfsd
str:需要编辑的字符串本身
AFindText:需要替换的内容
ARepText:被替换成的内容

解析:创建正则,匹配内容,替换

检测常用格式,邮箱,手机号,名字,大写,小写,在表单验证时,我们经常会需要去验证一些内容,举例几个常用的验证

function checkType (str, type) {
    switch (type) {
        case 'email':
            return /^[\w-]+(\.[\w-]+)*@[\w-]+(\.[\w-]+)+$/.test(str);
        case 'phone':
            return /^1[3|4|5|7|8][0-9]{9}$/.test(str);
        case 'tel':
            return /^(0\d{2,3}-\d{7,8})(-\d{1,4})?$/.test(str);
        case 'number':
            return /^[0-9]$/.test(str);
        case 'english':
            return /^[a-zA-Z]+$/.test(str);
        case 'chinese':
            return /^[\u4E00-\u9FA5]+$/.test(str);
        case 'lower':
            return /^[a-z]+$/.test(str);
        case 'upper':
            return /^[A-Z]+$/.test(str);
        default :
            return true;
    }
}
console.log(checkType ('hjkhjhT','lower'))   //false

解析:

checkType ('hjkhjhT','lower')'需要验证的字符串','匹配的格式'
email:验证邮箱
phone:验证手机号
tel:验证座机号
number:验证数字
english:验证英文字母
chinese:验证中文字
lower:验证小写
upper:验证大写

JS返回浏览器历史第一页:

JS返回浏览器历史第一页

我在浏览器控制台敲了 window.history 其实我想看看还有哪些API,他输出了以下信息,我看到length 这个属性让我眼前一亮,其他2个我不关心。

{
    length: 2,
    scrollRestoration: "auto",
    state: null
}

我尝试用length 去回退, 不成功!

window.history.go(-2);

我再尝试用length去减1, 成功了, 我猜应该是跟新标签页有关吧。 那我不管了,我已经有答案了

window.history.go(-1);

最后代码

var historyLen = window.history;
window.history.go(-(historyLen - 1));

测试了3个浏览器, 都是第一次打开浏览器测试

Chrome 会直接返回到新标签页
Safari 我这里测试是返回到百度,可能是我的设置问题
Firefox 直接关闭浏览器 

作为战斗在业务一线的前端,要想少加班,就要想办法提高工作效率。这里提一个小点,我们在业务开发过程中,经常会重复用到日期格式化url参数转对象浏览器类型判断节流函数等一类函数,这些工具类函数,基本上在每个项目都会用到,为避免不同项目多次复制粘贴的麻烦,我们可以统一封装,发布到npm,以提高开发效率。

这里,笔者已经封装并发布了自己的武器库 outils,如果你对本项目感兴趣,欢迎 star 本项目。当然你也可以在本项目的基础上封装自己的武器库。

常用函数汇总

这里先分类整理下,之前项目中多次用到的工具函数。

1.Array

1.1 arrayEqual

/**
 * 
 * @desc 判断两个数组是否相等
 * @param {Array} arr1 
 * @param {Array} arr2 
 * @return {Boolean}
 */ function arrayEqual(arr1, arr2) {
    if (arr1 === arr2) return true;
    if (arr1.length != arr2.length) return false;
    for (var i = 0; i < arr1.length; ++i) {
        if (arr1[i] !== arr2[i]) return false;
    }
    return true;
}

2.Class

2.1 addClass

/**
 * 
 * @desc   为元素添加class
 * @param  {HTMLElement} ele 
 * @param  {String} cls 
 */ 
var hasClass = require('./hasClass');
function addClass(ele, cls) {
    if (!hasClass(ele, cls)) {
        ele.className += ' ' + cls;
    }
}

2.2 hasClass

/**
 * 
 * @desc 判断元素是否有某个class
 * @param {HTMLElement} ele 
 * @param {String} cls 
 * @return {Boolean}
 */ 
function hasClass(ele, cls) {
    return (new RegExp('(\\s|^)' + cls + '(\\s|$)')).test(ele.className);
}

2.3 removeClass

/**
 * 
 * @desc 为元素移除class
 * @param {HTMLElement} ele 
 * @param {String} cls 
 */ 
var hasClass = require('./hasClass');
function removeClass(ele, cls) {
    if (hasClass(ele, cls)) {
        var reg = new RegExp('(\\s|^)' + cls + '(\\s|$)');
        ele.className = ele.className.replace(reg, ' ');
    }
}

3.1 getCookie

/**
 * 
 * @desc 根据name读取cookie
 * @param  {String} name 
 * @return {String}
 */ 
function getCookie(name) {
    var arr = document.cookie.replace(/\s/g, "").split(';');
    for (var i = 0; i < arr.length; i++) {
        var tempArr = arr[i].split('=');
        if (tempArr[0] == name) {
            return decodeURIComponent(tempArr[1]);
        }
    }
    return '';
}

3.2 removeCookie

var setCookie = require('./setCookie');
/**
 * 
 * @desc 根据name删除cookie
 * @param  {String} name 
 */
 function removeCookie(name) {
    // 设置已过期,系统会立刻删除cookie
    setCookie(name, '1', -1);
}

3.3 setCookie

/**
 * 
 * @desc  设置Cookie
 * @param {String} name 
 * @param {String} value 
 * @param {Number} days 
 */ 
function setCookie(name, value, days) {
    var date = new Date();
    date.setDate(date.getDate() + days);
    document.cookie = name + '=' + value + ';expires=' + date;
}

4.Device

4.1 getExplore

/**
 * 
 * @desc 获取浏览器类型和版本
 * @return {String} 
 */ 
function getExplore() {
    var sys = {},
        ua = navigator.userAgent.toLowerCase(),
        s;
    (s = ua.match(/rv:([\d.]+)\) like gecko/)) ? sys.ie = s[1]:
        (s = ua.match(/msie ([\d\.]+)/)) ? sys.ie = s[1] :
        (s = ua.match(/edge\/([\d\.]+)/)) ? sys.edge = s[1] :
        (s = ua.match(/firefox\/([\d\.]+)/)) ? sys.firefox = s[1] :
        (s = ua.match(/(?:opera|opr).([\d\.]+)/)) ? sys.opera = s[1] :
        (s = ua.match(/chrome\/([\d\.]+)/)) ? sys.chrome = s[1] :
        (s = ua.match(/version\/([\d\.]+).*safari/)) ? sys.safari = s[1] : 0;
    // 根据关系进行判断 if (sys.ie) return ('IE: ' + sys.ie)
    if (sys.edge) return ('EDGE: ' + sys.edge)
    if (sys.firefox) return ('Firefox: ' + sys.firefox)
    if (sys.chrome) return ('Chrome: ' + sys.chrome)
    if (sys.opera) return ('Opera: ' + sys.opera)
    if (sys.safari) return ('Safari: ' + sys.safari)
    return 'Unkonwn'
}

4.2 getOS

/**
 * 
 * @desc 获取操作系统类型
 * @return {String} 
 */ 
function getOS() {
    var userAgent = 'navigator' in window && 'userAgent' in navigator && navigator.userAgent.toLowerCase() || '';
    var vendor = 'navigator' in window && 'vendor' in navigator && navigator.vendor.toLowerCase() || '';
    var appVersion = 'navigator' in window && 'appVersion' in navigator && navigator.appVersion.toLowerCase() || '';

    if (/mac/i.test(appVersion)) return 'MacOSX' if (/win/i.test(appVersion)) return 'windows' if (/linux/i.test(appVersion)) return 'linux' if (/iphone/i.test(userAgent) || /ipad/i.test(userAgent) || /ipod/i.test(userAgent)) 'ios' if (/android/i.test(userAgent)) return 'android' if (/win/i.test(appVersion) && /phone/i.test(userAgent)) return 'windowsPhone'
}

5.Dom

5.1 getScrollTop

/**
 * 
 * @desc 获取滚动条距顶部的距离
 */ 
function getScrollTop() {
    return (document.documentElement && document.documentElement.scrollTop) || document.body.scrollTop;
}

5.2 offset

/**
 * 
 * @desc  获取一个元素的距离文档(document)的位置,类似jQ中的offset()
 * @param {HTMLElement} ele 
 * @returns { {left: number, top: number} }
 */ 
function offset(ele) {
    var pos = {
        left: 0,
        top: 0
    };
    while (ele) {
        pos.left += ele.offsetLeft;
        pos.top += ele.offsetTop;
        ele = ele.offsetParent;
    };
    return pos;
}

5.3 scrollTo

var getScrollTop = require('./getScrollTop');
var setScrollTop = require('./setScrollTop');
var requestAnimFrame = (function () {
    return window.requestAnimationFrame ||
        window.webkitRequestAnimationFrame ||
        window.mozRequestAnimationFrame ||
        function (callback) {
            window.setTimeout(callback, 1000 / 60);
        };
})();
/**
 * 
 * @desc  在${duration}时间内,滚动条平滑滚动到${to}指定位置
 * @param {Number} to 
 * @param {Number} duration 
 */ 
function scrollTo(to, duration) {
    if (duration < 0) {
        setScrollTop(to);
        return
    }
    var diff = to - getScrollTop();
    if (diff === 0) return var step = diff / duration * 10;
    requestAnimationFrame(
        function () {
            if (Math.abs(step) > Math.abs(diff)) {
                setScrollTop(getScrollTop() + diff);
                return;
            }
            setScrollTop(getScrollTop() + step);
            if (diff > 0 && getScrollTop() >= to || diff < 0 && getScrollTop() <= to) {
                return;
            }
            scrollTo(to, duration - 16);
        });
}

5.4 setScrollTop

/**
 * 
 * @desc 设置滚动条距顶部的距离
 */ 
function setScrollTop(value) {
    window.scrollTo(0, value);
    return value;
}

6.Keycode

6.1 getKeyName

var keyCodeMap = {
    8: 'Backspace',
    9: 'Tab',
    13: 'Enter',
    16: 'Shift',
    17: 'Ctrl',
    18: 'Alt',
    19: 'Pause',
    20: 'Caps Lock',
    27: 'Escape',
    32: 'Space',
    33: 'Page Up',
    34: 'Page Down',
    35: 'End',
    36: 'Home',
    37: 'Left',
    38: 'Up',
    39: 'Right',
    40: 'Down',
    42: 'Print Screen',
    45: 'Insert',
    46: 'Delete',

    48: '0',
    49: '1',
    50: '2',
    51: '3',
    52: '4',
    53: '5',
    54: '6',
    55: '7',
    56: '8',
    57: '9',

    65: 'A',
    66: 'B',
    67: 'C',
    68: 'D',
    69: 'E',
    70: 'F',
    71: 'G',
    72: 'H',
    73: 'I',
    74: 'J',
    75: 'K',
    76: 'L',
    77: 'M',
    78: 'N',
    79: 'O',
    80: 'P',
    81: 'Q',
    82: 'R',
    83: 'S',
    84: 'T',
    85: 'U',
    86: 'V',
    87: 'W',
    88: 'X',
    89: 'Y',
    90: 'Z',

    91: 'Windows',
    93: 'Right Click',

    96: 'Numpad 0',
    97: 'Numpad 1',
    98: 'Numpad 2',
    99: 'Numpad 3',
    100: 'Numpad 4',
    101: 'Numpad 5',
    102: 'Numpad 6',
    103: 'Numpad 7',
    104: 'Numpad 8',
    105: 'Numpad 9',
    106: 'Numpad *',
    107: 'Numpad +',
    109: 'Numpad -',
    110: 'Numpad .',
    111: 'Numpad /',

    112: 'F1',
    113: 'F2',
    114: 'F3',
    115: 'F4',
    116: 'F5',
    117: 'F6',
    118: 'F7',
    119: 'F8',
    120: 'F9',
    121: 'F10',
    122: 'F11',
    123: 'F12',

    144: 'Num Lock',
    145: 'Scroll Lock',
    182: 'My Computer',
    183: 'My Calculator',
    186: ';',
    187: '=',
    188: ',',
    189: '-',
    190: '.',
    191: '/',
    192: '`',
    219: '[',
    220: '\\',
    221: ']',
    222: '\''
};
/**
 * @desc 根据keycode获得键名
 * @param  {Number} keycode 
 * @return {String}
 */ 
function getKeyName(keycode) {
    if (keyCodeMap[keycode]) {
        return keyCodeMap[keycode];
    } else {
        console.log('Unknow Key(Key Code:' + keycode + ')');
        return '';
    }
};

7.Object

7.1 deepClone

/**
 * @desc 深拷贝,支持常见类型
 * @param {Any} values
 */ 
function deepClone(values) {
    var copy;

    // Handle the 3 simple types, and null or undefined if (null == values || "object" != typeof values) return values;

    // Handle Date if (values instanceof Date) {
        copy = new Date();
        copy.setTime(values.getTime());
        return copy;
    }

    // Handle Array if (values instanceof Array) {
        copy = [];
        for (var i = 0, len = values.length; i < len; i++) {
            copy[i] = deepClone(values[i]);
        }
        return copy;
    }

    // Handle Object if (values instanceof Object) {
        copy = {};
        for (var attr in values) {
            if (values.hasOwnProperty(attr)) copy[attr] = deepClone(values[attr]);
        }
        return copy;
    }

    throw new Error("Unable to copy values! Its type isn't supported.");
}

7.2 isEmptyObject

/**
 * 
 * @desc   判断`obj`是否为空
 * @param  {Object} obj
 * @return {Boolean}
 */ 
function isEmptyObject(obj) {
    if (!obj || typeof obj !== 'object' || Array.isArray(obj))
        return false return !Object.keys(obj).length
}

8.Random

8.1 randomColor

/**
 * 
 * @desc 随机生成颜色
 * @return {String} 
 */ 
function randomColor() {
    return '#' + ('00000' + (Math.random() * 0x1000000 << 0).toString(16)).slice(-6);
}

8.2 randomNum

/**
 * 
 * @desc 生成指定范围随机数
 * @param  {Number} min 
 * @param  {Number} max 
 * @return {Number} 
 */ 
function randomNum(min, max) {
    return Math.floor(min + Math.random() * (max - min));
}

9.Regexp

9.1 isEmail

/**
 * 
 * @desc   判断是否为邮箱地址
 * @param  {String}  str
 * @return {Boolean} 
 */ 
function isEmail(str) {
    return /\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*/.test(str);
}

9.2 isIdCard

/**
 * 
 * @desc  判断是否为身份证号
 * @param  {String|Number} str 
 * @return {Boolean}
 */ 
function isIdCard(str) {
    return /^(^[1-9]\d{7}((0\d)|(1[0-2]))(([0|1|2]\d)|3[0-1])\d{3}$)|(^[1-9]\d{5}[1-9]\d{3}((0\d)|(1[0-2]))(([0|1|2]\d)|3[0-1])((\d{4})|\d{3}[Xx])$)$/.test(str)
}

9.3 isPhoneNum

/**
 * 
 * @desc   判断是否为手机号
 * @param  {String|Number} str 
 * @return {Boolean} 
 */
 function isPhoneNum(str) {
    return /^(0|86|17951)?(13[0-9]|15[012356789]|17[678]|18[0-9]|14[57])[0-9]{8}$/.test(str)
}

9.4 isUrl

/**
 * 
 * @desc   判断是否为URL地址
 * @param  {String} str 
 * @return {Boolean}
 */ 
function isUrl(str) {
    return /[-a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]{2,6}\b([-a-zA-Z0-9@:%_\+.~#?&//=]*)/i.test(str);
}

10.String

10.1 digitUppercase

/**
 * 
 * @desc   现金额转大写
 * @param  {Number} n 
 * @return {String}
 */
 function digitUppercase(n) {
    var fraction = ['角', '分'];
    var digit = [
        '零', '壹', '贰', '叁', '肆',
        '伍', '陆', '柒', '捌', '玖'
    ];
    var unit = [
        ['元', '万', '亿'],
        ['', '拾', '佰', '仟']
    ];
    var head = n < 0 ? '欠' : '';
    n = Math.abs(n);
    var s = '';
    for (var i = 0; i < fraction.length; i++) {
        s += (digit[Math.floor(n * 10 * Math.pow(10, i)) % 10] + fraction[i]).replace(/零./, '');
    }
    s = s || '整';
    n = Math.floor(n);
    for (var i = 0; i < unit[0].length && n > 0; i++) {
        var p = '';
        for (var j = 0; j < unit[1].length && n > 0; j++) {
            p = digit[n % 10] + unit[1][j] + p;
            n = Math.floor(n / 10);
        }
        s = p.replace(/(零.)*零$/, '').replace(/^$/, '零') + unit[0][i] + s;
    }
    return head + s.replace(/(零.)*零元/, '元')
        .replace(/(零.)+/g, '零')
        .replace(/^整$/, '零元整');
};

11.Support

11.1 isSupportWebP

/**
 * 
 * @desc 判断浏览器是否支持webP格式图片
 * @return {Boolean} 
 */ 
function isSupportWebP() {
    return !![].map && document.createElement('canvas').toDataURL('image/webp').indexOf('data:image/webp') == 0;
}

12.Time

12.1 formatPassTime

/**
 * @desc   格式化${startTime}距现在的已过时间
 * @param  {Date} startTime 
 * @return {String}
 */ 
function formatPassTime(startTime) {
    var currentTime = Date.parse(new Date()),
        time = currentTime - startTime,
        day = parseInt(time / (1000 * 60 * 60 * 24)),
        hour = parseInt(time / (1000 * 60 * 60)),
        min = parseInt(time / (1000 * 60)),
        month = parseInt(day / 30),
        year = parseInt(month / 12);
    if (year) return year + "年前" if (month) return month + "个月前" if (day) return day + "天前" if (hour) return hour + "小时前" if (min) return min + "分钟前" else return '刚刚'
}

12.2 formatRemainTime

/**
 * 
 * @desc   格式化现在距${endTime}的剩余时间
 * @param  {Date} endTime  
 * @return {String}
 */ function formatRemainTime(endTime) {
    var startDate = new Date(); //开始时间 var endDate = new Date(endTime); //结束时间 var t = endDate.getTime() - startDate.getTime(); //时间差 var d = 0,
        h = 0,
        m = 0,
        s = 0;
    if (t >= 0) {
        d = Math.floor(t / 1000 / 3600 / 24);
        h = Math.floor(t / 1000 / 60 / 60 % 24);
        m = Math.floor(t / 1000 / 60 % 60);
        s = Math.floor(t / 1000 % 60);
    }
    return d + "天 " + h + "小时 " + m + "分钟 " + s + "秒";
}

13.Url

13.1 parseQueryString

/**
 * 
 * @desc   url参数转对象
 * @param  {String} url  default: window.location.href
 * @return {Object} 
 */ 
function parseQueryString(url) {
    url = url == null ? window.location.href : url
    var search = url.substring(url.lastIndexOf('?') + 1)
    if (!search) {
        return {}
    }
    return JSON.parse('{"' + decodeURIComponent(search).replace(/"/g, '\\"').replace(/&/g, '","').replace(/=/g, '":"') + '"}')
}

13.2 stringfyQueryString

/**
 * 
 * @desc   对象序列化
 * @param  {Object} obj 
 * @return {String}
 */ function stringfyQueryString(obj) {
    if (!obj) return '';
    var pairs = [];

    for (var key in obj) {
        var value = obj[key];

        if (value instanceof Array) {
            for (var i = 0; i < value.length; ++i) {
                pairs.push(encodeURIComponent(key + '[' + i + ']') + '=' + encodeURIComponent(value[i]));
            }
            continue;
        }

        pairs.push(encodeURIComponent(key) + '=' + encodeURIComponent(obj[key]));
    }

    return pairs.join('&');
}

14.Function

14.1 throttle

/**
 * @desc   函数节流。
 * 适用于限制`resize`和`scroll`等函数的调用频率
 *
 * @param  {Number}    delay          0 或者更大的毫秒数。 对于事件回调,大约100或250毫秒(或更高)的延迟是最有用的。
 * @param  {Boolean}   noTrailing     可选,默认为false。
 *                                    如果noTrailing为true,当节流函数被调用,每过`delay`毫秒`callback`也将执行一次。
 *                                    如果noTrailing为false或者未传入,`callback`将在最后一次调用节流函数后再执行一次.
 *                                    (延迟`delay`毫秒之后,节流函数没有被调用,内部计数器会复位)
 * @param  {Function}  callback       延迟毫秒后执行的函数。`this`上下文和所有参数都是按原样传递的,
 *                                    执行去节流功能时,调用`callback`。
 * @param  {Boolean}   debounceMode   如果`debounceMode`为true,`clear`在`delay`ms后执行。
 *                                    如果debounceMode是false,`callback`在`delay` ms之后执行。
 *
 * @return {Function}  新的节流函数
 */ function throttle(delay, noTrailing, callback, debounceMode) {

    // After wrapper has stopped being called, this timeout ensures that // `callback` is executed at the proper times in `throttle` and `end` // debounce modes. var timeoutID;

    // Keep track of the last time `callback` was executed. var lastExec = 0;

    // `noTrailing` defaults to falsy. if (typeof noTrailing !== 'boolean') {
        debounceMode = callback;
        callback = noTrailing;
        noTrailing = undefined;
    }

    // The `wrapper` function encapsulates all of the throttling / debouncing // functionality and when executed will limit the rate at which `callback` // is executed. function wrapper() {

        var self = this;
        var elapsed = Number(new Date()) - lastExec;
        var args = arguments;

        // Execute `callback` and update the `lastExec` timestamp. function exec() {
            lastExec = Number(new Date());
            callback.apply(self, args);
        }

        // If `debounceMode` is true (at begin) this is used to clear the flag // to allow future `callback` executions. function clear() {
            timeoutID = undefined;
        }

        if (debounceMode && !timeoutID) {
            // Since `wrapper` is being called for the first time and // `debounceMode` is true (at begin), execute `callback`.
            exec();
        }

        // Clear any existing timeout. if (timeoutID) {
            clearTimeout(timeoutID);
        }

        if (debounceMode === undefined && elapsed > delay) {
            // In throttle mode, if `delay` time has been exceeded, execute // `callback`.
            exec();

        } else if (noTrailing !== true) {
            // In trailing throttle mode, since `delay` time has not been // exceeded, schedule `callback` to execute `delay` ms after most // recent execution. // // If `debounceMode` is true (at begin), schedule `clear` to execute // after `delay` ms. // // If `debounceMode` is false (at end), schedule `callback` to // execute after `delay` ms.
            timeoutID = setTimeout(debounceMode ? clear : exec, debounceMode === undefined ? delay - elapsed : delay);
        }

    }

    // Return the wrapper function. return wrapper;

};

14.2 debounce

/**
 * @desc 函数防抖 
 * 与throttle不同的是,debounce保证一个函数在多少毫秒内不再被触发,只会执行一次,
 * 要么在第一次调用return的防抖函数时执行,要么在延迟指定毫秒后调用。
 * @example 适用场景:如在线编辑的自动存储防抖。
 * @param  {Number}   delay         0或者更大的毫秒数。 对于事件回调,大约100或250毫秒(或更高)的延迟是最有用的。
 * @param  {Boolean}  atBegin       可选,默认为false。
 *                                  如果`atBegin`为false或未传入,回调函数则在第一次调用return的防抖函数后延迟指定毫秒调用。
                                    如果`atBegin`为true,回调函数则在第一次调用return的防抖函数时直接执行
 * @param  {Function} callback      延迟毫秒后执行的函数。`this`上下文和所有参数都是按原样传递的,
 *                                  执行去抖动功能时,,调用`callback`。
 *
 * @return {Function} 新的防抖函数。
 */ 
var throttle = require('./throttle');
function debounce(delay, atBegin, callback) {
    return callback === undefined ? throttle(delay, atBegin, false) : throttle(delay, callback, atBegin !== false);
};

封装

除了对上面这些常用函数进行封装, 最重要的是支持合理化的引入,这里我们使用webpack统一打包成UMD 通用模块规范,支持webpackRequireJSSeaJS等模块加载器,亦或直接通过<script>标签引入。

但这样,还是不能让人满意。因为完整引入整个库,略显浪费,我们不可能用到所有的函数。那么,就支持按需引入吧

1.目录结构说明

│  .babelrc
│  .gitignore
│  .travis.yml
│  LICENSE
│  package.json
│  README.md
│  setCookie.js  // 拷贝到根路径的函数模块,方便按需加载
│  setScrollTop.js
│  stringfyQueryString.js
│   ...
│   ...
│  
├─min
│      outils.min.js  // 所有函数统一打包生成的全量压缩包
│      
├─script  // 本项目开发脚本目录
│      build.js  // 打包构建脚本
│      test.js  // 测试脚本
│      webpack.conf.js  // webpack打包配置文件
│      
├─src // 源码目录
│  │  index.js  // webpack入口文件
│  │  
│  ├─array
│  │      
│  ├─class
│  │      
│  ├─cookie
│  │      
│  ├─device
│  │      
│  ├─dom
│  │      
│  ├─keycode
│  │      
│  ├─object
│  │      
│  ├─random
│  │      
│  ├─regexp
│  │      
│  ├─string
│  │      
│  ├─support
│  │      
│  ├─time
│  │      
│  └─url
│          
└─test // 测试用例目录
    │  array.test.js
    │  class.test.js
    │  cookie.test.js
    │  device.test.js
    │  dom.test.js
    │  index.html
    │  keycode.test.js
    │  object.test.js
    │  random.test.js
    │  regexp.test.js
    │  string.test.js
    │  support.test.js
    │  time.test.js
    │  url.test.js
    │  
    └─_lib // 测试所用到的第三方库
            mocha.css
            mocha.js
            power-assert.js

2.构建脚本

这里主要说明一下项目中 build.js 的构建过程
第一步,构建全量压缩包,先删除min目录中之前的outils.min.js,后通过webpack打包并保存新的压缩包至min目录中:

......
 ......
 // 删除旧的全量压缩包
 rm(path.resolve(rootPath, 'min', `${pkg.name}.min.js`), err => {
     if (err) throw (err)
     webpack(config, function (err, stats) {
         if (err) throw (err)
         building.stop()
         process.stdout.write(stats.toString({
             colors: true,
             modules: false,
             children: false,
             chunks: false,
             chunkModules: false
         }) + '\n\n')
         resolve()
         console.log(chalk.cyan('  Build complete.\n'))
     })
 })
 ......
 ......

第二步,拷贝函数模块至根目录,先删除根目录中之前的函数模块,后拷贝src下面一层目录的所有js文件至根目录。这么做的目的是,拷贝到根路径,在引入的时候,直接require('outils/<方法名>')即可,缩短引入的路径,也算是提高点效率。

// 替换模块文件
    ......
    ......
    // 先删除根目录中之前的函数模块
    rm('*.js', err => {
        if (err) throw (err)
        let folderList = fs.readdirSync(path.resolve(rootPath, 'src'))
        folderList.forEach((item, index) => {
            // 拷贝`src`下面一层目录的所有`js`文件至根目录
            copy(`src/${item}/*.js`, rootPath, function (err, files) {
                if (err) throw err;
                if (index === folderList.length - 1) {
                    console.log(chalk.cyan('  Copy complete.\n'))
                    copying.stop()
                }
            })
        })
    })
    ......
    ......

3.书写测试用例

俗话说,不写测试用例的前端不是一个好程序员。那就不能怂,就是干。

但是因为时间关系,本项目暂时通过项目中的 test.js ,启动了一个koa静态服务器,来加载mocha网页端的测试页面,让笔者书写项目时,可以在本地对函数功能进行测试。
但是后续将使用travis-ci配合Github来做持续化构建,自动发布到npm。改用karmamochapower-assert做单元测试,使用Coverage测试覆盖率。这一部分,后续更新。

这里给大家推荐一个好用的断言库 power-assert ,这个库记住assert(value, [message])一个API就基本无敌,从此再也不用担心记不住断言库的API。

本项目的所有测试用例都在test目录下,大家可以作一定参考。

更新:单元测试,已使用karmamochapower-assert,使用Coverage测试覆盖率,并集成 travis-ci 配合Github来做持续化构建,可以参考本项目的travis配置文件 .travis.yml karma的配置文件 karma.conf.js

发布

首先放到Github托管一下,当然你也可以直接fork本项目,然后再加入你自己的函数。
以笔者项目,举个栗子:

1.添加自己的函数

src目录下,新建分类目录或者选择一个分类,在子文件夹中添加函数模块文件(建议一个小功能保存为一个JS文件)。

/**
 * 
 * @desc   判断是否NaN
 * @param  {Any} value 
 * @return {Boolean}
 */ 
function isNaN(value) {    
    return value !== value;
};

modules.export = isNaN

然后记得在src/index.js文件中暴露isNaN函数

2.单元测试

test文件新建测试用例

describe('#isNaN()', function () {
    it(`outils.isNaN(NaN) should return true`, function () {
        assert(outils.isNaN(NaN))
    })
    it(`outils.isNaN('value') should return false`, function () {
        assert.notEqual(outils.isNaN(NaN))
    })
})

然后记得在test/index.html中引入之前创建的测试用例脚本。

3.测试并打包

执行npm run test,看所有的测试用例是否通过。如果没有问题,执行npm run build构建,之后提交到个人的 github 仓库即可。

4.发布到npm

www.npmjs.com 注册账号,修改本地package.json中的nameversionauthor等信息,最后npm publish就大功告成了。
注意:向npm发包,要把镜像源切到 www.npmjs.com ,使用cnpm等第三方镜像源会报错。

使用

1.浏览器

直接下载min目录下的 outils.min.js ,通过<script>标签引入。

<script src="outils.min.js"></script> <script> var OS = outils.getOS()
  </script>

注意: 本仓库代码会持续更新,如果你需要不同版本的增量压缩包或源码,请到 github Release 页面下载对应版本号的代码。

2.Webpack、RequireJS、SeaJS等模块加载器

先使用npm安装outils

$ npm install --save-dev outils
// 完整引入 const outils = require('outils')
const OS = outils.getOS()
推荐使用方法 
// 按需引入require('outils/<方法名>') const getOS = require('outils/getOS')
const OS = getOS()

当然,你的开发环境有babel编译ES6语法的话,也可以这样使用:

import getOS from 'outils/getOS' // 或 import { getOS } from "outils";

总结

这里只是简单封装,发布到npm上,省去下次复制粘贴的功夫,或者直接Goole的时间。如果笔者的库中,没有你常用的函数,或者你有更好的建议,欢迎来本项目的 Github Issues 交流,如果觉得不错,欢迎 star 本项目。

当然,更好的建议是 fork 本项目,或者直接新建自己的项目,添加自己 想要的 、常用的 、记不住的 函数,甚至是可以抽象出来的功能,封装成自己顺手、熟悉的库。 这样才能打造出你自己的武器库,瞬间提高你的单兵作战(开发)能力。

libraries :https://github.com/wuxianqiang/libraries

目录

仿ECMAScript5中Object.create()函数

function inherit(obj) {
            if (obj === null) throw TypeError();
            if (Object.create) return Object.create(obj);
            var t = typeof obj;
            if (t !== "object" && t !== "function") throw TypeError();
            function Fn() {};
            Fn.prototype = obj;
            return new Fn();
        }

仿ECMAScript5中String.trim()函数

String.prototype.mytrim = function () {
            String.prototype.trim || function () {
                if (!this) return this; //空字符串不做处理
                return this.replace(/^\s+|\s+$/g, "") //使用正则表达式经行空格替换
            }
        }

仿ECMAScript5中Array.reduce()函数

var reduce = Array.prototype.reduce ? function (ary, fn, initial) {
            if (arguments.length > 2) { //如果reduce()方法存在的话
                return ary.reduce(fn, initial); //如果传入了一个初始值
            } else {
                return ary.reduce(fn); //否则初始值
            }
        } : function (ary, fn, initial) { //以特定的初始值开始,否则第一个值取自ary
            var i = 0,
                len = ary.length,
                accumulator;
            if (arguments.length > 2) {
                accumulator = initial;
            } else { //找到数组中第一个已经定义的索引
                if (len == 0) throw TypeError();
                while (i < len) {
                    if (i in ary) {
                        accumulator = ary[i++];
                        break;
                    } else {
                        i++;
                    }
                }
                if (i == len) throw TypeError();
            }
            while (i < len) { //对于数组中剩下的元素依次调用fn
                if (i in ary) {
                    accumulator = fn.call(undefined, accumulator, ary[i], i, ary)
                }
                i++;
            }
            return accumulator;
        }

⬆ back to top

在数组中查找所有出现的元素方法

function findAll(ary, ele) {
            var results = [],
                len = ary.length,
                pos = 0;
            while (pos < len) {
                pos = ary.indexOf(ele, pos);
                if (pos === -1) break;
                results.push(pos);
                pos++;
            }
            return results;
        }

⬆ back to top

数据类型检测,特殊情况特殊处理

function classOf(obj) {
            if (obj === null) return "Null";
            if (obj === undefined) return 'Undefined';
            return Object.prototype.toString.call(obj).slice(8, -1);
        }

⬆ back to top

仿ECMAScript5中Object.keys()函数

function keys(obj) {
            if (typeof obj !== "object") {
                throw TypeError();
            }
            var result = [];
            for (var prop in obj) {
                if (obj.hasOwnProperty(prop)) {
                    result.push(prop);
                }
            }
            return result;
        }

⬆ back to top

仿Math.max方法(不定实参函数)

function max() {
            var max = Number.NEGATIVE_INFINITY;
            for (var i = 0; i < arguments.length; i++) {
                if (arguments[i] > max) max = arguments[i];
            }
            return max;
        }

⬆ back to top

仿ECMAScript5中Function.bind()函数

if (!Function.prototype.bind) {
            Function.prototype.bind = function (obj) {
                var self = this,
                    boundArgs = arguments;
                return function () {
                    var args = [],
                        i;
                    for (i = 1; i < boundArgs.length; i++) args.push(boundArgs[i]);
                    for (i = 1; i < arguments.length; i++) args.push(arguments[i]);
                    return self.apply(obj, args);
                }
            }
        }

⬆ back to top

仿ECMAScript5中Array.map()函数

var map = Array.prototype.map ? function (ary, fn) {
            return ary.map(fn);
        } : function (ary, fn) {
            var results = [];
            for (var i = 0, len = ary.length; i < len; i++) {
                if (i in ary) {
                    results[i] = fn.call(null, ary[i], i, ary);
                }
            }
            return results;
        }

⬆ back to top

数组去重

Array.prototype.unique = function unique() {
            var obj = {};
            for (var i = 0; i < this.length; i++) {
                var current = this[i];
                if (obj[current] === current) {
                    current = this[this.length - 1];
                    this.length--;
                    i--;
                    continue;
                }
                obj[current] = current
            }
            obj = null;
            return this;
        }

⬆ back to top

冒泡排序

Array.prototype.bubbleSort = function bubbleSort() {
            var temp = null;
            for (var i = 0; i < this.length - 1; i++) {
                for (var k = 0; k < this.length - 1 - i; k++) {
                    if (this[k] > this[k + 1]) {
                        temp = this[k];
                        this[k] = this[k + 1];
                        this[k + 1] = temp;
                    }
                }
            }
            return this;
        }

⬆ back to top

仿String.match()方法实现

String.prototype.mymatch = function (reg) {
         var ary = [];
         var res = reg.exec(this);
         while (res) {
            ary.push(res[0]);
            res = reg.exec(this);
         }
         return ary;
        }

⬆ back to top

返回元素的第n层祖先元素

/**
        *返回元素ele的第n层祖先元素,如果不存在此类祖先或祖先不是Element,
        *(例如Document或者DocumentFragment)则返回null
        *如果n为0,则返回e本身。如果n为1(或省略),则返回其父元素
        *如果n为2,则返回其祖父元素,依次类推
        */
        function parent(ele, n) {
            if (n === nudefined) n = 1;
            while (n-- && ele) {
                ele = ele.parentNode;
            }
            if (!ele || ele.nodeTope !== 1) return null;
            return ele;
        }

⬆ back to top

返回元素的第n个兄弟元素

/**
 *返回元素ele的第n个兄弟元素
 *如果n为正,返回后续的第n个兄弟元素
 *如果n为负,返回前面的第n个兄弟元素
 *如果n为零,返回ele本身
 */
function sibling(ele, n) {
    while (ele && n !== 0) { //如果ele未定义,即刻返回它
        if (n > 0) { //查找后续的兄弟元素
            if (ele.nextElementSibling) {
                ele = ele.nextElementSibling;
            } else {
                for (ele = ele.nextSibling; ele && ele.nodeType !== 1; ele = ele.nextSibling) /*空循环*/;
            }
            n--;
        } else { //查找前面的兄弟元素
            if (ele.previousElementSibing) {
                ele = ele.previousElementSibling;
            } else {
                for (ele = ele.previousSibling; ele && ele.nodeType !== 1; ele = ele.previousSibling) /*空循环*/;
            }
            n++;
        }
    }
    return ele;
}

⬆ back to top

返回元素的第n个子代元素

/**
 *返回元素ele的第n代子元素,如果不存在则为null
 *负值n代表从后往前计数。0表示第一个子元素,而-1代表最后一个,-2代表倒数第二个,依次类推
 */
function child(ele, n) {
    if (ele.children) { //如果children数组存在
        if (n < 0) n += ele.children.length; //转换负的n为数组索引
        if (n < 0) return null; //如果它仍然为负,说明没有子元素
        return ele.children[n]; //返回指定的子元素
    }
    //如果e没有children数组,找到第一个子元素并向前数,或找到最后一个子元素并往回数
    if (n >= 0) { //n非负:从第一个子元素向前数
        //找到元素e的第一个子元素
        if (ele.firstElementChild) {
            ele = ele.firstElementChild;
        } else {
            for (ele = ele.firstChild; ele && ele.nodeType !== 1; ele = ele.nextSibling) /*空循环*/;
        }
        return sibling(ele, n); //返回第一个子元素的第n个兄弟元素
    } else { //n为负:从最后一个子元素往回数
        if (ele.lastElementChild) {
            ele = ele.lastElementChild;
        } else {
            for (ele = ele.lastChild; ele && ele.nodeType !== 1; ele = ele.previousSibling) /*空循环*/;
        }
        return sibling(ele, n + 1); //+1来转化最后1个子元素为最后1个兄弟元素
    }
}

⬆ back to top

表格的行排序

//根据指定表格每行第n个单元格的值,对第一个<tbody>中的行进行排序
//如果存在comparator函数则使用它,否则按字母表顺序比较
function sortrows(table, n, comparator) {
    var tbody = table.tBodies[0]; //第一个<tbody>,可能是隐式创建的
    var rows = tbody.getElementsByTagName("tr"); //tbody中的所有行
    rows = Array.prototype.slice.call(rows, 0); //真实数组中的快照
    //基于第n个<td>元素的值对行排序
    rows.sort(function (row1, row2) {
        var cell1 = row1.getElementsByTagName("td")[n]; //获得第n个单元格
        var cell2 = row2.getElementsByTagName("td")[n]; //两行都是
        var val1 = cell1.textContent || cell1.innerText; //获得文本内容
        var val2 = cell2.textContent || cell2.innerText; //两单元格都是
        if (comparator) return comparator(val1, val2); //进行比较
        if (val1 < val2) {
            return -1;
        } else if (val1 > val2) {
            return 1;
        } else {
            return 0;
        }
    }); //在tbody中按它们的顺序把行添加到最后
    //这将自动把它们从当前位置移走,故没必要预先删除它们
    //如果<tbody>还包含了除了<tr>的任何其他元素,这些节点将会悬浮到顶部位置
    for (var i = 0; i < rows.length; i++) tbody.appendChild(rows[i]);
}
//查找表格的<th>元素(假设只有一行),让它们可单击,
//以便单击列标题,按该列对行排序
function makeSortable(table) {
    var headers = table.getElementsByTagName("th");
    for (var i = 0; i < headers.length; i++) {
        (function (n) { //嵌套函数来创建本地作用域
            headers[i].onclick = function () {
                sortrows(table, n);
            };
        }(i)); //将i的值赋给局部变量n
    }
}

⬆ back to top

生成目录表

/**
 *
 *这个模块注册一个可在页面加载完成后自动运行的匿名函数。当执行这个函数时会去文档中查找
 *id为"TOC"的元素。如果这个元素不存在,就创建一个元素
 *
 *生成的TOC目录应当具有自己的CSS样式。整个目录区域的样式className设置为"TOCEntry"
 *同样我们为不同层级的目录标题定义不同的样式。<h1>标签生成的标题
 *className为"TOCLevel1",<h2>标签生成的标题className为"TOCLevel2",以此类推
 *段编号的样式为"TOCSectNum"
 *
 *完整的CSS样式代码如下:
 *
 *#TOC{border:solid black 1px;margin:10px;padding:10px;}
 *.TOCEntry{font-family:sans-serif;}
 *.TOCEntry a{text-decoration:none;}
 *.TOCLevel1{font-size:16pt;font-weight:bold;}
 *.TOCLevel2{font-size:12pt;margin-left:.5in;}
 *.TOCSectNum:after{content:":";}
 *
 *这段代码的最后一行表示每个段编号之后都有一个冒号和空格符。要想隐藏段编号,
 *请使用这行代码:
 *.TOCSectNum{display:none}
 *
 **/
(function () { //匿名函数定义了一个局部作用域
    //查找TOC容器元素
    //如果不存在,则在文档开头处创建一个
    var toc = document.getElementById("TOC");
    if (!toc) {
        toc = document.createElement("div");
        toc.id = "TOC";
        document.body.insertBefore(toc, document.body.firstChild);
    }
    //查找所有的标题元素
    var headings;
    if (document.querySelectorAll) //我们是否能用这个简单的方法?
        headings = document.querySelectorAll("h1,h2,h3,h4,h5,h6");
    else //否则,查找方法稍微麻烦一些
        headings = findHeadings(document.body, []); //递归遍历document的body,查找标题元素
    function findHeadings(root, sects) {
        for (var c = root.firstChild; c != null; c = c.nextSibling) {
            if (c.nodeType !== 1) continue;
            if (c.tagName.length == 2 && c.tagName.charAt(0) == "H")
                sects.push(c);
            else
                findHeadings(c, sects);
        }
        return sects;
    }
    //初始化一个数组来保持跟踪章节号
    var sectionNumbers = [0, 0, 0, 0, 0, 0]; //现在,循环已找到的标题元素
    for (var h = 0; h < headings.length; h++) {
        var heading = headings[h]; //跳过在TOC容器中的标题元素
        if (heading.parentNode == toc) continue; //判定标题的级别
        var level = parseInt(heading.tagName.charAt(1));
        if (isNaN(level) || level < 1 || level > 6) continue; //对于该标题级别增加sectionNumbers对应的数字
        //重置所有标题比它级别低的数字为零
        sectionNumbers[level - 1]++;
        for (var i = level; i < 6; i++) sectionNumbers[i] = 0; //现在,将所有标题级别的章节号组合产生一个章节号, 如2 .3 .1
        var sectionNumber = sectionNumbers.slice(0, level).join(".") //为标题级别增加章节号
        //把数字放在<span>中,使得其可以用样式修饰
        var span = document.createElement("span");
        span.className = "TOCSectNum";
        span.innerHTML = sectionNumber;
        heading.insertBefore(span, heading.firstChild); //用命名的锚点将标题包起来,以便为它增加链接
        var anchor = document.createElement("a");
        anchor.name = "TOC" + sectionNumber;
        heading.parentNode.insertBefore(anchor, heading);
        anchor.appendChild(heading); //现在为该节创建一个链接
        var link = document.createElement("a");
        link.href = "#TOC" + sectionNumber; //链接的目标地址
        link.innerHTML = heading.innerHTML; //链接文本与实际标题一致
        //将链接放在一个div中,div用基于级别名字的样式修饰
        var entry = document.createElement("div");
        entry.className = "TOCEntry TOCLevel" + level;
        entry.appendChild(link); //该div添加到TOC容器中
        toc.appendChild(entry);
    }
}());

⬆ back to top

从URL解析参数

/*
 *这个函数用来解析来自URL的查询串中的name=value参数对
 *它将name=value对存储在一个对象的属性中,并返回该对象
 *这样来使用它
 *
 *var args=urlArgs();//从URL中解析参数
 *var q=args.q||"";//如果参数定义了的话就使用参数;否则使用一个默认值
 *var n=args.n?parseInt(args.n):10;
 */
function urlArgs() {
    var args = {}; //定义一个空对象
    var query = location.search.substring(1); //查找到查询串,并去掉'?'
    var pairs = query.split("&"); //根据"&"符号将查询字符串分隔开
    for (var i = 0; i < pairs.length; i++) { //对于每个片段
        var pos = pairs[i].indexOf('='); //查找"name=value"
        if (pos == -1) continue; //如果没有找到的话,就跳过
        var name = pairs[i].substring(0, pos); //提取name
        var value = pairs[i].substring(pos + 1); //提取value
        value = decodeURIComponent(value); //对value进行解码
        args[name] = value; //存储为属性
    }
    return args; //返回解析后的参数
}

⬆ back to top

获取纯文本的元素内容

/**
 *一个参数,返回元素的textContent或innerText
 *两个参数,用value参数的值设置元素的textContent或innerText
 */
function textContent(element, value) {
    var content = element.textContent; //检测textContent是否有定义
    if (value === undefined) { //没传递value,因此返回当前文本
        if (content !== undefined) {
            return content;
        } else {
            return element.innerText;
        }
    } else { //传递了value,因此设置文本
        if (content !== undefined) {
            element.textContent = value;
        } else {
            element.innerText = value;
        }
    }
}

⬆ back to top

手写一个JSONP实现

//根据指定的URL发送一个JSONP请求
//然后把解析得到的响应数据传递给回调函数
//在URL中添加一个名为jsonp的查询参数,用于指定该请求的回调函数的名称
function getJSONP(url, callback) { //为本次请求创建一个唯一的回调函数名称
    var cbnum = "cb" + getJSONP.counter++; //每次自增计数器
    var cbname = "getJSONP." + cbnum; //作为JSONP函数的属性
    //将回调函数名称以表单编码的形式添加到URL的查询部分中
    //使用jsonp作为参数名,一些支持JSONP的服务
    //可能使用其他的参数名,比如callback
    if (url.indexOf("?") === -1) //URL没有查询部分
        url += "?jsonp=" + cbname; //作为查询部分添加参数
    else //否则
        url += "&jsonp=" + cbname; //作为新的参数添加它
    //创建script元素用于发送请求
    var script = document.createElement("script"); //定义将被脚本执行的回调函数
    getJSONP[cbnum] = function (response) {
        try {
            callback(response); //处理响应数据
        } finally { //即使回调函数或响应抛出错误
            delete getJSONP[cbnum]; //删除该函数
            script.parentNode.removeChild(script); //移除script元素
        }
    }; //立即触发HTTP请求
    script.src = url; //设置脚本的URL
    document.body.appendChild(script); //把它添加到文档中
}
getJSONP.counter = 0; //用于创建唯一回调函数名称的计数器

⬆ back to top

插入节点

//将child节点插入到parent中,使其成为第n个子节点
function insertAt(parent, child, n) {
    if (n < 0 || n > parent.childNodes.length) {
        throw new Error("invalid index");
    } else if (n == parent.childNodes.length) {
        parent.appendChild(child);
    } else {
        parent.insertBefore(child, parent.childNodes[n]);
    }
}

⬆ back to top

使用innerHTML实现outerHTML属性

//为那些不支持它的浏览器实现outerHTML属性
//假设浏览器确实支持innerHTML,并有个可扩展的Element.prototype,
//并且可以定义getter和setter
(function () { //如果outerHTML存在,则直接返回
    if (document.createElement("div").outerHTML) return; //返回this所引用元素的外部HTML
    function outerHTMLGetter() {
        var container = document.createElement("div"); //虚拟元素
        container.appendChild(this.cloneNode(true)); //复制到该虚拟节点
        return container.innerHTML; //返回虚拟节点的innerHTML
    }
    //用指定的值设置元素的外部HTML
    function outerHTMLSetter(value) { //创建一个虚拟元素,设置其内容为指定的值
        var container = document.createElement("div");
        container.innerHTML = value; //将虚拟元素中的节点全部移动到文档中
        while (container.firstChild) //循环,直到container没有子节点为止
            this.parentNode.insertBefore(container.firstChild, this); //删除所被取代的节点
        this.parentNode.removeChild(this);
    }
    //现在使用这两个函数作为所有Element对象的outerHTML属性的getter和setter
    //如果它存在则使用ES5的Object.defineProperty()方法,
    //否则,退而求其次,使用__defineGetter__()和__defineSetter__()
    if (Object.defineProperty) {
        Object.defineProperty(Element.prototype, "outerHTML", {
            get: outerHTMLGetter,
            set: outerHTMLSetter,
            enumerable: false,
            configurable: true
        });
    } else {
        Element.prototype.__defineGetter__("outerHTML", outerHTMLGetter);
        Element.prototype.__defineSetter__("outerHTML", outerHTMLSetter);
    }
}());

⬆ back to top

倒序排列子节点

//倒序排列节点n的子节点
function reverse(n) { //创建一个DocumentFragment作为临时容器
    var f = document.createDocumentFragment(); //从后至前循环子节点,将每一个子节点移动到文档片段中
    //n的最后一个节点变成f的第一个节点,反之亦然
    //注意,给f添加一个节点,该节点自动地会从n中删除
    while (n.lastChild) f.appendChild(n.lastChild); //最后,把f的所有子节点一次性全部移回n中
    n.appendChild(f);
}

⬆ back to top

查询窗口滚动条的位置

//以一个对象的x和y属性的方式返回滚动条的偏移量
function getScrollOffsets(w) { //使用指定的窗口,如果不带参数则使用当前窗口
    w = w || window; //除了IE 8及更早的版本以外,其他浏览器都能用
    if (w.pageXOffset != null) return {
        x: w.pageXOffset,
        y: w.pageYOffset
    }; //对标准模式下的IE(或任何浏览器)
    var d = w.document;
    if (document.compatMode == "CSS1Compat")
        return {
            x: d.documentElement.scrollLeft,
            y: d.documentElement.scrollTop
        }; //对怪异模式下的浏览器
    return {
        x: d.body.scrollLeft,
        y: d.body.scrollTop
    };
}

⬆ back to top

查询窗口的视口尺寸

//作为一个对象的w和h属性返回视口的尺寸
function getViewportSize(w) { //使用指定的窗口,如果不带参数则使用当前窗口
    w = w || window; //除了IE 8及更早的版本以外,其他浏览器都能用
    if (w.innerWidth != null) return {
        w: w.innerWidth,
        h: w.innerHeight
    }; //对标准模式下的IE(或任何浏览器)
    var d = w.document;
    if (document.compatMode == "CSS1Compat")
        return {
            w: d.documentElement.clientWidth,
            h: d.documentElement.clientHeight
        }; //对怪异模式下的浏览器
    return {
        w: d.body.clientWidth,
        h: d.body.clientWidth
    };
}

⬆ back to top

返回函数的名字

Function.prototype.getName = function () {
    return this.name || this.toString().match(/function\s*(\w*)\s*\(/)[1];
}

⬆ back to top

原生JS实现CSS动画1

//将e转化为相对定位的元素,使之左右"震动"
//第一个参数可以是元素对象或者元素的id
//如果第二个参数是函数,以e为参数,它将在动画结束时调用
//第三个参数指定e震动的距离,默认是5像素
//第四个参数指定震动多久,默认是500毫秒
function shake(e, oncomplete, distance, time) { //句柄参数
    if (typeof e === "string") e = document.getElementById(e);
    if (!time) time = 500;
    if (!distance) distance = 5;
    var originalStyle = e.style.cssText; //保存e的原始style
    e.style.position = "relative"; //使e相对定位
    var start = (new Date()).getTime(); //注意,动画的开始时间
    animate(); //动画开始
    //函数检查消耗的时间,并更新e的位置
    //如果动画完成,它将e还原为原始状态
    //否则,它更新e的位置,安排它自身重新运行
    function animate() {
        var now = (new Date()).getTime(); //得到当前时间
        var elapsed = now - start; //从开始以来消耗了多长时间?
        var fraction = elapsed / time; //是总时间的几分之几?
        if (fraction < 1) { //如果动画未完成
            //作为动画完成比例的函数,计算e的x位置
            //使用正弦函数将完成比例乘以4pi
            //所以,它来回往复两次
            var x = distance * Math.sin(fraction * 4 * Math.PI);
            e.style.left = x + "px"; //在25毫秒后或在总时间的最后尝试再次运行函数
            //目的是为了产生每秒40帧的动画
            setTimeout(animate, Math.min(25, time - elapsed));
        } else { //否则,动画完成
            e.style.cssText = originalStyle //恢复原始样式
            if (oncomplete) oncomplete(e); //调用完成后的回调函数
        }
    }
}

⬆ back to top

原生JS实现CSS动画2

function fadeOut(e, oncomplete, time) {
    if (typeof e === "string") e = document.getElementById(e);
    if (!time) time = 500; //使用Math.sqrt作为一个简单的“缓动函数”来创建动画
    //精巧的非线性:一开始淡出得比较快,然后缓慢了一些
    var ease = Math.sqrt;
    var start = (new Date()).getTime(); //注意:动画开始的时间
    animate(); //动画开始
    function animate() {
        var elapsed = (new Date()).getTime() - start; //消耗的时间
        var fraction = elapsed / time; //总时间的几分之几?
        if (fraction < 1) { //如果动画未完成
            var opacity = 1 - ease(fraction); //计算元素的不透明度
            e.style.opacity = String(opacity); //设置在e上
            setTimeout(animate, //调度下一帧
                Math.min(25, time - elapsed));
        } else { //否则,动画完成
            e.style.opacity = "0"; //使e完全透明
            if (oncomplete) oncomplete(e); //调用完成后的回调函数
        }
    }
}

⬆ back to top

仿HTML5的classList属性

/*
 *如果e有classList属性则返回它。否则,返回一个为e模拟DOMTokenList API的对象
 *返回的对象有contains()、add()、remove()、toggle()和toString()等方法
 *来检测和修改元素e的类集合。如果classList属性是原生支持的,
 *返回的类数组对象有length和数组索引属性。模拟DOMTokenList不是类数组对象,
 *但是它有一个toArray()方法来返回一个含元素类名的纯数组快照
 */
function classList(e) {
    if (e.classList) return e.classList; //如果e.classList存在,则返回它
    else return new CSSClassList(e); //否则,就伪造一个
}
//CSSClassList是一个模拟DOMTokenList的JavaScript类
function CSSClassList(e) {
    this.e = e;
} //如果e.className包含类名c则返回true否则返回false
CSSClassList.prototype.contains = function (c) { //检查c是否是合法的类名
    if (c.length === 0 || c.indexOf(" ") != -1)
        throw new Error("Invalid class name:'" + c + "'"); //首先是常规检查
    var classes = this.e.className;
    if (!classes) return false; //e不含类名
    if (classes === c) return true; //e有一个完全匹配的类名
    //否则,把c自身看做一个单词,利用正则表达式搜索c
    //\b在正则表达式里代表单词的边界
    return classes.search("\\b" + c + "\\b") != -1;
}; //如果c不存在,将c添加到e.className中
CSSClassList.prototype.add = function (c) {
    if (this.contains(c)) return; //如果存在,什么都不做
    var classes = this.e.className;
    if (classes && classes[classes.length - 1] != "")
        c = "" + c; //如果需要加一个空格
    this.e.className += c; //将c添加到className中
}; //将在e.className中出现的所有c都删除
CSSClassList.prototype.remove = function (c) { //检查c是否是合法的类名
    if (c.length === 0 || c.indexOf(" ") != -1)
        throw new Error("Invalid class name:'" + c + "'"); //将所有作为单词的c和多余的尾随空格全部删除
    var pattern = new RegExp("\\b" + c + "\\b\\s*", "g");
    this.e.className = this.e.className.replace(pattern, "");
}; //如果c不存在,将c添加到e.className中,并返回true
//否则,将在e.className中出现的所有c都删除,并返回false
CSSClassList.prototype.toggle = function (c) {
    if (this.contains(c)) { //如果e.className包含c
        this.remove(c); //删除它
        return false;
    } else { //否则
        this.add(c); //添加它
        return true;
    }
}; //返回e.className本身
CSSClassList.prototype.toString = function () {
    return this.e.className;
}; //返回在e.className中的类名
CSSClassList.prototype.toArray = function () {
    return this.e.className.match(/\b\w+\b/g) || [];
};

⬆ back to top

查询纯文本形式的内容

/**
 *一个参数,返回元素的textContent或innerText
 *两个参数,用value参数的值设置元素的textContent或innerText
 */
function textContent(element, value) {
    var content = element.textContent; //检测textContent是否有定义
    if (value === undefined) { //没传递value,因此返回当前文本
        if (content !== undefined) return content;
        else return element.innerText;
    } else { //传递了value,因此设置文本
        if (content !== undefined) element.textContent = value;
        else element.innerText = value;
    }
}

textContent属性在除了IE的所有当前的浏览器中都支持。在IE中,可以用Element的innerText属性来代替。 ⬆ back to top

查找元素的后代中节点中的所有Text节点

//返回元素e的纯文本内容,递归进入其子元素
//该方法的效果类似于textContent属性
function textContent(e) {
    var child, type, s = ""; //s保存所有子节点的文本
    for (child = e.firstChild; child != null; child = child.nextSibling) {
        type = child.nodeType;
        if (type === 3 || type === 4) //Text和CDATASection节点
            s += child.nodeValue;
        else if (type === 1) //递归Element节点
            s += textContent(child);
    }
    return s;
}

⬆ back to top

使用innerHTML实现insertAdjacentHTML()

//本模块为不支持它的浏览器定义了Element.insertAdjacentHTML
//还定义了一些可移植的HTML插入函数,它们的名字比insertAdjacentHTML更符合逻辑:
//Insert.before()、Insert.after()、Insert.atStart()和Insert.atEnd()
var Insert = (function () { //如果元素有原生的insertAdjacentHTML,
    //在4个函数名更明了的HTML插入函数中使用它
    if (document.createElement("div").insertAdjacentHTML) {
        return {
            before: function (e, h) {
                e.insertAdjacentHTML("beforebegin", h);
            },
            after: function (e, h) {
                e.insertAdjacentHTML("afterend", h);
            },
            atStart: function (e, h) {
                e.insertAdjacentHTML("afterbegin", h);
            },
            atEnd: function (e, h) {
                e.insertAdjacentHTML("beforeend", h);
            }
        };
    }
    //否则,无原生的insertAdjacentHTML
    //实现同样的4个插入函数,并使用它们来定义insertAdjacentHTML
    //首先,定义一个工具函数,传入HTML字符串,返回一个DocumentFragment,
    //它包含了解析后的HTML的表示
    function fragment(html) {
        var elt = document.createElement("div"); //创建空元素
        var frag = document.createDocumentFragment(); //创建空文档片段
        elt.innerHTML = html; //设置元素内容
        while (elt.firstChild) //移动所有的节点
            frag.appendChild(elt.firstChild); //从elt到frag
        return frag; //然后返回frag
    }
    var Insert = {
        before: function (elt, html) {
            elt.parentNode.insertBefore(fragment(html), elt);
        },
        after: function (elt, html) {
            elt.parentNode.insertBefore(fragment(html), elt.nextSibling);
        },
        atStart: function (elt, html) {
            elt.insertBefore(fragment(html), elt.firstChild);
        },
        atEnd: function (elt, html) {
            elt.appendChild(fragment(html));
        }
    }; //基于以上函数实现insertAdjacentHTML
    Element.prototype.insertAdjacentHTML = function (pos, html) {
        switch (pos.toLowerCase()) {
            case "beforebegin":
                return Insert.before(this, html);
            case "afterend":
                return Insert.after(this, html);
            case "afterbegin":
                return Insert.atStart(this, html);
            case "beforeend":
                return Insert.atEnd(this, html);
        }
    };
    return Insert; //最后返回4个插入函数
}());

⬆ back to top

拖拽

/**
 *Drag.js:拖动绝对定位的HTML元素
 *
 *这个模块定义了一个drag()函数,它用于mousedown事件处理程序的调用
 *随后的mousemove事件将移动指定元素,mouseup事件将终止拖动
 *这些实现能同标准和IE两种事件模型一起工作
 *
 *参数:
 *
 *elementToDrag:接收mousedown事件的元素或某些包含元素
 *它必须是定位的元素,元素的样式必须是行内样式
 *它的style.left和style.top值将随着用户的拖动而改变
 *
 *event:mousedown事件对象
 **/
function drag(elementToDrag, event) { //初始鼠标位置,转换为文档坐标
    var startX = event.clientX;
    var startY = event.clientY; //在文档坐标下,待拖动元素的初始位置
    //因为elementToDrag是绝对定位的,
    //所以我们可以假设它的offsetParent就是文档的body元素
    var origX = parseFloat(elementToDrag.style.left);
    var origY = parseFloat(elementToDrag.style.top); //计算mousedown事件和元素左上角之间的距离
    //我们将它另存为鼠标移动的距离
    if (document.addEventListener) { //标准事件模型
        //在document对象上注册捕获事件处理程序
        document.addEventListener("mousemove", moveHandler, true);
        document.addEventListener("mouseup", upHandler, true);
    } else if (document.attachEvent) { //用于IE5~8的IE事件模型
        //在IE事件模型中,
        //捕获事件是通过调用元素上的setCapture()捕获它们
        elementToDrag.setCapture();
        elementToDrag.attachEvent("onmousemove", moveHandler);
        elementToDrag.attachEvent("onmouseup", upHandler); //作为mouseup事件看待鼠标捕获的丢失
        elementToDrag.attachEvent("onlosecapture", upHandler);
    }
    //我们处理了这个事件,不让任何其他元素看到它
    if (event.stopPropagation) event.stopPropagation(); //标准模型
    else event.cancelBubble = true; //IE
    //现在阻止任何默认操作
    if (event.preventDefault) event.preventDefault(); //标准模型
    else event.returnValue = false; //IE
    /**
     * 当元素正在被拖动时, 这就是捕获mousemove事件的处理程序
     *它用于移动这个元素 
     **/
    function moveHandler(e) {
        if (!e) e = window.event; //IE事件模型
        //移动这个元素到当前鼠标位置,
        //通过滚动条的位置和初始单击的偏移量来调整
        var targetLeft = e.clientX - startX + origX;
        var targetTop = e.clientY - startY + origY;
        var minLeft = 0;
        var minTop = 0;
        var maxLeft = (document.documentElement.clientWidth || document.body.clientWidth) - elementToDrag.offsetWidth;
        var maxTop = (document.documentElement.clientHeight || document.body.clientHeight) - elementToDrag.offsetHeight;
        targetLeft = targetLeft > maxLeft ? maxLeft : (targetLeft < minLeft ? minLeft : targetLeft);
        targetTop = targetTop > maxTop ? maxTop : (targetTop < minTop ? minTop : targetTop);
        elementToDrag.style.left = targetLeft + "px";
        elementToDrag.style.top = targetTop + "px";
        if (e.stopPropagation) e.stopPropagation(); //标准
        else e.cancelBubble = true; //IE
    }
    /**
     *这是捕获在拖动结束时发生的最终mouseup事件的处理程序
     **/
    function upHandler(e) {
        if (!e) e = window.event; //IE事件模型
        //注销捕获事件处理程序
        if (document.removeEventListener) { //DOM事件模型
            document.removeEventListener("mouseup", upHandler, true);
            document.removeEventListener("mousemove", moveHandler, true);
        } else if (document.detachEvent) { //IE 5+事件模型
            elementToDrag.detachEvent("onlosecapture", upHandler);
            elementToDrag.detachEvent("onmouseup", upHandler);
            elementToDrag.detachEvent("onmousemove", moveHandler);
            elementToDrag.releaseCapture();
        }
        //并且不让事件进一步传播
        if (e.stopPropagation) e.stopPropagation(); //标准模型
        else e.cancelBubble = true; //IE
    }
}

⬆ back to top

在谷歌地图上显示地理位置信息

//获取当前位置然后通过Google地图显示
//如果当前浏览器不支持地理位置API,则抛出一个错误
function getmap() { //检查是否支持地理位置API
    if (!navigator.geolocation) throw "Geolocation not supported"; //开始请求地理位置信息,
    navigator.geolocation.getCurrentPosition(setMapURL);
    function setMapURL(pos) { //从参数对象(pos)中获取位置信息
        var latitude = pos.coords.latitude; //经度
        var longitude = pos.coords.longitude; //纬度
        var accuracy = pos.coords.accuracy; //米
        var scale = 10; //比例
        //构造一个URL,用于跳转到Google地图
        var url = "https://www.google.com/maps/@" + latitude + "," + longitude + "," + scale + "z"; //设置一个大致的缩放级别
        location = url;
    }
}

⬆ back to top

使用所有地理位置特性

//异步的获取我的位置,并在指定的元素中展示出来
function whereami(elt) { //将此对象作为第三个参数传递给getCurrentPosition()方法
    var options = { //设置为true,表示如果可以的话
        //获取高精度的位置信息(例如,通过GPS获取)
        //但是,要注意的是,这会影响电池寿命
        enableHighAccuracy: false, //可以近似:这是默认值
        //如果获取缓存过的位置信息就足够的话,可以设置此属性
        //默认值为0,表示强制检查新的位置信息
        maximumAge: 300000, //5分钟左后
        //愿意等待多长时间来获取位置信息?
        //默认值为无限长 [2] ,getCurrentPosition()方法永不超时
        timeout: 15000 //不要超过15秒
    };
    if (navigator.geolocation) //如果支持的话,就获取位置信息
        navigator.geolocation.getCurrentPosition(success, error, options);
    else
        elt.innerHTMl = "Geolocation not supported in this browser"; //当获取位置信息失败的时候,会调用此函数

    function error(e) { //error对象包含一些数字编码和文本消息,如下所示:
        //1:用户不允许分享他/她的位置信息
        //2:浏览器无法确定位置
        //3:发生超时
        elt.innerHTML = "Geolocation error" + e.code + ":" + e.message;
    }
    //当获取位置信息成功的时候,会调用此函数
    function success(pos) { //总是可以获取如下这些字段
        //但是要注意的是时间戳信息在outer对象中,而不在inner、coords对象中
        var msg = "时间是" +
            new Date(pos.timestamp).toLocaleString() + "地理位置是" +
            pos.coords.accuracy + "米范围内经度是" +
            pos.coords.latitude + "纬度是" +
            pos.coords.longitude + "."; //如果设备还返回了海拔信息,则将其添加进去
        if (pos.coords.altitude) {
            msg += "海拔是" + pos.coords.altitude + "±" +
                pos.coords.altitudeAccuracy + "千米.";
        }
        //如果设备还返回了速度和航向信息,也将它们添加进去
        if (pos.coords.speed) {
            msg += "速度是" +
                pos.coords.speed + "m/s方向是" +
                pos.coords.heading + ".";
        }
        elt.innerHTML = msg; //显示所有的位置信息
    }
}

⬆ back to top

优雅的图片翻转实现

/**
 *优雅的图片翻转实现方式
 *
 *要创建图片翻转效果,将此模块引入到HTML文件中
 *然后在任意<img>元素上使用data-rollover属性来指定翻转图片的URL即可
 *如下所示:
 *
 *<img src="normal_image.png "data-rollover="rollover_image.png">
 *
 */
function changeImage() { //所有处理逻辑都在一个匿名函数中:不定义任何符号
    //遍历所有的图片,查找data-rollover属性
    for (var i = 0; i < document.images.length; i++) {
        var img = document.images[i];
        var rollover = img.getAttribute("data-rollover");
        if (!rollover) continue; //跳过没有data-rollover属性的图片
        //确保将翻转的图片缓存起来
        (new Image()).src = rollover; //定义一个属性来标识默认的图片URL
        img.setAttribute("data-rollout", img.src); //注册事件处理函数来创建翻转效果
        img.onmouseover = function () {
            this.src = this.getAttribute("data-rollover");
        };
        img.onmouseout = function () {
            this.src = this.getAttribute("data-rollout");
        };
    }
}

⬆ back to top

使用canvas绘制多边形

//定义一个以(x,y)为中心,半径为r的规则n边形,c可以通过调用画布getContext()方法得到
//每个顶点都是均匀分布在圆周上
//将第一个顶点放置在最上面,或者指定一定角度
//除非最后一个参数是true,否则顺时针旋转
function polygon(c, n, x, y, r, angle, counterclockwise) {
    angle = angle || 0;
    counterclockwise = counterclockwise || false;
    c.moveTo(x + r * Math.sin(angle), //从第一个顶点开始一条新的子路径
        y - r * Math.cos(angle)); //使用三角法计算位置
    var delta = 2 * Math.PI / n; //两个顶点之间的夹角
    for (var i = 1; i < n; i++) { //循环剩余的每个顶点
        angle += counterclockwise ? -delta : delta; //调整角度
        c.lineTo(x + r * Math.sin(angle), //以下个顶点为端点添加线段
            y - r * Math.cos(angle));
    }
    c.closePath(); //将最后一个顶点和起点连接起来
}

⬆ back to top

使用canvas绘制雪花

var deg = Math.PI / 180; //用于角度制到弧度制的转换
//在画布的上下文c中,以左下角的点(x,y)和边长len,绘制一个n级别的科赫雪花分形
function snowflake(c, n, x, y, len) {
    c.save(); //保存当前变换
    c.translate(x, y); //变换原点为起始点
    c.moveTo(0, 0); //从新的原点开始一条新的子路径
    leg(n); //绘制雪花的第一条边
    c.rotate(-120 * deg); //现在沿着逆时针方向旋转120 o
    leg(n); //绘制第二条边
    c.rotate(-120 * deg); //再次旋转
    leg(n); //画最后一条边
    c.closePath(); //闭合子路径
    c.restore(); //恢复初始的变换
    //绘制n级别的科赫雪花的一条边
    //此函数在画完一条边的时候就离开当前点,
    //然后通过坐标系变换将当前点又转换成(0,0,)
    //这意味着画完一条边之后可以很简单地调用rotate()进行旋转
    function leg(n) {
        c.save(); //保存当前坐标系变换
        if (n == 0) { //不需要递归的情况下:
            c.lineTo(len, 0); //就绘制一条水平线段
        } else { //递归情况下:绘制4条子边,类似这个样子: - \/ -
            c.scale(1 / 3, 1 / 3); //子边长度为原边长的1/3
            leg(n - 1); //递归第一条子边
            c.rotate(60 * deg); //顺时针旋转60 o
            leg(n - 1); //第二条子边
            c.rotate(-120 * deg); //逆时针旋转120 o
            leg(n - 1); //第三条子边
            c.rotate(60 * deg); //通过旋转回到初始状态
            leg(n - 1); //最后一条边
        }
        c.restore(); //恢复坐标系变换
        c.translate(len, 0); //但是通过转换使得边的结束点为(0,0)
    }
}

⬆ back to top

在Web Worker中发起同步XMLHtttpRequest

//此文件会通过一个新的Worker()来载入,因此,它是运行在独立的线程中的,
//可以放心地使用同步XMLHttpRequest API
//消息是URL数组的形式。以字符串形式同步获取每个URL指定的内容,
//并将这些字符串数组传递回去。
onmessage = function (e) {
    var urls = e.data; //输入:要获取的URL
    var contents = []; //输出:URL指定的内容
    for (var i = 0; i < urls.length; i++) {
        var url = urls[i]; //每个URL
        var xhr = new XMLHttpRequest(); //开始一个HTTP请求
        xhr.open("GET", url, false); //false则表示进行同步请求
        xhr.send(); //阻塞住,一直到响应完成
        if (xhr.status !== 200) //如果请求失败则抛出错误
            throw Error(xhr.status + " " + xhr.statusText + ": " + url);
        contents.push(xhr.responseText); //否则,存储通过URL获取得到的内容
    }
    //最后,将这些URL内容以数组的形式传递回主线程
    postMessage(contents);
}

善于利用JS中的小技巧,不仅可以使代码更加简洁,而且逼格更高。

使用!!模拟Boolean()函数

原理:逻辑非操作一个数据对象时,会先将数据对象转换为布尔值,然后取反,两个!!重复取反,就实现了转换为布尔值的效果。

使用一元加(+)模拟Number()函数

原理:对非数值类型的数据使用一元加(+),会起到与Number()函数相同的效果。

null转换为0

undefined转换为NaN

false转换为0,true转换为1

对于字符串:

空字串转换为0

含有数字或者浮点数或者十六进制格式的数据(11, 0.3, 0xfe等),转换为相应的数值

含有其他格式字符,无法转换为数值的字符串,转换为NaN

对于对象,先调用valueOf()方法,在转换,若结果为NaN,那么再调用toString()方法,之后再转换

使用逻辑与(&&)进行短路操作

if(connected){
    login();
}

以上代码可以简写为:

connected && login();

也可用这种方法来检查对象中是否拥有某个属性

user && user.name

原理:逻辑与(&&)会首先对第一个操作数进行求值,只有求值结果为true时才会对第二个操作数求值。connected && login()中,若判断connected不为true,则不再进行下一步操作。
所谓的短路操作即第一个操作数可以决定结果,则不再对第二个操作数进行求值。

使用逻辑或(||)设置默认值

逻辑或(||)也属于短路操作,即当第一个操作数可以决定结果时,不再对第二个操作数进行求值。利用这个特点,我们可以给赋值语句设置默认值。只有当第一个操作数为null或者undefined时,才会把第二个操作数赋值给目标。

function User(name, age){
    this.name = name || "Liming";
}

上述代码中,如果函数中没有传入name参数,name的值为undefined,那么就会给this.name赋值为”Liming”。
ES6中可以为函数设置默认值,所以这种方法可能要成为过去式,但是其他地方还是很有用的。
ES6 写法 简洁了许多

let User = (name="Liming", age) => { }

获取数组最后n个元素

可以使用以下代码获取数组中最后n个元素

var array = [1, 2, 3, 4, 5, 6];
console.log(array.slice(-1));  //[6]
console.log(array.slice(-2));  //[5, 6]

原理:Array.prototype.slice(begin,end)可以用来裁剪数组,第二个参数的默认值是数组的长度值。若值传入一个参数,则会返回从指定索引开始到数组结尾的所有值。
而slice()方法还可以接收负值,当传入负值时,会自动加上数组的长度值使其转换为正值,于是便得到了最后的n个值。

合并大数组

常用的合并数组的方式是使用Array.concat()函数。该函数会创建一个新数组,将两个数组连接起来存储到新数组中,这会大量消耗内存。可以使用Array.push.apply(arr1, arr2),它不会创建新数组,而是将第二个数组合并到第一个数组中,以减少内存的消耗。

var a = [1,2];
var b = [3,4];
console.log(a.push.apply(a, b));      // [1,2,3,4]
//或者
Array.prototype.push.apply(a, b);      // a变成了[1,2,3,4]
console.log(a); //[1,2,3,4]

原理: Array.push()是在数组的末尾增加元素,但是如果使用a.push(b)会把整个数组b当作一个元素添加到数组a中。
而apply()方法,则允许将某个方法的参数以数组的形式传入,所以起到了将数组b中的元素追加到数组a中的效果。

NodeList转换为数组

使用document.querySelectorAll(‘div’)返回的是NodeList对象,虽然它很像数组,但是并不能使用诸如sort(),filter()等方法。你可以将其转换为真正的数组。

var eles = document.querySelectorAll('p');  //NodeList
var arrayElements = [].slice.call(eles);       //转化为数组
// 或者
var arrayElements = Array.prototype.slice.call(eles);
// 或者
var arrayElements = Array.from(eles); 

原理:

[].slice.call(eles):
首先创建了一个空数组[],然后调用他的slice()方法,但是在slice()方法的执行中,把this对象指向了eles,所以会对eles进行裁减,由于对slice()方法没有传入参数,所以相当于slice(0,eles.length),会按照元长度返回一个数组。

Array.prototype.slice.call(eles): 原理与上面相似,只不过这次没有创建空数组,而是直接使用了原型中的方法