1.手写 Promise .all & .race
MDN Promise
class xnPromise {
constractor(executor) {
this.status = 'pending'
this.value = undefined
this.reason = undefined
this.onResolvedCallbacks = []
this.onRejectedCallbacks = []
const resolve = value => {
if(this.status === 'pending') {
this.status = 'fulfilled'
this.value = value
this.onResolvedCalbacks.forEach(fn => fn())
}
}
const reject = reason => {
if(this.status === 'pending') {
this.status = 'rejected'
this.reason = reason
this.onRejectedCallbacks.forEach(fn => fn())
}
}
try {
executor(resolve, reject)
} catch(err) {
reject(err)
}
}
then(onFulfilled, onRejected) {
if(this.status === 'fulfilled') {
onFulfilled(this.value)
} else if(this.status === 'rejected') {
onRejected(this.reason)
} else if(this.status === 'pending') {
this.onResolvedCallbacks.push(() => {
onFulfilled(this.value)
})
this.onRejectedCallbacks.push(() => {
onRejected(this.reason)
})
}
}
static all(promiseArr) {
return new xnPromise((resolve, reject) => {
let result = []
promiseArr.forEach((promise, index) => {
promise.then(value => {
result[index] = value
if(result.length === promiseArr.length) {
resolve(result)
}
}, reject)
})
})
}
static race(promiseArr) {
return new xnPromise((resolve, reject) => {
promiseArr.forEach(promise => {
promise.then(value => {
resolve(value)
}, reject)
})
})
}
}
module.exports = xnPromise
1.1 jest 测试: xnPromise.all() .race()
展开 jest 测试代码
// file: xnPromise.spec.js
const xnPromise = require('./xnPromise.js')
it('基本功能:', (done) => {
let xnpromise
xnpromise = new xnPromise((resolve, reject) => {
setTimeout(() => {
resolve('success')
}, 213)
})
xnpromise.then(data => {
expect(data).toBe('success')
})
xnpromise = new xnPromise((resolve, reject) => {
setTimeout(() => {
reject('failure')
}, 213)
})
xnpromise.then(data => {
console.log('resolve data: ', data)
}, err => {
expect(err).toBe('failure')
done()
})
})
// test: .all()
test('xnPromise.all()', done => {
const promise1 = new xnPromise((resolve, reject) => {
setTimeout(() => {
resolve('promise1 success')
}, 213)
})
const promise2 = new xnPromise((resolve, reject) => {
setTimeout(() => {
resolve('promise2 success')
}, 213)
})
xnPromise.all([promise1, promise2]).then(result => {
expect(result).toEqual(['promise1 success', 'promise2 success'])
done()
})
})
// test: .race()
test('xnPromise.race()', done => {
const promise1 = new xnPromise((resolve, reject) => {
setTimeout(() => {
resolve('promise1 success')
}, 213)
})
const promise2 = new xnPromise((resolve, reject) => {
setTimeout(() => {
resolve('promise2 success')
}, 50)
})
// promise2 时间短 成功后 .race() 立马返回 'promise2 success'
xnPromise.race([promise1, promise2]).then(result => {
expect(result).toBe('promise2 success')
done()
})
})2. 节流throttle & 防抖debounce
2.1 节流 throttle
function throttle(fn, delay) {
let last = 0
return (...args) => {
const now = +Date.now()
// 两次事件执行时间间隔大于 delay,则可调用 fn
if(now - last > delay) {
last = now
fn.apply(this, args)
}
}
}
jest throttle
2.2 防抖 debounce
function debounce(fn, delay) {
let timer
return (...args) => {
if(timer) {
clearTimeout(timer)
}
// 只能有定时器调用 fn
timer = setTimeout(() => {
fn.apply(this, args)
}, delay)
}
}
jest debounce
3. 手写发布订阅 EventEmitter
监听 DOM 事件, 其实就是发布订阅模式, 订阅 DOM事件,当用户执行相应的操作时发布事件,也可以手动取消对事件的订阅
const el = document.querySelector('el')
el.addEventListener('click', () => {console.log('click1'))
// 可以订阅多个相同事件
el.addEventListener('click', () => {console.log('click2'))
// 可以取消订阅, 这里必须要使用与订阅时相同的函数
el.removeEventListener('click', handler)
vue中使用自定义事件也会用到 发布订阅模式
// 父组件
<my-component v-on:my-event="doSomething"></my-component>
// MyCoponent 组件
methods: {
fn() {
this.$emit('my-event', data)
}
}
首先就是 架子
class EventEmitter {
on() {} // 订阅事件
emit() {} // 触发事件
off() {} // 移除事件 => removeListener
once() {} // 执行一次
}
完整版
class EventEmitter {
private events = {} // 声明全局变量存储订阅的事件及其对应的执行函数
// 订阅事件方法
on(eventName, callback) {
this.events[eventName] = this.events[eventName] || []
this.events[eventName].push(callback)
}
// 触发事件方法
emit(eventName, data) {
// if(!this.events[eventName]) return
// this.events[eventName].forEach(cb => cb(data))
(this.events[eventName] || []).forEach(cb => cb(data))
}
// 移除订阅事件
removeListener(eventName, callback) {
if(this.events[eventName]){
this.events[eventName] = this.events[eventName].filter(cb => cb != callback)
}
}
// 只执行一次订阅的事件,然后移除
once(eventName, callback) {
let fn = () => {
callback() // fn 函数中调用原有的 callback
this.removeListener(eventName, fn) // 删除 fn, 再执行的时候之后执行一次
}
this.on(eventName, fn)
}
}