校招前端二面常考手写面试题汇总
- 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方法
// ![](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()))
二分查找
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来实现 sleep
const 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)); // 输出3
console.log(getValue(array, "[0].a.b[0]", 12)); // 输出 1
console.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); // dark
console.log(data.theme); // dark
以上代码运行后,控制台会输出以下代码:
dark
dark
通过观察以上的输出结果可知,使用
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); // 1
add(1)(2); // 3
add(1)(2)(3);// 6
add(1)(2, 3); // 6
add(1, 2)(3); // 6
add(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(); // 1
add(1)(2).toString(); // 3
add(1)(2)(3).toString();// 6
add(1)(2, 3).toString(); // 6
add(1, 2)(3).toString(); // 6
add(1, 2, 3).toString(); // 6
字符串查找
请使用最基本的遍历来实现判断字符串 a 是否被包含在字符串 b 中,并返回第一次出现的位置(找不到返回 -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;
}
还未添加个人签名 2022-07-31 加入
还未添加个人简介
评论