工作中常用JS,鞏固你的JS基礎(chǔ)

轉(zhuǎn)自掘金:https://juejin.im/post/6875152247714480136

作為前端開發(fā),JS是重中之重,最近結(jié)束了面試的高峰期,基本上offer也定下來了就等開獎(jiǎng),趁著這個(gè)時(shí)間總結(jié)下32個(gè)手寫JS問題,這些都是高頻面試題,希望對(duì)你能有所幫助。

關(guān)于源碼都緊遵規(guī)范,都可跑通MDN示例,其余的大多會(huì)涉及一些關(guān)于JS的應(yīng)用題和本人面試過程

01.數(shù)組扁平化

數(shù)組扁平化是指將一個(gè)多維數(shù)組變?yōu)橐粋€(gè)一維數(shù)組:

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, '') + ']');

方法三:函數(shù)遞歸

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(其實(shí)也屬于遞歸)

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

02.數(shù)組去重

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(防抖)

觸發(fā)高頻時(shí)間后n秒內(nèi)函數(shù)只會(huì)執(zhí)行一次,如果n秒內(nèi)高頻時(shí)間再次觸發(fā),則重新計(jì)算時(shí)間。

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

防抖常應(yīng)用于用戶進(jìn)行搜索輸入節(jié)約請(qǐng)求資源,window觸發(fā)resize事件時(shí)進(jìn)行防抖只觸發(fā)一次。

04.throttle(節(jié)流)

高頻時(shí)間觸發(fā),但n秒內(nèi)只會(huì)執(zhí)行一次,所以節(jié)流會(huì)稀釋函數(shù)的執(zhí)行頻率。

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

節(jié)流常應(yīng)用于鼠標(biāo)不斷點(diǎn)擊觸發(fā)、監(jiān)聽滾動(dòng)事件。

05. 函數(shù)珂里化

指的是將一個(gè)接受多個(gè)參數(shù)的函數(shù) 變?yōu)?接受一個(gè)參數(shù)返回一個(gè)函數(shù)的固定形式,這樣便于再次調(diào)用,例如f(1)(2)

經(jīng)典面試題:實(shí)現(xiàn)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

實(shí)現(xiàn)思路:Promise源碼實(shí)現(xiàn)

class Promise {
  constructor(exector) {
    const resolve = () => {

    }
    const reject = () => {

    }
    exector(resolve, reject);
  }
  then() {

  }
  catch() {

  }
  static resolve() {

  }
  static reject() {

  }
  static all() {

  }
  static race() {
    
  }
}

具體實(shí)現(xiàn):

// 模擬實(shí)現(xiàn)Promise
// Promise利用三大手段解決回調(diào)地獄:
// 1. 回調(diào)函數(shù)延遲綁定
// 2. 返回值穿透
// 3. 錯(cuò)誤冒泡

// 定義三種狀態(tài)
const PENDING = 'PENDING';      // 進(jìn)行中
const FULFILLED = 'FULFILLED';  // 已成功
const REJECTED = 'REJECTED';    // 已失敗

class Promise {
  constructor(exector) {
    // 初始化狀態(tài)
    this.status = PENDING;
    // 將成功、失敗結(jié)果放在this上,便于then、catch訪問
    this.value = undefined;
    this.reason = undefined;
    // 成功態(tài)回調(diào)函數(shù)隊(duì)列
    this.onFulfilledCallbacks = [];
    // 失敗態(tài)回調(diào)函數(shù)隊(duì)列
    this.onRejectedCallbacks = [];

    const resolve = value => {
      // 只有進(jìn)行中狀態(tài)才能更改狀態(tài)
      if (this.status === PENDING) {
        this.status = FULFILLED;
        this.value = value;
        // 成功態(tài)函數(shù)依次執(zhí)行
        this.onFulfilledCallbacks.forEach(fn => fn(this.value));
      }
    }
    const reject = reason => {
      // 只有進(jìn)行中狀態(tài)才能更改狀態(tài)
      if (this.status === PENDING) {
        this.status = REJECTED;
        this.reason = reason;
        // 失敗態(tài)函數(shù)依次執(zhí)行
        this.onRejectedCallbacks.forEach(fn => fn(this.reason))
      }
    }
    try {
      // 立即執(zhí)行executor
      // 把內(nèi)部的resolve和reject傳入executor,用戶可調(diào)用resolve和reject
      exector(resolve, reject);
    } catch(e) {
      // executor執(zhí)行出錯(cuò),將錯(cuò)誤內(nèi)容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捕獲錯(cuò)誤
          try {
            // 模擬微任務(wù)
            setTimeout(() => {
              const result = onFulfilled(self.value);
              // 分兩種情況:
              // 1. 回調(diào)函數(shù)返回值是Promise,執(zhí)行then操作
              // 2. 如果不是Promise,調(diào)用新Promise的resolve函數(shù)
              result instanceof Promise ? result.then(resolve, reject) : resolve(result);
            })
          } catch(e) {
            reject(e);
          }
        });
        self.onRejectedCallbacks.push(() => {
          // 以下同理
          try {
            setTimeout(() => {
              const result = onRejected(self.reason);
              // 不同點(diǎn):此時(shí)是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實(shí)例,直接返回
      return value;
    } else {
      // 如果不是Promise實(shí)例,返回一個(gè)新的Promise對(duì)象,狀態(tài)為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);
    // 記錄已經(jīng)成功執(zhí)行的promise個(gè)數(shù)
    let count = 0;
    return new Promise((resolve, reject) => {
      for (let i = 0; i < len; i++) {
        // Promise.resolve()處理,確保每一個(gè)都是promise實(shí)例
        Promise.resolve(promiseArr[i]).then(
          val => {
            values[i] = val;
            count++;
            // 如果全部執(zhí)行完,返回promise的狀態(tài)就可以改變了
            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標(biāo)簽統(tǒng)一自定義屬性data-src='default.png',當(dāng)檢測(cè)到圖片出現(xiàn)在窗口之后再補(bǔ)充src屬性,此時(shí)才會(huì)進(jìn)行圖片資源加載。

function lazyload() {
  const imgs = document.getElementsByTagName('img');
  const len = imgs.length;
  // 視口的高度
  const viewHeight = document.documentElement.clientHeight;
  // 滾動(dòng)條高度
  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;
    }
  }
}

// 可以使用節(jié)流優(yōu)化一下
window.addEventListener('scroll', lazyload);

08.滾動(dòng)加載

原理就是監(jiān)聽頁(yè)面滾動(dòng)事件,分析clientHeight、scrollTop、scrollHeight三者的屬性關(guān)系。

window.addEventListener('scroll', function() {
  const clientHeight = document.documentElement.clientHeight;
  const scrollTop = document.documentElement.scrollTop;
  const scrollHeight = document.documentElement.scrollHeight;
  if (clientHeight + scrollTop >= scrollHeight) {
    // 檢測(cè)到滾動(dòng)至頁(yè)面底部,進(jìn)行后續(xù)操作
    // ...
  }
}, false);

09. 將VirtualDom轉(zhuǎn)化為真實(shí)DOM結(jié)構(gòu)

這是當(dāng)前SPA應(yīng)用的核心概念之一

// vnode結(jié)構(gòu):
// {
//   tag,
//   attrs,
//   children,
// }

//Virtual DOM => DOM
function render(vnode, container) {
  container.appendChild(_render(vnode));
}
function _render(vnode) {
  // 如果是數(shù)字類型轉(zhuǎn)化為字符串
  if (typeof vnode === 'number') {
    vnode = String(vnode);
  }
  // 字符串類型直接就是文本節(jié)點(diǎn)
  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);
    })
  }
  // 子數(shù)組進(jìn)行遞歸操作
  vnode.children.forEach(child => render(child, dom));
  return dom;
}

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容