js前端手撕代码系列(promise,防抖,节流,原生ajax,封装promise等)

本文详细介绍了JavaScript前端开发中的防抖与节流概念,包括它们的区别、使用场景及具体实现。同时,文章探讨了原生AJAX的执行流程,并提供了手写Promise的实例,最后展示了如何将请求封装为Promise,以简化异步处理。

防抖与节流

介绍
  • 通过防抖(debounce) 和 节流(throttle) 的方式来减少调用频率,减少资源浪费
  • 防抖: n 秒后再执行该事件,若在 n 秒内被重复触发,则重新计时
    (用搭电梯形容:电梯第一个人进来后,等待15秒。如果过程中又有人进来,15秒等待重新计时,直到15秒后开始运送,这是防抖)
  • 节流: n 秒内只运行一次,若在 n 秒内重复触发只有一次生效
    (用搭电梯形容:电梯第一个人进来后,15秒后准时运送一次,这是节流)
区别
  • 相同点
    • 都可以通过使用 setTimeout 实现
    • 目的都是,降低回调执行频率。节省计算资源
  • 不同点
    • 函数防抖,在一段连续操作结束后,处理回调,利用clearTimeout和 setTimeout实现。函数节流,在一段连续操作中,每一段时间只执行一次,频率较高的事件中使用来提高性能
    • 函数防抖关注一定时间连续触发的事件,只在最后执行一次,而函数节流一段时间内只执行一次
使用场景
  • 防抖在连续的事件,只需触发一次回调的场景有:
    • 搜索框搜索输入。只需用户最后一次输入完,再发送请求
    • 手机号、邮箱验证输入检测
    • 窗口大小resize。只需窗口调整完成后,计算窗口大小。防止重复渲染
  • 节流在间隔一段时间执行一次回调的场景有:
    • 滚动加载,加载更多或滚到底部监听
    • 搜索框,搜索联想功能
具体实现
防抖
// 简易版的防抖函数实现debounce:clearTimeout以及setTimeout结合
function debounce(func, wait) { 
    let timeout;
    return function(){
        let self = this;
        let args = arguments;
        clearTimeout(timeout);
        timeout = setTimeout(function(){
            func.apply(self, args);
        }, wait);   
    }
}
// 加入立即执行版的防抖函数实现debounce:增加一个immediate参数
function debounce(func, wait, immediate) { 
    let timeout;
    return function(){
        let self = this;
        let args = arguments;
        if(timeout) clearTimeout(timeout);
        if(immediate){
            let callNow = !timeout;
            timeout = setTimeout(function(){
                timeout = null;
            }, wait)
            if(callNow){
                func.apply(self, args);
            }
        } else {
            timeout = setTimeout(function(){
                func.apply(self, args);
            }, wait);
        }
    }
}
节流
// 简易版节流:使用时间戳与定时器的写法,事件立即执行但停止触发后不执行
function throttled (func, wait) { 
    let oldTime = new Date();
    return function(...args){
        let nowData = new Date();
        if(nowData - oldTime >= wait){
            func.apply(null, args);
            oldTime = new Date();
        }
    }
 }
// 简易版节流:使用定时器写法,delay毫秒后第一次执行,第二次事件停止触发后依然会再一次执行
function throttled (func, wait) { 
    let timer = null;
    return function(...args){
        if(!timer){
            timer = setTimeout(() => {
                func.apply(null, args);
                timer = null;
            }, wait);
        }
    }
 }
// 将时间戳写法的特性与定时器写法的特性相结合,实现一个更加精确的节流
function throttled(func, wait) { 
    let oldTime = new Date();
    let timer = null;
    return function(){
        let curTime = new Date();
        let remain = wait - (curTime - oldTime);
        let self = this;
        let args = arguments;
        clearTimeout(timer);
        if(remain <= 0){
            func.apply(self, args);
            oldTime = new Date();
        } else {
            timer = setTimeout(func, remain);
        }
    }
 }

原生ajax

流程
  • 创建一个ajax对象 xhr
  • 打开地址
  • 发送
  • 等待数据
  • ajax的状态: ajax的状态有哪些
    • 0-未初始化,尚未调用open()方法
    • 1-启动,调用open()方法,已调用send()的方法,正在发送请求
    • 2-发送,已经调用send()方法,已接受到响应
    • 3-解析 正在解析响应数据
    • 4-完成,响应数据解析完成,客户端可以调用(一般使用xhr.readyState == 4 判断ajax请求是否结束)
  • 获取返回的结果:responseText
具体实现
function sendAjax(obj) {
    var url = obj.url
    var method = obj.method
    var async = obj.async==undefined? true:obj.async
    var data = obj.data
    function splitStr(data) {
        let str = ''
        for (var key in data) {
            let s = key + '=' + data.key + '&'
            str += s
        }
        return str.substring(0, str.length - 1)
    }
    // initial
    var xhttp
    if (window.XMLHttpRequest) xhttp = new XMLHttpRequest()
    else xhttp = new ActiveXObject()
    // send
    if (obj.method == 'get' || obj.method == 'GET') {
        if (data == undefined) {
            xhttp.open('GET', url, async)
            xhttp.send()
        } 
        else {
            xhttp.open('GET', url + splitStr(data), async)
            xhttp.send()
        }
    } 
    else if (method == 'post' || method == 'POST') {
        if(data==undefined) throw 'method POST can not without data to send'
        else {
            xhttp.open('POST',url,async)
            xhttp.setRuquestHeader('Content-type','application/x-www-form-urlencoded')
            xhttp.send(data)
        }

    } 
    else if (method == undefined || method=='') throw 'method can not be empty'
    // response
    xhttp.onreadystatechange=function(){
        if(xhttp.readyState==4){
            obj.success(JSON.parse(xhttp.responseText))
        }
        else if(xhttp.readyState==4 && (xhttp.status!=200 || xhttp.status!=304) ){
            obj.error()
        }
    }
}

// 调用格式
sendAjax({
    url: 'AJAX.json',
    method: 'get',
    async: true,
    data: {
        id: 100,
        username:"123456"
    },
    success: function (data) {
        console.log(data)
    },
    error: function () {
        console.log('error data')
    }
})

手写promise

未处理异步操作的简单同步的promise手写实现
class Promise{
    constructor(executor){
        this.status = 'pending';
        this.value = undefined;
        this.reason = undefined;
        let resolve = (value) => {
            if(this.status === 'pending'){
                this.status = 'fullfilled';
                this.value = value;
            }
        }
        let reject = (reason) => {
            if(this.status === 'pending'){
                this.status = 'rejected';
                this.reason = reason;
            }
        }
        try {
            executor(resolve, reject);
        } catch (error) {
            reject(error);
        }
    }
    then(onfullfilled, onrejected){
        if(this.status === 'fullfilled'){
            onfullfilled(this.value);
        }
        if(this.status === 'rejected'){
            onrejected(this.reason);
        }
    }
}
添加上异步操作的简单promise手写实现
class Promise{
    constructor(executor){
        this.status = 'pending';
        this.value = undefined;
        this.reason = undefined;
        // 添加回调数组
        this.onresolvedCallbacks = [];
        this.onrejectedCallbacks = [];
        let resolve = (value) => {
            if(this.status === 'pending'){
                this.status = 'fullfilled';
                this.value = value;
                // 依次执行
                this.onresolvedCallbacks.forEach(fn => fn());
            }
        }
        let reject = (reason) => {
            if(this.status === 'pending'){
                this.status = 'rejected';
                this.reason = reason;
                // 依次执行
                this.onrejectedCallbacks.forEach(fn => fn());
            }
        }
        try {
            executor(resolve, reject);
        } catch (error) {
            reject(error);
        }
    }
    then(onfullfilled, onrejected){
        if(this.status === 'fullfilled'){
            onfullfilled(this.value);
        }
        if(this.status === 'rejected'){
            onrejected(this.reason);
        }
        if(this.status === 'pending'){
            this.onresolvedCallbacks.push(() => {
                onfullfilled(this.value);
            })
            this.onrejectedCallbacks.push(() => {
                onfullfilled(this.reason);
            })
        }
    }
}

封装promise

未封装成promise时的请求
sendAjax({
    url: 'AJAX.json',
    method: 'get',
    async: true,
    data: {
        id: 100,
        username:"123456"
    },
    success: function (data) {
        console.log(data)
    },
    error: function () {
        console.log('error data')
    }
})
封装成promise时的请求
const sendAjax = () => {
    return new Promise((resolve, reject) => {
        sendAjax({
            url: 'AJAX.json',
            method: 'get',
            async: true,
            data: {
                id: 100,
                username:"123456"
            },
            success: resolve(data),
            error: reject(err),
        })
    })
}
调用格式
sendAjax().then((data) => {
	// 这里处理成功回调的逻辑
    console.log(data);
},(err) => {
    console.log(err);
})
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

努力不熬夜的小喵

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值