JS手写篇

节流 & 防抖

节流:用户频繁点击按钮,但只能3秒(timer)才执行一次

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 节流:技能冷却中
const throttle = (f, time) => {
let timer = null
return (...args) => {
if(timer) {return}
f.call(undefined, ...args)
timer = setTimeout(()=> {
timer = null
}, time)
}
}

const f = throttle(()=> {
console.log('hello')
}, 3000)

f() // 打印hello
f() // 技能冷却中

防抖:用户频繁拖动操作,希望拖动停止后再实现一个效果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 回城被打断
const debounce = (fn, time) => {
let timer = null
return(...args) => {
if(timer != null) {
clearTimeout(timer) // 打断回城
}
// 重新回城
timer = setTimeout(()=>{
fn.call(undefined, ...args) // 回城后调用fn
timer = null
}, time)
}
}

const f = debounce(()=> {
console.log('回城成功');
}, 3000)

发布订阅模式

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
const eventHub = {
map: {
// 事件队列
// click: [f1, f2]
// mouseDown: [f3, f4]
},
// 监听事件
on: (name, fn) => {
eventHub.map[name] = eventHub.map[name] || [] // 初始化
eventHub.map[name].push(fn)
},
// 触发事件
emit: (name, data) => {
const q = eventHub.map[name]
if(!q) {return}
q.map(f => f.call(null, data))
return undefined
},
// 取消监听
off: (name, fn)=> {
const q = eventHub.map[name]
if(!q) {return}
const index = q.indexOf(fn)
if(index < 0) {return}
q.splice(index,1)
}
}

eventHub.on('click', console.log);
eventHub.on('click',console.error);

setTimeout(()=>{
eventHub.emit('click', 'joyce')
}, 3000)

用class实现

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
class EventHub {
map = {}
on(name, fn) {
this.map[name] = this.map[name] || []
this.map[name].push(fn)
}
emit(name, data) {
const fnList = this.map[name] || []
fnList.forEach(fn => fn.call(undefined, data))
}
off(name, fn) {
const fnList = this.map[name] || []
const index = fnList.indexOf[fn]
if(index < 0) return
fn.splice(index, 1)
}
}

const e = new EventHub()
e.on('click', (name) => {
console.log('hi' + name)
})
e.on('click', (name) => {
console.log('hello' + name)
})
setTimeout(()=>{
e.emit('click', 'joyce')
},3000)

手写AJAX

1
2
3
4
5
6
7
8
9
10
11
12
var request = new XMLHttpRequest()
request.open('GET','/xxx')
request.onreadystatechange = function(){
if(request.readyState === 4){
if(request.status >= 200 || request.status === 304){
success(request)
}else{
fail(request)
}
}
}
request.send()

简易版Promise

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
class Promise2 {
#status = 'pending' // 私有属性,防止用户更改状态
constructor(fn){
this.q = []
const resolve = (data)=>{
this.#status = 'fulfilled'
const f1f1 = this.q.shift()
if(!f1f2 || !f1f2[0]) return
const x = f1f2[0].call(undefined, data)
if(x instanceof Promise2){
x.then((data)=> {
resolve(data)
}, (reason)=> {
reject(reason)
})
}else {
resolve(x)
}
}
const reject = (reason)=>{
this.#status = 'rejected'
const f1f1 = this.q.shift()
if(!f1f2 || !f1f2[1]) return
const x = f1f2[1].call(undefined, reason)
if(x instanceof Promise2){
x.then((data)=> {
resolve(data)
}, (reason)=> {
reject(reason)
})
}else {
resolve(x)
}
}
fn.call(undefined, resolve, reject)
}
then(f1,f2){
this.q.push([f1,f2])
}
}

const p = new Promise(function(resolve,reject){
setTimeout(function(){
reject('出错啦') // resolve('hello')
}, 3000)
})

p.then( (data)=>{console.log(data)}, (reason)=>{console.error(reason)} )

Promise.all

  • 在Promise上写,而不是原型
  • 参数是一个Promise数组,返回值是一个新Promise对象
  • 用一个数组来记录结果
  • 只要有一个reject,就整体reject
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    Promise.myAll = function(list){
    const results = []
    let count = 0 // 表示成功resolve的数量,当const等于list的长度时,说明全部成功
    return new Promise((resolve,reject) =>{
    list.map((promise,index) => {
    promise.then((result) => {
    results[index] = result // 将成功的结果记录在results里
    count += 1
    if(count === list.length) { // 全部成功后调用resolve
    resolve(results)
    }
    }, (reason) => {
    reject(reason)
    })
    })
    })
    }

深拷贝

  • 方法一:用JSON
  1. 该方法不支持日期、正则、函数、undefined等数据
  2. 不支持引用
    1
    const b = JSON.parse(JSON.stringify(a))
  • 方法二:用递归
  1. 检查环:a.self = a 造成的无法退出递归的问题,使用缓存cache
  2. 不拷贝原型上的属性(hasOwnProperty)
    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
    const deepClone = (a, cache) => {
    if(!cache){
    // 只有第一次深拷贝时创建缓存,后面递归的不会再创建
    // 使用Map而不是Object,Map的键可以是对象、函数,而Object的键只能是string/symbol
    cache = new Map() // 使用Map做映射,只要曾经拷贝过,就不会重复拷贝
    }
    if(a instanceof Object){
    // 判断是对象的实例
    if(cache.get(a)) { return cache.get(a) } // 检查环,因为a.self = a 无法退出递归
    let result
    if(a instanceof Function){
    if(a.prototype) { // 有 prototype 就是普通函数
    result = function(){ return a.apply(this, arguments) }
    } else { // 否则就是箭头函数
    result = (...args) => { return a.call(undefined, ...args) }
    }
    } else if (a instanceof Array) {
    result = []
    } else if (a instanceof Date) {
    result = new Date(a-0)
    } else if (a instanceof RegExp) {
    result = new RegExp(a.source, a.flags)
    } else {
    result = {}
    }

    cache.set(a, result)
    for(let key in a) {
    if(a.hasOwnProperty(key)){ // 只要自身属性,不拷贝原型上(继承)的属性
    result[key] = deepClone(a[key], cache)
    }
    }
    return result
    } else {
    // 其他基本类型
    return a
    }
    }

    const a = {
    number:1, bool:false, str:'1', empty1:undefined, empty2:null,
    array: [
    {name:'tony', age: 18},
    {name:'joyce', age: 24}
    ],
    date: new Date(2000,0,1,20,30,0),
    regex: /\.(j|t)sx/i,
    object: {name:'joyce', age: 24},
    f1: (a,b) => a+b,
    f2: function(a,b){ return a+b }
    }
    a.self = a

    const b = deepClone(a)

    b.self === b // true
    b.self = 'hello'
    a.self !== 'hello' // true

数组去重

  1. 方法一:使用Set

    1
    2
    3
    4
    5
    6
    var a1 = [1,2,2,3,3,3]
    var unique = function(a){
    return Array.from(new Set(a)) // return [...new Set(a)]
    }

    unique(a1) // 打印 [1,2,3]
  2. 方法二:使用计数排序,但只支持字符串

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    var a1 = [1,2,2,3,3,3]
    var unique = function(a){
    var map = {}
    for(let i=0; i<a.length; i++) {
    let number = a[i] // 1 ~ 3
    if(number === undefined) { continue }
    if(number in map) { continue }
    map[number] = true
    }
    // map = {1:true, 2:true, 3:true}
    return Object.keys(map)

    // 不用Object.keys的写法
    const result = []
    for(let key in map) {
    result.push(key)
    }
    return result
    }
  3. 方法三:使用Map,但兼容性稍差

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    var a1 = ['1',1,,2,,2,3,3,3,4,1,2]

    var unique = function(a){
    var map = new Map()
    for(let i=0; i<a.length; i++){
    let number = a[i] // 1 ~ 3
    if(number === undefined) { continue }
    if(map.has(number)){
    continue
    }
    map.set(number, true)
    }
    return [...map.keys()]
    }
0%