前端手写面试题合集
- 2022-12-07 浙江
本文字数:10962 字
阅读完需:约 36 分钟
实现 ES6 的 extends
function B(name){
this.name = name;
};
function A(name,age){
//1.将A的原型指向B
Object.setPrototypeOf(A,B);
//2.用A的实例作为this调用B,得到继承B之后的实例,这一步相当于调用super
Object.getPrototypeOf(A).call(this, name)
//3.将A原有的属性添加到新实例上
this.age = age;
//4.返回新实例对象
return this;
};
var a = new A('poetry',22);
console.log(a);
实现 apply 方法
思路: 利用
this
的上下文特性。apply
其实就是改一下参数的问题
Function.prototype.myApply = function(context = window, args) {
// 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); // 这里和call传参不一样
// 清除定义的this 不删除会导致context属性越来越多
delete context[key];
// 返回结果
return result;
}
// 使用
function f(a,b){
console.log(a,b)
console.log(this.name)
}
let obj={
name:'张三'
}
f.myApply(obj,[1,2]) //arguments[1]
实现 redux-thunk
redux-thunk
可以利用redux
中间件让redux
支持异步的action
// 如果 action 是个函数,就调用这个函数
// 如果 action 不是函数,就传给下一个中间件
// 发现 action 是函数就调用
const thunk = ({ dispatch, getState }) => (next) => (action) => {
if (typeof action === 'function') {
return action(dispatch, getState);
}
return next(action);
};
export default thunk
实现一个 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}
换下位置就可以了。
reduce 用法汇总
语法
array.reduce(function(total, currentValue, currentIndex, arr), initialValue);
/*
total: 必需。初始值, 或者计算结束后的返回值。
currentValue: 必需。当前元素。
currentIndex: 可选。当前元素的索引;
arr: 可选。当前元素所属的数组对象。
initialValue: 可选。传递给函数的初始值,相当于total的初始值。
*/
reduceRight()
该方法用法与reduce()
其实是相同的,只是遍历的顺序相反,它是从数组的最后一项开始,向前遍历到第一项
1. 数组求和
const arr = [12, 34, 23];
const sum = arr.reduce((total, num) => total + num);
// 设定初始值求和
const arr = [12, 34, 23];
const sum = arr.reduce((total, num) => total + num, 10); // 以10为初始值求和
// 对象数组求和
var result = [
{ subject: 'math', score: 88 },
{ subject: 'chinese', score: 95 },
{ subject: 'english', score: 80 }
];
const sum = result.reduce((accumulator, cur) => accumulator + cur.score, 0);
const sum = result.reduce((accumulator, cur) => accumulator + cur.score, -10); // 总分扣除10分
2. 数组最大值
const a = [23,123,342,12];
const max = a.reduce((pre,next)=>pre>cur?pre:cur,0); // 342
3. 数组转对象
var streams = [{name: '技术', id: 1}, {name: '设计', id: 2}];
var obj = streams.reduce((accumulator, cur) => {accumulator[cur.id] = cur; return accumulator;}, {});
4. 扁平一个二维数组
var arr = [[1, 2, 8], [3, 4, 9], [5, 6, 10]];
var res = arr.reduce((x, y) => x.concat(y), []);
5. 数组去重
实现的基本原理如下:
① 初始化一个空数组
② 将需要去重处理的数组中的第1项在初始化数组中查找,如果找不到(空数组中肯定找不到),就将该项添加到初始化数组中
③ 将需要去重处理的数组中的第2项在初始化数组中查找,如果找不到,就将该项继续添加到初始化数组中
④ ……
⑤ 将需要去重处理的数组中的第n项在初始化数组中查找,如果找不到,就将该项继续添加到初始化数组中
⑥ 将这个初始化数组返回
var newArr = arr.reduce(function (prev, cur) {
prev.indexOf(cur) === -1 && prev.push(cur);
return prev;
},[]);
6. 对象数组去重
const dedup = (data, getKey = () => { }) => {
const dateMap = data.reduce((pre, cur) => {
const key = getKey(cur)
if (!pre[key]) {
pre[key] = cur
}
return pre
}, {})
return Object.values(dateMap)
}
7. 求字符串中字母出现的次数
const str = 'sfhjasfjgfasjuwqrqadqeiqsajsdaiwqdaklldflas-cmxzmnha';
const res = str.split('').reduce((pre,next)=>{
pre[next] ? pre[next]++ : pre[next] = 1
return pre
},{})
// 结果
-: 1
a: 8
c: 1
d: 4
e: 1
f: 4
g: 1
h: 2
i: 2
j: 4
k: 1
l: 3
m: 2
n: 1
q: 5
r: 1
s: 6
u: 1
w: 2
x: 1
z: 1
8. compose 函数
redux compose
源码实现
function compose(...funs) {
if (funs.length === 0) {
return arg => arg;
}
if (funs.length === 1) {
return funs[0];
}
return funs.reduce((a, b) => (...arg) => a(b(...arg)))
}
实现一个 sleep 函数,比如 sleep(1000) 意味着等待 1000 毫秒
// 使用 promise来实现 sleep
const sleep = (time) => {
return new Promise(resolve => setTimeout(resolve, time))
}
sleep(1000).then(() => {
// 这里写你的骚操作
})
参考 前端进阶面试题详细解答
实现 new 的过程
new 操作符做了这些事:
创建一个全新的对象
这个对象的
__proto__
要指向构造函数的原型 prototype执行构造函数,使用
call/apply
改变 this 的指向返回值为
object
类型则作为new
方法的返回值返回,否则返回上述全新对象
function myNew(fn, ...args) {
// 基于原型链 创建一个新对象
let newObj = Object.create(fn.prototype);
// 添加属性到新对象上 并获取obj函数的结果
let res = fn.apply(newObj, args); // 改变this指向
// 如果执行结果有返回值并且是一个对象, 返回执行的结果, 否则, 返回新创建的对象
return typeof res === 'object' ? res: newObj;
}
// 用法
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.say = function() {
console.log(this.age);
};
let p1 = myNew(Person, "poety", 18);
console.log(p1.name);
console.log(p1);
p1.say();
二分查找
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("目标元素不在数组中");
// }
实现 findIndex 方法
var users = [
{id: 1, name: '张三'},
{id: 2, name: '张三'},
{id: 3, name: '张三'},
{id: 4, name: '张三'}
]
Array.prototype.myFindIndex = function (callback) {
// var callback = function (item, index) { return item.id === 4 }
for (var i = 0; i < this.length; i++) {
if (callback(this[i], i)) {
// 这里返回
return i
}
}
}
var ret = users.myFind(function (item, index) {
return item.id === 2
})
console.log(ret)
实现 JSONP 方法
利用
<script>
标签不受跨域限制的特点,缺点是只能支持get
请求
创建
script
标签设置
script
标签的src
属性,以问号传递参数,设置好回调函数callback
名称插入到
html
文本中调用回调函数,
res
参数就是获取的数据
function jsonp({url,params,callback}) {
return new Promise((resolve,reject)=>{
let script = document.createElement('script')
window[callback] = function (data) {
resolve(data)
document.body.removeChild(script)
}
var arr = []
for(var key in params) {
arr.push(`${key}=${params[key]}`)
}
script.type = 'text/javascript'
script.src = `${url}?callback=${callback}&${arr.join('&')}`
document.body.appendChild(script)
})
}
// 测试用例
jsonp({
url: 'http://suggest.taobao.com/sug',
callback: 'getData',
params: {
q: 'iphone手机',
code: 'utf-8'
},
}).then(data=>{console.log(data)})
设置
CORS: Access-Control-Allow-Origin:*
postMessage
解析 URL Params 为对象
let url = 'http://www.domain.com/?user=anonymous&id=123&id=456&city=%E5%8C%97%E4%BA%AC&enabled';
parseParam(url)
/* 结果
{ user: 'anonymous',
id: [ 123, 456 ], // 重复出现的 key 要组装成数组,能被转成数字的就转成数字类型
city: '北京', // 中文需解码
enabled: true, // 未指定值得 key 约定为 true
}
*/
function parseParam(url) {
const paramsStr = /.+\?(.+)$/.exec(url)[1]; // 将 ? 后面的字符串取出来
const paramsArr = paramsStr.split('&'); // 将字符串以 & 分割后存到数组中
let paramsObj = {};
// 将 params 存到对象中
paramsArr.forEach(param => {
if (/=/.test(param)) { // 处理有 value 的参数
let [key, val] = param.split('='); // 分割 key 和 value
val = decodeURIComponent(val); // 解码
val = /^\d+$/.test(val) ? parseFloat(val) : val; // 判断是否转为数字
if (paramsObj.hasOwnProperty(key)) { // 如果对象有 key,则添加一个值
paramsObj[key] = [].concat(paramsObj[key], val);
} else { // 如果对象没有这个 key,创建 key 并设置值
paramsObj[key] = val;
}
} else { // 处理没有 value 的参数
paramsObj[param] = true;
}
})
return paramsObj;
}
实现一个链表结构
链表结构
看图理解 next 层级
// 链表 从头尾删除、增加 性能比较好
// 分为很多类 常用单向链表、双向链表
// js模拟链表结构:增删改查
// node节点
class Node {
constructor(element,next) {
this.element = element
this.next = next
}
}
class LinkedList {
constructor() {
this.head = null // 默认应该指向第一个节点
this.size = 0 // 通过这个长度可以遍历这个链表
}
// 增加O(n)
add(index,element) {
if(arguments.length === 1) {
// 向末尾添加
element = index // 当前元素等于传递的第一项
index = this.size // 索引指向最后一个元素
}
if(index < 0 || index > this.size) {
throw new Error('添加的索引不正常')
}
if(index === 0) {
// 直接找到头部 把头部改掉 性能更好
let head = this.head
this.head = new Node(element,head)
} else {
// 获取当前头指针
let current = this.head
// 不停遍历 直到找到最后一项 添加的索引是1就找到第0个的next赋值
for (let i = 0; i < index-1; i++) { // 找到它的前一个
current = current.next
}
// 让创建的元素指向上一个元素的下一个
// 看图理解next层级
current.next = new Node(element,current.next) // 让当前元素指向下一个元素的next
}
this.size++;
}
// 删除O(n)
remove(index) {
if(index < 0 || index >= this.size) {
throw new Error('删除的索引不正常')
}
this.size--
if(index === 0) {
let head = this.head
this.head = this.head.next // 移动指针位置
return head // 返回删除的元素
}else {
let current = this.head
for (let i = 0; i < index-1; i++) { // index-1找到它的前一个
current = current.next
}
let returnVal = current.next // 返回删除的元素
// 找到待删除的指针的上一个 current.next.next
// 如删除200, 100=>200=>300 找到200的上一个100的next的next为300,把300赋值给100的next即可
current.next = current.next.next
return returnVal
}
}
// 查找O(n)
get(index) {
if(index < 0 || index >= this.size) {
throw new Error('查找的索引不正常')
}
let current = this.head
for (let i = 0; i < index; i++) {
current = current.next
}
return current
}
}
var ll = new LinkedList()
ll.add(0,100) // Node { ellement: 100, next: null }
ll.add(0,200) // Node { element: 200, next: Node { element: 100, next: null } }
ll.add(1,500) // Node {element: 200,next: Node { element: 100, next: Node { element: 500, next: null } } }
ll.add(300)
ll.remove(0)
console.log(ll.get(2),'get')
console.log(ll.head)
module.exports = LinkedList
实现 map 方法
回调函数的参数有哪些,返回值如何处理
不修改原来的数组
Array.prototype.myMap = function(callback, context){
// 转换类数组
var arr = Array.prototype.slice.call(this),//由于是ES5所以就不用...展开符了
mappedArr = [],
i = 0;
for (; i < arr.length; i++ ){
// 把当前值、索引、当前数组返回去。调用的时候传到函数参数中 [1,2,3,4].map((curr,index,arr))
mappedArr.push(callback.call(context, arr[i], i, this));
}
return mappedArr;
}
查找数组公共前缀(美团)
题目描述
编写一个函数来查找字符串数组中的最长公共前缀。
如果不存在公共前缀,返回空字符串 ""。
示例 1:
输入:strs = ["flower","flow","flight"]
输出:"fl"
示例 2:
输入:strs = ["dog","racecar","car"]
输出:""
解释:输入不存在公共前缀。
答案
const longestCommonPrefix = function (strs) {
const str = strs[0];
let index = 0;
while (index < str.length) {
const strCur = str.slice(0, index + 1);
for (let i = 0; i < strs.length; i++) {
if (!strs[i] || !strs[i].startsWith(strCur)) {
return str.slice(0, index);
}
}
index++;
}
return str;
};
实现有并行限制的 Promise 调度器
题目描述:JS 实现一个带并发限制的异步调度器 Scheduler
,保证同时运行的任务最多有两个
addTask(1000,"1");
addTask(500,"2");
addTask(300,"3");
addTask(400,"4");
的输出顺序是:2 3 1 4
整个的完整执行流程:
一开始1、2两个任务开始执行
500ms时,2任务执行完毕,输出2,任务3开始执行
800ms时,3任务执行完毕,输出3,任务4开始执行
1000ms时,1任务执行完毕,输出1,此时只剩下4任务在执行
1200ms时,4任务执行完毕,输出4
实现代码如下:
class Scheduler {
constructor(limit) {
this.queue = [];
this.maxCount = limit;
this.runCounts = 0;
}
add(time, order) {
const promiseCreator = () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log(order);
resolve();
}, time);
});
};
this.queue.push(promiseCreator);
}
taskStart() {
for (let i = 0; i < this.maxCount; i++) {
this.request();
}
}
request() {
if (!this.queue || !this.queue.length || this.runCounts >= this.maxCount) {
return;
}
this.runCounts++;
this.queue
.shift()()
.then(() => {
this.runCounts--;
this.request();
});
}
}
const scheduler = new Scheduler(2);
const addTask = (time, order) => {
scheduler.add(time, order);
};
addTask(1000, "1");
addTask(500, "2");
addTask(300, "3");
addTask(400, "4");
scheduler.taskStart();
实现 redux 中间件
简单实现
function createStore(reducer) {
let currentState
let listeners = []
function getState() {
return currentState
}
function dispatch(action) {
currentState = reducer(currentState, action)
listeners.map(listener => {
listener()
})
return action
}
function subscribe(cb) {
listeners.push(cb)
return () => {}
}
dispatch({type: 'ZZZZZZZZZZ'})
return {
getState,
dispatch,
subscribe
}
}
// 应用实例如下:
function reducer(state = 0, action) {
switch (action.type) {
case 'ADD':
return state + 1
case 'MINUS':
return state - 1
default:
return state
}
}
const store = createStore(reducer)
console.log(store);
store.subscribe(() => {
console.log('change');
})
console.log(store.getState());
console.log(store.dispatch({type: 'ADD'}));
console.log(store.getState());
2. 迷你版
export const createStore = (reducer,enhancer)=>{
if(enhancer) {
return enhancer(createStore)(reducer)
}
let currentState = {}
let currentListeners = []
const getState = ()=>currentState
const subscribe = (listener)=>{
currentListeners.push(listener)
}
const dispatch = action=>{
currentState = reducer(currentState, action)
currentListeners.forEach(v=>v())
return action
}
dispatch({type:'@@INIT'})
return {getState,subscribe,dispatch}
}
//中间件实现
export applyMiddleWare(...middlewares){
return createStore=>...args=>{
const store = createStore(...args)
let dispatch = store.dispatch
const midApi = {
getState:store.getState,
dispatch:...args=>dispatch(...args)
}
const middlewaresChain = middlewares.map(middleware=>middleware(midApi))
dispatch = compose(...middlewaresChain)(store.dispatch)
return {
...store,
dispatch
}
}
// fn1(fn2(fn3())) 把函数嵌套依次调用
export function compose(...funcs){
if(funcs.length===0){
return arg=>arg
}
if(funs.length===1){
return funs[0]
}
return funcs.reduce((ret,item)=>(...args)=>ret(item(...args)))
}
//bindActionCreator实现
function bindActionCreator(creator,dispatch){
return ...args=>dispatch(creator(...args))
}
function bindActionCreators(creators,didpatch){
//let bound = {}
//Object.keys(creators).forEach(v=>{
// let creator = creator[v]
// bound[v] = bindActionCreator(creator,dispatch)
//})
//return bound
return Object.keys(creators).reduce((ret,item)=>{
ret[item] = bindActionCreator(creators[item],dispatch)
return ret
},{})
}
怎么在制定数据源里面生成一个长度为 n 的不重复随机数组 能有几种方法 时间复杂度多少(字节)
第一版 时间复杂度为 O(n^2)
function getTenNum(testArray, n) {
let result = [];
for (let i = 0; i < n; ++i) {
const random = Math.floor(Math.random() * testArray.length);
const cur = testArray[random];
if (result.includes(cur)) {
i--;
break;
}
result.push(cur);
}
return result;
}
const testArray = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14];
const resArr = getTenNum(testArray, 10);
第二版 标记法 / 自定义属性法 时间复杂度为 O(n)
function getTenNum(testArray, n) {
let hash = {};
let result = [];
let ranNum = n;
while (ranNum > 0) {
const ran = Math.floor(Math.random() * testArray.length);
if (!hash[ran]) {
hash[ran] = true;
result.push(ran);
ranNum--;
}
}
return result;
}
const testArray = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14];
const resArr = getTenNum(testArray, 10);
第三版 交换法 时间复杂度为 O(n)
function getTenNum(testArray, n) {
const cloneArr = [...testArray];
let result = [];
for (let i = 0; i < n; i++) {
debugger;
const ran = Math.floor(Math.random() * (cloneArr.length - i));
result.push(cloneArr[ran]);
cloneArr[ran] = cloneArr[cloneArr.length - i - 1];
}
return result;
}
const testArray = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14];
const resArr = getTenNum(testArray, 14);
值得一提的是操作数组的时候使用交换法 这种思路在算法里面很常见
最终版 边遍历边删除 时间复杂度为 O(n)
function getTenNum(testArray, n) {
const cloneArr = [...testArray];
let result = [];
for (let i = 0; i < n; ++i) {
const random = Math.floor(Math.random() * cloneArr.length);
const cur = cloneArr[random];
result.push(cur);
cloneArr.splice(random, 1);
}
return result;
}
const testArray = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14];
const resArr = getTenNum(testArray, 14);
实现 Object.create
Object.create()
方法创建一个新对象,使用现有的对象来提供新创建的对象的__proto__
// 模拟 Object.create
function create(proto) {
function F() {}
F.prototype = proto;
return new F();
}
手写深度比较 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;
}
实现 Vue reactive 响应式
// Dep module
class Dep {
static stack = []
static target = null
deps = null
constructor() {
this.deps = new Set()
}
depend() {
if (Dep.target) {
this.deps.add(Dep.target)
}
}
notify() {
this.deps.forEach(w => w.update())
}
static pushTarget(t) {
if (this.target) {
this.stack.push(this.target)
}
this.target = t
}
static popTarget() {
this.target = this.stack.pop()
}
}
// reactive
function reactive(o) {
if (o && typeof o === 'object') {
Object.keys(o).forEach(k => {
defineReactive(o, k, o[k])
})
}
return o
}
function defineReactive(obj, k, val) {
let dep = new Dep()
Object.defineProperty(obj, k, {
get() {
dep.depend()
return val
},
set(newVal) {
val = newVal
dep.notify()
}
})
if (val && typeof val === 'object') {
reactive(val)
}
}
// watcher
class Watcher {
constructor(effect) {
this.effect = effect
this.update()
}
update() {
Dep.pushTarget(this)
this.value = this.effect()
Dep.popTarget()
return this.value
}
}
// 测试代码
const data = reactive({
msg: 'aaa'
})
new Watcher(() => {
console.log('===> effect', data.msg);
})
setTimeout(() => {
data.msg = 'hello'
}, 1000)
还未添加个人签名 2022-07-31 加入
还未添加个人简介
评论