js 手写面试题

125 阅读3分钟

1.图片懒加载

当图片出现在是视中时才加载图片,今天给分享两种方式

1. 用Element.getBoundingClientRect() 方法实现图片懒加载(注意在项目中使用节流函数)

getBoundingClientRect APi

 (function () {
        let box = document.querySelector('.box'),
            boxImg = document.querySelector('.box2'),
            HTML = document.documentElement;
        console.log('boximg', boxImg)
        const lazyImage = function lazyImage() {
            if (boxImg.isLoadreturn;
            console.log("走了美欧")
            let { bottom, top } = boxImg.getBoundingClientRect();
            console.log(bottom, top, HTML.clientHeight)
            // console.log('top',top)
            console.log('bottom',bottom,HTML.clientHeight,top)
            if (bottom <= HTML.clientHeight&&top>=0) {
                boxImg.isLoad = true;
                boxImg.src = boxImg.getAttribute('data-img');
                boxImg.onload = () => {
                    // boxImg.style.opacity = 1
                }

            }
        }
        lazyImage();
        window.addEventListener('scroll', lazyImage)

2.用IntersectionObserver方法可监听元素和可视窗口交叉的信息

IntersectionObserver API 使用教程


 (function () {
        let box = document.querySelector('.box'),
            boxImg = document.querySelector('img')
        HTML = document.documentElement
        const ob = new IntersectionObserver(changes => {
            // changes[0]监听第一个元素和窗口的交叉信息
            let { isIntersecting, target } = changes[0];
            // isIntersecting 刚出现在视口
            console.log(changes[0])
            if (isIntersecting) {
                boxImg.src = boxImg.getAttribute('data-img')
                boxImg.style.opacity = 1;
                ob.unobserve(target)
            }
        })
        ob.observe(boxImg)
    })()

2 . 手写个简单的promise


   // 三个常量用于表示状态
    const PENDING = 'pending'
    const RESOLVED = 'resolved'
    const REJECTED = 'rejected'
    function MyPromise(fn) {
        const that = this;
        this.state = PENDING;
        // value 变量用于保存resolve 或者reject 中的值
        this.value = null;
        // 用于保存then 中的回调函数,因为当执行完promise时这时候应该把then 中的回调保存起来用于状态改变时使用
        that.resolvedCallbacks = [];
        that.rejectedCallbacks = [];
        function resolve(value) {
            if (that.state = PENDING) {
                that.state = REJECTED
                this.value = value
                // 遍历回调函数数组并执行
                that.resolvedCallbacks.map(cd => cd(that.value))

            }
        }
        function rejecte(value) {
            if (that.state === PENDING) {
                that.state = RESOLVED
                that.value = value
                that.rejectedCallbacks.map(cb => cb(that.value))
            }
        }
        // 完成两个函数后,我们应该实现吐过promise中传入函数
        try {
            fn(resolve, reject)
        } catch (error) {
            reject(error)

        }
    }
    // 最后实现pormise.then
    MyPromise.prototype.then = function (onFulfilled, onRejected) {
        const that = this;
        // 判断两个参数是否为函数类型,因为这两个参数是可选参数
        onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : v => v;
        onRejected = typeof onRejected === 'function' ? onRejected : e =>  new TypeError(e) ;
            // 当状态不是等待态时,就去执行相对应的函数。如果状态是等待态的话,就往回调函数中 push 函数
            if (this.state === PENDING) {
                this.resolvedCallbacks.push(onFulfilled)
                this.rejectedCallbacks.push(onRejected)
            }
            if (this.state === RESOLVED) {
                onFulfilled(that.value)
            }
            if (this.state === REJECTED) {
                onRejected(that.value)
            }


        }

3 手写Promse.all()

1. 手写简单的promse.all

检测是不是promise实例




var isPromise = function isPromise(x) {
        if (x !== null && /^(object|function)$/.test(typeof x)) {
            // 上面证明是个对象或者是个函数
            var then;
            try {
                then = x.then
            } catch (error) {
                return false

            }
            if (typeof then === 'function'return

        }
        return false;
    }

peomise.all()的特点

  1. 接收一个数组
  2. 接受的数组里如果有的不是promise转为promise 结果还是那个结果
  3. 接收的数组里的promise 都成功才成功,有一个失败就全部失败

Promise.all = function all(promises) {
        if (!Array.isArray(promises)) throw new TypeError('promises must be an array');
        var n = 0,
            values = [];
        return new Promise(function (resolve, reject) {
            for (var i = 0; i < promises.length; i++) {
                (function (i) {
                    var promise = promises[i];
                    // 要是不是promise 转成个 成功的promise 值还是那个值
                    if (!isPromise(promise)) promise = Promise.resolve(promise);
                    
                    promise.then(function onfulfiled(value) {
                        // 当前实例是成功的
                        n++;
                        values[i] = value;
                        // 判断如果成功都成功,
                        if (n >= promises.lengthresolve(values)

                    }).catch(function onrejected(reason) {
                        // 一个失败全体失败
                        reject(reason)
                    })

                })(i)
            }
        })
    }

2手写带错误次数限制的promse.all();


 /* 加失败限制 */
    Promise.allLimit = function allLimit(promises, limit) {
        console.log('hahhahha',promises,limit)
        if (!Array.isArray(promises)) throw new TypeError('promises must be is Array');
        limit =+ limit; // 不是数字转为数字
        if (isNaN(limit)) limit = 1;
        if (limit < 1 || limit > promises.lengththrow new RangeError('limit invakid');
        let n = 0,
            m = 0,
            values = [];
        reasons = [];
        return new Promise(function (resolve, reject) {
            for (var i = 0; i < promises.length; i++) {
                (function (i) {
                    var promise = promises[i]; // 得到单个的promise
                    // 判断是不是promse 要是不是promise 转成promse  值还是那个值
                    if (!isPromise(promise)) promise = Promise.resolve(promise);
                    promise.then(function onfulfiled(value) {
                        n++;
                        values[i] = value;
                        reasons[i] = null;
                        if (n >= promises.lengthresolve(values)
                    }).catch(function onrejected(reason) {
                        // 一个失败集体失败
                        m++;
                        reasons[i] = reason;
                        console.log('hahahh',m,limit)
                        if (m === limit) {
                            reject(reasons)
                        } else {
                            n++
                            values[i] = null
                            if (n >= promises.lengthresolve(values)

                        }

                    })

                })(i)
            }

        })
    }
    Promise.allLimit([p1, p2, p3, p4,p5],2).then(values => {
        console.log("执行结果", values);
    }).catch(reason => {
        console.log("失败的理由", reason);
    })

4. 实现一个带有过期时间的localStoragea


const cacheDate = function cacheDate(query, limit, cacheName) {
        if (typeof limit !== 'number') limit = 60 * 60;
        if (typeof cacheName !== 'string') cacheName = 'cache';
        return new Promise(resolve => {
            let cache = localStorage.getItem(cacheName);
            if (cache) {  // 有缓存走这里
                if (new Date() - caches.time <= limit) return
            }
            // 没有缓存或者缓存过期了 走这个
            try {
                query().then(value => {
                    localStorage.setItem(cacheName, JSON.stringify({
                        time: +new Date(),
                        data: value
                    }))
                    resolve(value)
                })
            } catch (err) {
                throw TypeError('query must be an function an return promise')
            }
        })
    }
    /* 事例   写个json 文件用fetch 请求下 */
    cacheDate(() => {
        return fetch(`./data.json?_${+new Date()}`).then(response => {
            let { status, statusText, } = response;
            if (status >= 200 && status < 400) {
                return response.json();
            }
        })
    }, 60'hahah')

5. 手写实现le t

let 的特征

  1. 在块级作用域有效
  2. 不能重复生明
  3. 不能预处理,不存在变量提升,没声明代码之前不能调用
(function(){
  var c =3
  console.log(c) //1
})()
console.log(c)  //c is not defined

6. 手写const

const 的特征

  1. 它包含let 的所有特征
  2. 不能修改(要注意,数组和对象属于引用数据类型,const保存的是指向对象的指针,所以修改其中的属性时指针不变,可以修改)
  3. 使用时必须初始化(必须赋值)
 const _const = function _const(key, value) {
        window[key] = value;
        Object.defineProperty(window, key, {
            enumerablefalse,
            configurablefalse,
            get() {
                return value
            },
            set(newValue) {
                if (newValue !== value) {
                    throw new TypeError('只读变量不能修改')
                }
                return value

            }
        })

    }

7 . 实现一个鼠标滑到某元素上提示信息的效果

 (function () {
      const handle = function handle(ev) {
        let target = ev.target,
          // 获取到提示信息
          tooltip = target.getAttribute("tooltip"),
          tooltipBox = document.querySelector("#tooltipBox");
        // 提示信息存在盒子不存在,创建盒子
        if (tooltip) {
          if (!tooltipBox) {
            tooltipBox = document.createElement("div");
            tooltipBox.id = "tooltipBox";
            document.body.appendChild(tooltipBox);
          }
          let { clientX, clientY } = ev;
          // 把创建的信息插入到盒子里面
          tooltipBox.innerHTML = tooltip;

          tooltipBox.style.cssText = `
                display:block;
                position:fixed;
                top:${clientY + 10}px;
                left:${clientX + 10}px;
                padding:5px 10px;
                border:1px solid red;
                `;
          return;
        }
        // 提示信息不存在时删除提示信息盒子
        if (tooltipBox) tooltipBox.style.display = "none";
      };
      // document.querySelector('h2').addEventListener('mousemove',handle)
      document.addEventListener("mousemove", handle);
    })();

8 手写内置call

((proto) => {
            function myCall(thisArg, ...args) {
                thisArg = thisArg == undefined ? window : thisArg;
                let type = typeof thisArg;
                // thisArg 是原始类型的值无效所以要变成对象或者函数
              
                if (!/^(object|function)$/.test(type)) {
                    if (/^(symbol|bigint)$/.test(type)) {
                      // symbol|bigint 用new 不能直接转对象所以这样 
                        thisArg = Object(thisArg);
                    } else {
                      // 转对象
                        thisArg = new thisArg.constructor(thisArg);
                    }
                }
                let key = Symbol("key"); //新增的属性不能和thisArg 原来的属性相同
                thisArg[key] = this;  // 改变this指向
                let result = thisArg[key](...args); // 执行函数
                delete thisArg[key];  // 删除新增的属性
                return result;  // 返回结果
            }
            proto.myCall = myCall;
        }
        )(Function.prototype);

9 手写apply

(proto => {
    function myApply(thisArg, args) {
        thisArg = thisArg == undefined ? window : thisArg;
        let type = typeof thisArg;
       // thisArg 是原始类型的值无效所以要变成对象或者函数
        // thisArg 如果是对象或者函数不用做处理 
        if (!/^(object|function)$/.test(type)) {
            if (/^(symbol|bigint)$/.test(type)) {
                // 因为new 运算符不能把symbol 和bigint 变成对象 用Object 可以
                thisArg = Object(thisArg);
            } else {
              // 如果不是对象函数也不是symbol|bigint 那么就用new 运算符把他变成函数
                thisArg = new thisArg.constructor(thisArg);
            }
        }
        let key = Symbol('key');   // 新增的属性不能和thisArg 原来的属性冲突
        thisArg[key] = this;    
        let result = thisArg[key](args);
        delete thisArg[key];
        return result;
    }
    proto.myApply = myApply;
})(Function.prototype)

10 手写bind(主要用了函数柯理化的思想)

(proto => {
    function myBind(context, ...params) {
        let _this = this;
        return function Proxy(...args) {
           params = params.concat(args)
            _this.call(thisArg, params);
        }
    }
    proto.myBind = myBind;
})(Function.prototype)

11. 手写防抖

 let submit = document.querySelector('.submit');
    const debounce = function debounce(func, wait, immediate) {
        if (!typeof func == "function"throw new TypeError('func must is a function');
        if (typeof wait == "boolean") immediate = wait;
        if (typeof await != "number"await = 300;
        let timer = null
        /* 立即执行的判断 */
        return function operation(...args) {
            let now = !timer && immediate,
                result;
            timer = clearTimeout(timer);
            timer = setTimeout(() => {
                if (!immediate) func.call(this, ...args)
                //  先执行时清楚最后一次的定时器
                 timer = clearTimeout(timer);
            }, wait);
            if (now) result = func.call(this, ...args)
            return result
        }
    }

    let handle = function handle() {
        console.log('我是函数我执行了')
    }
    submit.onclick = debounce(handle, 1000true)

12. 手写节流函数

  /* 节流函数 */
    function handle() {
         console.log("aaa")
    }
    let throttle = function throttle(fn, wait) {
        let timeout = null// 计时器变量
            result = null,  // 上次执行的结果
            previous = 0//上次执行的时间 
        return function anymouse(...args) {
            let now = new Date,
                context = this;
            let remaining = wait - (now - previous);
            // 看下次执行的时间到没有时间到了清除上次的内容执行
            // console.log("Shiji",remaining);
            if (remaining <= 0) {
                clearTimeout(timeout);
                previous = now;
                result = fn.apply(context, args);
                // 首次执行
            } else if (!timeout) {
                timeout = setTimeout(() => {
                    previous = new Date;
                    result = fn.apply(context, args)
                },wait)
            }
            // console.log(result)
            return result
        }
    }
    window.addEventListener('scroll'throttle(handle, 1000))