本人目前大三,4月末进行了人生第一场面试。不尽人意。总觉得自己什么都了解,但是又觉得什么都不会。前几天看见涡流老哥的面试文章总结。来进行一些总结吧。
涡流老哥原博文。前端两年经验,历时一个月的面经和总结
有些的不对的地方,请指正,一起总结。
手写深拷贝
function deepClone(obj) {
let newObj = {};
if(typeof obj !== "object" || obj == null) {
return obj;
}
for(let key in obj) {
if(typeof obj[key] !== "object") { // 基本类型加function
newObj[key] = obj[key];
}else {
if(isType(obj[key]) == "Array") { // 判断数组
let newArray = [];
for(let index in obj[key]) { // 递归调用,然后需要返回当前非对象的值,然后加入
newArray[index] = deepClone(obj[key][index])
}
newObj[key] = newArray;
}else { // 对象类型
let newObject = {};
for(let newKey in obj[key]) {
newObject[newKey] = deepClone(obj[newKey]);
}
}
}
}
return newObj;
}
function isType(value) {
const type = Object.prototype.toString.call(value);
switch (type) {
case "[object Array]":
return "Array"
case "[object Object]":
return "Object"
default:
return typeof value;
}
}
手写节流和防抖
手写call / apply / bind
简单的实现
- 思路就是获取当前调用的函数,然后将其作为当前指定的this属性值。为了避免覆盖当前this对象的已有属性,那么将设置symbol值作为属性名。其他的都是一些边界判断。
const myCall = function (context, ...args) {
let func = this
context = context || window
if (typeof func !== 'function') throw new TypeError('this is not a function')
let caller = Symbol('caller')
context[caller] = func
let res = context[caller](...args)
delete context[caller]
return res
}
Function.prototype.myCall = myCall
function myApply(context, arr) {
let func = this;
context = context || window;
if (typeof func !== 'function') throw new TypeError('this is not a function')
//唯一的键值
let caller = Symbol('caller')
context[caller] = func
//函数返回值
let res;
if (!arr) {
res = context[caller]()
} else {
res = context[caller](...arr)
}
//删除该函数
delete context[caller]
return res
}
Function.prototype.myApply = myApply;
function myBind(context, ...args) {
const fn = this;
if(typeof fn !== "function") throw new TypeError("this is not function");
context = context || window;
// 因为bind需要返回一个函数,然后利用call、apply改变this即可。并传入参数。
return function(...args2){
fn.call(context, ...args,...args2)
};
}
Function.prototype.myBind = myBind
手写Promise.all / Promise.race / Promise.allSettled
实现它,我们必须的知道他们的作用是啥?
- all:如果传入的promises全部都变成fulfilled,那么promise的状态将变为fulfilled。如果有一个promises状态变为rejected,那么promsie状态将变为rejected。
function all(promises) {
return new Promise((resolve, reject) => {
let resArr = [];
for(let promise of promises) {
promise.then(res => {
resArr.push(res);
if(resArr.length === promises.length) { // 每次成功都需要判断
resolve(resArr)
}
})
promise.catch((err) => {
reject(err)
})
}
})
}
- race: 传入的promises最先改变状态的promise决定当前的promise状态。
function race(promises) {
return new Promise((resolve, reject) => {
for(let promise of promises) {
promise.then(res => {
resolve(res)
})
promise.catch((err) => {
reject(err)
})
}
})
}
- allSettled: 当传入的promises的状态全部都变化后,promise的状态变为fulfilled。
function allSettled(promises) {
return new Promise((resolve) => {
let resArr = []
for(let promise of promises) {
promise.then(res => {
resArr.push({
status: "fulfilled",
value: res
})
if(resArr.length === promises.length) {
resolve(resArr)
}
})
promise.catch((err) => {
resArr.push({
status: "rejected",
value: err
})
if(resArr.length === promises.length) { // 可能promise有异步任务,所以每一次状态改变都需要判断
resolve(resArr)
}
})
}
})
}
手写括号匹配
不知道是不是LeetCode-20。如果是的话,这一题使用栈的思想最好理解。
数组去重
- 通过set特点实现。
// set数据结构
Array.from(new Set(arr))
[...new Set(arr)]
- 通过对象键名不能重复实现。
// 利用对象的键不能重复的特点
function removeDup(arr) {
const obj = {}
arr.forEach((item) => {
if(!obj[item]) {
obj[item] = true
}
})
return Object.keys(obj)
}
将奇数排在前面,偶数排在后面。要求时间复杂度O(n)。空间复杂度O(1)(不能用splice)
不知道这一题是不是这个意思。
function oddEven(arr) {
// 先排序,这个排序算法可以自己去查询时间复杂度为o(n)的算法。
arr = arr.sort((a, b) => {
return a - b;
})
const odd = []; // 奇数
const even = []; // 偶数
for(let item of arr) {
if(item % 2 === 0) { // 偶数
even.push(item)
}else { // 奇数
odd.push(item)
}
}
return [...odd, ...even]
}
解析出URL中所有的部分
通过URL类解析出来的是这个样子,我们只需要处理一下searchParams和protocol即可
function parseUrl(url) {
const resUrlObj = new URL(url);
// console.log(resUrlObj)
const params = {}
const resObj = {};
// 处理查询参数
for(let [key, value] of resUrlObj.searchParams) {
params[key] = value
}
for(let key in resUrlObj) {
if(key == "protocol") { // 处理协议
resObj[key] = resUrlObj[key].slice(0, (resUrlObj[key].length - 1))
}else {
resObj[key] = resUrlObj[key]
}
}
return {...resObj, searchParams: params}
}
实现一个compare函数,比较两个对象是否相同
这里的比较应该不是值广义的地址,而是指对象的属性和值。
function compare(obj1, obj2) {
const obj1Keys = Object.keys(obj1);
const obj2Keys = Object.keys(obj2);
if(obj1Keys.length !== obj2Keys.length) return false;
for(let key of obj1Keys) {
if(!obj2Keys.includes(key)) {
return false;
}else {
if(typeof obj1[key] === "object" || typeof obj2[key] === "object") { // 递归调用,
// 注意:这里也可以判断数组,Object.keys([1,2,3]) => ["0", "1", "2"]
if(!compare(obj1[key], obj2[key])) return false;
} else {// 基本类型
if(obj1[key] !== obj2[key]) return false;
}
}
}
return true;
}
柯里化
是把接收多个参数的函数,变成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数,而且返回结果的新函数的技术。
实现思路
- 我们知道函数的length属性是判断形参的个数。
- 判断不断调用的函数传入的中参数是否大于等于函数的形参。
- 如果大于等于,就直接调用该函数,并传入参数
- 如果小于,那就再返回一个函数,并且内部返回递归调用的函数。
function curry(fn, ...args1) {
// 获取形参的个数
const length = fn.length;
const resFn = function(...args2) {
const totalArgs = [...args1, ...args2];
if(totalArgs.length >= length) {
// return resFn;
fn.call(this, ...totalArgs)
}else { // 参数还没有传完
return function(...args3) {
return resFn.call(this, ...args2, ...args3)
}
}
}
return resFn
}
中划线转大写
function lineChangeUpperCase(str) {
// 先依据_分割字符串
const strArr = str.split("_");
// 利用map映射将每个短横线后面的字符转大写
return strArr.map((item, index) => {
if(index === 0) {
return item;
}else {
return item.slice(0, 1).toUpperCase() + item.slice(1)
}
}).join("")
}
千位分割
使用es5实现es6的let关键字
利用自执行函数
// 下面代码不存在块级作用域
for(var i = 0; i < 5; i++) {
setTimeout(() => {
console.log(i); // 5 5 5 5 5
}, 0)
}
// let解决的问题
for(let i = 0; i < 5; i++) {
setTimeout(() => {
console.log(i); // 0 1 2 3 4
}, 0)
}
// 立即执行函数代替let
for(var i = 0; i < 5; i++){
(function(a){
setTimeout(() => {
console.log(a); // 0 1 2 3 4
}, 0)
})(i)
}
查找数组中的重复元素
- 双重for循环
function getRepeatElement(arr) {
let resArr = []
for(let i = 0; i < arr.length; i++) {
// for(let i in arr) { // 为什么for in 不正确
for( let j = i + 1; j < arr.length; j++) {
if(arr[i] == arr[j]) {
resArr.push(arr[i])
}
continue;
}
}
return resArr
}
- 数组中判断数组中方法(includes, indexOf)实现。
// indexOf / includes实现
function getRepeatElement1(arr) {
let resArr = []
for(let i = 0; i < arr.length; i++) {
let copyArr = [...arr]
let item = arr[i];
copyArr.splice(i, 1);
if(copyArr.includes(item)) {
// if(copyArr.indexOf(item) !== -1) {
resArr.push(item)
}
}
return resArr //[5, 3, 4, 4]
}
- 先进行排序,然后再通过比较相邻元素的方式。
// 先排序,然后再比较相邻元素。
function getRepeatElement3(arr) {
let resArr = []
copyArr = arr.sort();
for(let i = 0; i < arr.length - 1; i++) {
if(arr[i] === arr[i + 1]) {
resArr.push(arr[i]);
}
}
return resArr
}
数组乱序
- 通过sort结合Math.random()。
// 该方法有缺陷,大多数元素的位置是不变的
function mixArr(arr) {
return arr.sort(() => Math.random() - 0.5)
}
- 洗牌算法
// 洗牌算法
function shunflee(arr) {
const len = arr.length
while(len > 1) {
const index = parsetInt(Math.random() * len--)
[arr[index], arr[len]] = [arr[len], arr[index]]
}
return arr
}
手写快排
手写限制并发数量
手写红包算法(注意均衡分配和浮点数计算精度问题)
数组转树结构
螺旋矩阵
大数相加
找出出现次数最多的英语单词
节点倒序(将ul.id=list,将ul节点下的10000个li节点倒序。考虑性能。)
实现一个函数计算 "1+12-31+100-93"
判断链表是否有环
手写useReducer
手写useDidMount
手写useDidUpdate,模拟componentDidUpdate
手写usePrevious
爬楼梯
删除单向链表中的某个节点
就我自己而言,算法是我最大的障碍,上个月面字节,两道算法题都写得不是很好。你们有什么好的建议嘛?可以在留言区交流一下。感觉自己的思维逻辑很low,哎。