22道高頻JavaScript手寫面試題

JavaScript筆試部分

點(diǎn)擊關(guān)注本公眾號(hào)「程序員面試官」獲取文檔最新更新,并可以領(lǐng)取配套于本指南的 《前端面試手冊(cè)》 以及最標(biāo)準(zhǔn)的簡(jiǎn)歷模板.

實(shí)現(xiàn)防抖函數(shù)(debounce)

防抖函數(shù)原理:在事件被觸發(fā)n秒后再執(zhí)行回調(diào),如果在這n秒內(nèi)又被觸發(fā),則重新計(jì)時(shí)。

那么與節(jié)流函數(shù)的區(qū)別直接看這個(gè)動(dòng)畫實(shí)現(xiàn)即可。

image

手寫簡(jiǎn)化版:

// 防抖函數(shù)
const debounce = (fn, delay) => {
  let timer = null;
  return (...args) => {
    clearTimeout(timer);
    timer = setTimeout(() => {
      fn.apply(this, args);
    }, delay);
  };
};

適用場(chǎng)景:

  • 按鈕提交場(chǎng)景:防止多次提交按鈕,只執(zhí)行最后提交的一次
  • 服務(wù)端驗(yàn)證場(chǎng)景:表單驗(yàn)證需要服務(wù)端配合,只執(zhí)行一段連續(xù)的輸入事件的最后一次,還有搜索聯(lián)想詞功能類似

生存環(huán)境請(qǐng)用lodash.debounce

實(shí)現(xiàn)節(jié)流函數(shù)(throttle)

防抖函數(shù)原理:規(guī)定在一個(gè)單位時(shí)間內(nèi),只能觸發(fā)一次函數(shù)。如果這個(gè)單位時(shí)間內(nèi)觸發(fā)多次函數(shù),只有一次生效。

// 手寫簡(jiǎn)化版

// 節(jié)流函數(shù)
const throttle = (fn, delay = 500) => {
  let flag = true;
  return (...args) => {
    if (!flag) return;
    flag = false;
    setTimeout(() => {
      fn.apply(this, args);
      flag = true;
    }, delay);
  };
};

適用場(chǎng)景:

  • 拖拽場(chǎng)景:固定時(shí)間內(nèi)只執(zhí)行一次,防止超高頻次觸發(fā)位置變動(dòng)
  • 縮放場(chǎng)景:監(jiān)控瀏覽器resize
  • 動(dòng)畫場(chǎng)景:避免短時(shí)間內(nèi)多次觸發(fā)動(dòng)畫引起性能問題

深克隆(deepclone)

簡(jiǎn)單版:

const newObj = JSON.parse(JSON.stringify(oldObj));

局限性:

  1. 他無(wú)法實(shí)現(xiàn)對(duì)函數(shù) 、RegExp等特殊對(duì)象的克隆

  2. 會(huì)拋棄對(duì)象的constructor,所有的構(gòu)造函數(shù)會(huì)指向Object

  3. 對(duì)象有循環(huán)引用,會(huì)報(bào)錯(cuò)

面試版:

/**
 * deep clone
 * @param  {[type]} parent object 需要進(jìn)行克隆的對(duì)象
 * @return {[type]}        深克隆后的對(duì)象
 */
const clone = parent => {
  // 判斷類型
  const isType = (obj, type) => {
    if (typeof obj !== "object") return false;
    const typeString = Object.prototype.toString.call(obj);
    let flag;
    switch (type) {
      case "Array":
        flag = typeString === "[object Array]";
        break;
      case "Date":
        flag = typeString === "[object Date]";
        break;
      case "RegExp":
        flag = typeString === "[object RegExp]";
        break;
      default:
        flag = false;
    }
    return flag;
  };

  // 處理正則
  const getRegExp = re => {
    var flags = "";
    if (re.global) flags += "g";
    if (re.ignoreCase) flags += "i";
    if (re.multiline) flags += "m";
    return flags;
  };
  // 維護(hù)兩個(gè)儲(chǔ)存循環(huán)引用的數(shù)組
  const parents = [];
  const children = [];

  const _clone = parent => {
    if (parent === null) return null;
    if (typeof parent !== "object") return parent;

    let child, proto;

    if (isType(parent, "Array")) {
      // 對(duì)數(shù)組做特殊處理
      child = [];
    } else if (isType(parent, "RegExp")) {
      // 對(duì)正則對(duì)象做特殊處理
      child = new RegExp(parent.source, getRegExp(parent));
      if (parent.lastIndex) child.lastIndex = parent.lastIndex;
    } else if (isType(parent, "Date")) {
      // 對(duì)Date對(duì)象做特殊處理
      child = new Date(parent.getTime());
    } else {
      // 處理對(duì)象原型
      proto = Object.getPrototypeOf(parent);
      // 利用Object.create切斷原型鏈
      child = Object.create(proto);
    }

    // 處理循環(huán)引用
    const index = parents.indexOf(parent);

    if (index != -1) {
      // 如果父數(shù)組存在本對(duì)象,說明之前已經(jīng)被引用過,直接返回此對(duì)象
      return children[index];
    }
    parents.push(parent);
    children.push(child);

    for (let i in parent) {
      // 遞歸
      child[i] = _clone(parent[i]);
    }

    return child;
  };
  return _clone(parent);
};

局限性:

  1. 一些特殊情況沒有處理: 例如Buffer對(duì)象、Promise、Set、Map
  2. 另外對(duì)于確保沒有循環(huán)引用的對(duì)象,我們可以省去對(duì)循環(huán)引用的特殊處理,因?yàn)檫@很消耗時(shí)間

原理詳解實(shí)現(xiàn)深克隆

實(shí)現(xiàn)Event(event bus)

event bus既是node中各個(gè)模塊的基石,又是前端組件通信的依賴手段之一,同時(shí)涉及了訂閱-發(fā)布設(shè)計(jì)模式,是非常重要的基礎(chǔ)。

簡(jiǎn)單版:

class EventEmeitter {
  constructor() {
    this._events = this._events || new Map(); // 儲(chǔ)存事件/回調(diào)鍵值對(duì)
    this._maxListeners = this._maxListeners || 10; // 設(shè)立監(jiān)聽上限
  }
}


// 觸發(fā)名為type的事件
EventEmeitter.prototype.emit = function(type, ...args) {
  let handler;
  // 從儲(chǔ)存事件鍵值對(duì)的this._events中獲取對(duì)應(yīng)事件回調(diào)函數(shù)
  handler = this._events.get(type);
  if (args.length > 0) {
    handler.apply(this, args);
  } else {
    handler.call(this);
  }
  return true;
};

// 監(jiān)聽名為type的事件
EventEmeitter.prototype.addListener = function(type, fn) {
  // 將type事件以及對(duì)應(yīng)的fn函數(shù)放入this._events中儲(chǔ)存
  if (!this._events.get(type)) {
    this._events.set(type, fn);
  }
};

面試版:

class EventEmeitter {
  constructor() {
    this._events = this._events || new Map(); // 儲(chǔ)存事件/回調(diào)鍵值對(duì)
    this._maxListeners = this._maxListeners || 10; // 設(shè)立監(jiān)聽上限
  }
}

// 觸發(fā)名為type的事件
EventEmeitter.prototype.emit = function(type, ...args) {
  let handler;
  // 從儲(chǔ)存事件鍵值對(duì)的this._events中獲取對(duì)應(yīng)事件回調(diào)函數(shù)
  handler = this._events.get(type);
  if (args.length > 0) {
    handler.apply(this, args);
  } else {
    handler.call(this);
  }
  return true;
};

// 監(jiān)聽名為type的事件
EventEmeitter.prototype.addListener = function(type, fn) {
  // 將type事件以及對(duì)應(yīng)的fn函數(shù)放入this._events中儲(chǔ)存
  if (!this._events.get(type)) {
    this._events.set(type, fn);
  }
};

// 觸發(fā)名為type的事件
EventEmeitter.prototype.emit = function(type, ...args) {
  let handler;
  handler = this._events.get(type);
  if (Array.isArray(handler)) {
    // 如果是一個(gè)數(shù)組說明有多個(gè)監(jiān)聽者,需要依次此觸發(fā)里面的函數(shù)
    for (let i = 0; i < handler.length; i++) {
      if (args.length > 0) {
        handler[i].apply(this, args);
      } else {
        handler[i].call(this);
      }
    }
  } else {
    // 單個(gè)函數(shù)的情況我們直接觸發(fā)即可
    if (args.length > 0) {
      handler.apply(this, args);
    } else {
      handler.call(this);
    }
  }

  return true;
};

// 監(jiān)聽名為type的事件
EventEmeitter.prototype.addListener = function(type, fn) {
  const handler = this._events.get(type); // 獲取對(duì)應(yīng)事件名稱的函數(shù)清單
  if (!handler) {
    this._events.set(type, fn);
  } else if (handler && typeof handler === "function") {
    // 如果handler是函數(shù)說明只有一個(gè)監(jiān)聽者
    this._events.set(type, [handler, fn]); // 多個(gè)監(jiān)聽者我們需要用數(shù)組儲(chǔ)存
  } else {
    handler.push(fn); // 已經(jīng)有多個(gè)監(jiān)聽者,那么直接往數(shù)組里push函數(shù)即可
  }
};

EventEmeitter.prototype.removeListener = function(type, fn) {
  const handler = this._events.get(type); // 獲取對(duì)應(yīng)事件名稱的函數(shù)清單

  // 如果是函數(shù),說明只被監(jiān)聽了一次
  if (handler && typeof handler === "function") {
    this._events.delete(type, fn);
  } else {
    let postion;
    // 如果handler是數(shù)組,說明被監(jiān)聽多次要找到對(duì)應(yīng)的函數(shù)
    for (let i = 0; i < handler.length; i++) {
      if (handler[i] === fn) {
        postion = i;
      } else {
        postion = -1;
      }
    }
    // 如果找到匹配的函數(shù),從數(shù)組中清除
    if (postion !== -1) {
      // 找到數(shù)組對(duì)應(yīng)的位置,直接清除此回調(diào)
      handler.splice(postion, 1);
      // 如果清除后只有一個(gè)函數(shù),那么取消數(shù)組,以函數(shù)形式保存
      if (handler.length === 1) {
        this._events.set(type, handler[0]);
      }
    } else {
      return this;
    }
  }
};

實(shí)現(xiàn)具體過程和思路見實(shí)現(xiàn)event

實(shí)現(xiàn)instanceOf

// 模擬 instanceof
function instance_of(L, R) {
  //L 表示左表達(dá)式,R 表示右表達(dá)式
  var O = R.prototype; // 取 R 的顯示原型
  L = L.__proto__; // 取 L 的隱式原型
  while (true) {
    if (L === null) return false;
    if (O === L)
      // 這里重點(diǎn):當(dāng) O 嚴(yán)格等于 L 時(shí),返回 true
      return true;
    L = L.__proto__;
  }
}

模擬new

new操作符做了這些事:

  • 它創(chuàng)建了一個(gè)全新的對(duì)象
  • 它會(huì)被執(zhí)行[[Prototype]](也就是proto)鏈接
  • 它使this指向新創(chuàng)建的對(duì)象
  • 通過new創(chuàng)建的每個(gè)對(duì)象將最終被[[Prototype]]鏈接到這個(gè)函數(shù)的prototype對(duì)象上
  • 如果函數(shù)沒有返回對(duì)象類型Object(包含F(xiàn)unctoin, Array, Date, RegExg, Error),那么new表達(dá)式中的函數(shù)調(diào)用將返回該對(duì)象引用
// objectFactory(name, 'cxk', '18')
function objectFactory() {
  const obj = new Object();
  const Constructor = [].shift.call(arguments);

  obj.__proto__ = Constructor.prototype;

  const ret = Constructor.apply(obj, arguments);

  return typeof ret === "object" ? ret : obj;
}

實(shí)現(xiàn)一個(gè)call

call做了什么:

  • 將函數(shù)設(shè)為對(duì)象的屬性
  • 執(zhí)行&刪除這個(gè)函數(shù)
  • 指定this到函數(shù)并傳入給定參數(shù)執(zhí)行函數(shù)
  • 如果不傳入?yún)?shù),默認(rèn)指向?yàn)?window
// 模擬 call bar.mycall(null);
//實(shí)現(xiàn)一個(gè)call方法:
Function.prototype.myCall = function(context) {
  //此處沒有考慮context非object情況
  context.fn = this;
  let args = [];
  for (let i = 1, len = arguments.length; i < len; i++) {
    args.push(arguments[i]);
  }
  context.fn(...args);
  let result = context.fn(...args);
  delete context.fn;
  return result;
};

具體實(shí)現(xiàn)參考JavaScript深入之call和apply的模擬實(shí)現(xiàn)

實(shí)現(xiàn)apply方法

apply原理與call很相似,不多贅述

// 模擬 apply
Function.prototype.myapply = function(context, arr) {
  var context = Object(context) || window;
  context.fn = this;

  var result;
  if (!arr) {
    result = context.fn();
  } else {
    var args = [];
    for (var i = 0, len = arr.length; i < len; i++) {
      args.push("arr[" + i + "]");
    }
    result = eval("context.fn(" + args + ")");
  }

  delete context.fn;
  return result;
};

實(shí)現(xiàn)bind

實(shí)現(xiàn)bind要做什么

  • 返回一個(gè)函數(shù),綁定this,傳遞預(yù)置參數(shù)
  • bind返回的函數(shù)可以作為構(gòu)造函數(shù)使用。故作為構(gòu)造函數(shù)時(shí)應(yīng)使得this失效,但是傳入的參數(shù)依然有效
// mdn的實(shí)現(xiàn)
if (!Function.prototype.bind) {
  Function.prototype.bind = function(oThis) {
    if (typeof this !== 'function') {
      // closest thing possible to the ECMAScript 5
      // internal IsCallable function
      throw new TypeError('Function.prototype.bind - what is trying to be bound is not callable');
    }

    var aArgs   = Array.prototype.slice.call(arguments, 1),
        fToBind = this,
        fNOP    = function() {},
        fBound  = function() {
          // this instanceof fBound === true時(shí),說明返回的fBound被當(dāng)做new的構(gòu)造函數(shù)調(diào)用
          return fToBind.apply(this instanceof fBound
                 ? this
                 : oThis,
                 // 獲取調(diào)用時(shí)(fBound)的傳參.bind 返回的函數(shù)入?yún)⑼沁@么傳遞的
                 aArgs.concat(Array.prototype.slice.call(arguments)));
        };

    // 維護(hù)原型關(guān)系
    if (this.prototype) {
      // Function.prototype doesn't have a prototype property
      fNOP.prototype = this.prototype; 
    }
    // 下行的代碼使fBound.prototype是fNOP的實(shí)例,因此
    // 返回的fBound若作為new的構(gòu)造函數(shù),new生成的新對(duì)象作為this傳入fBound,新對(duì)象的__proto__就是fNOP的實(shí)例
    fBound.prototype = new fNOP();

    return fBound;
  };
}

詳解請(qǐng)移步JavaScript深入之bind的模擬實(shí)現(xiàn) #12

模擬Object.create

Object.create()方法創(chuàng)建一個(gè)新對(duì)象,使用現(xiàn)有的對(duì)象來(lái)提供新創(chuàng)建的對(duì)象的proto。

// 模擬 Object.create

function create(proto) {
  function F() {}
  F.prototype = proto;

  return new F();
}

實(shí)現(xiàn)類的繼承

類的繼承在幾年前是重點(diǎn)內(nèi)容,有n種繼承方式各有優(yōu)劣,es6普及后越來(lái)越不重要,那么多種寫法有點(diǎn)『回字有四樣寫法』的意思,如果還想深入理解的去看紅寶書即可,我們目前只實(shí)現(xiàn)一種最理想的繼承方式。

function Parent(name) {
    this.parent = name
}
Parent.prototype.say = function() {
    console.log(`${this.parent}: 你打籃球的樣子像kunkun`)
}
function Child(name, parent) {
    // 將父類的構(gòu)造函數(shù)綁定在子類上
    Parent.call(this, parent)
    this.child = name
}

/** 
 1. 這一步不用Child.prototype =Parent.prototype的原因是怕共享內(nèi)存,修改父類原型對(duì)象就會(huì)影響子類
 2. 不用Child.prototype = new Parent()的原因是會(huì)調(diào)用2次父類的構(gòu)造方法(另一次是call),會(huì)存在一份多余的父類實(shí)例屬性
3. Object.create是創(chuàng)建了父類原型的副本,與父類原型完全隔離
*/
Child.prototype = Object.create(Parent.prototype);
Child.prototype.say = function() {
    console.log(`${this.parent}好,我是練習(xí)時(shí)長(zhǎng)兩年半的${this.child}`);
}

// 注意記得把子類的構(gòu)造指向子類本身
Child.prototype.constructor = Child;

var parent = new Parent('father');
parent.say() // father: 你打籃球的樣子像kunkun

var child = new Child('cxk', 'father');
child.say() // father好,我是練習(xí)時(shí)長(zhǎng)兩年半的cxk

實(shí)現(xiàn)JSON.parse

var json = '{"name":"cxk", "age":25}';
var obj = eval("(" + json + ")");

此方法屬于黑魔法,極易容易被xss攻擊,還有一種new Function大同小異。

簡(jiǎn)單的教程看這個(gè)半小時(shí)實(shí)現(xiàn)一個(gè) JSON 解析器

實(shí)現(xiàn)Promise

我很早之前實(shí)現(xiàn)過一版,而且注釋很多,但是居然找不到了,這是在網(wǎng)絡(luò)上找了一版帶注釋的,目測(cè)沒有大問題,具體過程可以看這篇史上最易讀懂的 Promise/A+ 完全實(shí)現(xiàn)

var PromisePolyfill = (function () {
  // 和reject不同的是resolve需要嘗試展開thenable對(duì)象
  function tryToResolve (value) {
    if (this === value) {
    // 主要是防止下面這種情況
    // let y = new Promise(res => setTimeout(res(y)))
      throw TypeError('Chaining cycle detected for promise!')
    }

    // 根據(jù)規(guī)范2.32以及2.33 對(duì)對(duì)象或者函數(shù)嘗試展開
    // 保證S6之前的 polyfill 也能和ES6的原生promise混用
    if (value !== null &&
      (typeof value === 'object' || typeof value === 'function')) {
      try {
      // 這里記錄這次then的值同時(shí)要被try包裹
      // 主要原因是 then 可能是一個(gè)getter, 也也就是說
      //   1. value.then可能報(bào)錯(cuò)
      //   2. value.then可能產(chǎn)生副作用(例如多次執(zhí)行可能結(jié)果不同)
        var then = value.then

        // 另一方面, 由于無(wú)法保證 then 確實(shí)會(huì)像預(yù)期的那樣只調(diào)用一個(gè)onFullfilled / onRejected
        // 所以增加了一個(gè)flag來(lái)防止resolveOrReject被多次調(diào)用
        var thenAlreadyCalledOrThrow = false
        if (typeof then === 'function') {
        // 是thenable 那么嘗試展開
        // 并且在該thenable狀態(tài)改變之前this對(duì)象的狀態(tài)不變
          then.bind(value)(
          // onFullfilled
            function (value2) {
              if (thenAlreadyCalledOrThrow) return
              thenAlreadyCalledOrThrow = true
              tryToResolve.bind(this, value2)()
            }.bind(this),

            // onRejected
            function (reason2) {
              if (thenAlreadyCalledOrThrow) return
              thenAlreadyCalledOrThrow = true
              resolveOrReject.bind(this, 'rejected', reason2)()
            }.bind(this)
          )
        } else {
        // 擁有then 但是then不是一個(gè)函數(shù) 所以也不是thenable
          resolveOrReject.bind(this, 'resolved', value)()
        }
      } catch (e) {
        if (thenAlreadyCalledOrThrow) return
        thenAlreadyCalledOrThrow = true
        resolveOrReject.bind(this, 'rejected', e)()
      }
    } else {
    // 基本類型 直接返回
      resolveOrReject.bind(this, 'resolved', value)()
    }
  }

  function resolveOrReject (status, data) {
    if (this.status !== 'pending') return
    this.status = status
    this.data = data
    if (status === 'resolved') {
      for (var i = 0; i < this.resolveList.length; ++i) {
        this.resolveList[i]()
      }
    } else {
      for (i = 0; i < this.rejectList.length; ++i) {
        this.rejectList[i]()
      }
    }
  }

  function Promise (executor) {
    if (!(this instanceof Promise)) {
      throw Error('Promise can not be called without new !')
    }

    if (typeof executor !== 'function') {
    // 非標(biāo)準(zhǔn) 但與Chrome谷歌保持一致
      throw TypeError('Promise resolver ' + executor + ' is not a function')
    }

    this.status = 'pending'
    this.resolveList = []
    this.rejectList = []

    try {
      executor(tryToResolve.bind(this), resolveOrReject.bind(this, 'rejected'))
    } catch (e) {
      resolveOrReject.bind(this, 'rejected', e)()
    }
  }

  Promise.prototype.then = function (onFullfilled, onRejected) {
  // 返回值穿透以及錯(cuò)誤穿透, 注意錯(cuò)誤穿透用的是throw而不是return,否則的話
  // 這個(gè)then返回的promise狀態(tài)將變成resolved即接下來(lái)的then中的onFullfilled
  // 會(huì)被調(diào)用, 然而我們想要調(diào)用的是onRejected
    if (typeof onFullfilled !== 'function') {
      onFullfilled = function (data) {
        return data
      }
    }
    if (typeof onRejected !== 'function') {
      onRejected = function (reason) {
        throw reason
      }
    }

    var executor = function (resolve, reject) {
      setTimeout(function () {
        try {
        // 拿到對(duì)應(yīng)的handle函數(shù)處理this.data
        // 并以此為依據(jù)解析這個(gè)新的Promise
          var value = this.status === 'resolved'
            ? onFullfilled(this.data)
            : onRejected(this.data)
          resolve(value)
        } catch (e) {
          reject(e)
        }
      }.bind(this))
    }

    // then 接受兩個(gè)函數(shù)返回一個(gè)新的Promise
    // then 自身的執(zhí)行永遠(yuǎn)異步與onFullfilled/onRejected的執(zhí)行
    if (this.status !== 'pending') {
      return new Promise(executor.bind(this))
    } else {
    // pending
      return new Promise(function (resolve, reject) {
        this.resolveList.push(executor.bind(this, resolve, reject))
        this.rejectList.push(executor.bind(this, resolve, reject))
      }.bind(this))
    }
  }

  // for prmise A+ test
  Promise.deferred = Promise.defer = function () {
    var dfd = {}
    dfd.promise = new Promise(function (resolve, reject) {
      dfd.resolve = resolve
      dfd.reject = reject
    })
    return dfd
  }

  // for prmise A+ test
  if (typeof module !== 'undefined') {
    module.exports = Promise
  }

  return Promise
})()

PromisePolyfill.all = function (promises) {
  return new Promise((resolve, reject) => {
    const result = []
    let cnt = 0
    for (let i = 0; i < promises.length; ++i) {
      promises[i].then(value => {
        cnt++
        result[i] = value
        if (cnt === promises.length) resolve(result)
      }, reject)
    }
  })
}

PromisePolyfill.race = function (promises) {
  return new Promise((resolve, reject) => {
    for (let i = 0; i < promises.length; ++i) {
      promises[i].then(resolve, reject)
    }
  })
}

解析 URL Params 為對(duì)象

let url = 'http://www.domain.com/?user=anonymous&id=123&id=456&city=%E5%8C%97%E4%BA%AC&enabled';
parseParam(url)
/* 結(jié)果
{ user: 'anonymous',
  id: [ 123, 456 ], // 重復(fù)出現(xiàn)的 key 要組裝成數(shù)組,能被轉(zhuǎn)成數(shù)字的就轉(zhuǎn)成數(shù)字類型
  city: '北京', // 中文需解碼
  enabled: true, // 未指定值得 key 約定為 true
}
*/

function parseParam(url) {
  const paramsStr = /.+\?(.+)$/.exec(url)[1]; // 將 ? 后面的字符串取出來(lái)
  const paramsArr = paramsStr.split('&'); // 將字符串以 & 分割后存到數(shù)組中
  let paramsObj = {};
  // 將 params 存到對(duì)象中
  paramsArr.forEach(param => {
    if (/=/.test(param)) { // 處理有 value 的參數(shù)
      let [key, val] = param.split('='); // 分割 key 和 value
      val = decodeURIComponent(val); // 解碼
      val = /^\d+$/.test(val) ? parseFloat(val) : val; // 判斷是否轉(zhuǎn)為數(shù)字

      if (paramsObj.hasOwnProperty(key)) { // 如果對(duì)象有 key,則添加一個(gè)值
        paramsObj[key] = [].concat(paramsObj[key], val);
      } else { // 如果對(duì)象沒有這個(gè) key,創(chuàng)建 key 并設(shè)置值
        paramsObj[key] = val;
      }
    } else { // 處理沒有 value 的參數(shù)
      paramsObj[param] = true;
    }
  })

  return paramsObj;
}

模板引擎實(shí)現(xiàn)

let template = '我是{{name}},年齡{{age}},性別{{sex}}';
let data = {
  name: '姓名',
  age: 18
}
render(template, data); // 我是姓名,年齡18,性別undefined

function render(template, data) {
  const reg = /\{\{(\w+)\}\}/; // 模板字符串正則
  if (reg.test(template)) { // 判斷模板里是否有模板字符串
    const name = reg.exec(template)[1]; // 查找當(dāng)前模板里第一個(gè)模板字符串的字段
    template = template.replace(reg, data[name]); // 將第一個(gè)模板字符串渲染
    return render(template, data); // 遞歸的渲染并返回渲染后的結(jié)構(gòu)
  }
  return template; // 如果模板沒有模板字符串直接返回
}

轉(zhuǎn)化為駝峰命名

var s1 = "get-element-by-id"

// 轉(zhuǎn)化為 getElementById
var f = function(s) {
    return s.replace(/-\w/g, function(x) {
        return x.slice(1).toUpperCase();
    })
}

查找字符串中出現(xiàn)最多的字符和個(gè)數(shù)

例: abbcccddddd -> 字符最多的是d,出現(xiàn)了5次

let str = "abcabcabcbbccccc";
let num = 0;
let char = '';

 // 使其按照一定的次序排列
str = str.split('').sort().join('');
// "aaabbbbbcccccccc"

// 定義正則表達(dá)式
let re = /(\w)\1+/g;
str.replace(re,($0,$1) => {
    if(num < $0.length){
        num = $0.length;
        char = $1;        
    }
});
console.log(`字符最多的是${char},出現(xiàn)了${num}次`);

字符串查找

請(qǐng)使用最基本的遍歷來(lái)實(shí)現(xiàn)判斷字符串 a 是否被包含在字符串 b 中,并返回第一次出現(xiàn)的位置(找不到返回 -1)。

a='34';b='1234567'; // 返回 2
a='35';b='1234567'; // 返回 -1
a='355';b='12354355'; // 返回 5
isContain(a,b);

function isContain(a, b) {
  for (let i in b) {
    if (a[0] === b[i]) {
      let tmp = true;
      for (let j in a) {
        if (a[j] !== b[~~i + ~~j]) {
          tmp = false;
        }
      }
      if (tmp) {
        return i;
      }
    }
  }
  return -1;
}

實(shí)現(xiàn)千位分隔符

// 保留三位小數(shù)
parseToMoney(1234.56); // return '1,234.56'
parseToMoney(123456789); // return '123,456,789'
parseToMoney(1087654.321); // return '1,087,654.321'

function parseToMoney(num) {
  num = parseFloat(num.toFixed(3));
  let [integer, decimal] = String.prototype.split.call(num, '.');
  integer = integer.replace(/\d(?=(\d{3})+$)/g, '$&,');
  return integer + '.' + (decimal ? decimal : '');
}

正則表達(dá)式(運(yùn)用了正則的前向聲明和反前向聲明):

function parseToMoney(str){
    // 僅僅對(duì)位置進(jìn)行匹配
    let re = /(?=(?!\b)(\d{3})+$)/g; 
   return str.replace(re,','); 
}

判斷是否是電話號(hào)碼

function isPhone(tel) {
    var regx = /^1[34578]\d{9}$/;
    return regx.test(tel);
}

驗(yàn)證是否是郵箱

function isEmail(email) {
    var regx = /^([a-zA-Z0-9_\-])+@([a-zA-Z0-9_\-])+(\.[a-zA-Z0-9_\-])+$/;
    return regx.test(email);
}

驗(yàn)證是否是身份證

function isCardNo(number) {
    var regx = /(^\d{15}$)|(^\d{18}$)|(^\d{17}(\d|X|x)$)/;
    return regx.test(number);
}


公眾號(hào)

想要實(shí)時(shí)關(guān)注筆者最新的文章和最新的文檔更新請(qǐng)關(guān)注公眾號(hào)程序員面試官,后續(xù)的文章會(huì)優(yōu)先在公眾號(hào)更新.

簡(jiǎn)歷模板: 關(guān)注公眾號(hào)回復(fù)「模板」獲取

《前端面試手冊(cè)》: 配套于本指南的突擊手冊(cè),關(guān)注公眾號(hào)回復(fù)「fed」獲取

二維碼.png
?著作權(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ù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Swift1> Swift和OC的區(qū)別1.1> Swift沒有地址/指針的概念1.2> 泛型1.3> 類型嚴(yán)謹(jǐn) 對(duì)...
    cosWriter閱讀 11,653評(píng)論 1 32
  • 第3章 基本概念 3.1 語(yǔ)法 3.2 關(guān)鍵字和保留字 3.3 變量 3.4 數(shù)據(jù)類型 5種簡(jiǎn)單數(shù)據(jù)類型:Unde...
    RickCole閱讀 5,513評(píng)論 0 21
  • 前端開發(fā)面試題 面試題目: 根據(jù)你的等級(jí)和職位的變化,入門級(jí)到專家級(jí),廣度和深度都會(huì)有所增加。 題目類型: 理論知...
    怡寶丶閱讀 2,683評(píng)論 0 7
  • PNG 有PNG8和truecolor PNG PNG8類似GIF顏色上限為256,文件小,支持alpha透明度,...
    hudaren閱讀 1,834評(píng)論 0 0
  • 函數(shù)和對(duì)象 1、函數(shù) 1.1 函數(shù)概述 函數(shù)對(duì)于任何一門語(yǔ)言來(lái)說都是核心的概念。通過函數(shù)可以封裝任意多條語(yǔ)句,而且...
    道無(wú)虛閱讀 4,947評(píng)論 0 5

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