美文网首页
常用面试JS,巩固你的JS基础

常用面试JS,巩固你的JS基础

作者: sphenginx | 来源:发表于2020-10-12 11:09 被阅读0次

    转自掘金:https://juejin.im/post/6875152247714480136

    作为前端开发,JS是重中之重,最近结束了面试的高峰期,基本上offer也定下来了就等开奖,趁着这个时间总结下32个手写JS问题,这些都是高频面试题,希望对你能有所帮助。

    关于源码都紧遵规范,都可跑通MDN示例,其余的大多会涉及一些关于JS的应用题和本人面试过程

    01.数组扁平化

    数组扁平化是指将一个多维数组变为一个一维数组:

    const arr = [1, [2, [3, [4, 5]]], 6];
    // => [1, 2, 3, 4, 5, 6]
    

    方法一:使用flat()

    const res1 = arr.flat(Infinity)
    

    方法二:利用正则

    const res2 = JSON.parse('[' + JSON.stringify(arr).replace(/\[|\]/g, '') + ']');
    

    方法三:函数递归

    const res5 = [];
    const fn = arr => {
      for (let i = 0; i < arr.length; i++) {
        if (Array.isArray(arr[i])) {
          fn(arr[i]);
        } else {
          res5.push(arr[i]);
        }
      }
    }
    fn(arr);
    

    方法四:使用reduce(其实也属于递归)

    const flatten = arr => {
      return arr.reduce((pre, cur) => {
        return pre.concat(Array.isArray(cur) ? flatten(cur) : cur);
      }, [])
    }
    const res4 = flatten(arr);
    

    02.数组去重

    const arr = [1, 1, '1', 17, true, true, false, false, 'true', 'a', {}, {}];
    // => [1, '1', 17, true, false, 'true', 'a', {}, {}]
    

    方法一:利用Set

    const unique1 = [...new Set(arr)];
    

    方法二:利用indexOf 或者 includes

    const unique2 = arr => {
      const res = [];
      for (let i = 0; i < arr.length; i++) {
        //if (res.indexOf(arr[i]) === -1) res.push(arr[i]);
        if (!res.includes(arr[i])) res.push(arr[i]);
      }
      return res;
    }
    

    方法三:利用filter

    const unique4 = arr => {
      return arr.filter((item, index) => {
        return arr.indexOf(item) === index;
      });
    }
    

    03. debounce(防抖)

    触发高频时间后n秒内函数只会执行一次,如果n秒内高频时间再次触发,则重新计算时间。

    const debounce = (fn, time) => {
      let timeout = null;
      return function() {
        clearTimeout(timeout)
        timeout = setTimeout(() => {
          fn.apply(this, arguments);
        }, time);
      }
    };
    

    防抖常应用于用户进行搜索输入节约请求资源,window触发resize事件时进行防抖只触发一次。

    04.throttle(节流)

    高频时间触发,但n秒内只会执行一次,所以节流会稀释函数的执行频率。

    const throttle = (fn, time) => {
      let flag = true;
      return function() {
        if (!flag) return;
        flag = false;
        setTimeout(() => {
          fn.apply(this, arguments);
          flag = true;
        }, time);
      }
    }
    

    节流常应用于鼠标不断点击触发、监听滚动事件。

    05. 函数珂里化

    指的是将一个接受多个参数的函数 变为 接受一个参数返回一个函数的固定形式,这样便于再次调用,例如f(1)(2)

    经典面试题:实现add(1)(2)(3)(4)=10;add(1)(1,2,3)(2)=9;

    function add() {
      const _args = [...arguments];
      function fn() {
        _args.push(...arguments);
        return fn;
      }
      fn.toString = function() {
        return _args.reduce((sum, cur) => sum + cur);
      }
      return fn;
    }
    

    06. Promise

    实现思路:Promise源码实现

    class Promise {
      constructor(exector) {
        const resolve = () => {
    
        }
        const reject = () => {
    
        }
        exector(resolve, reject);
      }
      then() {
    
      }
      catch() {
    
      }
      static resolve() {
    
      }
      static reject() {
    
      }
      static all() {
    
      }
      static race() {
        
      }
    }
    
    

    具体实现:

    // 模拟实现Promise
    // Promise利用三大手段解决回调地狱:
    // 1. 回调函数延迟绑定
    // 2. 返回值穿透
    // 3. 错误冒泡
    
    // 定义三种状态
    const PENDING = 'PENDING';      // 进行中
    const FULFILLED = 'FULFILLED';  // 已成功
    const REJECTED = 'REJECTED';    // 已失败
    
    class Promise {
      constructor(exector) {
        // 初始化状态
        this.status = PENDING;
        // 将成功、失败结果放在this上,便于then、catch访问
        this.value = undefined;
        this.reason = undefined;
        // 成功态回调函数队列
        this.onFulfilledCallbacks = [];
        // 失败态回调函数队列
        this.onRejectedCallbacks = [];
    
        const resolve = value => {
          // 只有进行中状态才能更改状态
          if (this.status === PENDING) {
            this.status = FULFILLED;
            this.value = value;
            // 成功态函数依次执行
            this.onFulfilledCallbacks.forEach(fn => fn(this.value));
          }
        }
        const reject = reason => {
          // 只有进行中状态才能更改状态
          if (this.status === PENDING) {
            this.status = REJECTED;
            this.reason = reason;
            // 失败态函数依次执行
            this.onRejectedCallbacks.forEach(fn => fn(this.reason))
          }
        }
        try {
          // 立即执行executor
          // 把内部的resolve和reject传入executor,用户可调用resolve和reject
          exector(resolve, reject);
        } catch(e) {
          // executor执行出错,将错误内容reject抛出去
          reject(e);
        }
      }
      then(onFulfilled, onRejected) {
        onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
        onRejected = typeof onRejected === 'function'? onRejected :
          reason => { throw new Error(reason instanceof Error ? reason.message : reason) }
        // 保存this
        const self = this;
        return new Promise((resolve, reject) => {
          if (self.status === PENDING) {
            self.onFulfilledCallbacks.push(() => {
              // try捕获错误
              try {
                // 模拟微任务
                setTimeout(() => {
                  const result = onFulfilled(self.value);
                  // 分两种情况:
                  // 1. 回调函数返回值是Promise,执行then操作
                  // 2. 如果不是Promise,调用新Promise的resolve函数
                  result instanceof Promise ? result.then(resolve, reject) : resolve(result);
                })
              } catch(e) {
                reject(e);
              }
            });
            self.onRejectedCallbacks.push(() => {
              // 以下同理
              try {
                setTimeout(() => {
                  const result = onRejected(self.reason);
                  // 不同点:此时是reject
                  result instanceof Promise ? result.then(resolve, reject) : reject(result);
                })
              } catch(e) {
                reject(e);
              }
            })
          } else if (self.status === FULFILLED) {
            try {
              setTimeout(() => {
                const result = onFulfilled(self.value);
                result instanceof Promise ? result.then(resolve, reject) : resolve(result);
              });
            } catch(e) {
              reject(e);
            }
          } else if (self.status === REJECTED) {
            try {
              setTimeout(() => {
                const result = onRejected(self.reason);
                result instanceof Promise ? result.then(resolve, reject) : resolve(result);
              })
            } catch(e) {
              reject(e);
            }
          }
        });
      }
      catch(onRejected) {
        return this.then(null, onRejected);
      }
      static resolve(value) {
        if (value instanceof Promise) {
          // 如果是Promise实例,直接返回
          return value;
        } else {
          // 如果不是Promise实例,返回一个新的Promise对象,状态为FULFILLED
          return new Promise((resolve, reject) => resolve(value));
        }
      }
      static reject(reason) {
        return new Promise((resolve, reject) => {
          reject(reason);
        })
      }
      static all(promiseArr) {
        const len = promiseArr.length;
        const values = new Array(len);
        // 记录已经成功执行的promise个数
        let count = 0;
        return new Promise((resolve, reject) => {
          for (let i = 0; i < len; i++) {
            // Promise.resolve()处理,确保每一个都是promise实例
            Promise.resolve(promiseArr[i]).then(
              val => {
                values[i] = val;
                count++;
                // 如果全部执行完,返回promise的状态就可以改变了
                if (count === len) resolve(values);
              },
              err => reject(err),
            );
          }
        })
      }
      static race(promiseArr) {
        return new Promise((resolve, reject) => {
          promiseArr.forEach(p => {
            Promise.resolve(p).then(
              val => resolve(val),
              err => reject(err),
            )
          })
        })
      }
    }
    

    07. 图片懒加载

    可以给img标签统一自定义属性data-src='default.png',当检测到图片出现在窗口之后再补充src属性,此时才会进行图片资源加载。

    function lazyload() {
      const imgs = document.getElementsByTagName('img');
      const len = imgs.length;
      // 视口的高度
      const viewHeight = document.documentElement.clientHeight;
      // 滚动条高度
      const scrollHeight = document.documentElement.scrollTop || document.body.scrollTop;
      for (let i = 0; i < len; i++) {
        const offsetHeight = imgs[i].offsetTop;
        if (offsetHeight < viewHeight + scrollHeight) {
          const src = imgs[i].dataset.src;
          imgs[i].src = src;
        }
      }
    }
    
    // 可以使用节流优化一下
    window.addEventListener('scroll', lazyload);
    

    08.滚动加载

    原理就是监听页面滚动事件,分析clientHeight、scrollTop、scrollHeight三者的属性关系。

    window.addEventListener('scroll', function() {
      const clientHeight = document.documentElement.clientHeight;
      const scrollTop = document.documentElement.scrollTop;
      const scrollHeight = document.documentElement.scrollHeight;
      if (clientHeight + scrollTop >= scrollHeight) {
        // 检测到滚动至页面底部,进行后续操作
        // ...
      }
    }, false);
    
    

    09. 将VirtualDom转化为真实DOM结构

    这是当前SPA应用的核心概念之一

    // vnode结构:
    // {
    //   tag,
    //   attrs,
    //   children,
    // }
    
    //Virtual DOM => DOM
    function render(vnode, container) {
      container.appendChild(_render(vnode));
    }
    function _render(vnode) {
      // 如果是数字类型转化为字符串
      if (typeof vnode === 'number') {
        vnode = String(vnode);
      }
      // 字符串类型直接就是文本节点
      if (typeof vnode === 'string') {
        return document.createTextNode(vnode);
      }
      // 普通DOM
      const dom = document.createElement(vnode.tag);
      if (vnode.attrs) {
        // 遍历属性
        Object.keys(vnode.attrs).forEach(key => {
          const value = vnode.attrs[key];
          dom.setAttribute(key, value);
        })
      }
      // 子数组进行递归操作
      vnode.children.forEach(child => render(child, dom));
      return dom;
    }
    
    

    相关文章

      网友评论

          本文标题:常用面试JS,巩固你的JS基础

          本文链接:https://www.haomeiwen.com/subject/ppsupktx.html