我是All in AI的前端老兵shunwuyu
,带班攻战互联网大厂面试十年了。春招、秋招,都感觉与同学们一样热血沸腾。我以同学的口吻写了面试系列,希望对大厂入职大厂有所帮助。
如果您喜欢我的面试系列,欢迎点赞,收藏,也欢迎加我V(shunwuyu), 入群一起交流。
前言
能在小红书里找到美团面试,大家惊愕了吧。我这个面试来自美团一位实习生的文章,说他要去上课了,可以内推面他的坑位。没想一投,面试就安排到位了。 大二升大三的第一次大厂面试,不求黄袍加身,但求面试官温柔待我。反正远程实习干着,大厂都面面。
面试题截图
自我介绍
可以看介绍公式
判断数组的方法
- Array.isArray
instanceof
操作符
let arr = [1, 2, 3];
console.log(arr instanceof Array); // true
Object.prototype.toString.call()
- 使用
typeof
和length
属性
function isArrayLike(obj) {
return obj != null && typeof obj === 'object' && 'length' in obj;
}
let arr = [1, 2, 3];
console.log(isArrayLike(arr)); // true
小结: 普通考题,但能展现js基础能力,答题主要在于全面。
箭头函数和普通函数的区别
this
绑定:普通函数的this
是动态绑定的,箭头函数的this
是词法绑定的。- 构造函数:普通函数可以作为构造函数使用,箭头函数不能。
arguments
对象:普通函数有arguments
对象,箭头函数没有,但可以通过rest
参数替代。- 函数体简写:箭头函数支持简写形式,普通函数需要完整的函数体。
- 适用场景:根据具体需求选择合适的函数类型。
js中的异步方法
JavaScript 是一种单线程语言,这意味着它在同一时间只能执行一个任务。然而,JavaScript 通过异步编程模型来处理耗时的操作,如网络请求、文件读写等,从而避免阻塞主线程。
JavaScript 的异步方法主要有三种:回调函数、Promise 和 Async/Await。
-
回调函数callback
回调函数是最基本的异步处理方式。它通过将一个函数作为参数传递给另一个函数,在某个操作完成后调用这个回调函数。
优点:简单易懂。 缺点:(Callback Hell):多层嵌套的回调函数会导致代码难以阅读和维护。
-
Promise
Promise 是一种更现代的异步处理方式,它代表一个异步操作的最终完成(或失败)及其结果值。Promise 有三种状态:pending(进行中)、fulfilled(已完成)和 rejected(已失败)。
优点:更好的错误处理、避免回调地狱。 缺点:语法相对复杂。
-
Async/Await
Async/Await 是基于 Promise 的语法糖,使异步代码看起来更像同步代码,提高了代码的可读性和可维护性。
优点:代码更简洁、易读,错误处理更直观。
async/await原理
- 概念:
async/await
是基于Promise
的语法糖,目的是简化异步代码的书写,避免回调地狱,提供一种更类似于同步代码的异步编程体验。 async
函数:使用async
关键字定义的函数总是返回一个Promise
对象。即使函数内部没有明确返回Promise
,JavaScript 也会将返回值自动封装为Promise.resolve()
。await
表达式:await
用于暂停async
函数的执行,等待一个Promise
解析。await
后面必须是一个Promise
对象或可转化为Promise
的值。函数会在Promise
解析完成后恢复执行,并返回Promise
的结果。如果Promise
被拒绝,await
会抛出异常,可以使用try/catch
来捕获。- 事件循环:
async/await
并不会阻塞主线程,而是将异步操作推入事件循环的微任务队列(microtask queue)。当同步代码执行完毕后,JavaScript 引擎会检查微任务队列并执行相应的回调,这就是为什么await
不会阻塞整个程序。
总结:async/await
是基于 Promise
实现的语法糖,利用事件循环将异步任务放入微任务队列,使得异步代码看起来像同步代码,极大简化了代码的可读性。
事件冒泡机制
-
定义:事件冒泡是浏览器处理 DOM 事件的一种机制。当一个事件发生在一个元素上时,它首先会被该元素处理,然后事件会按照父子层级向上(从目标元素到根元素
document
)依次触发父级元素的相同事件监听器,直到到达最顶层。 -
事件传播阶段:事件传播分为三个阶段:
- 捕获阶段:事件从根元素向目标元素进行传播,但此时监听器并未触发。
- 目标阶段:事件到达目标元素,并触发目标元素的事件监听器。
- 冒泡阶段:事件从目标元素向上冒泡到其父级元素,依次触发所有祖先元素的事件监听器,直至根元素。
-
默认行为:事件冒泡是浏览器的默认行为,但可以通过
event.stopPropagation()
来阻止事件进一步冒泡。 -
实际应用:
- 事件委托:利用事件冒泡,可以在父级元素上绑定事件监听器,通过捕获冒泡中的事件来处理子元素的事件,提升性能,尤其适用于动态内容。
- 阻止冒泡:某些场景下,出于业务逻辑考虑,可能需要使用
event.stopPropagation()
来阻止事件冒泡,避免不必要的事件触发。
-
通个addEventListener的第三个参数,可以控制时间在哪个阶段触发。
addEventListener
的第三个参数可以是一个布尔值或一个对象,它的作用是指定事件监听器的选项,主要包括以下几种情况:
-
capture
(布尔值):- 当第三个参数为
true
时,表示事件监听器在捕获阶段触发(即从父元素往目标元素传播的过程中触发)。 - 当为
false
(默认值)时,事件监听器在冒泡阶段触发(即从目标元素往父元素传播的过程中触发)。
- 当第三个参数为
-
options
(对象): 第三个参数也可以是一个包含以下属性的对象,用于更精细地控制事件监听器的行为:capture
: (同上)控制是否在捕获阶段触发。once
: 当为true
时,监听器会在触发一次后自动移除,不再响应后续的事件。passive
: 当为true
时,表示监听器不会调用preventDefault()
,用于优化滚动性能,避免阻塞页面滚动。
事件循环
可以查看事件循环
解构url
直接上 死磕 36 个 JS 手写题(搞懂后,提升真的大)作为一个程序员,代码能力毋庸置疑是非常非常重要的,就像现在为什么大厂面 - 掘金 (juejin.cn)
vue 生命周期
- vue2 生命周期
- vue3 生命周期
- vue3 composition api 生命周期
- vue3 keep alive 两个生命周期
- react useEffect
请求数据应该放在哪个生命周期?为什么?
在 Vue 3 中,请求数据通常放在 onMounted
生命周期钩子中。原因是 onMounted
会在组件的 DOM 渲染完成后调用,这时可以安全地进行异步请求并更新组件的状态,而不会阻塞页面的初次渲染。
聊聊vue响应式数据的proxy
在 Vue 3 中,响应式系统采用了 Proxy
代替 Vue 2 中的 Object.defineProperty
,以更灵活、强大的方式实现响应式数据。
- 全面拦截:
Proxy
能拦截对对象的13种操作,包括读取属性、设置属性、has、删除属性(deleteProperty)、拦截返回对象的键名列表的操作(ownKeys)、获取属性描述符(getOwnPropertyDescriptor)、定义或修改属性描述符(defineProperty)、setPrototypeOf(设置对象原型操作)、获取对象原型(getPrototypeOf)、拦截new
操作符,即实例化对象操作等,不仅限于现有的属性,还能处理动态添加或删除的属性,甚至数组的变化。而defineProperty只能做getter、setter, 可读、可写、可配置、可迭代等操作。
let handler = { ownKeys(target) { console.log('Listing keys'); return Object.keys(target); } };
let handler = {
getOwnPropertyDescriptor(target, prop) {
console.log(`Getting descriptor for ${prop}`);
return Object.getOwnPropertyDescriptor(target, prop);
}
};
-
深度监听:对于嵌套的对象,必须递归地遍历所有属性,这会导致性能问题。
Object.defineProperty
需要递归遍历对象的所有嵌套属性,将其转换为响应式。这意味着在初始化时会有较高的性能开销。Proxy
可以自动代理对象及其嵌套属性,无需递归遍历。这使得初始化时的性能开销较低。Proxy
通过动态代理和透明代理的方式,避免了在初始化时递归遍历所有嵌套属性的需要。这种懒加载的方式不仅提高了初始化性能,还使得响应式系统更加灵活和强大。在 Vue 3 中,Proxy
的使用正是基于这些优点,使得响应式系统的实现更加高效和简洁。
function lazyReactive(target) {
const handler = {
get(target, key, receiver) {
const value = Reflect.get(target, key, receiver);
if (typeof value === 'object' && value !== null) {
// 懒代理:只有在访问时才代理嵌套对象
return lazyReactive(value);
}
return value;
},
set(target, key, value, receiver) {
const oldValue = Reflect.get(target, key, receiver);
const result = Reflect.set(target, key, value, receiver);
if (oldValue !== value) {
// 触发视图更新
updateView();
}
return result;
}
};
return new Proxy(target, handler);
}
// 模拟视图更新
function updateView() {
console.log('View updated');
}
const state = lazyReactive({
count: 0,
nested: {
name: 'Vue 3',
details: {
version: '3.0.0'
}
},
array: [1, 2, 3]
});
console.log('Initial state:', state);
state.nested.name = 'Vue 3.1'; // 懒代理,只在访问 nested 时代理
console.log('Updated nested.name:', state.nested.name);
state.nested.details.version = '3.1.0'; // 懒代理,只在访问 nested.details 时代理
console.log('Updated nested.details.version:', state.nested.details.version);
console.log('Final state:', state);
-
新增属性
defineProperty 不会对新增属性自动代理,proxy 会
function reactive(target) {
return new Proxy(target, {
get(target, key, receiver) {
return Reflect.get(target, key, receiver);
},
set(target, key, value, receiver) {
const result = Reflect.set(target, key, value, receiver);
console.log(`${key} changed to ${value}`);
return result; } }); }
const state = reactive({ count: 0 });
state.count = 1; // 输出: count changed to 1 console.log(state.count); // 1 // 新增属性 state.newProp = 'new value'; console.log(state.newProp); // 'new value' // 新增属性会触发响应式 state.newProp = 'updated value'; // 输出: newProp changed to updated value console.log(state.newProp); // 'updated value'
有一串只有"["、"]"、"{"、"}"、"("、")" 判断是否是合法
function isValid(s) {
// 用于存储左括号的栈
const stack = [];
// 括号对映射
const mapping = {
')': '(',
'}': '{',
']': '['
};
// 遍历字符串中的每个字符
for (const char of s) {
if (char in mapping) {
// 如果是右括号,检查栈顶是否有匹配的左括号 #只是一个占位符
const topElement = stack.length === 0 ? '#' : stack.pop();
if (mapping[char] !== topElement) {
return false;
}
} else {
// 如果是左括号,将其推入栈中
stack.push(char);
}
}
// 检查栈是否为空(所有的左括号都应该匹配)
return stack.length === 0;
}
// 示例用法
console.log(isValid("()")); // 输出 true
console.log(isValid("()[]{}")); // 输出 true
console.log(isValid("(]")); // 输出 false
console.log(isValid("([)]")); // 输出 false
console.log(isValid("{[]}")); // 输出 true
总结
最近我们挺多同学在小红书上找到一些面试线索的, 还有远程实习了, 大家也可以试试。