JavaScript:手写函数(节流/防抖-bind/call/apply-Promise)

491 阅读2分钟

手写节流和防抖

什么是节流和防抖

  • 节流:在预设值内,事件发生多次,函数只执行一次;即两次函数被触发的事件间隔至少为预设值
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>10</title>
    </link>
</head>

<body>
    <button id='mybtn'>click me</button>
    <script>
        const throttle = (fn, delay) => {
            let last = 0
            return () => {
                let now = new Date().getTime()
                // 如果离上次执行时间过小,不执行
                if (now - last < delay) return
                // 执行函数,并修改上一次执行的时间
                else {
                    last = now
                    fn()
                    return
                }

            }
        }
        document.getElementById('mybtn').addEventListener(
            'click',
            throttle((e) => {
                console.log('clicked!')
            }, 2000)
        )
    </script>
</body>

</html>
  • 防抖:Debounce连续快速触发事件时,只有在间隔时间大于delay时才执行。
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>10</title>
    </link>
</head>

<body>
    <button id='mybtn'>click me</button>
    <script>
        const debounce = (fn, delay) => {
            let timeoutID;
            return () => {
                clearTimeout(timeoutID);
                timeoutID = setTimeout(fn, delay);
            }
        }
        document.getElementById('mybtn').addEventListener('click', debounce(e => {
            console.log('clicked!');
        }, 1000))
    </script>
</body>

</html>

移动端的click事件就是使用了防抖,当touchStart后300ms内没有再次touchstart事件触发,这次点击才会触发click事件的监听函数(移动端点击事件会延迟300ms)

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

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>10</title>
    </link>
</head>

<body>
    <button id='mybtn'>click me</button>
    <script>
        document.getElementById('mybtn').addEventListener('click', function () {
            console.log("click!");
        })
        document.getElementById('mybtn').addEventListener('touchstart', function () {
            console.log("touchstart!");
        })
    </script>
</body>

</html>

手写bind/call/apply

bind/call/apply可以改变非箭头函数中this的指向,其语法是:

fn.call(target, arg1, arg2, ...)

fn.apply(target, [arg1, arg2, ...])

fn.bind(target)

当没有target参数时,会将this指向全局对象(window/global)

关于this的指向可以看JavaScript:this指向和箭头函数的part 2:当调用一个对象上的方法时,该方法中的this指向这个对象。

所以这三个的思路基本一致,在target上添加上方法fn,调用之后再删除这个方法即可。

call

Function.prototype.myCall = function (obj, ...args) {
  let target = obj || (typeof window === 'undefined' ? global : window)
  target.func = this // myCall函数的调用是:func.mycall(obj,...args),所以myCall中的this指向func
  target.func(...args) // func中this指向obj
  delete target.f
}
let a = {
  name: 'brynn',
  test: function (m, n) {
    console.log(this)
    console.log(m, n)
  },
}
let b = {
  name: 'brown',
}
a.test('test1', 'test2')
a.test.myCall(b, 'test1', 'test2')
a.test.call(b, 'test1', 'test2')
a.test.myCall(null, 'test1', 'test2')
a.test.call(null, 'test1', 'test2')

apply

Function.prototype.myApply = function (obj, args) {
  let target = obj || (typeof window === 'undefined' ? global : window)
  target.func = this // myCall函数的调用是:func.mycall(obj,...args),所以myCall中的this指向func
  target.func(...args) // func中this指向obj
  delete target.f
}
let a = {
  name: 'brynn',
  test: function (m, n) {
    console.log(this)
    console.log(m, n)
  },
}
let b = {
  name: 'brown',
}
a.test(['test1', 'test2'])
a.test.myApply(b, ['test1', 'test2'])
a.test.apply(b, ['test1', 'test2'])
a.test.myApply(null, ['test1', 'test2'])
a.test.apply(null, ['test1', 'test2'])

bind

Function.prototype.myBind = function (obj) {
  let target = obj || (typeof window === 'undefined' ? global : window)
  target.func = this // myCall函数的调用是:func.mycall(obj,...args),所以myCall中的this指向func
  return (...args) => {
    // console.log('查看', args, ...args)
    target.func(...args) // func中this指向obj
    delete target.f
  }
}
let a = {
  name: 'brynn',
  test: function (m, n) {
    console.log(this)
    console.log(m, n)
  },
}
let b = {
  name: 'brown',
}
a.test('test1', 'test2')
a.test.myBind(b)('test1', 'test2')
a.test.bind(b)('test1', 'test2')
a.test.myBind(null)('test1', 'test2')
a.test.bind(null)('test1', 'test2')