查找字符串中呈现最多的字符和个数
例: 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很类似,不多赘述
// 模仿 applyFunction.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) }) } })}// testlet 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办法// // 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 } }}// 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))}// testconst 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/01dateFormat(new Date('2020-04-01'), 'yyyy/MM/dd') // 2020/04/01dateFormat(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