查詢字串中出現最多的字元和個數
例: abbcccddddd -> 字元最多的是d,出現了5次
let str = "abcabcabcbbccccc";
let num = 0;
let char = '';
// 使其按照一定的次序排列
str = str.split('').sort().join('');
// "aaabbbbbcccccccc"
// 定義正規表示式
let re = /(\w)\1+/g;
str.replace(re,($0,$1) => {
if(num < $0.length){
num = $0.length;
char = $1;
}
});
console.log(`字元最多的是${char},出現了${num}次`);
手寫型別判斷函式
function getType(value) {
// 判斷資料是 null 的情況
if (value === null) {
return value + "";
}
// 判斷資料是引用型別的情況
if (typeof value === "object") {
let valueClass = Object.prototype.toString.call(value),
type = valueClass.split(" ")[1].split("");
type.pop();
return type.join("").toLowerCase();
} else {
// 判斷資料是基本資料型別的情況和函式的情況
return typeof value;
}
}
實現Event(event bus)
event bus既是node中各個模組的基石,又是前端元件通訊的依賴手段之一,同時涉及了訂閱-釋出設計模式,是非常重要的基礎。
簡單版:
class EventEmeitter {
constructor() {
this._events = this._events || new Map(); // 儲存事件/回撥鍵值對
this._maxListeners = this._maxListeners || 10; // 設立監聽上限
}
}
// 觸發名為type的事件
EventEmeitter.prototype.emit = function(type, ...args) {
let handler;
// 從儲存事件鍵值對的this._events中獲取對應事件回撥函式
handler = this._events.get(type);
if (args.length > 0) {
handler.apply(this, args);
} else {
handler.call(this);
}
return true;
};
// 監聽名為type的事件
EventEmeitter.prototype.addListener = function(type, fn) {
// 將type事件以及對應的fn函式放入this._events中儲存
if (!this._events.get(type)) {
this._events.set(type, fn);
}
};
面試版:
class EventEmeitter {
constructor() {
this._events = this._events || new Map(); // 儲存事件/回撥鍵值對
this._maxListeners = this._maxListeners || 10; // 設立監聽上限
}
}
// 觸發名為type的事件
EventEmeitter.prototype.emit = function(type, ...args) {
let handler;
// 從儲存事件鍵值對的this._events中獲取對應事件回撥函式
handler = this._events.get(type);
if (args.length > 0) {
handler.apply(this, args);
} else {
handler.call(this);
}
return true;
};
// 監聽名為type的事件
EventEmeitter.prototype.addListener = function(type, fn) {
// 將type事件以及對應的fn函式放入this._events中儲存
if (!this._events.get(type)) {
this._events.set(type, fn);
}
};
// 觸發名為type的事件
EventEmeitter.prototype.emit = function(type, ...args) {
let handler;
handler = this._events.get(type);
if (Array.isArray(handler)) {
// 如果是一個陣列說明有多個監聽者,需要依次此觸發裡面的函式
for (let i = 0; i < handler.length; i++) {
if (args.length > 0) {
handler[i].apply(this, args);
} else {
handler[i].call(this);
}
}
} else {
// 單個函式的情況我們直接觸發即可
if (args.length > 0) {
handler.apply(this, args);
} else {
handler.call(this);
}
}
return true;
};
// 監聽名為type的事件
EventEmeitter.prototype.addListener = function(type, fn) {
const handler = this._events.get(type); // 獲取對應事件名稱的函式清單
if (!handler) {
this._events.set(type, fn);
} else if (handler && typeof handler === "function") {
// 如果handler是函式說明只有一個監聽者
this._events.set(type, [handler, fn]); // 多個監聽者我們需要用陣列儲存
} else {
handler.push(fn); // 已經有多個監聽者,那麼直接往陣列裡push函式即可
}
};
EventEmeitter.prototype.removeListener = function(type, fn) {
const handler = this._events.get(type); // 獲取對應事件名稱的函式清單
// 如果是函式,說明只被監聽了一次
if (handler && typeof handler === "function") {
this._events.delete(type, fn);
} else {
let postion;
// 如果handler是陣列,說明被監聽多次要找到對應的函式
for (let i = 0; i < handler.length; i++) {
if (handler[i] === fn) {
postion = i;
} else {
postion = -1;
}
}
// 如果找到匹配的函式,從陣列中清除
if (postion !== -1) {
// 找到陣列對應的位置,直接清除此回撥
handler.splice(postion, 1);
// 如果清除後只有一個函式,那麼取消陣列,以函式形式儲存
if (handler.length === 1) {
this._events.set(type, handler[0]);
}
} else {
return this;
}
}
};
實現具體過程和思路見實現event
手寫 Promise.race
該方法的引數是 Promise 例項陣列, 然後其 then 註冊的回撥方法是陣列中的某一個 Promise 的狀態變為 fulfilled 的時候就執行. 因為 Promise 的狀態只能改變一次, 那麼我們只需要把 Promise.race 中產生的 Promise 物件的 resolve 方法, 注入到陣列中的每一個 Promise 例項中的回撥函式中即可.
Promise.race = function (args) {
return new Promise((resolve, reject) => {
for (let i = 0, len = args.length; i < len; i++) {
args[i].then(resolve, reject)
}
})
}
將數字每千分位用逗號隔開
數字有小數版本:
let format = n => {
let num = n.toString() // 轉成字串
let decimals = ''
// 判斷是否有小數
num.indexOf('.') > -1 ? decimals = num.split('.')[1] : decimals
let len = num.length
if (len <= 3) {
return num
} else {
let temp = ''
let remainder = len % 3
decimals ? temp = '.' + decimals : temp
if (remainder > 0) { // 不是3的整數倍
return num.slice(0, remainder) + ',' + num.slice(remainder, len).match(/\d{3}/g).join(',') + temp
} else { // 是3的整數倍
return num.slice(0, len).match(/\d{3}/g).join(',') + temp
}
}
}
format(12323.33) // '12,323.33'
數字無小數版本:
let format = n => {
let num = n.toString()
let len = num.length
if (len <= 3) {
return num
} else {
let remainder = len % 3
if (remainder > 0) { // 不是3的整數倍
return num.slice(0, remainder) + ',' + num.slice(remainder, len).match(/\d{3}/g).join(',')
} else { // 是3的整數倍
return num.slice(0, len).match(/\d{3}/g).join(',')
}
}
}
format(1232323) // '1,232,323'
實現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;
};
參考 前端進階面試題詳細解答
手寫 Promise.all
1) 核心思路
- 接收一個 Promise 例項的陣列或具有 Iterator 介面的物件作為引數
- 這個方法返回一個新的 promise 物件,
- 遍歷傳入的引數,用Promise.resolve()將引數"包一層",使其變成一個promise物件
- 引數所有回撥成功才是成功,返回值陣列與引數順序一致
- 引數陣列其中一個失敗,則觸發失敗狀態,第一個觸發失敗的 Promise 錯誤資訊作為 Promise.all 的錯誤資訊。
2)實現程式碼
一般來說,Promise.all 用來處理多個併發請求,也是為了頁面資料構造的方便,將一個頁面所用到的在不同介面的資料一起請求過來,不過,如果其中一個介面失敗了,多個請求也就失敗了,頁面可能啥也出不來,這就看當前頁面的耦合程度了
function promiseAll(promises) {
return new Promise(function(resolve, reject) {
if(!Array.isArray(promises)){
throw new TypeError(`argument must be a array`)
}
var resolvedCounter = 0;
var promiseNum = promises.length;
var resolvedResult = [];
for (let i = 0; i < promiseNum; i++) {
Promise.resolve(promises[i]).then(value=>{
resolvedCounter++;
resolvedResult[i] = value;
if (resolvedCounter == promiseNum) {
return resolve(resolvedResult)
}
},error=>{
return reject(error)
})
}
})
}
// test
let p1 = new Promise(function (resolve, reject) {
setTimeout(function () {
resolve(1)
}, 1000)
})
let p2 = new Promise(function (resolve, reject) {
setTimeout(function () {
resolve(2)
}, 2000)
})
let p3 = new Promise(function (resolve, reject) {
setTimeout(function () {
resolve(3)
}, 3000)
})
promiseAll([p3, p1, p2]).then(res => {
console.log(res) // [3, 1, 2]
})
手寫 apply 函式
apply 函式的實現步驟:
- 判斷呼叫物件是否為函式,即使我們是定義在函式的原型上的,但是可能出現使用 call 等方式呼叫的情況。
- 判斷傳入上下文物件是否存在,如果不存在,則設定為 window 。
- 將函式作為上下文物件的一個屬性。
- 判斷引數值是否傳入
- 使用上下文物件來呼叫這個方法,並儲存返回結果。
- 刪除剛才新增的屬性
- 返回結果
// apply 函式實現
Function.prototype.myApply = function(context) {
// 判斷呼叫物件是否為函式
if (typeof this !== "function") {
throw new TypeError("Error");
}
let result = null;
// 判斷 context 是否存在,如果未傳入則為 window
context = context || window;
// 將函式設為物件的方法
context.fn = this;
// 呼叫方法
if (arguments[1]) {
result = context.fn(...arguments[1]);
} else {
result = context.fn();
}
// 將屬性刪除
delete context.fn;
return result;
};
實現字串翻轉
在字串的原型鏈上新增一個方法,實現字串翻轉:
String.prototype._reverse = function(a){
return a.split("").reverse().join("");
}
var obj = new String();
var res = obj._reverse ('hello');
console.log(res); // olleh
需要注意的是,必須透過例項化物件之後再去呼叫定義的方法,不然找不到該方法。
轉化為駝峰命名
var s1 = "get-element-by-id"
// 轉化為 getElementById
var f = function(s) {
return s.replace(/-\w/g, function(x) {
return x.slice(1).toUpperCase();
})
}
二叉樹層次遍歷
// 二叉樹層次遍歷
class Node {
constructor(element, parent) {
this.parent = parent // 父節點
this.element = element // 當前儲存內容
this.left = null // 左子樹
this.right = null // 右子樹
}
}
class BST {
constructor(compare) {
this.root = null // 樹根
this.size = 0 // 樹中的節點個數
this.compare = compare || this.compare
}
compare(a,b) {
return a - b
}
add(element) {
if(this.root === null) {
this.root = new Node(element, null)
this.size++
return
}
// 獲取根節點 用當前新增的進行判斷 放左邊還是放右邊
let currentNode = this.root
let compare
let parent = null
while (currentNode) {
compare = this.compare(element, currentNode.element)
parent = currentNode // 先將父親儲存起來
// currentNode要不停的變化
if(compare > 0) {
currentNode = currentNode.right
} else if(compare < 0) {
currentNode = currentNode.left
} else {
currentNode.element = element // 相等時 先覆蓋後續處理
}
}
let newNode = new Node(element, parent)
if(compare > 0) {
parent.right = newNode
} else if(compare < 0) {
parent.left = newNode
}
this.size++
}
// 層次遍歷 佇列
levelOrderTraversal(visitor) {
if(this.root == null) {
return
}
let stack = [this.root]
let index = 0 // 指標 指向0
let currentNode
while (currentNode = stack[index++]) {
// 反轉二叉樹
let tmp = currentNode.left
currentNode.left = currentNode.right
currentNode.right = tmp
visitor.visit(currentNode.element)
if(currentNode.left) {
stack.push(currentNode.left)
}
if(currentNode.right) {
stack.push(currentNode.right)
}
}
}
}
// 測試
var bst = new BST((a,b)=>a.age-b.age) // 模擬sort方法
// ![](http://img-repo.poetries.top/images/20210522203619.png)
// ![](http://img-repo.poetries.top/images/20210522211809.png)
bst.add({age: 10})
bst.add({age: 8})
bst.add({age:19})
bst.add({age:6})
bst.add({age: 15})
bst.add({age: 22})
bst.add({age: 20})
// 使用訪問者模式
class Visitor {
constructor() {
this.visit = function (elem) {
elem.age = elem.age*2
}
}
}
// ![](http://img-repo.poetries.top/images/20210523095515.png)
console.log(bst.levelOrderTraversal(new Visitor()))
實現一個拖拽
<style>
html, body {
margin: 0;
height: 100%;
}
#box {
width: 100px;
height: 100px;
background-color: red;
position: absolute;
top: 100px;
left: 100px;
}
</style>
<div id="box"></div>
window.onload = function () {
var box = document.getElementById('box');
box.onmousedown = function (ev) {
var oEvent = ev || window.event; // 相容火狐,火狐下沒有window.event
var distanceX = oEvent.clientX - box.offsetLeft; // 滑鼠到可視區左邊的距離 - box到頁面左邊的距離
var distanceY = oEvent.clientY - box.offsetTop;
document.onmousemove = function (ev) {
var oEvent = ev || window.event;
var left = oEvent.clientX - distanceX;
var top = oEvent.clientY - distanceY;
if (left <= 0) {
left = 0;
} else if (left >= document.documentElement.clientWidth - box.offsetWidth) {
left = document.documentElement.clientWidth - box.offsetWidth;
}
if (top <= 0) {
top = 0;
} else if (top >= document.documentElement.clientHeight - box.offsetHeight) {
top = document.documentElement.clientHeight - box.offsetHeight;
}
box.style.left = left + 'px';
box.style.top = top + 'px';
}
box.onmouseup = function () {
document.onmousemove = null;
box.onmouseup = null;
}
}
}
判斷括號字串是否有效(小米)
題目描述
給定一個只包括 '(',')','{','}','[',']' 的字串 s ,判斷字串是否有效。
有效字串需滿足:
- 左括號必須用相同型別的右括號閉合。
- 左括號必須以正確的順序閉合。
示例 1:
輸入:s = "()"
輸出:true
示例 2:
輸入:s = "()[]{}"
輸出:true
示例 3:
輸入:s = "(]"
輸出:false
答案
const isValid = function (s) {
if (s.length % 2 === 1) {
return false;
}
const regObj = {
"{": "}",
"(": ")",
"[": "]",
};
let stack = [];
for (let i = 0; i < s.length; i++) {
if (s[i] === "{" || s[i] === "(" || s[i] === "[") {
stack.push(s[i]);
} else {
const cur = stack.pop();
if (s[i] !== regObj[cur]) {
return false;
}
}
}
if (stack.length) {
return false;
}
return true;
};
非同步併發數限制
/**
* 關鍵點
* 1. new promise 一經建立,立即執行
* 2. 使用 Promise.resolve().then 可以把任務加到微任務佇列,防止立即執行迭代方法
* 3. 微任務處理過程中,產生的新的微任務,會在同一事件迴圈內,追加到微任務佇列裡
* 4. 使用 race 在某個任務完成時,繼續新增任務,保持任務按照最大併發數進行執行
* 5. 任務完成後,需要從 doingTasks 中移出
*/
function limit(count, array, iterateFunc) {
const tasks = []
const doingTasks = []
let i = 0
const enqueue = () => {
if (i === array.length) {
return Promise.resolve()
}
const task = Promise.resolve().then(() => iterateFunc(array[i++]))
tasks.push(task)
const doing = task.then(() => doingTasks.splice(doingTasks.indexOf(doing), 1))
doingTasks.push(doing)
const res = doingTasks.length >= count ? Promise.race(doingTasks) : Promise.resolve()
return res.then(enqueue)
};
return enqueue().then(() => Promise.all(tasks))
}
// test
const timeout = i => new Promise(resolve => setTimeout(() => resolve(i), i))
limit(2, [1000, 1000, 1000, 1000], timeout).then((res) => {
console.log(res)
})
實現日期格式化函式
輸入:
dateFormat(new Date('2020-12-01'), 'yyyy/MM/dd') // 2020/12/01
dateFormat(new Date('2020-04-01'), 'yyyy/MM/dd') // 2020/04/01
dateFormat(new Date('2020-04-01'), 'yyyy年MM月dd日') // 2020年04月01日
const dateFormat = (dateInput, format)=>{
var day = dateInput.getDate()
var month = dateInput.getMonth() + 1
var year = dateInput.getFullYear()
format = format.replace(/yyyy/, year)
format = format.replace(/MM/,month)
format = format.replace(/dd/,day)
return format
}
實現陣列的flat方法
function _flat(arr, depth) {
if(!Array.isArray(arr) || depth <= 0) {
return arr;
}
return arr.reduce((prev, cur) => {
if (Array.isArray(cur)) {
return prev.concat(_flat(cur, depth - 1))
} else {
return prev.concat(cur);
}
}, []);
}
字串最長的不重複子串
題目描述
給定一個字串 s ,請你找出其中不含有重複字元的 最長子串 的長度。
示例 1:
輸入: s = "abcabcbb"
輸出: 3
解釋: 因為無重複字元的最長子串是 "abc",所以其長度為 3。
示例 2:
輸入: s = "bbbbb"
輸出: 1
解釋: 因為無重複字元的最長子串是 "b",所以其長度為 1。
示例 3:
輸入: s = "pwwkew"
輸出: 3
解釋: 因為無重複字元的最長子串是 "wke",所以其長度為 3。
請注意,你的答案必須是 子串 的長度,"pwke" 是一個子序列,不是子串。
示例 4:
輸入: s = ""
輸出: 0
答案
const lengthOfLongestSubstring = function (s) {
if (s.length === 0) {
return 0;
}
let left = 0;
let right = 1;
let max = 0;
while (right <= s.length) {
let lr = s.slice(left, right);
const index = lr.indexOf(s[right]);
if (index > -1) {
left = index + left + 1;
} else {
lr = s.slice(left, right + 1);
max = Math.max(max, lr.length);
}
right++;
}
return max;
};
樹形結構轉成列表(處理選單)
[
{
id: 1,
text: '節點1',
parentId: 0,
children: [
{
id:2,
text: '節點1_1',
parentId:1
}
]
}
]
轉成
[
{
id: 1,
text: '節點1',
parentId: 0 //這裡用0表示為頂級節點
},
{
id: 2,
text: '節點1_1',
parentId: 1 //透過這個欄位來確定子父級
}
...
]
實現程式碼如下:
function treeToList(data) {
let res = [];
const dfs = (tree) => {
tree.forEach((item) => {
if (item.children) {
dfs(item.children);
delete item.children;
}
res.push(item);
});
};
dfs(data);
return res;
}
判斷物件是否存在迴圈引用
迴圈引用物件本來沒有什麼問題,但是序列化的時候就會發生問題,比如呼叫JSON.stringify()
對該類物件進行序列化,就會報錯: Converting circular structure to JSON.
下面方法可以用來判斷一個物件中是否已存在迴圈引用:
const isCycleObject = (obj,parent) => {
const parentArr = parent || [obj];
for(let i in obj) {
if(typeof obj[i] === 'object') {
let flag = false;
parentArr.forEach((pObj) => {
if(pObj === obj[i]){
flag = true;
}
})
if(flag) return true;
flag = isCycleObject(obj[i],[...parentArr,obj[i]]);
if(flag) return true;
}
}
return false;
}
const a = 1;
const b = {a};
const c = {b};
const o = {d:{a:3},c}
o.c.b.aa = a;
console.log(isCycleObject(o)
查詢有序二維陣列的目標值:
var findNumberIn2DArray = function(matrix, target) {
if (matrix == null || matrix.length == 0) {
return false;
}
let row = 0;
let column = matrix[0].length - 1;
while (row < matrix.length && column >= 0) {
if (matrix[row][column] == target) {
return true;
} else if (matrix[row][column] > target) {
column--;
} else {
row++;
}
}
return false;
};
二維陣列斜向列印:
function printMatrix(arr){
let m = arr.length, n = arr[0].length
let res = []
// 左上角,從0 到 n - 1 列進行列印
for (let k = 0; k < n; k++) {
for (let i = 0, j = k; i < m && j >= 0; i++, j--) {
res.push(arr[i][j]);
}
}
// 右下角,從1 到 n - 1 行進行列印
for (let k = 1; k < m; k++) {
for (let i = k, j = n - 1; i < m && j >= 0; i++, j--) {
res.push(arr[i][j]);
}
}
return res
}
實現call方法
call做了什麼:
- 將函式設為物件的屬性
- 執行和刪除這個函式
- 指定
this
到函式並傳入給定引數執行函式 - 如果不傳入引數,預設指向為
window
// 模擬 call bar.mycall(null);
//實現一個call方法:
// 原理:利用 context.xxx = self obj.xx = func-->obj.xx()
Function.prototype.myCall = function(context = window, ...args) {
if (typeof this !== "function") {
throw new Error('type error')
}
// this-->func context--> obj args--> 傳遞過來的引數
// 在context上加一個唯一值不影響context上的屬性
let key = Symbol('key')
context[key] = this; // context為呼叫的上下文,this此處為函式,將這個函式作為context的方法
// let args = [...arguments].slice(1) //第一個引數為obj所以刪除,偽陣列轉為陣列
// 繫結引數 並執行函式
let result = context[key](...args);
// 清除定義的this 不刪除會導致context屬性越來越多
delete context[key];
// 返回結果
return result;
};
//用法:f.call(obj,arg1)
function f(a,b){
console.log(a+b)
console.log(this.name)
}
let obj={
name:1
}
f.myCall(obj,1,2) //否則this指向window