面试前要精心做好准备,简历上写的知识点和原理都需要准备好,项目上多想想难点和亮点,这是面试时能和别人不一样的地方。
还有就是表现出自己的谦虚好学,以及对于未来持续进阶的规划,企业招人更偏爱稳定的人。
万事开头难,但是程序员这一条路坚持几年后发展空间还是非常大的,一切重在坚持。
开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】
前端面试题汇总
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)
})
}
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;
// }
function create(Con, ...args) {
let obj = {}
Object.setPrototypeOf(obj, Con.prototype)
let result = Con.apply(obj, args)
return result instanceof Object ? result : obj
}
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))
}, [])
}
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});
});
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));
/\*\*
● @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;
};
/\*\*
● @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
};
例: 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]
}
};
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);
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);
}
})
Vue的插件必须使用Vue.use;只是vuex会默认检测到是vue的官方插件,看不到vue.use;vue.use执行时,会默认调用里面的install;
{{$store.state.count}}
{{$store.state.msg}}
{{$store.getters.str}}
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;
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;
补充
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;
// }