Open huruji opened 6 years ago
const isPlainObject = (obj) => (
obj && typeof obj === 'object' && Object.getPrototypeOf(obj) === Object.prototype
);
isPlainObject({}) // true
isPlainObject(new Date()) // false
这是最快的取整方法,位运算取整需要注意的是只是取的整数,如果需要和 Math.floor
结果一致,负数应该再减一
取反操作
const floor = (num) => ((num >= 0) ? ~~num : (~~num) - 1)
左移运算符
const floor = (num) => (num >= 0) ? (num << 0) : ((num << 0) - 1)
右移运算符
const floor = (num) => (num >= 0) ? (num >> 0) : ((num >> 0) - 1)
异或运算符
const floor = (num) => (num >= 0) ? (num^0) : ((num^0) - 1)
右移一位即可
5>>1 // 2
6>>1 // 3
核心就是使用数组的join方法
方法一
const repeat = (target, n) => (new Array(n + 1)).join(target)
方法二,创建类数组对象
const repeat = (target, n) => (
Array.prototype.join.call({
length: n + 1
}, target)
)
方法三,利用闭包缓存方法与对象
const repeat = (() => {
const join = Array.prototype.join, obj = {};
return (target, n) => {
obj.length = n + 1;
return join.call(obj, target);
}
})()
方法四,使用数组的concat方法
function repeat(target, n) {
return (n <= 0) ? '' : target.concat(repeat(target, --n));
}
方法五,利用算法提高效率,使用二分法
const repeat = (target, n) => {
let s = target, total = '';
while(n > 0) {
if(n % 2 == 1) total += s;
if(n == 1) break;
s += s;
n = n >> 1;
}
return total;
}
相对于直接使用 undefined
可以节省几个字符
例如检查一个值是否是 undefined
let arg = target === void(0) ? 'a' : 'b'
const camelize = (target) => {
if(target.indexOf('-') < 0 && target.indexOf('_') < 0) return target;
return target.replace(/[-_][^-_]/g, (match) =>(
match.charAt(1).toUpperCase() ))
}
camelize('my-name') // myName
const underscored = (target) => (
target.replace(/([a-z\d])([A-Z])/g, '$1_$2').replace(/\-/g, '_').toLowerCase()
)
const stripTags = (target) => {
return String(target || '').replace(/<[^>]+>/g, '');
}
在实际运用中,我们需要考虑 <script>
标签,不应该让 script
标签内的脚本显示出来,因此需要一个函数删除 script
标签内的内容
const stripScript = (target) => (
String(target || '').replace(/<script[^>]*>[\s\S]*?<\/script>/img, '')
)
const escapeHTML = (target) => (
target.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''')
)
最常见的就是月份和天前面置0
思路一:使用0作为数组的join的方法参数组合之后再截取字符串
const pad = (target, n) => {
const zero = new Array(n).join('0');
let str = zero + target;
return str.substr(-n);
}
pad(12, 4) /// 0012
可以将new数组的个数先计算出来,这样就省去了截取字符串的一步
const pad = (target, n) => (
new Array((n + 1) - target.toString().split('').length).join('0') + target
)
pad(12, 4) /// 0012
思路二:先创建一个含有n个零的大数,再截取
创建这个大数可以使用 Math.pow
、使用左移运算符 << 、或者使用科学计数法。
function pad(target, n) {
return (Math.pow(10, n) + '' + target).slice(-n);
}
function pad(target, n) {
return ((1 << n).toString(2) + target).slice(-n)
}
function pad(target, n) {
return (1e20 + '' + target).slice(-n)
}
思路三:创建拥有N个0的小数,用toFixed方法就行
function pad(target, n) {
return (0..toFixed(n) + target).slice(-n)
}
(0).toFixed(3) // 0.000
0..toFixed(2) // 0.00 注意有两个点,不然报错
printf
方法来格式化字符串function format(str, object) {
let arr = [].slice.call(arguments, 1);
return str.replace(/\\?#\{([^{}]+)\}/gm, (match, name) => {
if(match.charAt(0) == '\\') return match.slice(1);
let index = +name;
if(index >= 0) return arr[index];
if(object && object[name] !== void(0)) return object[name];
return '';
})
}
format('#{0} is a #{1}', 'huruji', 'boy') // huruji is a boy
format('#{name} is a #{sex}', {name: 'huruji', sex: 'boy'}) // huruji is a boy
indexOf
方法Array.prototype.indexOf = function(item, index) {
let n = item.length, i = ~~index;
if(i < 0) i += n;
for(; i < n; i++)
if(this[i] === item) return i;
return -1
}
这段代码第一个神奇的地方在于使用了双否 ~~
运算将其他类型的参数转换为0,先使用Number方法转换得到再使用 否运算
,而~NaN === -1
第二个神奇的地方在于使用 i += n
实现负数从数组尾部算起的功能
~i
代替 i > -1
我们有时需要判断数组、字符串存在某个元素时,进行某项操作:
if(arr.indexOf(item) > -1) {
// some code
}
这时候可以使用否操作 ~
来装X
if(~arr.indexOf(item)) {
// some code
}
bind
、apply
、call
出自 JavaScript 框架设计,非常骚气的黑魔法
const bind = function(bind) {
return {
bind: bind.bind(bind),
call: bind.bind(bind.call),
apply: bind.bind(bind.apply)
}
}(Function.prototype.bind)
使用
const concat = bind.apply([].concat);
concat([1,2,3], [4,5]); // [1, 2, 3, 4, 5]
//等价于
[].concat.apply([1,2,3], [4, 5]);
const concat1 = bind.call([].concat);
concat1([1,2,3], 4, 5, 6) // [1, 2, 3, 4, 5, 6]
//等价于
[].concat.call([1,2,3], 4, 5, 6);
const concat2 = bind.bind([].concat);
concat2.bind([1,2,3], 14, 15)();
//等价于
[].concat.bind([1,2,3])(14, 15);
用一张简单的代码图表示其中的原理:
柯里化之后给了我们多一次传参的机会,这样就可通过判断返回不同的结果了。
1.支持每次只传一个参数的 正宗柯里化函数
function curry(fn) {
function inner(len, args) {
if(len <= 0) return fn.apply(null, args);
return function(x) {
return inner(len - 1, args.concat(x))
}
}
return inner(fn.length, []);
}
使用:
function sum(x, y, z){
return x + y + z;
}
curry(sum)(12)(13)(14) // 39
2.支持一次传多个参数的 简化柯里化函数
function curry(fn) {
function inner(len, args) {
if(len <=0) return fn.apply(null, args);
return function() {
return inner(len - arguments.length, args.concat([].slice.apply(arguments)))
}
}
return inner(fn.length, []);
}
使用
curry(sum)(12, 13)()(14) // 39
与柯里化相似,柯里化缺点在于每次参数都是通过push的,但有些时候有些参数是已经拥有的,我们需要做的是填充没有的参数,这个时候我们可以使用占位操作来代表需要填充的参数,如undefined,不过推荐使用 Object.create(null)
,因为纯空对象没有原型,因此没有toString、valueOf等一系列继承自Object的方法。
这种技术和柯里化技术很适合在延迟调用的场景中使用。
var _ = Object.create(null);
function partial(fn) {
var args = Array.prototype.slice.call(arguments, 1);
return args.length < 1 ? fn : function() {
var innerArgs = [].slice.call(arguments);
args.forEach((e,i) => {
if(e === _) args[i] = innerArgs.shift();
});
return fn.apply(null, args)
}
}
使用
function sum() {
return Array.prototype.slice.call(arguments).reduce((total, e)=> total + e, 0)
}
const c = partial(sum, 1, 2, _, 4, _);
c(3, 5) // 15
Safari使用 new Date('2018-08-09 09:12')
返回 NaN,原因是safari的Date对象不支持这种格式,可以将 -
转换为 /
即可兼容所有浏览器
typeof data === ‘string’ && new Date(data.replace('-', '/'))
方法一:
const isXML = function(elem) {
const documentElement = elem && (elem.ownerDocument || elem).documentElement;
return documentElement ? documentElement.nodeName !== 'HTML' : false;
}
XML相对于HTML来说,XML没有className、getElementById,并且NodeName是区分大小写的。
通过获取文档的顶层文档对象(在html文档中就是HTML节点,判断nodeName是否为HTML),关于属性 ownerDocument
可在这里查阅
方法二:
以上是sizzle的实现,但是事实上,XML的节点是有可能是HTML标签的(虽然这种情况极其少),moottools的slick的实现是通过大量属性判断的:
const isXML = function(document) {
return (!!document.xmlVersion) ||
(!!document.xml) ||
(Object.prototype.toString.call(document) == '[object XMLDocument]') ||
(document.nodeType == 9 && document.documentElement.nodeName != 'HTML')
}
方法三:
标准浏览器中暴露了HTML文档的构造器HTMLDocument,而IE下的XML元素有selectNodes属性,因此可以判断是否是HTMLDocument的实例以及是否拥有selectNodes属性。
const isXML = window.HTMLDocument ? function(doc) {
return !(doc instanceof HTMLDocument);
} : function(doc) {
return "selecNodes" in doc;
}
方法四: 面对属性,其实使用JavaScript就可以随意添加属性,因此属性判断方法很容易被攻破,因此功能法才会更加有效,XML和HTML都支持createElement方法,但是XML区分大小写,因此可以创建两个大小写元素,判断是否相等即可,这是比较严谨的方法:
const isXML = function(doc) {
return doc.createElement('p').nodeName !== doc.createElement('P').nodeName;
}
const contains = function(a, b, same) {
// same属性,是否可以是同一个node
if(a === b) return !!same;
if(!b.parentNode) return false;
if(a.contains) {
return a.contains(b);
} else if(a.compareDocumentPosition) {
return !!(a.compareDocumentPosition(b) & 16);
}
while((b= b.parentNode))
if(a === b) return true;
return false;
}
contains
是IE的私有属性,现在chrome、firfox也有这个方法。compareDocumentPosition
方法是判断两个节点的位置,具体有以下:
有时候两个元素的位置可能满足上表的两种情况,如返回20,因此判断中使用了与运算来判断是否包含这个情况,我们需要注意到所有的结果都是2的倍数,因此与运算可以保证这点。
关于 compareDocumentPosition
两个节点相同返回0,a节点在前返回-1,b节点在前返回1
const sortNodes = function(a, b) {
var p = "parentNode",
ap = a[p],
bp = b[p];
if(a === b) {
return 0;
} else if(ap == bp) {
while(a = a.nextSibling) {
if(a === b) return -1
}
return 1;
} else if(!ap) {
return -1;
} else if(!bp) {
return 1;
}
var al = [], ap = a;
while(ap && ap.nodeType === 1) {
al[al.length] = ap;
ap = ap[p];
}
var bl = [], bp = b;
while(bp && bp.nodeType === 1) {
bl[bl.length] = bp;
bp = bp[p];
}
ap = al.pop();
bp = bl.pop();
while(ap === bp) {
ap = al.pop();
bp = bl.pop();
}
if(ap && bp) {
while(ap = ap.nextSibling) {
if(ap === bp) return -1;
}
return 1;
}
return ap ? 1: -1;
}
思路是:先判断两个节点是否相同,判断两个节点的父节点是否相同(相同则使用nextSibling属性),这里需要注意的是,如果节点就是根节点,就没有父节点(这个估计很多人会漏掉),最后也是最核心的方法就是不断向上取父元素,直到根元素,之后不断pop出来,判断是否父元素相等。(最最核心)。
很明显,上面的整个函数可以作为数组sort方法的参数,只要将取得的元素节点数组化就行了。
var eles = [].slice.call(document.getElementsByTagName('*'))
.sort(sortNodes)
一定需要自动播放时,可以添加静音muted属性即可
<video src="./test.mp4" width="600" height="400" controls autoplay muted></video>
ref: Chrome 66禁止声音自动播放之后
将 callback 的调用转化为 Promise 调用
const promisify = (fn) => (...args) => new Promise((resolve, reject) => {
args.push(resolve)
fn.apply(this, args)
})
测试
const testCb = function (a, b, callback) {
setTimeout(() => {
callback(a + b)
}, 1000)
}
promisify(testCb)(10, 12)
.then(data => {
console.log(data)
})
1.实现千分位分隔符
使用正则表达式
这里涉及到了
\B
匹配非单词边界、零宽正向先行断言、零宽负向先行断言,参考:正则表达式的先行断言(lookahead)和后行断言(lookbehind)使用
toLocaleString
方法2.精确到指定位数的小数
使用科学计数法
3.统计数组中同项出现的次数
4.使用解构赋值删除不必要的对象属性