js常见手写题梳理

1、使用XHR发起对 /api 发起GET请求 与 POST 请求:

在这里插入图片描述
XHR: 创、开、配、达、错、发

回调函数写法:

let xhr = new XMLHTTPRequest();
xhr.open("GET", url, true);
xhr.responseType = "json";
xhr.setRequestHeader("Accept", "application/json");
xhr.onreadystatechange = function() {
    let state = xhr.readyState;
    if(state === 4) {
        if(this.status === 200 || this.status === 304) callback(this.response);
        else console.log("error"+this.statusText);
    }
};
xhr.onerror = function() {
    console.log(this.statusText);
};
xhr.send();

Promise写法:

function ajax(url) {
    return new Promise(function(resolve, reject) {
        let xhr = new XMLHTTPRequest();
        xhr.open("GET", url, true);
        xhr.responseType = "json";
        xhr.setRequestHeader("Accept", "application/json");
        xhr.onreadystatechange = function() {
            let state = xhr.readyState;
            if(state === 4) {
                if(this.status===200 || this.status===304) resolve(this.response);
                else reject(this.statusText);
            }
        }
        xhr.onerror = function() {
            reject(this.statusText);
        }
        xhr.send();
    });
}

2、引用类型的拷贝

(1)浅拷贝

let arr1 = [];
let arr2 = arr1;

(2)仅适合一维数组、一维对象的深拷贝

let arr1 = [1, 2, 3];

let arr2 = arr1.slice();
let arr3 = Object.assign([], arr1);
let arr4 = [].concat(arr1);
let arr5 = [...arr1];

(3)多维数组、多维对象的深拷贝

JSON.parse(JSON.stringify( obj )); 是一种有损的方法,当要拷贝的对象中有 Date对象、Error对象、函数、undefined、NaN、infinity、循环引用 等,是不能使用的,那我们索性就不用吧。

常用的地方有 LocalStorage 和 SessionStorage 需要用到。

function deepClone(obj) {
    if(obj===null || typeof obj!=="function" && typeof obj!=="object") return obj;
    let res = Array.isArray(obj) ? [] : {};
    for(let i of Object.keys(obj)) {
        res[i] = deepClone(obj[i]);
    }
    return res;
}

循环引用,相互引用就是这个有向图存在环,用带visited数组的dfs就可以搞定了。

function deepclone(obj) {
    let map = new Map();
    function dfs(obj) {
        if(obj === null) return obj;
        if(typeof obj!=="object" && typeof obj!=="function") return obj;
        if(map.has(obj)) return map.get(obj);
        let res = Array.isArray(obj) ? [] : {};
        map.set(obj, res);
        for(let i of Object.keys(obj)) res[i] = dfs(obj[i]);
        return res;
    }
    let res = dfs(obj);
    return res;
}

3、debounce与throttle

先是概念,防抖的概念是,某事件触发后,需要等待n秒再执行,如果在等待时间内被触发了,那就需要重新等待n秒再执行,以此类推

适用场景:监听窗口大小调整resize事件、屏幕滚动scroll事件、鼠标移动事件mousemove

// 函数防抖的实现
function debounce(fn, wait) {
  let timer = null;

  return function() {
    let context = this,
        args = arguments;

    // 如果此时存在定时器的话,则取消之前的定时器重新记时
    if (timer) {
      clearTimeout(timer);
      timer = null;
    }

    // 设置定时器,使事件间隔指定事件后执行
    timer = setTimeout(() => {
      fn.apply(context, args);
    }, wait);
  };
}

节流的概念是,将时间分段,每段n秒,每段时间内只能最多响应一次事件,至于响应第几次可以自定义。

监听input输入

可以相应第一次,最后一次如何响应暂时不知。

function throttle(fn, ms) {
    let pre = Date.now();
    let isfirst = true;
    return function() {
        let ctx = this;
        let args = arguments;
        let cur = Date.now();
        if(isfirst) {
            isfirst = false;
            return fn.call(ctx, args);
        }
        if(cur-pre >= ms) {
            pre = Date.now();
            return fn.call(ctx, args);
        }
    }
}

4、浏览器事件循环算法

事件循环中至少涉及以下角色:执行栈、宏任务队列,微任务队列、异步代码、同步代码。

script文件会按顺序执行,遇到同步代码直接入栈执行,执行完后出栈

遇到异步代码会先触发执行,然后挂起,直到收到成功或者失败事件后,将回调加入宏任务队列和微任务队列

像 setTimeout、setInterval 的回调就是加入宏任务队列

像 Promise.then 的回调就是加入微任务队列

执行栈处理两种回调任务的算法是这样的:

如果执行栈为空,先检查微任务队列是否为空,如果不为空,则一个个的入栈执行,执行完出栈,直到微任务队列为空

再取一个宏任务来执行,执行完后,再检查微任务队列是否为空,为空清空,不为空则再取一个宏任务入栈执行,以此类推。

因此,我们可以得出一个应用:

如果不紧急的常规任务,我们可以将他设置为宏任务来处理。

如果是紧急任务,我们就可以给他设置成微任务A,加在微任务队列的队尾,A将在之前的微任务之后执行,在所有的宏任务之前执行。

Lazyman

5、setTimeout 与 setInterval 互相实现

实现setTimeout

function _setTimeout(fn, ms) {
    let timer = setInterval(function() {
        fn();
        clearInterval(timer);
    }, ms);
}

实现setInterval (递归setTimeout)

function _setInterval(fn, ms) {
    setTimeout(function run() {
        fn();
        setTimeout(run, ms);
    }, ms);
}

递归setTimeout 与 直接使用 setInerval 的区别是什么呢?

当我们fn可能耗时过长,超出我们设定的时长时,我们可以使用递归setTimeout,递归setTimeout不仅能保证每次运行的间隔一样,还能每轮递归将间隔时间按一定的规律变化,而setInterval的间隔时间是把fn的执行时间给包含进去的。

6、图片懒加载

图片懒加载的原理实际上可以抽象成两个矩形是否重叠的问题。

有两个矩形,一个是viewport,一个是要懒加载的图片盒子

请添加图片描述

那么,当两个矩形重叠时,将 img 的 src 用自定义属性 data-src 来替换即可完成加载。

其实,想要用户体验好些的话,可以预留距离 Δd 来进行图片加载。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值