一些JS使用的小技巧整理,如快速生成随机数,使对象可迭代、将多维数组展平等
将多维数组展平
第一种:不指定展平级别,只展平一级1
2
3
4
5
6
7var arr = ['a', 'b', ['c', ['d']]]
var flat1_1 = [].concat.apply([], arr)
var flat1_2 = [].concat(...arr)
var flat1_3 = arr.flat()
console.log(flat1_1,flat1_2,flat1_3)   // ["a", "b", "c", ["d"]]
第二种:不指定展平级别,能把数组完全展开1
2
3
4
5
6
7
8
9
10// 把数组转化成字符串再转化成数组(不能指定递归深度)
var flat2_1 = arr => arr.toString().split(',')  // toString() 也可使用join(',')替代
// 递归➕map(不能指定递归深度)
var flat2_2 = arr => [].concat(...arr.map(i=>Array.isArray(i)?flat2_2(i):i))
// 利用 flat() 函数
var flat2_3 = arr.flat(Infinity)
console.log(flat2_1(arr), flat2_2(arr), flat2_3)   // ["a", "b", "c", "d"]
第三种:能指定展平级别1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26// 利用 flat() 函数
var flat3_1 = arr.flat(2)  // 展平两级
// 普通递归(能指定递归深度)
var flat3_2 = (arr, num) => {
  let result = []
  if(!num) num = 1
  function temp(arr) {
    arr.forEach((i,index) => {
      if(Array.isArray(i)) {
        if(num != 1) {
          num--
          temp(i)
        } else {
          result.push(...i)
        }
      } else {
        result.push(i)
      }
    })
  }
  temp(arr)
  return result
}
console.log(flat3_1,flat3_2(arr, 5))   // ["a", "b", "c", "d"]
字符串大小写取反
| 1 | var result = str => { | 
快速过滤数组false值
| 1 | let arr = [0,1,2,3,4,true,false,undefined,null,''] | 
生成随机数
| 1 | // 可设置随机数长度 | 
生成星级评分
| 1 | let startScore = rate => "★★★★★☆☆☆☆☆".slice(5 - rate, 10 - rate) | 
获取URL查询参数
| 1 | // location.search = "?name=Nola&age=24" | 
快速生成数组
| 1 | // 生成连续数组 | 
一个数字变成千分位
| 1 | let numFormat = num => (num.toString().indexOf ('.') !== -1) ? num.toLocaleString() : num.toString().replace(/(\d)(?=(?:\d{3})+$)/g, '$1,') | 
用代码画一个键盘
| 1 | (_=>[..."`1234567890-=~~QWERTYUIOP[]\\~ASDFGHJKL;'~~ZXCVBNM,./~"].map(x=>(o+=`/${b='_'.repeat(w=x<y?2:' 667699'[x=["BS","TAB","CAPS","ENTER"][p++]||'SHIFT',p])}\\|`,m+=y+(x+' ').slice(0,w)+y+y,n+=y+b+y+y,l+=' __'+b)[73]&&(k.push(l,m,n,o),l='',m=n=o=y),m=n=o=y='|',p=l=k=[])&&k.join` | 
取整 ~~ 和 | 0 和 >> 0
对正数来说 ~~ + | 0  >> 0 运算结果与 Math.floor( ) 运算结果相同,而对于负数来说与Math.ceil( )的运算结果相同1
2
3
4
5
6
7
8~~2.5 === Math.floor(2.5)   // 2
~~-2.5 === Math.ceil(-2.5)  // -2
2.5 | 0        // 2
-2.5 | 0       // -2
2.5 >> 0        // 2
-2.5 >> 0      // -2
判断是否是奇数 & 1 和 % 2
| 1 | // 判断是否是奇数 | 
利用短路运算符 || 和 && 替代简单的if语句
||返回遇到的第一个真值,&&返回遇到的第一个假值1
2(A || B) && fun()  // 相当于 if(A || B) fun()
A && B && fun()    // 相当于 if(A && B) fun()
数组中取最大/小值
| 1 | let min = Math.min(...arr) | 
创建一个可迭代对象
| 1 | var foo = { | 
使 a === a - 1 为true
使用 Infinity 可以做到,但是怎么才能得到正负Infinity的值?方法一:数值运算的值,超过了Number允许表示的范围;方法二:将一个不为0的正负数除以01
2
3
4
5
6
7
8
9
10
11
12
13
14// 无穷小的数
a = -Infinity
console.log(a === a - 1)  // true
// 无穷大的数
a = Infinity
console.log(a === a - 1)  // true
// 无穷数的运算
Infinity + Infinity  // Infinity
Infinity - Infinity  // NaN
Infinity * Infinity  // Infinity
Infinity / Infinity  // NaN
Infinity * 0         // NaN
使 a == 1 && a == 2 && a == 3 返回 true
在引擎读取 a 的值时,在方法内部做处理
实现宽松相等 ==1
2
3
4
5
6
7
8
9
10
11
12
13// 方法一:使用 toString 或者 valueOf
a = {
  i: 1,
  toString: () => a.i++, 
  valueOf: () => a.i++,
}
// 方法二:使用 Proxy 
a = new Proxy({ i: 1 }, {
  get(obj) { return () => obj.i++ }
}) 
console.log(a == 1 && a == 2 && a == 3)  // true
实现严格相等 ===1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19// 方法一:
i = 1
Object.defineProperty(window, 'a', {
  get: () => i++ 
})
// 方法二:
value = function* () {
  let i = 1
  while(true) yield i++
}()
Object.defineProperty(window, 'a', {
  get() {
    return value.next().value
  }
})
console.log(a === 1 && a === 2 && a === 3)  // true
使 a == b && a == c && c != b 为 true
代码实现1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20let a,b,c
// 方法一:利用复杂类型指向引用的特性,将 b,c 设置为复杂类型
// "[]" != []
a = false, b = [], c = []
console.log( '结果--array', a == b && a == c && c != b )   // true
// "{}" != {}
a = "[object Object]", b = {}, c = {}
console.log( '结果--object', a == b && a == c && c != b )   // true
// "[object Function]" != function(){}
a = "function(){}", b = function(){}, c = function(){}
console.log( '结果--function', a == b && a == c && c != b )   // true
// 方法二:利用proxy劫持
b = 1, c = 2
a = new Proxy({ i: 1 }, { 
  get(obj) { return () => obj.i++ } 
})
console.log( '结果--proxy', a == b && a == c && c != b )   // true
为什么[“1”, “2”, “3”].map(parseInt) 返回值是[1, NaN, NaN]
map()调用callback函数时,会给它传递三个参数:当前正在遍历的元素、元素索引、原数组本身
parseInt接受两个参数:元素、进制数
遍历时parseInt的第二个参数被传进来的是元素的索引值,所以…1
2
3
4
5["1", "2", "3"].map(parseInt) 
// [1, NaN, NaN]
["1", "2", "3"].map(item => parseInt(item))
// [1, 2, 3]
利用 a 标签解析 URL
| 1 | function parseURL(url) { | 
删除链接中某个参数
URLSearchParams MDN1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30// 利用URLSearchParams
function delSearchParam(name, search = location.search) {
  let _params = new URLSearchParams(search.substr(1))
  _params.delete(name)
  return `?${_params.toString()} `
}
// 普通的
function delSearchParam(name, search = location.search) {
	let _arr = search.split("?")[1].split("&")
	// 	参数变成数组
	let _map = {}
	for(let i = 0; i < _arr.length; i++) {
		let _t = _arr[i].split("=")
		_map[_t[0]] = _t[1]
  }
	// 	删除这个参数
	if(_map[name]) {
		delete _map[name]
  }
	// 	把删除后的数组变成字符串
	let _result = '?'
	for(let i in _map) {
		_result += `${i}=${_map[i]}&`
  }
	// 	删除字符串最后一个元素
	return _result.substr(0, _result.length-1)
}
delSearchParam('a', '?a=1&b=2&c=3')
实现吸顶
参考文章–交叉观察者
MDN–IntersectionObserver
方法一
效果1
2
3
4.target {
  position: sticky;
  top: 0;
}
使用条件:
- 父元素不能overflow:hidden或者overflow:auto属性
- 必须指定top、bottom、left、right4 个值之一,否则只会处于相对定位
- 父元素的高度不能低于sticky元素的高度
- sticky元素仅在其父元素内生效
方法二
效果1
2
3
4
5
6
7
8
9
10
11
12
13
14
15var a = document.querySelector('.a')
var b = document.querySelector('.b')
new IntersectionObserver(function(e) {
  let offsetTop = a.getBoundingClientRect().top
  if(offsetTop < 0) {
    a.style.position = 'fixed'
    a.style.top = 0
    a.style.left = '50%'
    a.style.transform = 'translateX(-50%)'
  } else {
    a.style.position = 'relative'
  }
}, {
  threshold: [1]
}).observe(a)
时间相关的计算
| 1 | // 计算当前日期天数 | 
实现深拷贝
代码实现 参考文章 Map MDN
PS:扩展符...可实现数组的深拷贝1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114// Step 1:深拷贝数组和对象
function clone(target) {
  if(typeof target == 'object') {
    let _target = Array.isArray(target) ? [] : {}
    for(let i in target) {
      _target[i] = clone(target[i])
    }
    return _target
  } else {
    return target
  }
}
// Step 2:但是如果循环引用,上面的代码就很会导致栈内存溢出
// 【解决】利用Map,拷贝前检查有无拷贝过 
// => Map是强引用类型,内存不会自动释放,会造成非常大的额外消耗(任何值都可以作为一个键或一个值)
// => 使用WeakMap弱引用类型,内存会自动释放(键必须是对象,而值可以是任意的)
function clone(target, map = new WeakMap()) {
  if (typeof target === 'object') {
    let _target = Array.isArray(target) ? [] : {}
    if(map.get(target)) {               
      return map.get(target)      
    }
    map.set(target, _target) 
    for(let i in target) {
      _target[i] = clone(target[i], map)
    }
    return _target
  } else {
    return target
  }
}
// Step 3:做一下性能优化
// 【优化点】执行效率:while、for > for in
// 其中for in执行效率是三者中最差的,所以不能用for in
if(false) { 
  let arr = [...new Array(1000000).keys()]
  console.time()
  
  // 第 1 种:while     用时:8.023193359375ms
  let i = 0
  while(i < arr.length) { i++ }
  
  // 第 2 种:for       用时:7.807861328125ms
  for(let i = 0; i < arr.length; i++) { }
  
  // 第 3 种:for in    用时:190.059814453125ms
  for(let i in arr) { }
  
  // 第 4 种:for in    用时:10.40576171875ms
  arr.forEach(() => {})
  
  console.timeEnd()
}
function clone(target, map = new WeakMap()) {
  if (typeof target === 'object') {
    let isArray = Array.isArray(target);
    let _target = isArray ? [] : {}
    if(map.get(target)) {               
      return map.get(target)      
    }
    map.set(target, _target) 
    let keys = isArray ? undefined : Object.keys(target)
    forEach(keys || target, (value, i) => {
      if (keys) {
        i = value
      }
      _target[i] = clone(target[i], map)
    })
    return _target
  } else {
    return target
  }
}
function forEach(array, iteratee) {
  let index = -1;
  const length = array.length;
  while (++index < length) {
    iteratee(array[index], index);
  }
  return array;
}
// Step 4:兼容其他数据类型
// 以上代码只兼容object和array两种数据类型,值如果存在null、function则会报错
// 一个简单的测试
let target = {
    a: undefined,
    b: 'hello',
    c: {
       child: 'child'
    },
    d: [1, 2, 3],
    f: { f: { f: { f: { f: { f: { f: { f: { f: { f: { f: { f: {} } } } } } } } } } } },
}
let result = clone(target)
target.target = target          // 循环引用
target.c.child = 'new child'    // 修改原对象
console.log(result)             // 打印拷贝值
防抖
防抖查看效果:地址1
2
3
4
5
6
7
8
9
10// 触发间隔超过指定间隔的任务才会执行,连续发生动作会刷新时间
function debounce(fn, delay, ...args) {
  let timer = null
  return function () {
    clearTimeout(timer)
    timer = setTimeout(() => {
      fn.apply(this, args)
    }, delay)
  }
}
节流
节流查看效果:地址1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24// 在指定间隔内任务只执行一次,连续发生的动作会被忽略
function throttle(fn, delay, ...args) {
  let prev = Date.now()
  return function () {
    let now = Date.now()
    if (now - prev >= delay) {
      fn.apply(this, args)
      prev = Date.now()
    }
  }
}
function throttle(fn, delay, ...args) {
  let timer
  return function () {
    if (!timer) {
      timer = null
      timer = setTimeout(() => {
        timer = null
        fn.apply(this, args)
      }, delay)
    }
  }
}