高级JavaScript手写函数指南

843 阅读6分钟

本文为笔者认为很棒的一些JS手写,当然除此之外还有
Promise(A+规范)以及Promise相关API全部实现
Webpack实现
Axios实现
虚拟列表实现
等这些也非常有意义,但由于篇幅略长,因此单独开辟一篇文章,有需要的直接去看

当可以不看源码就将它实现时,我想,那就意味着,你真正的掌握了它,因为世界上并没有那么多记性很好的人

—— 沃滋基·硕德

金额千分位切割

function split3(str = '123456789012') {
    return str.replace(/(?<!^)(?=(\d{3})+$)/g, '-')
}
console.log(split3())  // 123-456-789-012

银行卡四位分割

function split4(str = '123456789012') {
    return str.replace(/(?<=^(\d{4})+)(?!$)/g, '-')
}
console.log(split4())  // 1234-5678-9012

CommonJS 实现

const { resolve, extname } = require('path')
const fs = require('fs')
const { runInThisContext } = require('vm')
// Module类(module.exports那个module的类)
class Module {
    constructor() {
        this.exports = {}
    }
}
// 能够处理的文件类型
Module.extnames = ['.js', '.json']
// CJS缓存存放位置
Module.cache = {}
// require实现
function require_(path) {
    // 将路径处理位绝对路径
    path = resolve(path)
    // 如果文件类型不满足js文件或json文件,则不做任何处理
    if (!Module.extnames.includes(extname(path))) return
    // 判断文件是否可以访问(如果路径文件不存在,则退出)
    try {
        fs.accessSync(path)
    } catch (error) {
        return
    }
    // 判断缓存中是否存在当前路径对应缓存导出结果,如果存在则直接返回
    if (Module.cache[path]) return Module.cache[path].exports
    // 创建当前文件路径的module,保存导出结果
    const module = new Module()
    // 将该module加入缓存(缓存对象的key位该文件绝对路径,这一步必须在处理文件内容之前做,保证我们的CJS可以和官方CJS一样处理循环引用)
    Module.cache[path] = module
    // 如果是JSON文件,则直接读取文件内容,保存在module.exports中,并返回
    if (extname(path) === '.json') {
        const content = fs.readFileSync(path, 'utf8')
        module.exports = content
        return module.exports
    }
    // 如果是JS文件
    else if (extname(path) === '.js') {
        // 读取文件内容,包裹在字符串(function(require,module,exports){  })中
        const script = `(function (require, module, exports) { ${fs.readFileSync(path, 'utf8')} })`
        // 然后在全局环境(global)中运行该字符串,获取该函数(防止污染局部作用域)
        const fn = runInThisContext(script)
        // 执行该函数,传入实现的require,module,module.exports,函数执行完毕,模块内导出的结果将保存在module.exports中
        fn.call(null, require_, module, module.exports)
        // 返回module.exports
        return module.exports
    }
}

冒泡排序

// 稳定 O(n^2) O(1)
function bubble(arr) {
    for (let i = arr.length; i >= 0; i--) {
        for (let j = 0; j < i; j++) {
            arr[j] > arr[j + 1] && ([arr[j], arr[j + 1]] = [arr[j + 1], arr[j]])
        }
    }
    return arr
}
console.log('bubble:', bubble([3, 1, 38, 5, 47, 15, 36, 26, 27, 2, 46, 4, 19, 50, 48]))

选择排序

// 不稳定 O(n^2) O(1)
function select(arr) {
    let maxIdx
    for (let i = arr.length - 1; i >= 0; i--) {
        maxIdx = i
        for (let j = 0; j < i; j++) {
            arr[j] > arr[maxIdx] && (maxIdx = j)
        }
        [arr[i], arr[maxIdx]] = [arr[maxIdx], arr[i]]
    }
    return arr
}
console.log('select:', select([3, 1, 38, 5, 47, 15, 36, 26, 27, 2, 46, 4, 19, 50, 48]))

插入排序

// 稳定 O(n^2) O(1)
function insert(arr) {
    for (let i = 1; i < arr.length; i++) {
        const curr = arr[i]
        for (let j = i - 1; j >= -1; j--) {
            if (curr < arr[j]) {
                arr[j + 1] = arr[j]
            } else {
                arr[j + 1] = curr; break
            }
        }
    }
    return arr
}
console.log('insert:', insert([3, 1, 38, 5, 47, 15, 36, 26, 27, 2, 46, 4, 19, 50, 48]))

快速排序

// 不稳定  O(n*log2n) O(log2n)
function quick(arr) {
    if (arr.length <= 1) return arr
    const mid = arr.pop()
    const left = [], right = []
    for (const v of arr) {
        v < mid ? left.push(v) : right.push(v)
    }
    return [...quick(left), mid, ...quick(right)]
}
console.log(' quick:', quick([3, 1, 38, 5, 47, 15, 36, 26, 27, 2, 46, 4, 19, 50, 48]))

归并排序

// 稳定 O(n*log2n) O(1)
function merge(arr) {
    if (arr.length <= 1) return arr
    const mid = Math.floor(arr.length / 2)
    const left = arr.slice(0, mid), right = arr.slice(mid)
    return mergeSort(merge(left), merge(right))
}
function mergeSort(left, right) {
    const res = []
    while (left.length && right.length) {
        left[0] < right[0] ? res.push(left.shift()) : res.push(right.shift())
    }
    return res.concat(left, right)
}
console.log(' merge:', merge([3, 1, 38, 5, 47, 15, 36, 26, 27, 2, 46, 4, 19, 50, 48]))

希尔排序

// 不稳定 O(n^1.3) O(1)
function shell(arr) {
    for (let i = Math.floor(arr.length / 2); i >= 1; i = Math.floor(i / 2)) {
        for (let j = i; j < arr.length; j++) {
            const curr = arr[j]
            for (let k = j - i; k >= -i; k -= i) {
                if (curr < arr[k]) {
                    arr[k + i] = arr[k]
                } else {
                    arr[k + i] = curr; break
                }
            }
        }
    }
    return arr
}
console.log(' shell:', shell([3, 1, 38, 5, 47, 15, 36, 26, 27, 2, 46, 4, 19, 50, 48]))

数组转树 && 树转数组

const arr = [
    { id: 1, parentId: null, name: 'a' },
    { id: 2, parentId: null, name: 'b' },
    { id: 3, parentId: 1, name: 'c' },
    { id: 4, parentId: 2, name: 'd' },
    { id: 5, parentId: 1, name: 'e' },
    { id: 6, parentId: 3, name: 'f' },
    { id: 7, parentId: 4, name: 'g' },
    { id: 8, parentId: 7, name: 'h' },
]
// 数组转树
function toTree(arr) {
    const trees = []
    const map = new Map()
    for (const v of arr) map.set(v.id, v)
    for (const v of arr) {
        const pId = v.parentId
        // 如果map中不存在当前元素parentId,则该元素为根元素
        if (!map.has(pId)) {
            trees.push(v)
        }
        // 如果map中存在 当前元素的parentId,则该元素不为根元素
        else {
            const parent = map.get(pId)
            !(parent.children instanceof Array) && (parent.children = [])
            parent.children.push(v)
        }
    }
    return trees
}
console.log(trees = toTree(arr))
// 树转数组(广度优先)
function toArr(trees) {
    const arr = [...trees]
    for (let i = 0; i < arr.length; i++) {
        (arr[i].children instanceof Array) && arr.push(...arr[i].children)
    }
    return arr
}
console.log(toArr(trees))

Function.prototype.call 实现

Function.prototype.call_ = function (that, ...args) {
    // call方法传入的this,既该函数中的that,当that是null或undefined时指向全局上下文,如果that是基本类型则指向该类型的包装类型,如果是引用类型则指向该引用类型
    that = [null, undefined].includes(that) ? window : Object(that)
    const symbol = Symbol()
    that[symbol] = this
    const res = that[symbol](...args)
    delete that[symbol]
    return res
}

Function.prototype.bind 实现

Function.prototype.bind_ = function (that, ...args) {
    // call方法传入的this,既该函数中的that,当that是null或undefined时指向全局上下文,如果that是基本类型则指向该类型的包装类型,如果是引用类型则指向该引用类型
    that = [null, undefined].includes(that) ? window : Object(that)
    const excutor = this
    function fn(...args1) {
        return excutor.call(this instanceof fn ? this : that, ...args, ...args1)
    }
    fn.prototype = excutor.prototype
    return fn
}

防抖

// pre标识 控制做防抖的函数是在点击那一刻立即执行,还是延迟时间之后才执行
function debounce(callback, timeout, pre) {
    let timer = null
    let flag = false
    return function (...args) {
        if (pre) {
            clearTimeout(timer)
            !flag && (flag = true) && callback.call(this, ...args)
            timer = setTimeout(() => flag = false, timeout);
        } else {
            clearTimeout(timer)
            timer = setTimeout(() => callback.call(this, ...args), timeout);
        }
    }
}

节流

// pre标识 控制做节流的函数是在点击那一刻立即执行,还是延迟时间之后才执行
function throttle(callback, timeout, pre) {
    let timer = null
    return function (...args) {
        if (pre) {
            if (!timer) {
                callback.call(this, ...args)
                timer = setTimeout(() => timer = null, timeout);
            }
        } else {
            if (!timer) {
                timer = setTimeout(() => { callback.call(this, ...args); timer = null }, timeout);
            }
        }
    }
}

柯理化

function curry(fn) {
    const length = fn.length, args = []
    return function step(...args1) {
        args.push(...args1)
        return args.length >= length ? fn(...args) : step
    }
}

compose

function compose(...fns) {
    if (fns.length === 0) return val => val
    if (fns.length === 1) return fns[0]
    return fns.reduce((p, c) => (...args) => p(c(...args)))
}

观察者模式

class Observer {
    constructor(callback) {
        this.callback = callback
    }
    run(...args) {
        this.callback(...args)
    }
}
class Subject {
    constructor() {
        this.observers = []
    }
    add(...observers) {
        this.observers.push(...observers)
    }
    notify(...args) {
        this.observers.forEach(ob => ob.run(...args))
    }
}

发布订阅模式

class EventEmit {
    constructor() {
        this._events = {}
    }
    on(type, callback) {
        if (!this._events[type]) this._events[type] = []
        this._events[type].push(callback)
    }
    off(type, callback) {
        if (!this._events[type]) return
        this._events[type] = this._events[type].filter(cb => cb !== callback)
    }
    emit(type, ...args) {
        if (!this._events[type]) return
        this._events[type].forEach(cb => cb(...args))
    }
    once(type, callback) {
        // 注意:这里必须用箭头函数,否则this捕获不正确
        const wrapper = (...args) => {
            callback(...args)
            this.off(type, wrapper)
        }
        this.on(type, wrapper)
    }
}

深拷贝

// type:精确类型判断
const type = val => Object.prototype.toString.call(val).slice(8).replace(/]/, '').toLowerCase()
// wm:用来处理循环引用的 缓存 WeakMap
const wm = new WeakMap()
// 深拷贝实现:
function deepCopy(val) {
    // 如果是基本类型直接返回,注意: !(val instanceof Object) 用来处理基本类型的包装类型
    if (['number', 'string', 'boolean', 'null', 'undefined', 'symbol'].includes(type(val)) && !(val instanceof Object)) return val
    // 如果是对象,数组,arguments对象
    else if (['object', 'array', 'arguments'].includes(type(val))) {
        // 处理循环引用
        if (wm.has(val)) return wm.get(val)
        const copyVal = new val.constructor()
        wm.set(val, copyVal)
        // 递归copy
        Object.keys(val).forEach(k => copyVal[k] = deepCopy(val[k]))
        return copyVal
    }
    // 如果是set
    else if (['set'].includes(type(val))) {
        // 处理循环引用
        if (wm.has(val)) return wm.get(val)
        const copyVal = new val.constructor()
        wm.set(val, copyVal)
        // 递归copy
        val.forEach(v => copyVal.add(deepCopy(v)))
        return copyVal
    }
    // 如果是map
    else if (['map'].includes(type(val))) {
        // 处理循环引用
        if (wm.has(val)) return wm.get(val)
        const copyVal = new val.constructor()
        wm.set(val, copyVal)
        // 递归copy
        val.forEach((v, k) => copyVal.set(k, deepCopy(v)))
        return copyVal
    }
    // 如果是正则
    else if (['regexp'].includes(type(val))) {
        const source = val.source
        const modify = val.toString().match(/(?<=\/)\w*$/)[0]
        const copyVal = new val.constructor(source, modify)
        copyVal.lastIndex = val.lastIndex
        return copyVal
    }
    // function(a, b, c) { return a + b + c }
    // 如果是函数
    else if (['function'].includes(type(val))) {
        // 如果是箭头函数(判断依据是否有原型对象,当然可能存在非箭头函数但是也没有原型对象,但这里不考虑),直接eval原函数字符串返回
        if (!val.prototype) return eval(val.toString())
        // 如果是普通函数,正则获取函数参数与函数体,重新new Function(参数,函数体)获取拷贝函数返回
        const param = val.toString().match(/(?<=\(\s*)(\n|.)*(?=\s*\)\s*\{)/)[0]
        const body = val.toString().match(/(?<=\s*\)\s*\{)[\d\D]*(?=\})/)[0]
        return param ? new Function(...param.split(','), body) : new Function(body)
    }
    // 如果其他引用类型值,直接使用该值构造函数并传入该值,返回拷贝后的该值
    else {
        return new val.constructor(val)
    }
}
// 测试deepCopy函数:
; (function testDeepCopy(deepCopy) {
    const origin = [1, '', false, null, undefined, Symbol(), [0], { a: 1 }, new Set([2]), new Map([[1, 2]]), /\w*/g, () => 1, function c(a, b, c) { return a + b + c }]
    const copy = deepCopy(origin)
    console.log('拷贝结果:', copy);
    const res = []
    origin.forEach((v, i) => {
        res.push(copy[i] === origin[i])
        i === 10 && res.push(copy[i])
        i === 11 && res.push(copy[i]())
        i === 12 && res.push(copy[i](1, 2, 3))
    })
    // 
    console.log('如果测试结果与测试结果返回的数组每一个元素开起来相同,意味着当前测试通过:')
    console.log('测试结果:', res);
    console.log('正确结果:', [true, true, true, true, true, true, false, false, false, false, false, /\w*/g, false, 1, false, 6]);
})(deepCopy);

generator 函数执行器

// spawn:generator函数执行器(理解这个函数有助于理解generator与async函数)
function spawn(generator) {
    return new Promise((resolve, reject) => {
        const gen = generator()
        function step(nextFn) {
            let nextVal
            try {
                nextVal = nextFn()
            } catch (error) {
                return reject(error)
            }
            if (nextVal.done) return resolve(nextVal.value)
            Promise.resolve(nextVal.value).then(
                v => step(() => gen.next(v)),
                err => step(() => gen.throw(err))
            )
        }
        step(() => gen.next())
    })
}
// 测试spawn函数
; (function testSpawn(deepCopy) {
    function* generator() {
        const v1 = yield 1
        const v2 = yield Promise.resolve(2)
        const v3 = yield new Promise(r => setTimeout(() => r(v1 + v2), 1000))
        console.log(v3);
    }
    async function async_() {
        const v1 = await 1
        const v2 = await Promise.resolve(2)
        const v3 = await new Promise(r => setTimeout(() => r(v1 + v2), 1000))
        console.log(v3);
    }
    console.log('如果下面两个返回最终值相同,则当前测试通过:');
    console.log('async:', async_());
    console.log('gener:', spawn(generator));
})(spawn);

图片懒加载(交叉观察器版本)

// lazyLoad中的参数imgs是个img元素的数组,每个imgs元素需要有data-src属性保存图片地址,一个规范的img元素类似这样: <img data-src='图片地址' /> 
function lazyLoad(...imgs) {
    // 创建交叉观察器
    const io = new IntersectionObserver(entries =>
        entries.forEach(item => {
            // 如果观察到图片出现,则请求图片资源,并取消对当前图片的观察
            if (item.isIntersecting) {
                item.target.src = item.target.dataset.src
                io.unobserve(item.target)
            }
        }))
    // 使用交叉观察器监视所有图片
    imgs.forEach(v => io.observe(v))
}

图片懒加载(基础版本)

// lazyLoad中的参数imgs是个img元素的数组,每个imgs元素需要有data-src属性保存图片地址,一个规范的img元素类似这样: <img data-src='图片地址' /> 
function lazyLoad(...imgs) {
    // 获取视口高度
    const height = window.innerHeight
    // 滚动回调
    function callback() {
        // 如果没有需要监听元素,则删除当前滚动监听
        if (!imgs.length) window.removeEventListener('scroll', callback)
        // imgs 的浅拷贝,用于imgs的读写分离,类似redux源码中createStore中监听队列的读写分离操作
        const nextImgs = imgs.slice()
        // 对每一个图片元素进行处理
        imgs.forEach((item, i) => {
            // 获取当前元素举例视口顶部的高度
            const top = item.getBoundingClientRect().top
            // 获取当前元素到视口底部的距离
            const distance = top - height
            // 当该距离小于0,说明该元素出现在时候中,此时添加该元素的src,获取图片资源,同时取消对该元素的监听
            if (distance <= 0) {
                item.src = item.dataset.src
                nextImgs.splice(i, 1)
            }
        })
        // 将读元素(imgs) 同步为 写元素(nextImgs)
        imgs = nextImgs
    }
    // 监听滚动
    window.addEventListener('scroll', callback)
}

AJAX

function ajax(method, url, data, success, failed) {
    method = method.toLowerCase()
    const xhr = window.XMLHttpRequest ? new XMLHttpRequest() : new ActiveXObject()
    xhr.onreadystatechange = function () {
        if (xhr.readyState === 4) {
            if (xhr.status === 304 || (xhr.status >= 200 && xhr.status < 300)) {
                success(xhr.response)
            } else {
                failed(xhr)
            }
        }
    }
    xhr.open(method, url)
    method === 'post' && xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded')
    xhr.send(data || null)
}

Redux 中 createStore 实现

function createStore(reducer, preloadedState, enhancer) {
    // 1,如果有增强器,则直接返回增强器处理的结果
    if (enhancer) return enhancer(createStore)(reducer, preloadedState)
    // 2,Redux状态保存位置
    let currentState = preloadedState
    // 3,Redux保存(读)订阅函数的数组
    let currentListeners = []
    // 4,Redux保存(写)订阅函数的数组
    let nextListeners = currentListeners
    // 5,当前是否正在 dispatch 的标志
    let isDispatching = false
    // 6,getState实现(注意:当dispatch时,不允许getState,既不允许读Redux状态)
    function getState() {
        if (isDispatching) throw new Error('isDispatching')
        return currentState
    }
    // 7,最常见的dispatch实现
    function dispatch(action) {
        // 当dispatch时,不允许又dispatch
        if (isDispatching) throw new Error('isDispatching')
        // dispatch标识为true,表示正在dispatch,同时使用reducer更新Redux的state,不管这个过程是否成功,最后都得关闭dispatch表示,既将isDispatching恢复为false
        try {
            isDispatching = true
            currentState = reducer(currentState, action)
        } finally {
            isDispatching = false
        }
        // 将 写订阅函数的数组 赋值给 读订阅函数的数组(既读取的时候要拿最新的订阅函数数组),
        // 再赋值给这里声明的listener(这一步操作结合subscribe的实现,目测是进行内存优化)
        let listeners = currentListeners = nextListeners
        // 执行所有订阅函数
        for (let i = 0; i < listeners.length; i++) {
            // 不直接  listeners[i]() ,目测应该是防止订阅函数能够拿到this,既当前订阅函数数组listener,从而造成隐患(既订阅函数可以修改this)
            const listener = listeners[i]
            listener()
        }
        // 返回dispatch接受的action
        return action
    }
    // 8,添加订阅函数 实现
    function subscribe(listener) {
        // 当添加订阅函数时,不允许dispatch
        if (isDispatching) throw new Error('isDispatching')
        // 将currenListeners与nextListener分离,目的是,这里添加订阅函数属于写操作,那么不能干扰读操作中的订阅函数,因此进行读写分离
        shadowCopy()
        // 添加订阅函数
        nextListeners.push(listener)
        // 声明isSubscribed,标识当前订阅函数是否被订阅
        let isSubscribed = true
        // 返回取消订阅函数
        return () => {
            // 如果当前函数没有被订阅,则什么都不做(目测应用场景既防止 取消函数的订阅之后,又取消)
            if (!isSubscribed) return
            // 当取消订阅函数时,不允许dispatch
            if (isDispatching) throw new Error('isDispatching')
            // 订阅表示恢复为false,表示该函数的订阅取消
            isSubscribed = false
            // 将currenListeners与nextListener分离,目的是,这里删除订阅函数属于写操作,那么不能干扰读操作中的订阅函数,因此进行读写分离
            shadowCopy()
            // 从读订阅函数数组中 删除当前订阅函数
            const idx = nextListeners.indexOf(listener)
            nextListeners.splice(idx, 1)
            // 目测是为了内存优化,防止每个订阅函数都指向一个巨大的currentListeners,以至于该内存不能被释放
            // 呼应dispatch中的 代码:let listeners = currentListeners = nextListeners
            // 即使将currentListeners置为null,因为listener保存了所有在dispatch那一刻所存在的订阅函数,因此  currentListeners = null 是没问题的
            currentListeners = null
        }
    }
    // shadowCopy:用于读订阅函数数组(currentListeners),写订阅函数数组(nextListeners)分离
    function shadowCopy() {
        currentListeners === nextListeners && (nextListeners = currentListeners.slice())
    }
    // 执行一次dispatch,初始化数据,type这里应该是个随机值(避免与reducer内部重名)
    dispatch({ type: '123456789' })
    // 返回 getState, dispatch, subscribe
    return { getState, dispatch, subscribe }
}

Redux 中 combineReducers 实现

function combineReducers(composeReducers) {
    // goodReducers:过滤掉composeReducers中不是函数的reducer之后留下的所有reducer
    // composeReducersKeys:composeReducers的key
    const goodReducers = {}, composeReducersKeys = Object.keys(composeReducers)
    // 过滤开始
    for (const key of composeReducersKeys) {
        typeof composeReducers[key] === 'function' && (goodReducers[key] = composeReducers[key])
    }
    // 返回所有合法reducer(既是reducer是函数)最终整合在一起的终极reducer
    return (state = {}, action) => {
        // goodReducersKeys:所有合法reducer的key
        const goodReducersKeys = Object.keys(goodReducers)
        // nextState:保存所有reducer更新后的state
        // hasChanged:判断nextState与上一次redux中state是否发生变化的标识
        let nextState = {}, hasChanged = false
        // 执行所有合法reducer
        for (const key of goodReducersKeys) {
            // 获取每一个reducer
            const reducer = goodReducers[key]
            // 每一个reducer开始更新上一次redux的state
            nextState[key] = reducer(state[key], action)
            // 如果每一个reducer更新后返回的state(既这里的nextState[key])与上次的state(既这里的state[key])不同,
            // 则说明redux状态state发生了变化,因此将hasChanged置为true,既state发生变化
            hasChanged = hasChanged || nextState[key] !== state[key]
        }
        // 目测是为了处理一种特殊情况,既state中没有该子reducer对应的子state,所有state[key]返回undefined
        // 但是确实存在子reducer,且该reducer返回值恰好也是undefined,如果出现这种情况,那也意味着Redux中state前后发生了变化
        // 因此hasChangedx需要置为true,既state发生变化
        hasChanged = hasChanged || goodReducersKeys.length !== Object.keys(state).length
        // 如果Redux state 在reducer更新前后发生了变化,则返回最新的state(既这里的nextState),否则直接返回原state
        // 这样做的目的,网上说是内存优化(优化一点是一点)
        return hasChanged ? nextState : state
    }
}

Redux 中 applyMiddleware 实现

function applyMiddleware(...middlewares) {
    return createStore => (reducer, preloadedState) => {
        // 获取原始store
        const store = createStore(reducer, preloadedState)
        // 声明一个将来会被中间件增强的dispatch(虽然此刻这个dispatch什么都不能做)
        let dispatch = () => { throw new Error('dont dispatch') }
        // 创建一个容器,保存getState,与dispatch,之后将用来交给每一个中间件
        const storeAPI = {
            getState: store.getState,
            // 注意:容器的dispatch不是直接用之前声明的dispatch做赋值,而是形成闭包,这么做的目的是为了保证之后交给中间件的dispatch一直都是指向同一个既上面声明的那个dispatch,这可能不太好理解,不过我建议结合redux-thunk源码,多看几遍就能明白这段代码中的奥妙~
            dispatch: (...args) => dispatch(...args)
        }
        // 将每一个中间件传入上面的容器对象,给中间件使用getState与dispatch(一直都是上面声明的那个dispatch)的能力
        const chain = middlewares.map(middleware => middleware(storeAPI))
        // 使用compose函数将所有中间件进行组合,最终再传入原始的dispatch,这个行为既完整了对原始dispatch行为的增强,这时候返回的既增强后的dispatch
        // 将增强后的dispatch赋值给上面声明的dispatch(每个中间件接受到的dispatch最终用的都是这个dispatch),这可能不太好理解,但希望你能理解~
        dispatch = compose(...chain)(store.dispatch)
        // 返回 createStore 的api,getState与subscribe
        // 以及被中间件增强后的dispatch(既该函数内声明的dispatch)
        return { ...store, dispatch }
    }
}
// compose函数
function compose(...fns) {
    if (fns.length === 0) return val => val
    if (fns.length === 1) return fns[0]
    return fns.reduce((p, c) => (...args) => p(c(...args)))
}

Redux-Thunk 实现

function createReduxThunk(...args) {
    return ({ dispatch, getState }) => next => action => {
        if (typeof action === 'function') {
            // 建议结合 Redux 中 applyMiddleware 源码理解
            return action(dispatch, getState, ...args)
        }
        return next(action)
    }
}

基本拖拽实现

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    <style>
        #container {
            margin: 100px 0 0 100px;
            width: 500px;
            height: 500px;
            background-color: blue;
            position: relative;
        }

        #app {
            width: 100px;
            height: 100px;
            background-color: red;
            position: absolute;
            top: 100px;
            left: 100px;
        }
    </style>
</head>

<body>
    <div id='container'>
        <div id='app'></div>
    </div>

    <script>
        function drag(id) {
            const app = document.getElementById(id)
            app.onmousedown = (e) => {
                const ev = e
                // 鼠标距离拖动区域上 左距离
                const innerTop = e.clientY - app.offsetTop
                const innerLeft = e.clientX - app.offsetLeft
                document.onmousemove = (ev) => {
                    // app拖动距离 = 鼠标距离拖动元素的父级距离 -  鼠标距离拖动区域距离
                    let moveTop = ev.clientY - innerTop
                    let moveLeft = ev.clientX - innerLeft
                    // 设置限制区域 阻止app拖动到父元素外面 
                    const dad = app.parentNode
                    if (moveLeft < 0) moveLeft = 0
                    if (moveLeft > dad.offsetWidth - app.offsetWidth) moveLeft = dad.offsetWidth - app.offsetWidth
                    if (moveTop < 0) moveTop = 0
                    if (moveTop > dad.offsetHeight - app.offsetHeight) moveTop = dad.offsetHeight - app.offsetHeight
                    // 设置app拖动距离 到定位top left值
                    app.style.top = moveTop + 'px'
                    app.style.left = moveLeft + 'px'
                }
                document.onmouseup = (e) => {
                    // 抬起鼠标 解除onmousemove onmouseup绑定事件 
                    document.onmousemove = null
                    document.onmouseup = null
                }
            }
        }
        drag('app')
    </script>
</body>

</html>