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)即可。
手寫簡(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));
局限性:
他無(wú)法實(shí)現(xiàn)對(duì)函數(shù) 、RegExp等特殊對(duì)象的克隆
會(huì)拋棄對(duì)象的constructor,所有的構(gòu)造函數(shù)會(huì)指向Object
對(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);
};
局限性:
- 一些特殊情況沒有處理: 例如Buffer對(duì)象、Promise、Set、Map
- 另外對(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」獲取
