This article for the author think great some JS handwritten, of course, in addition to the Promise (A+ specification) and Promise related API all realize Webpack realization Axios virtual list realization, these are also very meaningful, but because the length is slightly long, so open up A separate article, there is A need to see directly

When you can implement it without looking at the source code, IT means, I think, that you really have it, because there aren’t that many people with good memories in the world

— Walzicki Shude

Amount of thousandths cut

function split3(str = '123456789012') {
    return str.replace(/ (? 
      .The '-')}console.log(split3())  / / 123-456-789-012
Copy the code

Four-digit bank card segmentation

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

CommonJS implementation

const { resolve, extname } = require('path')
const fs = require('fs')
const { runInThisContext } = require('vm')
// Module class (module.exports: Module class)
class Module {
    constructor() {
        this.exports = {}
    }
}
// The type of file that can be processed
Module.extnames = ['.js'.'.json']
// CJS cache location
Module.cache = {}
/ / the require implementation
function require_(path) {
    // The path is treated as an absolute path
    path = resolve(path)
    // If the file type is not js file or JSON file, no processing is done
    if(! Module.extnames.includes(extname(path)))return
    // Determine whether the file is accessible (if the path file does not exist, exit)
    try {
        fs.accessSync(path)
    } catch (error) {
        return
    }
    // Check whether the export result corresponding to the current path exists in the cache
    if (Module.cache[path]) return Module.cache[path].exports
    // Create the module of the current file path to save the exported result
    const module = new Module()
    // Add the module to the cache (cache the key bits of the object and the absolute path to the file, this step must be done before processing the file contents, to ensure that our CJS can handle circular references like official CJS)
    Module.cache[path] = module
    // If it is a JSON file, the contents of the file are read directly, saved in module.exports, and returned
    if (extname(path) === '.json') {
        const content = fs.readFileSync(path, 'utf8')
        module.exports = content
        return module.exports
    }
    // If it is a JS file
    else if (extname(path) === '.js') {
        Function (require,module,exports){}
        const script = `(function (require, module, exports) { ${fs.readFileSync(path, 'utf8')}}) `
        // Then run the string in the global environment to get the function (to prevent contamination of local scopes)
        const fn = runInThisContext(script)
        // Implement require, module, module.exports. The output from the module is saved in module.exports
        fn.call(null, require_, module.module.exports)
        / / return the module exports
        return module.exports
    }
}
Copy the code

Bubble sort

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]))
Copy the code

Selection sort

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]))
Copy the code

Insertion sort

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]))
Copy the code

Quick sort

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]))
Copy the code

Merge sort

// Stable 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]))
Copy the code

Hill sorting

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]))
Copy the code

Array to tree && tree to array

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'},]// Array to tree
function toTree(arr) {
    const trees = []
    const map = new Map(a)for (const v of arr) map.set(v.id, v)
    for (const v of arr) {
        const pId = v.parentId
        // If the current element parentId does not exist in the map, the element is the root element
        if(! map.has(pId)) { trees.push(v) }// If parentId of the current element exists in the map, the element is not the root element
        else {
            constparent = map.get(pId) ! (parent.childreninstanceof Array) && (parent.children = [])
            parent.children.push(v)
        }
    }
    return trees
}
console.log(trees = toTree(arr))
// Tree to array (width first)
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))
Copy the code

Function. The prototype. The call

Function.prototype.call_ = function (that, ... args) {
    The call method passes this, which is that in the function, to the global context if that is null or undefined, to the wrapper type of that type if that is a primitive type, and to the reference type if that is a reference type
    that = [null.undefined].includes(that) ? window : Object(that)
    const symbol = Symbol()
    that[symbol] = this
    constres = that[symbol](... args)delete that[symbol]
    return res
}
Copy the code

Function. The prototype. The bind

Function.prototype.bind_ = function (that, ... args) {
    The call method passes this, which is that in the function, to the global context if that is null or undefined, to the wrapper type of that type if that is a primitive type, and to the reference type if that is a reference type
    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.prototypereturn fn
}
Copy the code

Image stabilization

// Pre identifies whether the function is executed immediately after the click or after the delay
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); }}}Copy the code

The throttle

// Pre identifies whether the throttling function is executed immediately after the click or after the delay
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); }}}}Copy the code

Ke the physical and chemical

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

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))) }Copy the code

Observer model

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)) } }Copy the code

Publish and subscribe model

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) {
        // Note: the arrow function must be used here, otherwise this is captured incorrectly
        const wrapper = (. args) = >{ callback(... args)this.off(type, wrapper)
        }
        this.on(type, wrapper)
    }
}
Copy the code

Deep copy

// type: accurate type judgment
const type = val= > Object.prototype.toString.call(val).slice(8).replace(/]/.' ').toLowerCase()
// wm: cache WeakMap used to handle circular references
const wm = new WeakMap(a)// Deep copy implementation:
function deepCopy(val) {
    // If the basic type is returned directly, note:! (Val instanceof Object) is used to handle the wrapper types of primitive types
    if (['number'.'string'.'boolean'.'null'.'undefined'.'symbol'].includes(type(val)) && ! (valinstanceof Object)) return val
    // Arguments object
    else if (['object'.'array'.'arguments'].includes(type(val))) {
        // Handle circular references
        if (wm.has(val)) return wm.get(val)
        const copyVal = new val.constructor()
        wm.set(val, copyVal)
        / / a recursive copy
        Object.keys(val).forEach(k= > copyVal[k] = deepCopy(val[k]))
        return copyVal
    }
    // if set is set
    else if (['set'].includes(type(val))) {
        // Handle circular references
        if (wm.has(val)) return wm.get(val)
        const copyVal = new val.constructor()
        wm.set(val, copyVal)
        / / a recursive copy
        val.forEach(v= > copyVal.add(deepCopy(v)))
        return copyVal
    }
    // If map is used
    else if (['map'].includes(type(val))) {
        // Handle circular references
        if (wm.has(val)) return wm.get(val)
        const copyVal = new val.constructor()
        wm.set(val, copyVal)
        / / a recursive copy
        val.forEach((v, k) = > copyVal.set(k, deepCopy(v)))
        return copyVal
    }
    // If it is regular
    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 }
    // if it is a function
    else if (['function'].includes(type(val))) {
        // If it is an arrow function (depending on whether there is a prototype object, there may be a non-arrow function but there is no prototype object, but that is not considered here), eval the original function string
        if(! val.prototype)return eval(val.toString())
        // If it is a normal Function, the re gets the Function parameters and the Function body, and new Function(parameter, Function body) gets the copy Function return
        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)
    }
    // If any other reference type value is passed, the value constructor is used directly and the copied value is returned
    else {
        return new val.constructor(val)
    }
}
// Test the deepCopy function:
; (function testDeepCopy(deepCopy) {
    const origin = [1.' '.false.null.undefined.Symbol(),0] and {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 result:', 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('If the test result opens the same as each element of the array returned by the test result, it means that the current test passed:')
    console.log('Test Results:', res);
    console.log('Correct result:'[true.true.true.true.true.true.false.false.false.false.false./\w*/g.false.1.false.6]);
})(deepCopy);
Copy the code

Generator Function executor

// spawn: generator executor (understanding this function helps understand generator and async functions)
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())
    })
}
// Test spawn function
; (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('If the following two return the same final value, the current test passes:');
    console.log('async:', async_());
    console.log('gener:', spawn(generator));
})(spawn);
Copy the code

Image lazy loading (Cross Viewer version)

// The lazyLoad parameter imgs is an array of IMG elements. Each IMGS element needs a data-src attribute to hold the image address. A canonical IMG element looks like this: 
function lazyLoad(. imgs) {
    // Create a cross viewer
    const io = new IntersectionObserver(entries= >
        entries.forEach(item= > {
            // If an image appears, request the image resource and cancel the observation of the current image
            if (item.isIntersecting) {
                item.target.src = item.target.dataset.src
                io.unobserve(item.target)
            }
        }))
    // Use cross viewer to monitor all images
    imgs.forEach(v= > io.observe(v))
}
Copy the code

Image lazy loading (base version)

// The lazyLoad parameter imgs is an array of IMG elements. Each IMGS element needs a data-src attribute to hold the image address. A canonical IMG element looks like this: 
function lazyLoad(. imgs) {
    // Get the viewport height
    const height = window.innerHeight
    // Scroll back
    function callback() {
        // Delete the current scroll listener if there is no element to listen on
        if(! imgs.length)window.removeEventListener('scroll', callback)
        // A shallow copy of the IMGS, used for read-write separation of the IMGS, similar to the read-write separation of the queue in createStore in redux
        const nextImgs = imgs.slice()
        // Process each image element
        imgs.forEach((item, i) = > {
            // Gets the height of the top of the current element's sample viewport
            const top = item.getBoundingClientRect().top
            // Gets the distance from the current element to the bottom of the viewport
            const distance = top - height
            // When the distance is less than 0, it indicates that the element appears in the. At this time, add the element SRC, obtain the image resources, and cancel the listening on the element
            if (distance <= 0) {
                item.src = item.dataset.src
                nextImgs.splice(i, 1)}})// Synchronize read elements (imgs) to write elements (nextImgs)
        imgs = nextImgs
    }
    // Listen for scrolling
    window.addEventListener('scroll', callback)
}
Copy the code

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)}Copy the code

Redux createStore implementation

function createStore(reducer, preloadedState, enhancer) {
    // 1, if there is an enhancer, the result of the enhancer processing is returned directly
    if (enhancer) return enhancer(createStore)(reducer, preloadedState)
    // 2, Redux state save position
    let currentState = preloadedState
    // 3, Redux saves (reads) an array of subscription functions
    let currentListeners = []
    // 4, Redux holds (writes) an array of subscription functions
    let nextListeners = currentListeners
    // 5, whether the flag is currently being dispatched
    let isDispatching = false
    // 6, getState implementation (note: when dispatch, getState is not allowed, neither Redux state is allowed)
    function getState() {
        if (isDispatching) throw new Error('isDispatching')
        return currentState
    }
    // 7, the most common implementation of dispatch
    function dispatch(action) {
        // Dispatch is not allowed
        if (isDispatching) throw new Error('isDispatching')
        // If the dispatch flag is true, it indicates that dispatch is being dispatched, and the reducer is used to update the state of Redux, no matter whether this process is successful or not, the dispatch expression must be closed at last, since isDispatching is restored to false
        try {
            isDispatching = true
            currentState = reducer(currentState, action)
        } finally {
            isDispatching = false
        }
        // Assign the array of write subscription functions to the array of read subscription functions (fetching the latest array of subscription functions when reading),
        // Assign to the listener declared here (this step is combined with the implementation of subscribe, which visually optimizes memory)
        let listeners = currentListeners = nextListeners
        // Execute all subscription functions
        for (let i = 0; i < listeners.length; i++) {
            // Listeners [I]() are not direct listeners[I]()
            const listener = listeners[i]
            listener()
        }
        // Returns the action accepted by dispatch
        return action
    }
    // 8, add subscription function implementation
    function subscribe(listener) {
        // Dispatch is not allowed when subscription functions are added
        if (isDispatching) throw new Error('isDispatching')
        // Separate the listeners from the nextListener. The listeners are write listeners, and the listeners are separate from the listeners
        shadowCopy()
        // Add a subscription function
        nextListeners.push(listener)
        // declare isSubscribed to identify whether the current subscribed function isSubscribed
        let isSubscribed = true
        // Returns the unsubscribe function
        return () = > {
            // If the current function is not subscribed, nothing is done.
            if(! isSubscribed)return
            // Dispatch is not allowed when unsubscribing a function
            if (isDispatching) throw new Error('isDispatching')
            // Subscribe means revert to false, indicating that the function is unsubscribed
            isSubscribed = false
            // Separate the listeners from the nextListener. The listeners are writable, and the listeners are writable
            shadowCopy()
            // Remove the current subscription function from the read subscription function array
            const idx = nextListeners.indexOf(listener)
            nextListeners.splice(idx, 1)
            // Visual listeners are memory optimizations to prevent each subscription function from pointing to a huge currentListeners that cannot be freed
            // Echo the code in dispatch: let listeners = currentListeners = nextListeners
            CurrentListeners = null are valid even if they are null because the Listener saves all subscription functions that existed at the time of dispatch
            currentListeners = null}}// Listeners can read currentListeners and write nextListeners separately
    function shadowCopy() {
        currentListeners === nextListeners && (nextListeners = currentListeners.slice())
    }
    // Execute a dispatch to initialize data, type should be a random value (avoid internal same name as reducer)
    dispatch({ type: '123456789' })
    // return getState, dispatch, subscribe
    return { getState, dispatch, subscribe }
}
Copy the code

Redux combineReducers implementation

function combineReducers(composeReducers) {
    // goodReducers: Filter all reducers left after the reducer that is not a function in composeReducers
    // composeReducersKeys: composeReducers keys
    const goodReducers = {}, composeReducersKeys = Object.keys(composeReducers)
    // Start filtering
    for (const key of composeReducersKeys) {
        typeof composeReducers[key] === 'function' && (goodReducers[key] = composeReducers[key])
    }
    // Return all the final reducers that were incorporated into the legal reducer
    return (state = {}, action) = > {
        // goodReducersKeys: all valid reducer keys
        const goodReducersKeys = Object.keys(goodReducers)
        NextState: Save all states updated by the reducer
        // hasChanged: flag to determine whether nextState changed from the last redux
        let nextState = {}, hasChanged = false
        // Execute all legal reducer
        for (const key of goodReducersKeys) {
            // Get each reducer
            const reducer = goodReducers[key]
            // Each reducer starts updating the state of the last redux
            nextState[key] = reducer(state[key], action)
            // If the state (nextState[key]) returned after each reducer update is different from the state (state[key]),
            // the redux state hasChanged, so hasChanged is set to truehasChanged = hasChanged || nextState[key] ! == state[key] }// Visual inspection is to deal with a special case that there is no state corresponding to this reducer, and all state[keys] return undefined
        // However, there is a reducer and the reducer return value happens to be undefined. If this happens, it also means that the state in Redux has changed before and after
        // Therefore hasChangedx needs to be set to true if state changeshasChanged = hasChanged || goodReducersKeys.length ! = =Object.keys(state).length
        If the Redux state has changed before or after the Reducer update, return the latest state (i.e. NextState), otherwise return the original state
        // The purpose of this is to optimize memory.
        return hasChanged ? nextState : state
    }
}
Copy the code

ApplyMiddleware implementation in Redux

function applyMiddleware(. middlewares) {
    return createStore= > (reducer, preloadedState) = > {
        // Get the original store
        const store = createStore(reducer, preloadedState)
        // Declare a dispatch that will be enhanced by middleware in the future (although this dispatch does nothing at the moment)
        let dispatch = () = > { throw new Error('dont dispatch')}// Create a container to hold getState and dispatch, which will be passed to each middleware
        const storeAPI = {
            getState: store.getState,
            / / note: The container dispatches do not directly assign to the previously declared dispatches, but form closures. The purpose of this is to ensure that subsequent dispatches to the middleware always refer to the same declared dispatch, which may be a little confusing. Redux-thunk (redux-thunk) redux-thunk (redux-thunk
            dispatch: (. args) = >dispatch(... args) }// Pass each middleware to the container object above, giving the middleware the ability to use getState and Dispatch (always the same dispatch declared above)
        const chain = middlewares.map(middleware= > middleware(storeAPI))
        // Use the compose function to combine all the middleware and finally pass in the original dispatch. This behavior completes the enhancement of the original dispatch behavior and returns the enhanced dispatch
        // Assign the enhanced dispatch to the dispatch declared above (every dispatch received by middleware ends up using this dispatch). This may not be easy to understand, but hopefully it makes sensedispatch = compose(... chain)(store.dispatch)// Return createStore API, getState and SUBSCRIBE
        // And middleware enhanced dispatches (that is, dispatches declared in this function)
        return { ...store, dispatch }
    }
}
/ / compose function
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))) }Copy the code

Story – Thunk implementation

function createReduxThunk(. args) {
    return ({ dispatch, getState }) = > next= > action= > {
        if (typeof action === 'function') {
            // Redux applyMiddleware
            returnaction(dispatch, getState, ... args) }return next(action)
    }
}
Copy the code

Basic drag and drop implementation

<! DOCTYPEhtml>
<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
                // Mouse distance to the left of the drag area
                const innerTop = e.clientY - app.offsetTop
                const innerLeft = e.clientX - app.offsetLeft
                document.onmousemove = (ev) = > {
                    // App drag distance = mouse distance Drag element's parent distance - mouse distance drag area distance
                    let moveTop = ev.clientY - innerTop
                    let moveLeft = ev.clientX - innerLeft
                    // Set restrictions to prevent app from dragging outside the parent element
                    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
                    // Set the app drag distance to position top left value
                    app.style.top = moveTop + 'px'
                    app.style.left = moveLeft + 'px'
                }
                document.onmouseup = (e) = > {
                    // Lift the mouse to remove the onMousemove onmouseup binding
                    document.onmousemove = null
                    document.onmouseup = null
                }
            }
        }
        drag('app')
    </script>
</body>

</html>
Copy the code