校招前端二面常考手写面试题汇总
- 2022-12-19 浙江
本文字数:9401 字
阅读完需:约 31 分钟
实现 filter 方法
Array.prototype.myFilter=function(callback, context=window){
let len = this.length newArr = [], i=0
for(; i < len; i++){ if(callback.apply(context, [this[i], i , this])){ newArr.push(this[i]); } } return newArr;}
实现一下 hash 路由
基础的html代码:
<html> <style> html, body { margin: 0; height: 100%; } ul { list-style: none; margin: 0; padding: 0; display: flex; justify-content: center; } .box { width: 100%; height: 100%; background-color: red; } </style> <body> <ul> <li> <a href="#red">红色</a> </li> <li> <a href="#green">绿色</a> </li> <li> <a href="#purple">紫色</a> </li> </ul> </body></html>
简单实现:
<script> const box = document.getElementsByClassName('box')[0]; const hash = location.hash window.onhashchange = function (e) { const color = hash.slice(1) box.style.background = color }</script>
封装成一个 class:
<script> const box = document.getElementsByClassName('box')[0]; const hash = location.hash class HashRouter { constructor (hashStr, cb) { this.hashStr = hashStr this.cb = cb this.watchHash() this.watch = this.watchHash.bind(this) window.addEventListener('hashchange', this.watch) } watchHash () { let hash = window.location.hash.slice(1) this.hashStr = hash this.cb(hash) } } new HashRouter('red', (color) => { box.style.background = color })</script>
二叉树层次遍历
// 二叉树层次遍历
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()))
二分查找
function search(arr, target, start, end) { let targetIndex = -1;
let mid = Math.floor((start + end) / 2);
if (arr[mid] === target) { targetIndex = mid; return targetIndex; }
if (start >= end) { return targetIndex; }
if (arr[mid] < target) { return search(arr, target, mid + 1, end); } else { return search(arr, target, start, mid - 1); }}
// const dataArr = [1, 2, 3, 4, 5, 6, 7, 8, 9];// const position = search(dataArr, 6, 0, dataArr.length - 1);// if (position !== -1) {// console.log(`目标元素在数组中的位置:${position}`);// } else {// console.log("目标元素不在数组中");// }
实现一个 padStart()或 padEnd()的 polyfil
String.prototype.padStart 和 String.prototype.padEnd是ES8中新增的方法,允许将空字符串或其他字符串添加到原始字符串的开头或结尾。我们先看下使用语法:
String.padStart(targetLength,[padString])
用法:
'x'.padStart(4, 'ab') // 'abax''x'.padEnd(5, 'ab') // 'xabab'
// 1. 若是输入的目标长度小于字符串原本的长度则返回字符串本身'xxx'.padStart(2, 's') // 'xxx'
// 2. 第二个参数的默认值为 " ",长度是为1的// 3. 而此参数可能是个不确定长度的字符串,若是要填充的内容达到了目标长度,则将不要的部分截取'xxx'.padStart(5, 'sss') // ssxxx
// 4. 可用来处理日期、金额格式化问题'12'.padStart(10, 'YYYY-MM-DD') // "YYYY-MM-12"'09-12'.padStart(10, 'YYYY-MM-DD') // "YYYY-09-12"
polyfill 实现:
String.prototype.myPadStart = function (targetLen, padString = " ") { if (!targetLen) { throw new Error('请输入需要填充到的长度'); } let originStr = String(this); // 获取到调用的字符串, 因为this原本是String{},所以需要用String转为字符串 let originLen = originStr.length; // 调用的字符串原本的长度 if (originLen >= targetLen) return originStr; // 若是 原本 > 目标 则返回原本字符串 let diffNum = targetLen - originLen; // 10 - 6 // 差值 for (let i = 0; i < diffNum; i++) { // 要添加几个成员 for (let j = 0; j < padString.length; j++) { // 输入的padString的长度可能不为1 if (originStr.length === targetLen) break; // 判断每一次添加之后是否到了目标长度 originStr = `${padString[j]}${originStr}`; } if (originStr.length === targetLen) break; } return originStr;}console.log('xxx'.myPadStart(16))console.log('xxx'.padStart(16))
还是比较简单的,而padEnd的实现和它一样,只需要把第二层for循环里的${padString[j]}${orignStr}换下位置就可以了。
转化为驼峰命名
var s1 = "get-element-by-id"
// 转化为 getElementById
var f = function(s) { return s.replace(/-\w/g, function(x) { return x.slice(1).toUpperCase(); })}
参考 前端进阶面试题详细解答
实现一个队列
基于链表结构实现队列
const LinkedList = require('./实现一个链表结构')
// 用链表默认使用数组来模拟队列,性能更佳class Queue { constructor() { this.ll = new LinkedList() } // 向队列中添加 offer(elem) { this.ll.add(elem) } // 查看第一个 peek() { return this.ll.get(0) } // 队列只能从头部删除 remove() { return this.ll.remove(0) }}
var queue = new Queue()
queue.offer(1)queue.offer(2)queue.offer(3)var removeVal = queue.remove(3)
console.log(queue.ll,'queue.ll')console.log(removeVal,'queue.remove')console.log(queue.peek(),'queue.peek')
字符串最长的不重复子串
题目描述
给定一个字符串 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;};
手写深度比较 isEqual
思路:深度比较两个对象,就是要深度比较对象的每一个元素。=> 递归
递归退出条件:
被比较的是两个值类型变量,直接用“===”判断
被比较的两个变量之一为
null,直接判断另一个元素是否也为null提前结束递推:
两个变量
keys数量不同传入的两个参数是同一个变量
递推工作:深度比较每一个
key
function isEqual(obj1, obj2){ //其中一个为值类型或null if(!isObject(obj1) || !isObject(obj2)){ return obj1 === obj2; }
//判断是否两个参数是同一个变量 if(obj1 === obj2){ return true; }
//判断keys数是否相等 const obj1Keys = Object.keys(obj1); const obj2Keys = Object.keys(obj2); if(obj1Keys.length !== obj2Keys.length){ return false; }
//深度比较每一个key for(let key in obj1){ if(!isEqual(obj1[key], obj2[key])){ return false; } }
return true;}
查找字符串中出现最多的字符和个数
例: 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 isPhone(tel) { var regx = /^1[34578]\d{9}$/; return regx.test(tel);}
实现一个 add 方法完成两个大数相加
// 题目let a = "9007199254740991";let b = "1234567899999999999";
function add(a ,b){ //...}
实现代码如下:
function add(a ,b){ //取两个数字的最大长度 let maxLength = Math.max(a.length, b.length); //用0去补齐长度 a = a.padStart(maxLength , 0);//"0009007199254740991" b = b.padStart(maxLength , 0);//"1234567899999999999" //定义加法过程中需要用到的变量 let t = 0; let f = 0; //"进位" let sum = ""; for(let i=maxLength-1 ; i>=0 ; i--){ t = parseInt(a[i]) + parseInt(b[i]) + f; f = Math.floor(t/10); sum = t%10 + sum; } if(f!==0){ sum = '' + f + sum; } return sum;}
实现一个 sleep 函数,比如 sleep(1000) 意味着等待 1000 毫秒
// 使用 promise来实现 sleepconst sleep = (time) => { return new Promise(resolve => setTimeout(resolve, time))}
sleep(1000).then(() => { // 这里写你的骚操作})
实现 getValue/setValue 函数来获取 path 对应的值
// 示例var object = { a: [{ b: { c: 3 } }] }; // path: 'a[0].b.c'var array = [{ a: { b: [1] } }]; // path: '[0].a.b[0]'
function getValue(target, valuePath, defaultValue) {}
console.log(getValue(object, "a[0].b.c", 0)); // 输出3console.log(getValue(array, "[0].a.b[0]", 12)); // 输出 1console.log(getValue(array, "[0].a.b[0].c", 12)); // 输出 12
实现
/** * 测试属性是否匹配 */export function testPropTypes(value, type, dev) { const sEnums = ['number', 'string', 'boolean', 'undefined', 'function']; // NaN const oEnums = ['Null', 'Object', 'Array', 'Date', 'RegExp', 'Error']; const nEnums = [ '[object Number]', '[object String]', '[object Boolean]', '[object Undefined]', '[object Function]', '[object Null]', '[object Object]', '[object Array]', '[object Date]', '[object RegExp]', '[object Error]', ]; const reg = new RegExp('\\[object (.*?)\\]');
// 完全匹配模式,type应该传递类似格式[object Window] [object HTMLDocument] ... if (reg.test(type)) { // 排除nEnums的12种 if (~nEnums.indexOf(type)) { if (dev === true) { console.warn(value, 'The parameter type belongs to one of 12 types:number string boolean undefined Null Object Array Date RegExp function Error NaN'); } }
if (Object.prototype.toString.call(value) === type) { return true; }
return false; }}
const syncVarIterator = { getter: function (obj, key, defaultValue) { // 结果变量 const defaultResult = defaultValue === undefined ? undefined : defaultValue;
if (testPropTypes(obj, 'Object') === false && testPropTypes(obj, 'Array') === false) { return defaultResult; }
// 结果变量,暂时指向obj持有的引用,后续将可能被不断的修改 let result = obj;
// 得到知道值 try { // 解析属性层次序列 const keyArr = key.split('.');
// 迭代obj对象属性 for (let i = 0; i < keyArr.length; i++) { // 如果第 i 层属性存在对应的值则迭代该属性值 if (result[keyArr[i]] !== undefined) { result = result[keyArr[i]];
// 如果不存在则返回未定义 } else { return defaultResult; } } } catch (e) { return defaultResult; }
// 返回获取的结果 return result; }, setter: function (obj, key, val) { // 如果不存在obj则返回未定义 if (testPropTypes(obj, 'Object') === false) { return false; }
// 结果变量,暂时指向obj持有的引用,后续将可能被不断的修改 let result = obj;
try { // 解析属性层次序列 const keyArr = key.split('.');
let i = 0;
// 迭代obj对象属性 for (; i < keyArr.length - 1; i++) { // 如果第 i 层属性对应的值不存在,则定义为对象 if (result[keyArr[i]] === undefined) { result[keyArr[i]] = {}; }
// 如果第 i 层属性对应的值不是对象(Object)的一个实例,则抛出错误 if (!(result[keyArr[i]] instanceof Object)) { throw new Error('obj.' + keyArr.splice(0, i + 1).join('.') + 'is not Object'); }
// 迭代该层属性值 result = result[keyArr[i]]; }
// 设置属性值 result[keyArr[i]] = val;
return true; } catch (e) { return false; } },};
使用 promise 来实现
创建 enhancedObject 函数
const enhancedObject = (target) => new Proxy(target, { get(target, property) { if (property in target) { return target[property]; } else { return searchFor(property, target); //实际使用时要对value值进行复位 } }, });
let value = null;function searchFor(property, target) { for (const key of Object.keys(target)) { if (typeof target[key] === "object") { searchFor(property, target[key]); } else if (typeof target[property] !== "undefined") { value = target[property]; break; } } return value;}
使用 enhancedObject 函数
const data = enhancedObject({ user: { name: "test", settings: { theme: "dark", }, },});
console.log(data.user.settings.theme); // darkconsole.log(data.theme); // dark
以上代码运行后,控制台会输出以下代码:
darkdark
通过观察以上的输出结果可知,使用
enhancedObject函数处理过的对象,我们就可以方便地访问普通对象内部的深层属性。
实现一个拖拽
<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; } }}
版本号排序的方法
题目描述:有一组版本号如下 ['0.1.1', '2.3.3', '0.302.1', '4.2', '4.3.5', '4.3.4.5']。现在需要对其进行排序,排序的结果为 ['4.3.5','4.3.4.5','2.3.3','0.302.1','0.1.1']
arr.sort((a, b) => { let i = 0; const arr1 = a.split("."); const arr2 = b.split(".");
while (true) { const s1 = arr1[i]; const s2 = arr2[i]; i++; if (s1 === undefined || s2 === undefined) { return arr2.length - arr1.length; }
if (s1 === s2) continue;
return s2 - s1; }});console.log(arr);
实现千位分隔符
// 保留三位小数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 : '');}
树形结构转成列表(处理菜单)
[ { 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;}
请实现一个 add 函数,满足以下功能
add(1); // 1add(1)(2); // 3add(1)(2)(3);// 6add(1)(2, 3); // 6add(1, 2)(3); // 6add(1, 2, 3); // 6
function add(...args) { // 在内部声明一个函数,利用闭包的特性保存并收集所有的参数值 let fn = function(...newArgs) { return add.apply(null, args.concat(newArgs)) }
// 利用toString隐式转换的特性,当最后执行时隐式转换,并计算最终的值返回 fn.toString = function() { return args.reduce((total,curr)=> total + curr) }
return fn}
考点:
使用闭包, 同时要对 JavaScript 的作用域链(原型链)有深入的理解
重写函数的
toSting()方法
// 测试,调用toString方法触发求值
add(1).toString(); // 1add(1)(2).toString(); // 3add(1)(2)(3).toString();// 6add(1)(2, 3).toString(); // 6add(1, 2)(3).toString(); // 6add(1, 2, 3).toString(); // 6
字符串查找
请使用最基本的遍历来实现判断字符串 a 是否被包含在字符串 b 中,并返回第一次出现的位置(找不到返回 -1)。
a='34';b='1234567'; // 返回 2a='35';b='1234567'; // 返回 -1a='355';b='12354355'; // 返回 5isContain(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;}
还未添加个人签名 2022-07-31 加入
还未添加个人简介








评论