2024年Web前端面试题(最全、最详细、持续更新)_web前端面试问题

总结

面试前要精心做好准备,简历上写的知识点和原理都需要准备好,项目上多想想难点和亮点,这是面试时能和别人不一样的地方。

还有就是表现出自己的谦虚好学,以及对于未来持续进阶的规划,企业招人更偏爱稳定的人。

万事开头难,但是程序员这一条路坚持几年后发展空间还是非常大的,一切重在坚持。

开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】

前端面试题汇总

JavaScript

前端资料汇总

function deepClone(source) {
if (source instanceof Object === false) return source;
let target = Array.isArray(source) ? [] : {};
for (let i in source) {
if (source.hasOwnProperty(i)) {
if (typeof source[i] === 'object') {
target[i] = deepClone(source[i]);
} else {
target[i] = source[i];
}
}
}
return target;
}

手写延时器
function delay(time) {
	return new Promise((res) => {
	setTimeout(() => {
	res()
	}, time)
	})
}

url截取参数转换为对象
let strUrl = 'www.baidu.com?a=1&b=2&c=3&d=4';
function getUrlParams(url) {
let obj = {};
let paramsAry = url.substring(url.indexOf('?') + 1).split('&')
paramsAry.forEach(item => {
let itm = item.split('=')
obj[itm[0]] = itm[1]
});
return obj;
}
getUrlParams(strUrl)

数组扁平化(拉平)
let ary = [1, 2, 3, [3, 4, [5, 6]], 5, [3, 4]];
function flattenArray(ary) {
  // 1
  // return ary.join(",").split(",").map(v => parseFloat(v))
  // 2
  // return ary.flat(3)
  // 3
  // return ary.flat(Infinity)
  // 4
  // return ary.reduce((pre,cur)=>{
  // return pre.concat(Array.isArray(cur)?fn(cur):cur)
  // },[])
  // 5
  // while (ary.some(Array.isArray)) {
  // ary = [].concat(...ary);
  // }
  // return ary;
  // 6
  // return JSON.parse("["+JSON.stringify(ary).replace(/(\[|\])/g, "")+"]")
  // 7
  // return JSON.stringify(ary).replace(/(\[|\])/g, "").split(",").map(v => parseFloat(v))
  //8
  let result = [];
  for (let item of arr) {
    if (Array.isArray(item)) {
      result = result.concat(flattenArray(item));
    } else {
      result.push(item);
    }
  }
  return result;
}
console.log(flattenArray(ary));

 // function flattenArray(arr, result = []) {
 // for (let i = 0; i < arr.length; i++) {
 // const item = arr[i];
 // if (Array.isArray(item)) {
 // flattenArray(item, result); // 递归处理子数组
 // } else {
 // result[result.length] = item; // 手动添加非数组元素到结果数组
 // }
 // }
 // return result;
 // }

new操作符
function create(Con, ...args) {
let obj = {}
Object.setPrototypeOf(obj, Con.prototype)
let result = Con.apply(obj, args)
return result instanceof Object ? result : obj
}

map实现
Array.prototype.mapMap = function (fn, thisArg) {
console.log(fn, thisArg);
if (typeof fn !== 'function') {
throw new Error(${fn} is not a function)
}
return this.reduce((pre, cur, index, ary) => {
return pre.concat(fn.call(thisArg, cur, index, ary))
}, [])
}

forEach实现
Array.prototype.myForEach = function(callback) {
if (typeof callback !== 'function') {
throw new Error(${callback} is not a function)
}
for (let i = 0; i < this.length; i++) {
callback(this[i], i, this);
}
};

// 示例用法
const arr = [1, 2, 3];
arr.myForEach((item, index, array) => {
console.log(第${index}个元素是${item},数组是${array});
});

找出2个数组中不重复出现的数字
  • 输入 array1 = [1,2,3,4,5] array2 = [2,3,4,6], 输出 [1,5,6]
数组对象去重
function uniquedObj(params) {
let map = new Map();
let res = []
params.forEach(item => {
if (!map.has(item.id)) {
res.push(item)
map.set(item.id, item.id)
}
})
return res;
}
let a1 = [
{ id: 1, name: '12' },
{ id: 12, name: '122' },
{ id: 13, name: '133' },
{ id: 1, name: '12' },
{ id: 12, name: '122' },
]
console.log(uniquedObj(a1));

合并2个有序链表
/\*\*

● @param {ListNode} l1 
● @param {ListNode} l2 
● @return {ListNode} 
\*/
const mergeTwoLists = function(l1, l2) {
// 定义头结点,确保链表可以被访问到
let head = new ListNode()
// cur 这里就是咱们那根“针”
let cur = head
// “针”开始在 l1 和 l2 间穿梭了
while(l1 && l2) {
// 如果 l1 的结点值较小
if(l1.val<=l2.val) {
// 先串起 l1 的结点
cur.next = l1
// l1 指针向前一步
l1 = l1.next
} else {
// l2 较小时,串起 l2 结点
cur.next = l2
// l2 向前一步
l2 = l2.next
}
// “针”在串起一个结点后,也会往前一步
cur = cur.next 

}

// 处理链表不等长的情况
cur.next = l1!==null?l1:l2
// 返回起始结点
return head.next
};

删除重复元素
/\*\*

● @param {ListNode} head 
● @return {ListNode} 
\*/
const deleteDuplicates = function(head) {
// 设定 cur 指针,初始位置为链表第一个结点
let cur = head;
// 遍历链表
while(cur != null && cur.next != null) {
// 若当前结点和它后面一个结点值相等(重复)
if(cur.val === cur.next.val) {
// 删除靠后的那个结点(去重)
cur.next = cur.next.next;
} else {
// 若不重复,继续遍历
cur = cur.next;
}
}
return head;
};

删除链表的倒数第 N 个结点
/\*\*

● @param {ListNode} head 
● @param {number} n 
● @return {ListNode} 
\*/
const removeNthFromEnd = function(head, n) {
// 初始化 dummy 结点
const dummy = new ListNode()
// dummy指向头结点
dummy.next = head
// 初始化快慢指针,均指向dummy
let fast = dummy
let slow = dummy
// 快指针闷头走 n 步
while(n!==0){
fast = fast.next
n--
}
// 快慢指针一起走
while(fast.next){
fast = fast.next
slow = slow.next
}
// 慢指针删除自己的后继结点
slow.next = slow.next.next
// 返回头结点
return dummy.next
}; 

手写翻转链表
/\*\*

● @param {ListNode} head 
● @return {ListNode} 
\*/
const reverseList = function(head) {
// 初始化前驱结点为 null
let pre = null;
// 初始化目标结点为头结点
let cur = head;
// 只要目标结点不为 null,遍历就得继续
while (cur !== null) {
// 记录一下 next 结点
let next = cur.next;
// 反转指针
cur.next = pre;
// pre 往前走一步
pre = cur;
// cur往前走一步
cur = next;
}
// 反转结束后,pre 就会变成新链表的头结点
return pre
};

leetcode 88 合并2个有序数组 将num2合入nums1

例: nums1 = [0], m = 0, nums2 = [1], n = 1 输出[1]

var merge = function (nums1, m, nums2, n) {
let len = m + n;
while (n > 0) {
if (m <= 0) {
nums1[--len] = nums2[--n]
continue
}
nums1[--len] = nums1[m - 1] >= nums2[n - 1] ? nums1[--m] : nums2[--n]
}
};

leetcode 70 爬楼梯
const climbStairs = (n) => {
let prev = 1;
let cur = 1;
for (let i = 2; i < n + 1; i++) {
const temp = cur;   // 暂存上一次的cur
cur = prev + cur;   // 当前的cur = 上上次cur + 上一次cur
prev = temp;        // prev 更新为 上一次的cur
}
return cur;
}
console.log(climbStairs(3));

手写题

对象结构转树结构
const arr = [
  { id: 12, parentId: 1, name: "朝阳区" },
  { id: 241, parentId: 24, name: "田林街道" },
  { id: 31, parentId: 3, name: "广州市" },
  { id: 13, parentId: 1, name: "昌平区" },
  { id: 2421, parentId: 242, name: "上海科技绿洲" },
  { id: 21, parentId: 2, name: "静安区" },
  { id: 242, parentId: 24, name: "漕河泾街道" },
  { id: 22, parentId: 2, name: "黄浦区" },
  { id: 11, parentId: 1, name: "顺义区" },
  { id: 2, parentId: 0, name: "上海市" },
  { id: 24, parentId: 2, name: "徐汇区" },
  { id: 1, parentId: 0, name: "北京市" },
  { id: 2422, parentId: 242, name: "漕河泾开发区" },
  { id: 32, parentId: 3, name: "深圳市" },
  { id: 33, parentId: 3, name: "东莞市" },
  { id: 3, parentId: 0, name: "广东省" },
];
function getTree(arr) {
  const newArr = arr.sort((a, b) => b.parentId - a.parentId);
  for (let i = 0; i < newArr.length; i++) {
    let item = newArr[i];
    if (item.parentId) {
      newArr.forEach((arrItem) => {
        if (arrItem.id === item.parentId) {
          if (arrItem.children) {
            arrItem.children.push(item);
          } else {
            arrItem.children = [item];
          }
        }
      });
    }
  }
  return newArr.filter((item) => !item.parentId).sort((a, b) => a.id - b.id);
}
const tree = getTree(arr);
console.log("tree: ", JSON.stringify(tree, null, 2));

树结构转对象结构
const obj = {
  id: 0,
  value: "test\_0",
  children: [
    {
      id: 1,
      value: "test\_1",
    },
    {
      id: 2,
      value: "test\_2",
    },
    {
      id: 3,
      value: "test\_3",
      children: [
        {
          id: 31,
          value: "test\_31",
        },
        {
          id: 32,
          value: "test\_32",
        },
        {
          id: 33,
          value: "test\_33",
          children: [
            {
              id: 331,
              value: "test\_331",
            },
            {
              id: 332,
              value: "test\_332",
            },
          ],
        },
      ],
    },
  ],
};

const arr = [];
function changeObj(obj) {
  arr.push({ id: obj.id, value: obj.value });
  if (obj.children) {
    for (let i = 0; i < obj.children.length; i++) {
      changeObj(obj.children[i]);
    }
  }
}
changeObj(obj);
console.log(arr);
// [
// { id: 0, value: 'test\_0' },
// { id: 1, value: 'test\_1' },
// { id: 2, value: 'test\_2' },
// { id: 3, value: 'test\_3' },
// { id: 31, value: 'test\_31' },
// { id: 32, value: 'test\_32' },
// { id: 33, value: 'test\_33' },
// { id: 331, value: 'test\_331' },
// { id: 332, value: 'test\_332' }
// ]

字符串转数组
// 输入
const str = `
1 21 3

 4 5 6 
 7 8 9
 `;
// 输出
// arr=[
// ['1','21','3'],
// ['4','5','6'],
// ['7','8','9']
// ]
let newStr = str.replace(/\s|\n/g, function (a, b, c, d) {
  if (a === "\n") {
    return "n";
  } else {
    return "s";
  }
});

let arr = newStr
  .split("n")
  .map((item) => item.split("s").filter((item) => item))
  .filter((item) => item.length);
console.log("arr: ", arr);

源码

封装jequry中的AJAX的应用
        function ajax(options){
            // 准备一个默认的对象
            let default_op={
                type:"get",
                async:true,
                cache:true,
                success:null,
                data:null
            }
            // 循环options,给default中属性名重新赋值;
            for(let key in options){
                default_op[key]=options[key];
            }
            if(default_op.type.toLowerCase()==="get"){
                // 为了解决传参;get请求需要将data的值拼到url的后面;
                let str=`?`;
                for(let key in default_op.data){
                    str+=`${key}=${default\_op.data[key]}&`
                }
                str=str.slice(0,str.length-1);
                default_op.url+=str;
                if(!default_op.cache){
                    // 如果不走缓存,在后面添加时间戳;
                    default_op.url+= `&time=${Date.now()}`;
                }
            }
            let xhr = new XMLHttpRequest;
            // 取到default\_op中的值;给open方法传入参数;
            xhr.open(default_op.type,default_op.url,default_op.async);
            xhr.onreadystatechange=function(){
                if(xhr.readyState===4&&/^2\d{2}/.test(xhr.status)){
                    // 把请求回来的数据转成JSON格式的对象,传给success的回调;
                    let val = JSON.parse(xhr.responseText);
                    default_op.success(val);
                }else if(xhr.readyState===4){
                    // 如果请求不成功,执行失败的回调;
                    default_op.error();
                }
            }
            // 发送请求;
            if(default_op.type==="get"){
                default_op.data=null;
            }
            xhr.send(default_op.data);
        }
        ajax({
            url:"data.txt",
            type:"get",
            data:{username:"a",password:"b"},
            cache:false,
            success:function(data){
                console.log(data);
            }
        })

JQ源码简单理解
    

jquery核心源码
    

Vuex的源码

Vue的插件必须使用Vue.use;只是vuex会默认检测到是vue的官方插件,看不到vue.use;vue.use执行时,会默认调用里面的install;
2024年Web前端面试题(最全、最详细、持续更新)_web前端面试问题_第1张图片


    
{{$store.state.count}}
Vuex核心源码封装

    
{{$store.state.msg}} {{$store.getters.str}}
VueRouter源码
class VueRouter{
    constructor(options){
        const {routes}=options;
        // 监听当前页面的hash值的切换
        // 当第一次解析页面时,会有一个默认的hash值
        /// 循环遍历routes,把path和component重新放入一个新的对象中
        // {"/home/:id":home}
        this.routeMap = routes.reduce((prev,next)=>{
            prev[next.path]=next.component;
            return prev;
        },{});
        // 
        // this ==> VueRouter的实例,也是每一个组件上的\_router
        Vue.util.defineReactive(this.route={},'path',"/");
        window.addEventListener("load",()=>{
            // 如果没有hash值,那么给其赋默认值/;如果本来就有hash,什么也不做;
            location.hash?null:location.hash="/";
        })
        window.addEventListener("hashchange",()=>{
            // 当页面hash值发生改变以后,会触发这个方法;1.a标签 2.手动
            // 获取当当前页面的hash值,获取到#后面的字符串;
            let path = location.hash.slice(1);
            this.route.path = path;
        })
    }
}
//在Vuex注入了$store,在路由注入\_router
VueRouter.install=function(\_Vue){

    _Vue.mixin({
        // 给每一个组件新增一个\_router的属性,这个属性的属性值是VueRouter的实例
        beforeCreate(){
            // this==> 每一个组件实例
            if(this.$options&&this.$options.router){
                // 给每一个组件实例新增\_router属性,属性值就是VueRouter的实例; 
              这是给Vm这个Vue实例新增
                this._router=this.$options.router;
            }else{
                // 给vm的组件的实例新增
                this._router=this.$parent && this.$parent._router;
            }
            // 给每一个实例添加$route属性,
            Object.defineProperty(this,"$route",{
                value:{
                    route:this._router.route// ? 这个route
                }
            });

            // 注册两个内置组件
            // router-link router-view
            // 注册全局组件
            
            let child = {}
            Vue.component("router-link",{ 
                props:{
                    to:String
                },
                // template:"
", render(createElement){// h是一个createdElement,这个方法可以直接接受一个组件; createElement 用来创建虚拟的DOM //return createElement("a",{},首页) // render : 渲染函数 // render: 将虚拟DOM可以转成真实的DOM;这个函数返回什么, 那么最终router-link就渲染成什么 // this==> 当前的组件实例 // return + 组件;可以把组件渲染成一个真实的DOM; // return h(child); // return // $slots return this.$slots.default } }); // router-view : 根据页面的hash值显示对应的组件 Vue.component("router-view",{ render(createdElement){ // 这个传递一个动态的组件名字 return createElement(this._router.routeMap[this._router.route.path]) } }) } }) }; let router=new VueRouter({ routes:[] }) let vm = new Vue({ router, render(){ } }) export default VueRouter;
redux源码
function createStore(reducer) {
    let state;
    let getState = () => JSON.parse(JSON.stringify(state));
    // action : type 要改的数据
    function dispatch(action) {
        state = reducer(state, action);
        listeners.forEach(item => {
            if (typeof item === "function") {
                item();
            }
        })
    }
    let listeners = [];// 存储订阅的事件的一个容器;当调用dispatch的时候,让这个事件池中的方法执行;
    dispatch({});// 为了初始化数据
    let subcribe = (fn) => {
        listeners.push(fn);
        return () => {
            listeners = listeners.filter(item => item !== fn);
        }
    }
    return {
        getState,
        dispatch,
        subcribe
    }
}
export default createStore;
补充

    

react-redux工程化
import React from "react";
// import store from "../store/index.js";
import actions from "../store/actions/counter";
// react-redux: 将store的公共数据放到组件的属性上;
// 属性和状态的更新都能引发视图的更新;
// react-redux要求返回一个连接后的组件;
import {connect} from "react-redux";
// 可以将store中的数据和action的动作以属性的方式传递给该组件

class Counter extends React.Component{
    // constructor(){
    // super();
    // this.state={num:store.getState().counter.num}
    // }
    // componentDidMount(){
    // // 在redux中,A组件通过dispatch更新了store中的数据,同时B组件也使用了store中这个数据,
  但是B组件不会自动更新;
    // store.subscribe(()=>{
    // this.setState({num:store.getState().counter.num})
    // })
    // }
    add=()=>{
        // store.dispatch(actions.add());
        //当使用方法时,保持和action中的add一致;
        this.props.add();
    }
    min=()=>{
        // store.dispatch(actions.min());
        this.props.min();
    }
    render(){
        // 1. 组件事件 ==> 2. action-type==>3.actions==> 4.reducer==>5.组件
        //想更新视图,需要调用render方法,那么执行setState就会调用render;
        // 每当dispatch时,都要更新视图的;那么把setState方法进行订阅;
       return  
{this.props.num}
} } //源码 // connect : 第一个参数: 函数 第二个参数: 是当前组件 // actions : 是一个返回类型的对象; // mapStateToProps\mapDisPatchToProps都是在connect函数内部调用的 let mapStateToProps=state=>({...state.counter}); // 当执行connect时,会默认调用这个箭头函数, 并且将store中的state数据传给当前的函数state参数;返回当前组件对应的数据,并且放在了当前组件的行间属性上; let mapDisPatchToProps=(dispatch)=>{// dispatch==>store.dispatch return {// 这个对象会放在组件的属性上; add:()=>{dispatch(actions.add())}, min:()=>{dispatch(actions.min())} } } // 都是将这两个函数的返回值放到组件的属性上; export default connect(mapStateToProps,actions)(Counter); //在执行connect时,判断第二个参数是否是一个函数,如果是函数,则将函数的执行结果放在组件的行间属性上, 如果是一个对象,那么会默认调用一个bindActionsCreator这个方法,将该方法的返回值放在组件行间属性上 当前属性传给组件 // action 就是connect传入的action dispatch是store中的dispatch方法 // let bindActionCreator=(action,dispatch)=>{ // let obj ={}; // for(let key in action){ // obj[key]= ()=>{ // dispatch(action[key]()) // } // } // return obj; // }
Promise封装

2024年Web前端面试题(最全、最详细、持续更新)_web前端面试问题_第2张图片

    
                    
                    

你可能感兴趣的:(程序员,前端,面试,学习)