以下是个人总结,也有一些是copy大神的,现在放到一起,方便以后查阅(有不对的地方,还望大家能够提出,我会尽快加以改正)。
!!强制转boolean
根据真值,假值 判断,返回true
,false
假值: 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返回浏览器历史第一页:
我在浏览器控制台敲了 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.Cookie
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
通用模块规范,支持webpack
、RequireJS
、SeaJS
等模块加载器,亦或直接通过<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
。改用karma
,mocha
,power-assert
做单元测试,使用Coverage
测试覆盖率。这一部分,后续更新。
这里给大家推荐一个好用的断言库 power-assert ,这个库记住assert(value, [message])
一个API就基本无敌,从此再也不用担心记不住断言库的API。
本项目的所有测试用例都在test
目录下,大家可以作一定参考。
更新:单元测试,已使用karma
,mocha
,power-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
中的name
、version
、author
等信息,最后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()函数
- 仿ECMAScript5中String.trim()函数
- 仿ECMAScript5中Array.reduce()函数
- 仿ECMAScript5中Object.keys()函数
- 仿ECMAScript5中Function.bind()函数
- 仿ECMAScript5中Array.map()函数
- 仿Math.max()方法实现
- 仿String.match()方法实现
- 仿HTML5的classList属性实现
- 仿Function.name属性实现
- 返回元素的第n层祖先元素
- 返回元素的第n个兄弟元素
- 返回元素的第n个子代元素
- 原生JS实现CSS动画之震动
- 原生JS实现CSS动画之隐藏
- 在数组中查找所有出现的元素方法
- 数据类型检测之特殊情况特殊处理
- 使用innerHTML实现outerHTML属性
- 插入节点
- 倒序排列子节点
- 查询窗口滚动条的位置
- 查询窗口的视口尺寸
- 表格的行排序
- 生成目录表
- 数组去重
- 冒泡排序
- 从URL解析参数
- 获取纯文本的元素内容
- 手写一个JSONP实现
- 查询纯文本形式的内容
- 查找元素的后代中节点中的所有Text节点
- 使用innerHTML实现insertAdjacentHTML
- 拖拽
- 在谷歌地图上显示地理位置信息
- 使用所有地理位置特性
- 优雅的图片翻转实现
- 使用canvas绘制多边形
- 使用canvas绘制雪花
- 在Web Worker中发起同步XMLHtttpRequest
仿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;
}
在数组中查找所有出现的元素方法
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;
}
数据类型检测,特殊情况特殊处理
function classOf(obj) {
if (obj === null) return "Null";
if (obj === undefined) return 'Undefined';
return Object.prototype.toString.call(obj).slice(8, -1);
}
仿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;
}
仿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;
}
仿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);
}
}
}
仿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;
}
数组去重
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;
}
冒泡排序
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;
}
仿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;
}
返回元素的第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;
}
返回元素的第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;
}
返回元素的第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个兄弟元素
}
}
表格的行排序
//根据指定表格每行第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
}
}
生成目录表
/**
*
*这个模块注册一个可在页面加载完成后自动运行的匿名函数。当执行这个函数时会去文档中查找
*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);
}
}());
从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; //返回解析后的参数
}
获取纯文本的元素内容
/**
*一个参数,返回元素的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;
}
}
}
手写一个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; //用于创建唯一回调函数名称的计数器
插入节点
//将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]);
}
}
使用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);
}
}());
倒序排列子节点
//倒序排列节点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);
}
查询窗口滚动条的位置
//以一个对象的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
};
}
查询窗口的视口尺寸
//作为一个对象的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
};
}
返回函数的名字
Function.prototype.getName = function () {
return this.name || this.toString().match(/function\s*(\w*)\s*\(/)[1];
}
原生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); //调用完成后的回调函数
}
}
}
原生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); //调用完成后的回调函数
}
}
}
仿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) || [];
};
查询纯文本形式的内容
/**
*一个参数,返回元素的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;
}
使用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个插入函数
}());
拖拽
/**
*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
}
}
在谷歌地图上显示地理位置信息
//获取当前位置然后通过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;
}
}
使用所有地理位置特性
//异步的获取我的位置,并在指定的元素中展示出来
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; //显示所有的位置信息
}
}
优雅的图片翻转实现
/**
*优雅的图片翻转实现方式
*
*要创建图片翻转效果,将此模块引入到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");
};
}
}
使用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(); //将最后一个顶点和起点连接起来
}
使用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)
}
}
在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): 原理与上面相似,只不过这次没有创建空数组,而是直接使用了原型中的方法